在Linux下,如何以编程方式检查给定的NIC是否支持传输时间戳? [英] How do I programmatically check if transmit timestamps are supported by a given NIC, under Linux?

查看:64
本文介绍了在Linux下,如何以编程方式检查给定的NIC是否支持传输时间戳?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图找到一种在 Linux 下使用 C 以编程方式检查软件传输时间戳 (SOF_TIMESTAMPING_TX_SOFTWARE) 是否受给定 NIC 支持的方法,以便恢复到其他类型的时间戳(或完全禁用它们).

I am trying to find a way to programmatically check, under Linux and using C, if software transmit timestamps (SOF_TIMESTAMPING_TX_SOFTWARE) are supported by a given NIC, in order to revert to other kinds of timestamps (or disable them completely) if they are not supported.

尤其是,我的目标是在调用 ioctl(SIOCSHWTSTAMP)并检查其返回值时,检查它们是否像硬件时间戳一样受支持(可以找到更新的文档)此处).

In particular, my goal would be to check if they are supported like it can be done for hardware timestamps, when calling ioctl(SIOCSHWTSTAMP) and checking its return value (the updated documentation can be found here).

ethtool -T<接口名称> 已经提供了此信息,但是我认为调用 system() popen(),因为 ethtool 可能未安装在系统上,并且我绝对不希望将其作为运行程序的先决条件.

ethtool -T <interface name> is already providing this information, but I do not think it could be a good idea to call system() or popen(), as ethtool may not be installed on the system and I definitely do not want to put it as a prerequisite to run my program.

在进行一些试验时,我使用了来自这个问题的代码:

When experimenting a bit, I used an adaptation of the code coming from this question:

#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个数据包,但请求硬件发送时间戳,依此类推.

This code will send out 10 packets requesting software transmit timestamps, then it will try to send other 10 packets, but requesting hardware transmit timestamps, and so on.

它以发送包的接口名称作为参数.我注意到,根据

It takes as argument the interface name over which the packets should be sent. I noticed that, when transmit hardware/software timestamps are supported, everything is working as expected, according to the kernel timestamping documentation, as in the enp0s31f6 (ethernet) interface case:

$ 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 所述:

Instead, if I try to launch the sample program over a wireless interface, not supporting any kind of transmit timestamp, as reported by ethtool:

$ 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 错误(并且在设置了所有时间后都会过期):

For what concerns software transmit timestamps, I never get any message looped back to the error queue, causing an indefinite wait if -1 is specified as poll timeout, or an EAGAIN error if a timeout is specified (and it expires, when it is set, all the times):

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 设置为 1 0 ).

The result is the same both when using UDP sockets and when using raw sockets (by setting #define RAW_SOCKET to 1 or to 0).

为了避免等待永远不会出现的环回消息(或等待超时),我可以通过一种方式以编程方式检查 SOF_TIMESTAMPING_TX_SOFTWARE 是否受支持.给定的接口并最终在我的程序中禁用了整个机制,然后才尝试检索无法检索的传输时间戳?

In order to avoid waiting for a looped back message which will never come (or to wait for a timeout expiration), is there a way in which I can programmatically check if SOF_TIMESTAMPING_TX_SOFTWARE is supported over a given interface and eventually disable the whole mechanism in my program, before trying to retrieve transmit timestamps which cannot be retrieved?

非常感谢您.

推荐答案

您应使用与 ethtool 相同的界面.有一个名为 SIOCETHTOOL 的特定ioctl,它从驱动程序级别检索有关时间戳功能的信息.这是一个简短的示例(为简洁起见,缺少错误处理等):

You should use the same interface that ethtool uses. There is a specific ioctl called SIOCETHTOOL, that retrieves the information about timestamping capabilities from driver level. This is a short example (error handling etc. is missing for the sake of brevity):

// 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 时间戳.这样,您应该可以确定是否支持时间戳.

Same for RX timestamps. This way you should be able find out if timestamps are supported or not.

这篇关于在Linux下,如何以编程方式检查给定的NIC是否支持传输时间戳?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆