接收UDP数据包时是否可以读取TTL IP头字段? [英] Is it possible to read the TTL IP header field when receiving UDP packets?

查看:22
本文介绍了接收UDP数据包时是否可以读取TTL IP头字段?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 UDP 套接字发送数据包,我想检查接收数据包的 IP 标头中的 TTL 字段.是否可以?

I am using UDP socket to send packets and I want to check the TTL field in IP header of received packet. Is it possible?

我注意到一个 IP_HDRINCL 套接字选项,但它似乎只适用于 RAW 套接字.

I notice a IP_HDRINCL sockoption but it seems to work only for RAW socket.

推荐答案

i您可以使用 recvmsg() 接口获取该信息.首先你需要告诉系统你想要访问这些信息:

iYou can get that information using the recvmsg() interface. First you need to tell the system that you want to access this information:

int yes = 1;
setsockopt(soc, IPPROTO_IP, IP_RECVTTL, &yes, sizeof(yes));

然后准备接收缓冲区:

// Note that IP packets can be fragmented and 
// thus larger than the MTU. In theory they can 
// be up to UINT16_MAX bytes long!
const size_t largestPacketExpected = 1500;
uint8_t buffer[largestPacketExpected];
struct iovec iov[1] = { { buffer, sizeof(buffer) } };

如果您还想知道数据包的来源(使用 recvfrom() 而不是 recv() 时也会得到),您需要该地址的存储空间:

If you also want to know from where the packet came from (that you also get when using recvfrom() instead of recv()), you'll need storage for that address as well:

// sockaddr_storage is big enough for any socket address your system
// supports, like sockaddr_in or sockaddr_in6, etc.
struct sockaddr_storage srcAddress;

最后,您需要存储控制数据.每个控制数据项都有一个固定大小的标头 (struct cmsghdr),在大多数系统上为 12 字节,后跟有效载荷数据,其大小和解释取决于控制项的类型.在您的情况下,有效负载数据只是一个字节,即 TTL 值.但是,必须考虑一些对齐要求,因此您不能只保留 13 个字节,实际上在大多数系统上您的缓冲区需要更大,这就是系统为此提供了一个方便的宏的原因:

And finally you need storage for the control data. Every control data item has a fixed size header (struct cmsghdr) that is 12 bytes in size on most systems, followed by payload data whose size and interpretation depends on the kind of control item. In your case, the payload data is just one byte, the TTL value. However, there are some alignment requirements one has to take into account, so you cannot just reserve 13 bytes, in fact your buffer needs to be larger on most systems, that's why the system offers a handy macro for that:

uint8_t ctrlDataBuffer[CMSG_SPACE(sizeof(uint8_t))];

如果你想检索多个控制数据项,你可以这样定义你的缓冲区:

In case you want to retrieve multiple control data items, you'd define your buffer like that:

uint8_t ctrlDataBuffer[
    CMSG_SPACE(x) 
    + CMSG_SPACE(y) 
    + CMSG_SPACE(z) 
];

其中 xyz 是返回的有效负载数据的大小.CMSG_SPACE(0) 返回没有任何额外负载数据的普通头的大小,它应该等于 sizeof(struct cmsghdr).但在您的情况下,有效载荷数据只是一个字节.

With x, y, and z being the size of payload data returned. The size of a plain header without any additional payload data is returned by CMSG_SPACE(0) and it should equal sizeof(struct cmsghdr). But in your case, the payload data is just one byte.

现在您需要将所有这些组合到一个 struct msghdr 中:

Now you need to put all that together to a struct msghdr:

struct msghdr hdr = {
    .msg_name = &srcAddress,
    .msg_namelen = sizeof(srcAddress),
    .msg_iov = iov,
    .msg_iovlen = 1,
    .msg_control = ctrlDataBuffer,
    .msg_controllen = sizeof(ctrlDataBuffer)
};

请注意,您可以将所有您不感兴趣的字段设置为 NULL(指针)或 0(长度).如果您愿意,您可以仅检索源地址或仅检索数据包有效载荷或仅检索控制数据以及这三者的任意组合.

Note that you can set all fields you are not interested in to NULL (pointers) or 0 (lengths). You can retrieve only the source address if you like or just the packet payload or only the control data as well as any combination of those three.

最后你可以从套接字读取:

And finally you can read from the socket:

ssize_t bytesReceived = recvmsg(soc, &hdr, 0);

返回值就像 recv() 一样,-1 表示错误,0 表示对方关闭了流(但这仅在 TCP 的情况下才有可能,您无法检索 TTL forTCP 套接字),否则您将获得写入 buffer 的字节数.

The return value is just like for recv(), -1 means error, 0 means the other side has closed the stream (but that's only possible in case of TCP and you cannot retrieve TTL for TCP sockets) and otherwise you get the number of bytes written to buffer.

如何处理 srcAddress?

if (srcAddress.ss_family == AF_INET) {
    struct sockaddr_in * saV4 = (struct sockaddr_in *)&scrAddress;
    // ...

} else if (srcAddress.ss_family == AF_INET6) {
    struct sockaddr_in6 * saV6 = (struct sockaddr_in6 *)&scrAddress;
    // ...

} // and so on

好的,但是现在控制数据呢?您需要按照下图进行处理:

Okay, but now what about the control data? You need to process it as shown below:

int ttl = -1;
struct cmsghdr * cmsg = CMSG_FIRSTHDR(&hdr); 
for (; cmsg; cmsg = CMSG_NXTHDR(&hdr, cmsg)) {
    if (cmsg->cmsg_level == IPPROTO_IP
        && cmsg->cmsg_type == IP_RECVTTL
    ) {
        uint8_t * ttlPtr = (uint8_t *)CMSG_DATA(cmsg);
        ttl = *ttlPtr;
        break;
    }
}
// ttl is now either the real ttl or -1 if something went wrong

CMSG_DATA() 宏为您提供了一个正确对齐的指向实际控制数据负载的指针.同样,可能会有内存需求的填充,所以永远不要尝试直接访问数据.

The CMSG_DATA() macro gives you a correctly aligned pointer to the actual control data payload. Again, there might be padding for memory aliment requirements so never try to access the data directly.

与使用原始套接字相比,这种方法的优点是:

The advantages of this method over using a raw socket is:

  • 此代码不需要 root 权限.
  • sendmsg() 比原始套接字更具可移植性.
  • 该套接字是一个普通的 UDP 套接字,其行为与任何其他 UDP 套接字一样.
  • This code doesn't require root rights.
  • sendmsg() is more portable than raw sockets.
  • The socket is a normal UDP socket and behaves like any other UDP socket.

有关您可以通过这种方式获取哪些其他信息的更多信息,您需要查看操作系统的 API 文档(例如 ip 的手册页).例如,这里有一个指向 [OpenBSD 手册页][1] 的链接.请注意,您还可以通过其他级别"获取有关表单的信息.(例如 SOL_SOCKET),记录在该级别的手册页中.

For more information on which other information you can obtain that way, you need to check the API documentation of your operation system (e.g. the man page of ip). Here's a link to [the man page from OpenBSD][1] for example. Note that you can also obtain information on form other "levels" (e.g. SOL_SOCKET), documented on the man page of that level.

哦,如果您想知道,CMSG_LEN()CMSG_SPACE() 类似,但不完全相同.CMSG_LEN(x) 返回负载大小为 x 的控制数据实际使用的实际字节数,而 CMSG_SPACE(x) 返回有效载荷大小为 x 的控制数据实际使用的实际字节数,包括在有效载荷数据之后正确对齐下一个控制数据标头所需的任何填充.因此,当为多个控制数据项保留存储空间时,您总是必须使用CMSG_SPACE()!如果您自己创建这样的结构(例如,当使用 sendmsg() 也存在).

Oh, and in case you wonder, CMSG_LEN() is similar to CMSG_SPACE() but not identical. CMSG_LEN(x) returns the actual amount of bytes really in use by control data whose payload size is x, whereas CMSG_SPACE(x) returns the the actual amount of bytes really in use by control data whose payload size is x including any padding required after the payload data to correctly align the next control data header. Thus when reserving storage for multiple control data items, you always must use CMSG_SPACE()! You only use CMSG_LEN() for setting the cmsg_len field in struct cmsghdr in case you are creating such structures yourself (e.g. when using sendmsg() which exists as well).

还有最后一件重要的事情要知道:如果您不小心将 ctrlDataBuffer 设置得太小,并不是说您根本不会获得任何控制数据或遇到错误,控制数据然后将被截断.这种截断由一个标志指示(hdr 的标志字段在输入时被忽略,但它可能在输出时包含标志):

And one last important thing to know: In case you accidentally made the ctrlDataBuffer too small, it's not that you won't get any control data at all or run into an error, the control data will then just be truncated. This truncation is indicated by a flag (the flags field of hdr is ignored on input but it may contain flags on output):

// After recvmsg()...
if (hdr.msg_flags & MSG_CTRUNC) {
    // Control data buffer was too small to make all data fit!
}

如果您愿意,如果您选择的数据缓冲区太小,您可以获得相同的行为.只需查看此代码:

If you like, you can get identical behavior in case your data buffer has been chosen too small. Just check out this code:

ssize_t bytesReceived = recvmsg(soc, &hdr, MSG_TRUNC);
if (hdr.msg_flags & MSG_TRUNC) {
    // The data buffer was too small, data has been read but it
    // was truncated. bytesReceived does *NOT* contain the amount of
    // bytes read but the amount of bytes that would have been read if
    // the data buffer had been of sufficient size!
}

当然,知道销毁数据包后的正确大小可能不是很有用.但是你可以这样做:

Of course, knowing the correct size after destroying packet may not be really useful. But then you can just do this:

ssize_t bytesReceived = recvmsg(soc, &hdr, MSG_TRUNC | MSG_PEEK);

这样数据会重新存储在套接字缓冲区中,因此您可以再次读取它,现在您知道了所需的缓冲区大小.但是,类似的东西不适用于控制数据.您需要提前知道正确的控制数据大小,或者您需要编写一些试错代码,例如在循环中增加控制数据缓冲区,直到不再设置 MSG_CTRUNC.通常一旦找到合适的大小,您就会记住它,因为对于给定的套接字,控制数据的数量通常是恒定的,除非您进行 setsockopt() 调用会改变它.默认情况下,UDP 套接字根本不返回控制数据,除非您请求了某些东西.

That way the data resits in the socket buffer, so you can read it again, now that you know the required buffer size for this. Something similar is not available for control data, though. You need to know the correct control data size in advance or you need to write some trial and error code, e.g. increasing the control data buffer in a loop until the MSG_CTRUNC is not set any longer. Usually once you found a good size, you can remember it as the amount of control data is usually constant for a given socket unless you make setsockopt() calls that would change it. By default a UDP socket returns no control data at all unless you have requested something.

这篇关于接收UDP数据包时是否可以读取TTL IP头字段?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆