0

我正在尝试获取通过 1 根以太网电缆连接的 2 台计算机之间的硬件时间戳中的数据包传输延迟。然而,获得延迟的结果是11.5 micro-seconds,这比我预期的要高出几纳秒。

设计

我试图获得延迟的方式如下图所示delay = ((t3 - t0) - (t2 - t1)) / 2

    client          server
      |    ping       |
    t0|-------------->|t1
      |               |
      |    pong       |
    t3|<--------------|t2
      |               |
      v               v

这里t0/t2是网卡记录的Linux硬件发送时间,t1/t3是硬件接收时间戳。有关 linux socket 硬件时间的详细信息可以在这里找到:https ://docs.kernel.org/networking/timestamping.html 。据我了解,t0/t2网卡将第一个字节传输到线路t1/t3的时间,是网卡从线路接收第一个字节的时间。因此,我计算的延迟纯粹是传播延迟,应该在几纳秒内。除了证明我的假设之外,我还尝试改变数据包大小(以256/512/1400字节为单位),结果没有任何变化,都在11.5 microseconds. 我对那些硬件时间戳和延迟计算的理解是否正确?

执行

我使用的两台电脑是Dell OptiPlex-7090Ubuntu 20.04,网卡版本是Ethernet Connection (14) I219-LM.

完整的测试代码可以在这里找到:https ://github.com/ChuanyuXue/Hardware-RTT 。它记录硬件时间戳和软件时间戳。

实用程序.c:

void die(char *s)
{
    perror(s);
    exit(1);
}

int setup_receiver(int fd, int port)
{
    struct sockaddr_in addr;

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(port);

    if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {
        die("bind()");
    }

    int val = SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE;

    if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val)) == -1)
    {
        die("setsockopt() HW receiving");
    }

    int enable = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPNS, &enable, sizeof(enable)) < 0)
    {
        die("setsockopt() SW receiving");
    }

    return 0;
}

int setup_sender(int fd)
{
    int timestamp_flags = SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE;

    if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &timestamp_flags, sizeof(timestamp_flags)) < 0)
    {
        die("setsockopt() HW sending");
    }

    int enable = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPNS, &enable, sizeof(enable)))
    {
        die("setsockopt() SW receiving");
    }
}

int setup_adapter(int fd, char *dev_name)
{
    struct hwtstamp_config hwts_config;
    struct ifreq ifr;

    memset(&hwts_config, 0, sizeof(hwts_config));
    hwts_config.tx_type = HWTSTAMP_TX_ON;
    hwts_config.rx_filter = HWTSTAMP_FILTER_ALL;
    memset(&ifr, 0, sizeof(ifr));
    snprintf(ifr.ifr_name, IFNAMSIZ, "%s", dev_name);
    ifr.ifr_data = (void *)&hwts_config;
    if (ioctl(fd, SIOCSHWTSTAMP, &ifr) == -1)
    {
        die("ioctl()");
    }
}

void send_single(int fd, char *address, int port)
{
    /*
    Send one message
     */
    struct sockaddr_in si_server;
    memset(&si_server, 0, sizeof(si_server));
    si_server.sin_family = AF_INET;
    si_server.sin_port = htons(port);
    if (inet_aton(address, &si_server.sin_addr) == 0)
    {
        die("inet_aton()");
    }

    char buffer[BUFFER_LEN];

    struct iovec iov = (struct iovec){.iov_base = buffer, .iov_len = BUFFER_LEN};
    struct msghdr msg = (struct msghdr){.msg_name = &si_server,
                                        .msg_namelen = sizeof si_server,
                                        .msg_iov = &iov,
                                        .msg_iovlen = 1};
    ssize_t send_len = sendmsg(fd, &msg, 0);
    if (send_len < 0)
    {
        printf("[!] Error sendmsg()");
    }

    // -------------- obtain the loopback packet

    char data[BUFFER_LEN], control[BUFFER_LEN];
    struct iovec entry;
    struct sockaddr_in from_addr;
    int res;

    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 = (caddr_t)&from_addr;
    msg.msg_namelen = sizeof(from_addr);
    msg.msg_control = &control;
    msg.msg_controllen = sizeof(control);

    // wait until get the loopback
    while (recvmsg(fd, &msg, MSG_ERRQUEUE) < 0)
    {
    }

    // encode the returned packet
    struct cmsghdr *cmsg;

    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
         cmsg = CMSG_NXTHDR(&msg, cmsg))
    {
        if (cmsg->cmsg_level == SOL_SOCKET &&
            cmsg->cmsg_type == SCM_TIMESTAMPING)
        {
            struct timespec *ts =
                (struct timespec *)CMSG_DATA(cmsg);
            printf("HD-SEND    TIMESTAMP %ld.%09ld\n", ts[2].tv_sec, ts[2].tv_nsec);
        }

        if (cmsg->cmsg_level == SOL_SOCKET &&
            cmsg->cmsg_type == SO_TIMESTAMPNS)
        {
            struct timespec *ts =
                (struct timespec *)CMSG_DATA(cmsg);
            printf("SW-SEND    TIMESTAMP %ld.%09ld\n", ts->tv_sec, ts->tv_nsec);
        }
    }
}

void recv_single(int fd)
{
    char data[BUFFER_LEN], ctrl[BUFFER_LEN];
    struct msghdr msg;
    struct iovec iov;
    ssize_t len;
    struct cmsghdr *cmsg;

    memset(&msg, 0, sizeof(msg));
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = ctrl;
    msg.msg_controllen = sizeof(ctrl);
    iov.iov_base = data;
    iov.iov_len = sizeof(data);
    struct timespec start;

    if (recvmsg(fd, &msg, 0) < 0)
    {
        printf("[!] Error recvmsg()");
    }

    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
         cmsg = CMSG_NXTHDR(&msg, cmsg))
    {
        if (cmsg->cmsg_level == SOL_SOCKET &&
            cmsg->cmsg_type == SCM_TIMESTAMPING)
        {
            struct timespec *ts =
                (struct timespec *)CMSG_DATA(cmsg);
            printf("HW-RECV    TIMESTAMP %ld.%09ld\n", ts[2].tv_sec, ts[2].tv_nsec);
        }

        if (cmsg->cmsg_level == SOL_SOCKET &&
            cmsg->cmsg_type == SCM_TIMESTAMPNS)
        {

            struct timespec *ts =
                (struct timespec *)CMSG_DATA(cmsg);
            printf("SW-RECV    TIMESTAMP %ld.%09ld\n", ts->tv_sec, ts->tv_nsec);
        }
    }
}

客户端.c

int main(int argc, char *argv[])
{
    const char *address = "192.168.0.23";
    const int port = 54321;

    int fd_in = socket(AF_INET, SOCK_DGRAM, 0);
    int fd_out = socket(AF_INET, SOCK_DGRAM, 0);

    setup_adapter(fd_in, "eth0");
    setup_adapter(fd_out, "eth0");

    setup_sender(fd_out);
    setup_receiver(fd_in, port);

    int count = 0;
    while (1)
    {
        printf("[ ---- Iter-%5d ----------------------------- ]\n", count++);
        send_single(fd_out, (char *)address, port);
        recv_single(fd_in);
        usleep(10000);
    }
}

服务器.c

int main(int argc, char *argv[])
{
    const char *address = "192.168.0.22";
    const int port = 54321;

    int fd_in = socket(AF_INET, SOCK_DGRAM, 0);
    int fd_out = socket(AF_INET, SOCK_DGRAM, 0);

    setup_adapter(fd_in, "eth0");
    setup_adapter(fd_out, "eth0");

    setup_sender(fd_out);
    setup_receiver(fd_in, port);

    int count = 0;
    while (1)
    {
        printf("[ ---- Iter-%5d ----------------------------- ]\n", count++);
        recv_single(fd_in);
        usleep(10000);
        send_single(fd_out, (char *)address, port);
    }
}

从结果中,我可以看到硬件时间戳比软件时间戳更稳定,并且抖动非常低(对不起,我的帐户不允许嵌入图片到问题中):

https://github.com/ChuanyuXue/Hardware-RTT/blob/main/0301_exp_hwtime/01.png

https://github.com/ChuanyuXue/Hardware-RTT/blob/main/0301_exp_hwtime/02.png

https://github.com/ChuanyuXue/Hardware-RTT/blob/main/0301_exp_hwtime/03.png

https://github.com/ChuanyuXue/Hardware-RTT/blob/main/0301_exp_hwtime/04.png

https://github.com/ChuanyuXue/Hardware-RTT/blob/main/0301_exp_hwtime/06.png

以上结果让我觉得我得到的时间戳是正确的,因为硬件延迟具有较小的延迟和非常低的抖动,但是我在计算时忽略了一些问题delay = ((t3 - t0) - (t2 - t1)) / 2。有没有人可以让我知道原因或在其他环境中测试我的代码?

4

0 回答 0