开源中文网

您的位置: 首页 > 编程开发 > C语言 > 正文

SO_DONTROUTE套接口选项释疑

来源:  作者:

1 引子 
在上一篇关于如何将套接口绑定到网络接口上的文章中,我曾经以为采用SO_DONTROUTE套接口选项能够实现和SO_BINDTODEVICE选项同样的功能。但是实践证明不是这样。那么,其原因到底是为什么呢?SO_DONTROUTE套接口选项真正的作用是什么呢?本文将对此予以解答。 

2 问题求解 
在socket(7)中对SO_DONTROUTE选项的说明如下: 

SO_DONTROUTE 

Don't send via a gateway, only send to directly connected hosts. 

The same effect can be achieved by setting the MSG_DONTROUTE 

flag on a socket send(2) operation. Expects an integer boolean 

flag. 

这段话的核心意思是SO_DONTROUTE选项将导致数据包不经由网关发送,而是发往直接相连的主机。该套接口选项合法的值是整数形式的布尔标志值。 

上述说明看起来似乎很明了,但是我google到的一些资料又说这个套接口选项将会绕过(bypass)路由表发送数据包,而且连W. Richard Stevens也是这样说的:“此选项规定发出的分组将旁路底层协议的正常路由机制。例如,对于IPv4,分组被指向适当的本地接口,也就是目的地址的网络和子网部分所确定的本地接口。如果本地接口不能由目的地址确定(例如,目的主机不再一个点对点链路的另一端上,也不在一个共享网络上),则返回ENETUNREACH错误。……此选项经常由路由守护进程(routed和gated)用来旁路路由表(路由表不正确的情况下),强制一个分组从某个特定接口发出。”[UNIX网络编程(第1卷),清华大学出版社,第157页]。我不得不承认,正是这里的最后一句话,直接导致我认为可以通过SO_DONTROUTE套接口选项完成和SO_BINDTODEVICE同样的功能。但是实际上却并不是这样的。 

为了解决这个问题,我编写了一个测试程序udpsend2.c。该程序要求要有三个参数,其中,第二个参数是数据包目的地的IP地址,第三个参数是一个开关,当为on时开启SO_DONTROUTE选项,当为off时关闭SO_DONTROUTE选项,默认情况下该选项是打开的。如果您理解该程序有困难,请先阅读《UNIX网络编程(第1卷)》。 

/* 

* $file: udpsend2.c 

* $func: test SO_DONTROUTE socket option, 

* sending UDP packets. 

* $author: rockins 

* $email: ybc2084@163.com 

* $date: Sat Jan 20 16:38:23 CST 2007 

* $all copyleft, say, completely free 

*/ 



#include <sys/types.h> 

#include <sys/socket.h> 

#include <netinet/in.h> 

#include <net/if.h> 

#include <unistd.h> 

#include <signal.h> 



#define BUFF_SIZE 512 /*data buffer size*/ 

#define REMOTE_PORT 9999 /*remote port, not important*/ 



signed int len = 0; /*sending bytes in once time*/ 



int 

main(int argc, char *argv[]) 



int sock; 

struct sockaddr_in remote_addr; 

int dontroute = 1; 

unsigned char buff[BUFF_SIZE]; 

int i; 



/*check arguments*/ 

if (argc != 3) { 

printf("usage: a.out <REMOTE_IP> [on | off]\n" 

"default is switch on SO_DONTROUTE option\n"); 

exit(-1); 





/*determine switch SO_DONTROUTE or not*/ 

if (!strcmp(argv[2], "on")) 

dontroute = 1; 

else if (!strcmp(argv[2], "off")) 

dontroute = 0; 



/*stuff buffer*/ 

for (i = 0; i < BUFF_SIZE; i++) { 

buff[i] = 'X'; 





/*create socket*/ 

if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 

perror("socket()"); 

exit(-1); 





/*set SO_DONTROUTE option*/ 

if (setsockopt(sock, SOL_SOCKET, SO_DONTROUTE, 

&dontroute, sizeof(dontroute)) < 0) { 

perror("setsockopt()"); 

exit(-1); 





/*remote address structure*/ 

memset(&remote_addr, 0, sizeof(struct sockaddr_in)); 

remote_addr.sin_family = AF_INET; 

remote_addr.sin_port = htons(REMOTE_PORT); 

remote_addr.sin_addr.s_addr = inet_addr(argv[1]); 



/*data sending process*/ 

len = sendto(sock, buff, BUFF_SIZE, 0, 

(struct sockaddr *)&remote_addr, 

sizeof(struct sockaddr_in)); 

if (len < 0) { 

perror("sendto()"); 

exit(-1); 





printf("send %d to remote end...\n", len); 

return (0); 



如下编译程序: 

[root@cyc src]# gcc -g -o udpsend2 udpsend2.c 

3 对比测试 
首先介绍一下初始时刻测试环境的设置情况,如下所示: 

[root@cyc src]# uname -a 

Linux cyc 2.4.20-8 #4 Sat Jan 20 19:42:09 CST 2007 i686 i686 i386 GNU/Linux 

[root@cyc src]# ifconfig 

eth0 Link encap:Ethernet HWaddr 00:0D:87:EA:E3:AF 

inet addr:202.115.26.224 Bcast:202.115.26.255 Mask:255.255.255.0 

UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 

RX packets:40016 errors:0 dropped:0 overruns:0 frame:0 

TX packets:85327 errors:0 dropped:0 overruns:0 carrier:0 

collisions:0 txqueuelen:100 

RX bytes:3063524 (2.9 Mb) TX bytes:36469606 (34.7 Mb) 

Interrupt:11 Base address:0xc000 

[root@cyc src]# route 

Kernel IP routing table 

Destination Gateway Genmask Flags Metric Ref Use Iface 

202.115.26.0 * 255.255.255.0 U 0 0 0 eth0 

default 202.115.26.1 0.0.0.0 UG 0 0 0 eth0 

这里,作者所在的子网为202.115.26.0/24,默认网关为202.115.26.1,用于发送数据包的主机为202.115.26.224。用于接收数据的两台远端主机的IP地址分别为202.115.26.193和202.112.14.184。202.115.26.193与发送主机202.115.26.224位于同一网段内,通过交换机相连;而202.112.14.184则与发送主机202.115.26.224之间没有直接相连,中间通过多个路由器连接。 




3.1 同网段的测试 
首先测试的是202.115.26.193。路由表设置如下所示: 

[root@cyc src]# route 

Kernel IP routing table 

Destination Gateway Genmask Flags Metric Ref Use Iface 

202.115.26.0 * 255.255.255.0 U 0 0 0 eth0 

default 202.115.26.1 0.0.0.0 UG 0 0 0 eth0 

如下启动程序: 

[root@cyc src]# ./udpsend2 202.115.26.193 on 

send 512 to remote end... 

程序报告发送了512字节的数据给202.115.26.193,尽管对方并没有真正接收数据包。如我们所知,UDP是无连接的,它只负责将数据包发出去,至于对方有没有接收到,UDP是不管的。 

接下来,去掉路由表中的默认路由: 

[root@cyc src]# route del default 

[root@cyc src]# route 

Kernel IP routing table 

Destination Gateway Genmask Flags Metric Ref Use Iface 

202.115.26.0 * 255.255.255.0 U 0 0 0 eth0 

再次运行udpsend2,结果如下: 

[root@cyc src]# ./udpsend2 202.115.26.193 on 

send 512 to remote end... 

结论:当目的主机与发送主机处于同一个网段内时,无论内核是否设置了默认路由,数据包总能发送给目的主机(尽管目的主机可能并没有在等待接收数据包)。但是,子网必须要在路由表中。比如,如果上面将子网202.115.26.0所在的路由表项也删除的话,就会得到网络不可达的结果,这一点是很容易理解的。 

3.2 不同网段测试 
其次来测试当发送主机与目的主机位于不同网段时的情况,此时的目的主机为202.112.14.184。发送主机上的路由表设置如下,并且发送主机能够ping通目的主机。这表明虽然202.112.14.184与202.115.26.224之间虽然并没有直接相连,但是能够通过路由器转发数据包来完成通信: 

[root@cyc src]# route 

Kernel IP routing table 

Destination Gateway Genmask Flags Metric Ref Use Iface 

202.115.26.0 * 255.255.255.0 U 0 0 0 eth0 

default 202.115.26.1 0.0.0.0 UG 0 0 0 eth0 

[root@cyc src]# ping 202.112.14.181 

PING 202.112.14.181 (202.112.14.181) 56(84) bytes of data. 

64 bytes from 202.112.14.181: icmp_seq=1 ttl=125 time=0.364 ms 

运行程序udpsend2,结果显示如下: 

[root@cyc src]# ./udpsend2 202.112.14.181 on 

sendto(): Network is unreachable 

将默认路由(也即网关地址)从路由表中删除,再次运行程序: 

[root@cyc src]# ./udpsend2 202.112.14.181 on 

sendto(): Network is unreachable 

结论:无论路由表中是否包含了默认路由(也即网关地址),当两主机之间没有直接相连时(即使能够通过中间路由器通信),设置了SO_DONTROUTE也将导致网络不可达错误。而网络不可达错误通常程序中有函数返回ENETUNREACH的结果。在本例中,ENETUNREACH错误是由sendto()函数引发的。 

4 总的结论 
根据上面的测试结果,我认为:SO_DONTROUTE套接口选项并没有完全绕过路由表,而只是绕过了路由表中网关(或者说默认路由)所在的表项。因此,Linux的socket(7)中的说明是正确并且无歧义的,而《UNIX网络编程(第1卷)》中的说法则带有一定的歧义性,容易给人造成误解。当然,这也可能是译者翻译不准确的缘故。总的来讲,当目的主机与发送方直接相连时,可以通过SO_DONTROUTE来实现从指定的网络接口发出数据包。但是,当接受者与发送方并没有直接相连时,就不能这样做了。而需要考虑通过SO_BINDTODEVICE选项或者RAW套接口或者PACKET套接口来实现。(注:RAW套接口能实现网络层的直接数据访问,而PACKET套接口则能实现链路层的直接数据访问)。 


一个人的评论:我认为《UNIX网络编程(第1卷)》的说话还是比较准确的,他和相连不相连没有关系,关键是“例如,对于IPv4,分组被指向适当的本地接口,也就是目的地址的网络和子网部分所确定的本地接口。如果本地接口不能由目的地址确定(例如,目的主机不再一个点对点链路的另一端上,也不在一个共享网络上),则返回ENETUNREACH错误 


所以如果你执行这条命令后 就可以发出去了sudo ip route add 202.112.14.0/24 dev eth0 

Tags:SO_DONTROUTE套接口
相关文章列表:
关于开源中文网 - 联系我们 - 广告服务 - 网站地图 - 版权声明