启用 ETF qdisc 数据包后,仅发送几秒钟

启用 ETF qdisc 数据包后,仅发送几秒钟

我有两台通过以太网电缆连接的计算机,并安装了 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 上使用mqprioqdisc 和 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 地址。

相关内容