gpt4 book ai didi

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

转载 作者:太空狗 更新时间:2023-10-29 12:08:41 28 4
gpt4 key购买 nike

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

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

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

在进行一些试验时,我使用了来自 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 个数据包,但请求硬件传输时间戳,依此类推。

它以应该发送数据包的接口(interface)名称作为参数。我注意到,根据 kernel timestamping documentation,当支持传输硬件/软件时间戳时,一切都按预期工作。 ,如 enp0s31f6 (以太网)接口(interface)案例:

$ 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

相反,如果我尝试通过无线接口(interface)启动示例程序,不支持任何类型的传输时间戳,如 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如果指定了超时则出错(并且超时,设置时,所有时间):

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 是否存在?在给定接口(interface)上支持并最终禁用我程序中的整个机制,然后再尝试检索无法检索的传输时间戳?

非常感谢您。

最佳答案

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

// 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 时间戳相同。这样您应该能够确定是否支持时间戳。

关于c - 在 Linux 下,如何以编程方式检查给定 NIC 是否支持传输时间戳?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57164125/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com