我正在使用UDP套接字发送数据包,我想检查接收数据包的IP头中的TTL字段。可能吗?
我注意到IP_HDRINCL sockoption但它似乎只适用于RAW套接字。
答案 0 :(得分:2)
您可以使用recvmsg()
界面获取该信息。首先,您需要告诉系统您要访问此信息:
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()
时也会得到),您还需要存储该地址:< / p>
// sockaddr_storage is big enough for any socket address your system
// supports, like sockaddr_in or sockaddr_in6, etc.
struct sockaddr_storage sourceAddress;
最后,您需要存储控制数据。每个控制数据项都有一个固定大小的头(struct cmsghdr
),在大多数系统上大小为12字节,其次是有效载荷数据,其大小和解释取决于控制项的类型。在您的情况下,有效负载数据只是一个字节,即TTL值。但是,有一些必须考虑的对齐要求,所以你不能只保留13个字节,实际上你的缓冲区需要在大多数系统上更大,这就是系统为此提供一个方便的宏的原因: / p>
uint8_t ctrlDataBuffer[CMSG_SPACE(sizeof(uint8_t))];
如果您想要检索多个控制数据项,您可以像这样定义缓冲区:
uint8_t ctrlDataBuffer[
CMSG_SPACE(x)
+ CMSG_SPACE(y)
+ CMSG_SPACE(z)
];
x
,y
和z
是返回的有效负载数据的大小。 CMSG_SPACE(0)
返回没有任何其他有效负载数据的普通标头的大小,它应该等于sizeof(struct cmsghdr)
。但在您的情况下,有效载荷数据只是一个字节。
现在你需要把所有这些放在一起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
(长度)。如果您愿意,您只能检索源地址,或仅检索数据包有效负载或仅检索控制数据以及这三者的任意组合。
最后你可以从套接字中读取:
ssize_t bytesReceived = recvmsg(soc, &hdr, 0);
返回值与recv()
类似,-1表示错误,0表示另一方已关闭流(但只有在TCP情况下才可以,并且您无法检索TCP的TTL套接字),否则你得到写入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
好的,但现在控制数据怎么样?您需要按如下所示进行处理:
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()
宏为您提供了一个指向实际控制数据有效负载的正确对齐指针。同样,可能存在内存消耗要求的填充,因此永远不要尝试直接访问数据。
此方法优于使用原始套接字的优点是:
sendmsg()
比原始套接字更便携。有关您可以通过这种方式获得的其他信息的更多信息,您需要查看操作系统的API文档(例如ip
的手册页)。例如,这里是the man page from OpenBSD的链接。请注意,您还可以获取有关其他&#34;等级&#34;的信息。 (例如SOL_SOCKET),记录在该级别的手册页上。
哦,如果你想知道,CMSG_LEN()
与CMSG_SPACE()
相似但不完全相同。 CMSG_LEN(x)
返回有效负载大小为x
的控制数据实际使用的实际字节数,而CMSG_SPACE(x)
返回有效负载大小为的控制数据实际使用的实际字节数。 x
包括有效负载数据之后所需的任何填充,以正确对齐下一个控制数据头。因此,在为多个控制数据项保留存储空间时,您始终必须使用CMSG_SPACE()
!您只能使用CMSG_LEN()
来设置cmsg_len
中的struct cmsghdr
字段,以防您自己创建此类结构(例如,当使用同时存在的sendmsg()
时)。
还有一件重要的事情要知道:如果你不小心让ctrlDataBuffer
太小,那并不是说你根本没有获得任何控制数据或遇到错误,然后,控制数据将被截断。此截断由一个标志指示(输入时忽略hdr
的标志字段,但输出中可能包含标志):
// After recvmsg()...
if (hdr.msg_flags & MSG_CTRUNC) {
// Control data buffer was too small to make all data fit!
}
如果您愿意,如果您的数据缓冲区选择得太小,您可以获得相同的行为。只需查看此代码:
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!
}
当然,在&#34;摧毁&#34;之后知道正确的尺寸。数据包可能不是很有用。但是你可以这样做:
ssize_t bytesReceived = recvmsg(soc, &hdr, MSG_TRUNC | MSG_PEEK);
这样数据会在套接字缓冲区中重新生成,因此您可以再次读取它,因为您已知道所需的缓冲区大小。但是,类似的东西不适用于控制数据。您需要提前知道正确的控制数据大小,或者需要编写一些试验和错误代码,例如:在循环中增加控制数据缓冲区,直到MSG_CTRUNC
不再设置为止。通常一旦找到了合适的大小,就可以记住它,因为控制数据的数量通常对于给定的套接字是不变的,除非你进行setsockopt()
调用会改变它。默认情况下,除非您请求了某些内容,否则UDP套接字根本不会返回任何控制数据。
答案 1 :(得分:1)
当您使用UDP套接字时,所有标头都将被删除(解封装),因此您将无法获取TTL字段值或IP标头的任何其他字段,但如果您有兴趣获取它或设置它,使用原始套接字并构建标头,使用原始套接字将标头传递给您的应用程序,包括您构建的标头(IP +传输)图层标头。