【问题标题】:Timestamp outgoing packets时间戳传出数据包
【发布时间】:2012-07-31 00:12:52
【问题描述】:

我正在尝试获取传出数据包的准确时间戳(使用原始套接字发送)。根据Linux/Documentation/networking/timestamping.txt,“对于发送时间戳,传出的数据包将循环回套接字的错误队列,并附上发送时间戳。可以使用 recvmsg(flags=MSG_ERRQUEUE) 接收它。”。

不幸的是,recvmsg 在原始套接字上调用时总是返回-1(使用socket(PF_INET, SOCK_RAW, IPPROTO_RAW) 创建并且SO_TIMESTAMP 设置为1setsockopt)。我究竟做错了什么?有没有更好的方法来获取传出数据包的准确时间戳?

附录(信息):

我还尝试从通过 UDP 套接字发送的数据包中获取时间戳(源代码如下),recvmsg 返回-1:错误是“资源暂时不可用”(EAGAIN)。

附录(源代码):

#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>

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

int main(int argc, char* argv[])
{
    char* destination_ip = "10.0.0.1";
    int destination_port = 1234;

    int sock;
    if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
        die("socket()");
    }

    int timestamp_flags = SOF_TIMESTAMPING_TX_SOFTWARE;
    if (setsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING, &timestamp_flags, sizeof(timestamp_flags)) < 0) {
        die("setsockopt()");
    }

    struct sockaddr_in si_server;
    memset(&si_server, 0, sizeof(si_server));
    si_server.sin_family = AF_INET;
    si_server.sin_port = htons(destination_port);
    if (inet_aton(destination_ip, &si_server.sin_addr) == 0) {
        die("inet_aton()");
    }

    const int buffer_len = 256;
    char buffer[buffer_len];

    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, (const sockaddr*) &si_server, sizeof(si_server)) < 0) {
            die("sendto()");
        }

        // Obtain the sent packet timestamp.
        char data[256];
        struct msghdr msg;
        struct iovec entry;
        struct sockaddr_in from_addr;
        struct {
            struct cmsghdr cm;
            char control[512];
        } control;
        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);        
        if (recvmsg(sock, &msg, MSG_ERRQUEUE) < 0) {
            die("recvmsg()");
        }
    }
    return 0;
}

【问题讨论】:

  • 如果你使用 SOCK_STREAM 你会得到不同的时间?
  • 顺便说一句,我使用 setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));禁用 Nagel 算法。我认为你也应该这样做。但我想知道你是否可以连续使用两个 setsockopt?
  • 显然你也可以在你想要的任何级别添加 sockopt,以获得真正的时间我猜你会在 IPPROTO_RAW 级别设置 SO_TIMESTAMP
  • 从错误队列中读取不是非阻塞的吗?我猜你会得到 EAGAIN,因为当你调用 recvmsg() 时时间戳还没有准备好。也许尝试使用 poll() 并等待 POLLERR。

标签: c linux timestamp raw-sockets


【解决方案1】:

查看Linux内核源代码,我发现负责将包含数据包时间戳的消息放入错误队列的函数是skb_tx_timestamp。这个函数应该由网卡驱动调用,不幸的是,e1000 驱动没有调用它(硬件时间戳也有类似的函数,但这显然取决于支持它的网卡驱动)。

根据去年 9 月的 this NetDev discussion,“没有驱动程序调用 skb_tx_timestamp()”和“您需要调整 NIC 驱动程序才能使用此 TX 时间戳”。在e1000_main.c 上添加对skb_tx_timestampe1000_xmit_frame 的调用后,我能够获取传出数据包的时间戳(通过UDP 套接字)。不过,我无法获得原始套接字上传出数据包的时间戳(我仍然得到EAGAIN)。

【讨论】:

    【解决方案2】:

    很难知道您做错了什么,因为我们看不到您的代码。

    但是: 文档说SO_TIMESTAMP 用于传入数据包,而SO_TIMESTAMPING 用于传出数据包。

    内核文档包含一个完整的示例,您可以将其用作基础 - 尽管它使用的是 UDP,但您应该能够将其调整为使用 RAW 套接字。 查看linux内核Documentation/networking/timestamping/timestamping.c

    编辑:似乎没有普遍支持传输时间戳,例如here。即使在今天,也只有少数 nic 驱动程序实现了软件支持,而少数具有硬件支持。

    【讨论】:

    • 我的错,我添加了源代码(尝试先从 UDP 获取时间戳)。是的,我的代码基于 timestamping.c。但是,它仍然不起作用(recvmsg 告诉我“资源暂时不可用”)。
    • 这听起来像 EAGAIN,这意味着错误队列中没有任何内容(目前)。所以你必须重新尝试 recvmsg ,或者使用 select()/poll() 来在有东西时得到通知。带有 MSG_ERRQUEUE 标志的 recvmsg 不会阻塞,直到有东西到达。但请参阅我的最后一次编辑。
    • 我刚刚看到您的最后一次编辑 - 请参阅我自己的回复 :)。我们都发现了相同的问题,但我仍然有一个问题:它似乎不适用于 RAW 套接字。知道为什么吗?
    • 嗯,使用当前代码,你没有得到 EAGAIN 只是运气,所以至少要确保你按照我在上一条评论中所说的去做。此外,请尝试验证您的原始数据包是否确实进入了网络,例如用wireshark调试它。
    • 你说得对,我很幸运 :)。我开始使用 UDP 套接字获取 EAGAIN(但 select 让它消失了)。至于原始数据包,select 阻塞(不使用超时),是的,原始数据包进入网络(tcpdump 显示它们,我也得到响应)。
    【解决方案3】:

    sock_tx_timestamp 仅在当前内核代码中为 SOCK_DGRAM 套接字调用。

    顺便说一句,文档 Documentation/networking/timestamping/timestamping.c 不是很准确。

    SO_TIMESTAMP / SO_TIMESTAMPNS / SO_TIMESTAMPING / SIOCGSTAMP / SIOCGSTAMPNS 是相似的。它们中的任何一个都将使应用程序能够获取接收到的数据包的时间戳。

    使用 SOF_TIMESTAMPING_TX_SOFTWARE,上述任何一个标志都会在 MSG_ERRQUEUE 中为应用程序提供一个 CMSG,指示发送数据包的时间戳。

    但是 SOF_TIMESTAMPING_RX_SOFTWARE 根本没用。它甚至不能用于禁用接收数据包的时间戳报告。

    【讨论】:

      【解决方案4】:

      我认为,您必须将时间戳标记作为

      int timestamp_flags |= SOF_TIMESTAMPING_TX_SOFTWARE;
      timestamp_flags |= SOF_TIMESTAMPING_SOFTWARE;
      

      在从 msg 队列错误中获取数据之前,您还必须检查所有系统调用的 strerror。这将帮助您摆脱 EAGAIN 消息。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-05-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-12
        • 1970-01-01
        相关资源
        最近更新 更多