10

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

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

附录(信息):

我还尝试从通过 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;
}
4

4 回答 4

10

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

根据去年 9 月的这个 NetDev 讨论,“没有驱动程序调用 skb_tx_timestamp()”和“你需要调整你的 NIC 驱动程序来使用这个 TX 时间戳”。skb_tx_timestamp添加对e1000_xmit_frameon的调用后e1000_main.c,我能够获取传出数据包的时间戳(通过 UDP 套接字)。不过,我无法获得 RAW 套接字上传出数据包的时间戳(我仍然得到EAGAIN)。

于 2012-07-31T19:21:48.297 回答
5

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

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

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

编辑:似乎没有普遍支持传输时间戳,请参见此处。即使在今天,也只有少数 nic 驱动程序实现了软件支持,少数有硬件支持。

于 2012-07-31T00:34:34.940 回答
2

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 根本没用。它甚至不能用于禁用接收数据包时间戳的报告。

于 2014-05-29T06:10:12.503 回答
0

我想,你必须提到时间戳标志

int timestamp_flags |= SOF_TIMESTAMPING_TX_SOFTWARE;
timestamp_flags |= SOF_TIMESTAMPING_SOFTWARE;

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

于 2015-02-26T11:02:24.627 回答