我有两台通过以太网电缆连接的计算机,并安装了 Ubuntu 22.04。我在计算机 A) 上有一个客户端,它正在向计算机 B) 上的服务器发送 UDP 数据包。我正在研究以 TAPRIO qdisc 作为其父级的 ETF qdisc 对流量的影响。我使用 c 套接字库作为客户端的源代码,利用 SO_PRIORITY 套接字选项和 SCM_TXTIME 控制消息类型。
为了可视化 ETF qdisc 的效果,我将 5 秒间隔中每个数据包的 txtime 设置为该 5 秒间隔的末尾。因此,我预计服务器端每 5 秒就会爆发一次。
问题是,从 PFIFO qdisc 更改为上述 ETF 和 TAPRIO qdisc 设置后,我只能在服务器端看到突发 2 或 3 次。此后,客户端不再发送任何数据包。当切换回 PFIFO qdisc 同时保持客户端和服务器处于活动状态时,数据包将按预期到达,不会出现突发情况。当客户端和服务器再次处于活动状态时,更改为 TAPRIO 和 ETF qdisc 会产生一些突发,之后仅此而已。
什么会导致这种行为?
以下是我的代码的相关部分:
//setting socket options:
static void setsockopt_txtime(int fd)
{
struct sock_txtime so_txtime_val = { .clockid = CLOCK_TAI };
struct sock_txtime so_txtime_val_read = { 0 };
socklen_t vallen = sizeof(so_txtime_val);
so_txtime_val.flags = (SOF_TXTIME_REPORT_ERRORS);
if (setsockopt(fd, SOL_SOCKET, SO_TXTIME,
&so_txtime_val, sizeof(so_txtime_val)))
error(1, errno, "setsockopt txtime");
if (getsockopt(fd, SOL_SOCKET, SO_TXTIME,
&so_txtime_val_read, &vallen))
error(1, errno, "getsockopt txtime");
if (vallen != sizeof(so_txtime_val) ||
memcmp(&so_txtime_val, &so_txtime_val_read, vallen))
error(1, 0, "getsockopt txtime: mismatch");
}
//sending message and setting txtime for message
static int l2_send(int fd, void* buf, int len, __u64 txtime, struct sockaddr_in *servaddr)
{
char control[CMSG_SPACE(sizeof(txtime))] = {};
struct cmsghdr* cmsg;
struct msghdr msg;
struct iovec iov;
ssize_t cnt;
iov.iov_base = buf;
iov.iov_len = len;
memset(&msg, 0, sizeof(msg));
msg.msg_name = (struct sockaddr*)servaddr;
msg.msg_namelen = sizeof(*servaddr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
//We specify the transmission time in the CMSG.
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_TXTIME;
cmsg->cmsg_len = CMSG_LEN(sizeof(__u64));
*((__u64*)CMSG_DATA(cmsg)) = txtime;
cnt = sendmsg(fd, &msg, 0);
if (cnt < 1) {
//pr_err("sendmsg failed: %m");
printf("sending message failed!\n");
return cnt;
}
printf("messaage sent!\ntxtime:%lf\n\n",(double)txtime);
return cnt;
}
double timer_difference(struct timespec* tval_before, struct timespec* tval_after, struct timespec* tval_result)
{
clock_gettime(CLOCK_TAI, tval_after);
timespecsub(tval_after, tval_before, tval_result);
double time_elapsed = (double)tval_result->tv_sec + ((double)tval_result->tv_nsec / 1000000000.0f);
return time_elapsed;
}
//Relevant part of main funtion for calculating txtime:
int main(int argc, char* argv[]) {
.
.
struct timespec txtime_base,txtime_base_after,txtime_difference;
// Creating socket file descriptor
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
setsockopt_txtime(sockfd);
memset(&servaddr, 0, sizeof(servaddr));
// Filling server information
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(server_port);
inet_aton("10.0.0.70", &servaddr.sin_addr.s_addr);
if (connect(sockfd , &servaddr, sizeof(servaddr)))
error(1, errno, "connect");
clock_gettime(CLOCK_TAI, &txtime_base);
clock_gettime(CLOCK_TAI, &txtime_base_after);
txtime = txtime_base.tv_sec * (__u64)1000000000+ TXTIME_PERIOD*(__u64)8*(__u64)1000000000 + txtime_base.tv_nsec;
while (1)
{
if(TXTIME_PERIOD<timer_difference(&txtime_base,&txtime_base_after,&txtime_difference)) //setting txtime here
{
clock_gettime(CLOCK_TAI, &txtime_base);
txtime = txtime_base.tv_sec * (__u64)1000000000+ (__u64)TXTIME_PERIOD*(__u64)2*(__u64)1000000000 + txtime_base.tv_nsec;
printf("in txtime if!\n");
}
while (time_elapsed < send_interval) //sending interval without txtime here
{
time_elapsed = timer_difference(&tval_before, &tval_after, &tval_result);
}
txtime=txtime+(__u64)1000; //creating 1 μs gap betwwen packets of one burst
l2_send(sockfd, (const char*)send_buffer, strlen(send_buffer), txtime,&servaddr);
.
.
答案1
最有可能的问题是 ARP 数据包没有设置 SO_TXTIME 元数据标志。 ETF 将丢弃所有没有 SO_TXTIME 标志的数据包,包括 ARP 数据包。 Linux 每 30 秒使邻居条目 AFAIR 失效,然后它无法使用 ARP 解析 10.0.0.70 IP 的 MAC,因为它们被丢弃。
解决方法是在 prio band != 0 上使用mqprio
qdisc 和 etf leaf。在代码中,您必须为setsockopt(..., SO_PRIORITY, ...)
发送者套接字设置一些优先级 != 0。这样,只有您生成的流量通过 ETF qdisc,而其他流量(例如 ARP)使用可能具有的优先级 0pfifo_fast
或其他 qdisc,不会丢弃传出数据包。
替代解决方案:使用静态邻居条目,它们是永久性的,如果 Linux 在邻居表(或 ARP 表,如果更熟悉的话)中看到 IP 的 MAC:
ip neigh add 10.0.0.70 dev eth0 lladdr 00:00:00:02:02:02
其中00:00:00:02:02:02
是配置了 10.0.0.70 IP 的网卡的 MAC 地址。