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>#endifvoid 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.txtint 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.它以发送包的接口名称作为参数.我注意到,根据内核时间戳文档,如 enp0s31f6 (以太网)界面案例: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 enp0s31f6Program started.source IP: 192.168.1.210Iteration: 0 - HW_STAMPS? 0Test started.Sent packet number 0/10HW: 0 s, 0 nsts[1] - ???: 0 s, 0 nsSW: 1563878575 s, 690256891 nsSent packet number 1/10HW: 0 s, 0 nsts[1] - ???: 0 s, 0 nsSW: 1563878576 s, 690468816 nsSent packet number 2/10HW: 0 s, 0 nsts[1] - ???: 0 s, 0 nsSW: 1563878577 s, 691003245 nsSent packet number 3/10HW: 0 s, 0 nsts[1] - ???: 0 s, 0 nsSW: 1563878578 s, 691365791 nsSent packet number 4/10HW: 0 s, 0 nsts[1] - ???: 0 s, 0 nsSW: 1563878579 s, 691940147 nsSent packet number 5/10HW: 0 s, 0 nsts[1] - ???: 0 s, 0 nsSW: 1563878580 s, 692198712 nsSent packet number 6/10HW: 0 s, 0 nsts[1] - ???: 0 s, 0 nsSW: 1563878581 s, 692543005 nsSent packet number 7/10HW: 0 s, 0 nsts[1] - ???: 0 s, 0 nsSW: 1563878582 s, 692856348 nsSent packet number 8/10HW: 0 s, 0 nsts[1] - ???: 0 s, 0 nsSW: 1563878583 s, 693098097 nsSent packet number 9/10HW: 0 s, 0 nsts[1] - ???: 0 s, 0 nsSW: 1563878584 s, 693612477 nsIteration: 1 - HW_STAMPS? 1Test started.Sent packet number 0/10HW: 1563878585 s, 717541747 nsts[1] - ???: 0 s, 0 nsSW: 0 s, 0 nsSent packet number 1/10HW: 1563878586 s, 718023872 nsts[1] - ???: 0 s, 0 nsSW: 0 s, 0 nsSent packet number 2/10HW: 1563878587 s, 718505122 nsts[1] - ???: 0 s, 0 nsSW: 0 s, 0 nsSent packet number 3/10HW: 1563878588 s, 719091997 nsts[1] - ???: 0 s, 0 nsSW: 0 s, 0 nsSent packet number 4/10HW: 1563878589 s, 719689747 nsts[1] - ???: 0 s, 0 nsSW: 0 s, 0 nsSent packet number 5/10HW: 1563878590 s, 720231247 nsts[1] - ???: 0 s, 0 nsSW: 0 s, 0 nsSent packet number 6/10HW: 1563878591 s, 720462747 nsts[1] - ???: 0 s, 0 nsSW: 0 s, 0 nsSent packet number 7/10HW: 1563878592 s, 721012872 nsts[1] - ???: 0 s, 0 nsSW: 0 s, 0 nsSent packet number 8/10HW: 1563878593 s, 721272372 nsts[1] - ???: 0 s, 0 nsSW: 0 s, 0 nsSent packet number 9/10HW: 1563878594 s, 721588497 nsts[1] - ???: 0 s, 0 nsSW: 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 wlp1s0Time stamping parameters for wlp1s0:Capabilities: software-receive (SOF_TIMESTAMPING_RX_SOFTWARE) software-system-clock (SOF_TIMESTAMPING_SOFTWARE)PTP Hardware Clock: noneHardware Transmit Timestamp Modes: noneHardware 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 wlp1s0Program started.source IP: 172.22.116.105Iteration: 0 - HW_STAMPS? 0Test 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 bufferstruct ifreq ifr = {.ifr_name = "eth1", .ifr_data = (void*)&tsi};// Create a socket for the ioctl commandint fd = socket(AF_INET, SOCK_DGRAM, 0);// Perform the ioctlioctl(fd, SIOCETHTOOL, &ifr);// and analyze the resultsif (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是否支持传输时间戳?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!
09-22 11:16