什么是正确的(便携式,稳定的)方式来获得收到的数据包的ToS字节? 我正在做recvmsg()和Linux上的UDP如果我setsockopt()IP_RECVTOS / IPV6_RECVTCLASS我可以得到的ToS,但IP_RECVTOS似乎并没有在我的BSD系统上可用。 什么是正确的方法来做到这一点?
我主要希望这在BSD和Solaris上工作。
编辑:澄清:我目前使用recvmsg(),我得到TTL和TOS在Linux的msg_control字段,但为了得到TTL和TOS我需要setsockopt() – 启用IP_RECVTTL和IP_RECVTOS。 而且,由于Solaris和BSD(目前与FreeBSD一起工作)没有我能看到的IP_RECVTOS,所以在循环访问CMSG数据时,我没有得到TOS。
我尝试启用IP_RECVOPTS和IP_RECVRETOPTS,但我仍然没有得到任何IP_TOStypesCMSG。
编辑2:我希望ToS能够(尽可能)validation它在传输过程中没有被覆盖。 例如,如果一个VoIP应用程序突然发现它没有获得EF标记的数据包,那么有些事情是错误的,应该有一个警报。 (不,我不指望EF在公共互联网上受到尊重或保留)
我想TTL基本上只是因为我可以。 假设这可以用来触发“我和另一方之间的networking中发生了变化”的警报,这可能会有助于了解某些事物是否同时停止工作。
我在想如果你可以创建两个套接字。
一个专门用于发送的DGRAM类型的套接字
一个专用于接收的原始套接字。
由于您使用的是UDP,因此您可以在Raw Sock Fd上调用bind + recvFrom,然后手动解包IP头以确定TOS或TTL。
当你想发送时,使用DGRAM sockFd,这样你就不必费心去自己实际创建UDP和IP包。
内核可能会将收到的缓冲区传递给套接字或UDP套接字,而不是Raw套接字,或者传递给Raw套接字。 如果是这种情况(或者如果是实施依赖的话),那么我们又回到了原点。 但是,你可以尝试在原始套接字上调用绑定,看看是否有帮助。 我知道这可能是一个黑客,但在网上搜索BSD setsockopt返回什么都没有。
编辑 :我写了一个示例程序它有点达到目标。
下面的代码创建了两个套接字(一个raw和一个udp)。 udp套接字绑定在实际的端口上,我希望能够接收数据,而原始的套接字绑定在端口0上。我在Linux上测试了这个功能,就像我期望的那样,两个套接字都收到了端口2905的任何数据。 但是我能够检索TTL和TOS值。 不要downvote代码的质量。 我只是在试验它是否会起作用。
进一步编辑:通过UDP套接字禁用接收 。 我进一步增强了禁止UDP数据包接收的代码。 使用setsockopt,我将UDP的套接字接收缓冲区设置为0.这确保内核不会将数据包传递到UDP套接字。 恕我直言,你现在可以使用UDP套接字专门发送和原始套接字读取。 这应该适用于BSD和Solaris。
#include<stdio.h> #include<stdlib.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<netinet/ip.h> #include<arpa/inet.h> #include<string.h> #include "protHeaders.x" #include "gen.h" int main(void) { S32 rawSockFd; S32 udpSockFd; struct sockaddr_in rsin; struct sockaddr_in usin; S32 one = 1; const S32* val = &one; struct timeval tv; fd_set rfds; S32 maxFd; S16 ret; S8 rawBuffer[2048]; S8 udpBuffer[2048]; struct sockaddr udpFrom,rawFrom; socklen_t rLen,uLen; memset(rawBuffer,0,sizeof(rawBuffer)); memset(udpBuffer,0,sizeof(udpBuffer)); memset(udpFrom,0,sizeof(udpFrom)); memset(rawFrom,0,sizeof(rawFrom)); if ((rawSockFd = socket(PF_INET,SOCK_RAW,IPPROTO_UDP)) < 0) { perror("socket:create"); RETVALUE(RFAILED); } /* doing the IP_HDRINCL call */ if (setsockopt(rawSockFd,IPPROTO_IP,IP_HDRINCL,val,sizeof(one)) < 0) { perror("server:setsockopt"); RETVALUE(RFAILED); } rsin.sin_family = AF_INET; rsin.sin_addr.s_addr = htonl(INADDR_ANY); rsin.sin_port = htons(0); usin.sin_family = AF_INET; usin.sin_addr.s_addr = htons(INADDR_ANY); usin.sin_port = htons(2905); if(bind(rawSockFd,(struct sockaddr *)&rsin, sizeof(rsin)) < 0 ) { perror("server: bind failed"); RETVALUE(RFAILED); } if ((udpSockFd = socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP)) < 0) { perror("socket:create"); RETVALUE(RFAILED); } if(bind(udpSockFd,(struct sockaddr *)&usin, sizeof(usin)) < 0 ) { perror("server: bind failed on udpsocket"); RETVALUE(RFAILED); } /*set upd socket receive buffer to 0 */ one = 0; if (setsockopt(udpSockFd,SOL_SOCKET,SO_RCVBUF,(char *)&one,sizeof(one)) < 0) { perror("server:setsockopt on udpsocket failed"); RETVALUE(RFAILED); } tv.tv_sec = 0; tv.tv_usec = 0; maxFd = (rawSockFd > udpSockFd)? rawSockFd:udpSockFd; while(1) { FD_ZERO(&rfds); FD_SET(rawSockFd,&rfds); FD_SET(udpSockFd,&rfds); ret = select(maxFd+1,&rfds,0,0,&tv); if ( ret == -1) { perror("Select Failed"); RETVALUE(RFAILED); } if(FD_ISSET(rawSockFd,&rfds)) { printf("Raw Socked Received Message\n"); if(recvfrom(rawSockFd,rawBuffer,sizeof(rawBuffer),0,&rawFrom,&rLen) == -1) { perror("Raw socket recvfrom failed"); RETVALUE(RFAILED); } /*print the tos */ printf("TOS:%x\n",*(rawBuffer+1)); printf("TTL:%x\n",*(rawBuffer+8)); } if(FD_ISSET(udpSockFd,&rfds)) { printf("UDP Socked Received Message\n"); if(recvfrom(udpSockFd,udpBuffer,sizeof(udpBuffer),0,&udpFrom,&uLen) == -1) { perror("Udp socket recvfrom failed"); RETVALUE(RFAILED); } printf("%s\n",udpBuffer); } } RETVALUE(ROK); }
“适当的”和标准的解决方案可能是使用cmsg(3)
。 您将在Stevens的“Unix网络编程”一书中找到完整的描述,这是一本必读书。
谷歌代码搜索找到我这个使用的例子 。
我的理解是,首先BSD不支持IP_RECVTOS之类的功能,其次BSD原始套接字不支持接收UDP或TCP数据包。 但是还有两种方法可以做到这一点,首先通过直接或通过libpcap使用/ dev / bpf接口。 第二,通过使用DIVERT插座,可以将指定的交通流量分流到用户区域。
有没有人在BSD盒子上面测试过上面的代码? (它可能在Solaris上工作…)
在Linux上,这种方法是可行的,但是如前所述,在传出套接字上使用IP_TOS的setsockopt()来设置传出TOS字节,并在传入套接字上使用IP_RECVTOS设置setsockopt(),并使用recvmsg()检索TOS字节。
不幸的是,这种事情通常会因不同的* ixs而有所不同。 在Solaris上,您想使用带有IP_TOS
getsockopt
; 我不了解BSD。
有关详细信息,请参阅man 7 ip 。