首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在Linux下,如何以编程方式检查给定NIC是否支持传输时间戳?

在Linux下,如何以编程方式检查给定NIC是否支持传输时间戳?
EN

Stack Overflow用户
提问于 2019-07-23 20:22:04
回答 1查看 1.7K关注 1票数 2

我正在尝试找到一种方法,在Linux下,使用C语言,通过编程检查给定的网卡是否支持软件传输时间戳(SOF_TIMESTAMPING_TX_SOFTWARE),以便在不支持时恢复到其他类型的时间戳(或完全禁用它们)。

具体地说,我的目标是在调用ioctl(SIOCSHWTSTAMP)并检查其返回值(更新文档可以在here中找到)时,检查它们是否像硬件时间戳那样被支持。

ethtool -T <interface name>已经提供了这些信息,但我不认为调用system()popen()是个好主意,因为系统上可能没有安装ethtool,而且我绝对不想把它作为运行我的程序的先决条件。

在进行一些实验时,我使用了来自this question的代码的改编

代码语言:javascript
复制
#include <arpa/inet.h>
#include <linux/net_tstamp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/errqueue.h>
#include <sys/ioctl.h>
#include <linux/sockios.h>
#include <net/if.h>
#include <unistd.h>
#include <time.h>
#include <poll.h>
#include <linux/if.h>

#define RAW_SOCKET 0 // Set to 0 to use an UDP socket, set to 1 to use raw socket
#define NUM_TESTS 2

#if RAW_SOCKET
#include <linux/if_packet.h>
#include <net/ethernet.h>
#endif

void die(char* s)
{
    perror(s);
    exit(1);
}

// Wait for data to be available on the socket error queue, as detailed in https://www.kernel.org/doc/Documentation/networking/timestamping.txt
int pollErrqueueWait(int sock,uint64_t timeout_ms) {
    struct pollfd errqueueMon;
    int poll_retval;

    errqueueMon.fd=sock;
    errqueueMon.revents=0;
    errqueueMon.events=0;

    while((poll_retval=poll(&errqueueMon,1,timeout_ms))>0 && errqueueMon.revents!=POLLERR);

    return poll_retval;
}

int run_test(int argc, char* argv[], int hw_stamps, int sock, void *si_server_ptr)
{
    #if RAW_SOCKET
        struct sockaddr_ll si_server=*(struct sockaddr_ll *) si_server_ptr;
    #else
        struct sockaddr_in si_server=*(struct sockaddr_in *) si_server_ptr;
    #endif
    fprintf(stdout,"Test started.\n");

    int flags;
    if(hw_stamps) {
        struct ifreq hwtstamp;
        struct hwtstamp_config hwconfig;

        // Set hardware timestamping
        memset(&hwtstamp,0,sizeof(hwtstamp));
        memset(&hwconfig,0,sizeof(hwconfig));

        // Set ifr_name and ifr_data (see: man7.org/linux/man-pages/man7/netdevice.7.html)
        strncpy(hwtstamp.ifr_name,argv[1],sizeof(hwtstamp.ifr_name));
        hwtstamp.ifr_data=(void *)&hwconfig;

        hwconfig.tx_type=HWTSTAMP_TX_ON;
        hwconfig.rx_filter=HWTSTAMP_FILTER_ALL;

        // Issue request to the driver
        if (ioctl(sock,SIOCSHWTSTAMP,&hwtstamp)<0) {
            die("ioctl()");
        }

        flags=SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE;
    } else {
       flags=SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_TX_SOFTWARE;
    }

    if(setsockopt(sock,SOL_SOCKET,SO_TIMESTAMPING,&flags,sizeof(flags))<0) {
        die("setsockopt()");
    }

    const int buffer_len = 256;
    char buffer[buffer_len];

    // Send 10 packets
    const int n_packets = 10;
    for (int i = 0; i < n_packets; ++i) {
        sprintf(buffer, "Packet %d", i);
        if (sendto(sock, buffer, buffer_len, 0, (struct sockaddr*) &si_server, sizeof(si_server)) < 0) {
            die("sendto()");
        }

        fprintf(stdout,"Sent packet number %d/%d\n",i,n_packets);
        fflush(stdout);

        // Obtain the sent packet timestamp.
        char data[256];
        struct msghdr msg;
        struct iovec entry;
        char ctrlBuf[CMSG_SPACE(sizeof(struct scm_timestamping))];

        memset(&msg, 0, sizeof(msg));
        msg.msg_iov = &entry;
        msg.msg_iovlen = 1;
        entry.iov_base = data;
        entry.iov_len = sizeof(data);
        msg.msg_name = NULL;
        msg.msg_namelen = 0;
        msg.msg_control = &ctrlBuf;
        msg.msg_controllen = sizeof(ctrlBuf);
        // Wait for data to be available on the error queue
        pollErrqueueWait(sock,-1); // -1 = no timeout is set
        if (recvmsg(sock, &msg, MSG_ERRQUEUE) < 0) {
            die("recvmsg()");
        }

        // Extract and print ancillary data (SW or HW tx timestamps)
        struct cmsghdr *cmsg = NULL;
        struct scm_timestamping hw_ts;

        for(cmsg=CMSG_FIRSTHDR(&msg);cmsg!=NULL;cmsg=CMSG_NXTHDR(&msg, cmsg)) {
            if(cmsg->cmsg_level==SOL_SOCKET && cmsg->cmsg_type==SO_TIMESTAMPING) {
                hw_ts=*((struct scm_timestamping *)CMSG_DATA(cmsg));
                fprintf(stdout,"HW: %lu s, %lu ns\n",hw_ts.ts[2].tv_sec,hw_ts.ts[2].tv_nsec);
                fprintf(stdout,"ts[1] - ???: %lu s, %lu ns\n",hw_ts.ts[1].tv_sec,hw_ts.ts[1].tv_nsec);
                fprintf(stdout,"SW: %lu s, %lu ns\n",hw_ts.ts[0].tv_sec,hw_ts.ts[0].tv_nsec);
            }
        }

        // Wait 1s before sending next packet
        sleep(1);
    }
    return 0;
}

int main(int argc, char* argv[]) {
    int sock;
    char* destination_ip = "192.168.1.211";
    int destination_port = 1234;
    struct in_addr sourceIP;

    fprintf(stdout,"Program started.\n");

    if(argc!=2) {
        fprintf(stderr,"Error. You should specify the interface name.\n");
        exit(1);
    }

    // Create socket
    #if RAW_SOCKET
        if ((sock = socket(AF_PACKET,SOCK_RAW,htons(ETH_P_ALL))) < 0) {
            die("RAW socket()");
        }
    #else
        if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
            die("UDP socket()");
        }
    #endif

    struct ifreq ifindexreq;
    #if RAW_SOCKET
        struct sockaddr_ll si_server;
        int ifindex=-1;

        // Get interface index
        strncpy(ifindexreq.ifr_name,argv[1],IFNAMSIZ);
        if(ioctl(sock,SIOCGIFINDEX,&ifindexreq)!=-1) {
                ifindex=ifindexreq.ifr_ifindex;
        } else {
            die("SIOCGIFINDEX ioctl()");
        }

        memset(&si_server, 0, sizeof(si_server));
        si_server.sll_ifindex=ifindex;
        si_server.sll_family=AF_PACKET;
        si_server.sll_protocol=htons(ETH_P_ALL);
    #else
        struct sockaddr_in si_server;

        // Get source IP address
        strncpy(ifindexreq.ifr_name,argv[1],IFNAMSIZ);
        ifindexreq.ifr_addr.sa_family = AF_INET;
        if(ioctl(sock,SIOCGIFADDR,&ifindexreq)!=-1) {
            sourceIP=((struct sockaddr_in*)&ifindexreq.ifr_addr)->sin_addr;
        } else {
            die("SIOCGIFADDR ioctl()");
        }

        bzero(&si_server,sizeof(si_server));
        si_server.sin_family = AF_INET;
        si_server.sin_port = htons(destination_port);
        si_server.sin_addr.s_addr = sourceIP.s_addr;
        fprintf(stdout,"source IP: %s\n",inet_ntoa(sourceIP));
    #endif

    // bind() to interface
    if(bind(sock,(struct sockaddr *) &si_server,sizeof(si_server))<0) {
        die("bind()");
    }

    #if !RAW_SOCKET
        // Set destination IP (re-using si_server)
        if (inet_aton(destination_ip, &si_server.sin_addr) == 0) {
            die("inet_aton()");
        }
    #endif

    for(int i=0;i<NUM_TESTS;i++) {
        fprintf(stdout,"Iteration: %d - HW_STAMPS? %d\n",i,i%2);
        run_test(argc,argv,i%2,sock,(void *)&si_server);
    }

    close(sock);

    return 0;
}

此代码将发送10个请求软件传输时间戳的数据包,然后它将尝试发送其他10个数据包,但请求硬件传输时间戳,依此类推。

它以应通过其发送数据包的接口名称作为参数。我注意到,当支持传输硬件/软件时间戳时,根据kernel timestamping documentation,一切都按预期工作,就像enp0s31f6 (以太网)接口的情况一样:

代码语言:javascript
复制
$ sudo ./test enp0s31f6
Program started.
source IP: 192.168.1.210
Iteration: 0 - HW_STAMPS? 0
Test started.
Sent packet number 0/10
HW: 0 s, 0 ns
ts[1] - ???: 0 s, 0 ns
SW: 1563878575 s, 690256891 ns
Sent packet number 1/10
HW: 0 s, 0 ns
ts[1] - ???: 0 s, 0 ns
SW: 1563878576 s, 690468816 ns
Sent packet number 2/10
HW: 0 s, 0 ns
ts[1] - ???: 0 s, 0 ns
SW: 1563878577 s, 691003245 ns
Sent packet number 3/10
HW: 0 s, 0 ns
ts[1] - ???: 0 s, 0 ns
SW: 1563878578 s, 691365791 ns
Sent packet number 4/10
HW: 0 s, 0 ns
ts[1] - ???: 0 s, 0 ns
SW: 1563878579 s, 691940147 ns
Sent packet number 5/10
HW: 0 s, 0 ns
ts[1] - ???: 0 s, 0 ns
SW: 1563878580 s, 692198712 ns
Sent packet number 6/10
HW: 0 s, 0 ns
ts[1] - ???: 0 s, 0 ns
SW: 1563878581 s, 692543005 ns
Sent packet number 7/10
HW: 0 s, 0 ns
ts[1] - ???: 0 s, 0 ns
SW: 1563878582 s, 692856348 ns
Sent packet number 8/10
HW: 0 s, 0 ns
ts[1] - ???: 0 s, 0 ns
SW: 1563878583 s, 693098097 ns
Sent packet number 9/10
HW: 0 s, 0 ns
ts[1] - ???: 0 s, 0 ns
SW: 1563878584 s, 693612477 ns
Iteration: 1 - HW_STAMPS? 1
Test started.
Sent packet number 0/10
HW: 1563878585 s, 717541747 ns
ts[1] - ???: 0 s, 0 ns
SW: 0 s, 0 ns
Sent packet number 1/10
HW: 1563878586 s, 718023872 ns
ts[1] - ???: 0 s, 0 ns
SW: 0 s, 0 ns
Sent packet number 2/10
HW: 1563878587 s, 718505122 ns
ts[1] - ???: 0 s, 0 ns
SW: 0 s, 0 ns
Sent packet number 3/10
HW: 1563878588 s, 719091997 ns
ts[1] - ???: 0 s, 0 ns
SW: 0 s, 0 ns
Sent packet number 4/10
HW: 1563878589 s, 719689747 ns
ts[1] - ???: 0 s, 0 ns
SW: 0 s, 0 ns
Sent packet number 5/10
HW: 1563878590 s, 720231247 ns
ts[1] - ???: 0 s, 0 ns
SW: 0 s, 0 ns
Sent packet number 6/10
HW: 1563878591 s, 720462747 ns
ts[1] - ???: 0 s, 0 ns
SW: 0 s, 0 ns
Sent packet number 7/10
HW: 1563878592 s, 721012872 ns
ts[1] - ???: 0 s, 0 ns
SW: 0 s, 0 ns
Sent packet number 8/10
HW: 1563878593 s, 721272372 ns
ts[1] - ???: 0 s, 0 ns
SW: 0 s, 0 ns
Sent packet number 9/10
HW: 1563878594 s, 721588497 ns
ts[1] - ???: 0 s, 0 ns
SW: 0 s, 0 ns

相反,如果我尝试通过无线接口启动示例程序,而不支持任何类型的传输时间戳,正如ethtool所报告的那样

代码语言:javascript
复制
$ ethtool -T wlp1s0
Time stamping parameters for wlp1s0:
Capabilities:
    software-receive      (SOF_TIMESTAMPING_RX_SOFTWARE)
    software-system-clock (SOF_TIMESTAMPING_SOFTWARE)
PTP Hardware Clock: none
Hardware Transmit Timestamp Modes: none
Hardware Receive Filter Modes: none

对于软件传输时间戳,我从不会收到任何返回错误队列的消息,如果将-1指定为poll超时,则会导致无限期等待,或者如果指定了超时,则会导致EAGAIN错误(当设置超时时,它会一直过期):

代码语言:javascript
复制
sudo ./test wlp1s0
Program started.
source IP: 172.22.116.105
Iteration: 0 - HW_STAMPS? 0
Test started.
Sent packet number 0/10
.....<stops here>.....

在使用UDP套接字和使用原始套接字时,结果都是相同的(通过将#define RAW_SOCKET设置为10)。

为了避免等待永远不会到来的环回消息(或等待超时到期),有没有一种方法可以在尝试检索无法检索的传输时间戳之前,通过编程检查给定接口是否支持SOF_TIMESTAMPING_TX_SOFTWARE,并最终禁用程序中的整个机制?

非常提前感谢您。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-07-23 21:26:02

您应该使用与ethtool相同的接口。有一个名为SIOCETHTOOL的特定ioctl,它从驱动程序级别检索有关时间戳功能的信息。这是一个简短的示例(为简洁起见,省略了错误处理等内容):

代码语言:javascript
复制
// Specify the ethtool parameter family (timestamping)
struct ethtool_ts_info tsi = {.cmd = ETHTOOL_GET_TS_INFO};

// Specify interface to use (eth1 in this example) and pass data buffer
struct ifreq ifr = {.ifr_name = "eth1", .ifr_data = (void*)&tsi};

// Create a socket for the ioctl command
int fd = socket(AF_INET, SOCK_DGRAM, 0);

// Perform the ioctl
ioctl(fd, SIOCETHTOOL, &ifr);

// and analyze the results
if (tsi.so_timestamping & SOF_TIMESTAMPING_TX_HARDWARE)
    printf("%s supports hardware tx timestamps\n", ifr.ifr_name);
if (tsi.so_timestamping & SOF_TIMESTAMPING_TX_SOFTWARE)
    printf("%s supports software tx timestamps\n", ifr.ifr_name);

RX时间戳也是如此。通过这种方式,您应该能够了解是否支持时间戳。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57164125

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档