http://blog.csdn.net/rstevens/archive/2006/05/08/713219.aspx
1. 概述:
在多 link 环境中,如何保证数据在正确的 link 上传输是一个棘手的问题。
本文主要分析 linux socket 绑定机制的实现,从而帮助开发人员更好的了解 socket 绑定的本质。
2. linux kernel 提供给 user space 的接口
linux 提供一个新的 setsockopt() 选项 SO_BINDTODEVICE 作为 user space 的接口。
源码:net/core/sock.c:
在编译内核的时候,必须打开 CONFIG_NETDEVICES 选项,否则 SO_BINDTODEVICE 不起作用;
在 struct sock 结构中,专门有一个域 bound_dev_if;在调用 setsockopt() 后,bound_dev_if 被设置为所绑定的 interface 的 index 。bound_dev_if 将会影响后续的数据包的发送和接收。
sk->bound_dev_if = dev->ifindex;
socket 绑定之所以能保证数据在所绑定的 interface 上传输,是因为 linux 在路由选择的时候,需要考虑 socket 所绑定的interface(也就是 sock->bound_dev_if)。下面从发送和接收数据两个方面来分析这种影响:
3. socket 绑定对发送数据包的影响
源码:include/net/route.h
net/ipv4/route.c
影响1:
任何传输层协议,最终都要走到 ip_route_output() 进行路由选择,linux 是根据 socket 的源地址、目的地址、TOS值以及所绑定的 interface(如果有)作为关键字进行路由查找的。因此,在 socket 绑定情况下,如果一个路由项的源地址、目的地址、TOS 都和 socket 匹配,但是 out interface 和 socket 所绑定的 interface 不一致,那么该路由项也不会被选中。
这也正是绑定的意义所在:必须保证数据包被路由到所绑定的 interface 上。
影响2:
socket 绑定对发送数据的路由选择的另一个重要影响是:即使在 route cache 和 route table 中都查不到路由,该数据包仍然会被送到所绑定的 interface 上。具体代码在ip_route_output_slow() 中。
4. socket 绑定对接收数据包的影响
源码:net/ipv4/ip_input.c
Net/ipv4/tcp_ipv4.c
Net/ipv4/udp.c
接收数据包需要在 ip_route_input() 中进行路由选择,socket 绑定对此阶段没有影响。
路由以后,到本地的包走到ip_local_deliver(),ip_local_deliver_finish(),此后,根据传输层协议的不同,分别走到tcp_v4_rcv() 或 udp_rcv()。
这两个函数都首先需要找到该数据包所对应的 struct sock 结构:
tcp_v4_rcv() 在查找sock 时,会严格检查 sock 的bound_dev_if 与数据包进来的 interface 是否一致,如果两者不一致,则该 sock 不会被匹配。
udp_rcv() 在查找 sock 时,也检查这两个值是否匹配(具体代码在udp_v4_lookup_longway()中),但是就算不匹配,也可能接收这个包,原因是 UDP 相对于 TCP 来说,在一致性检查上没有那么严格。它的策略是:在源地址、目的地址、目的端口、绑定的 interface 四项检查中,寻找匹配程度最高的那个 sock。
5. socket 绑定使用时候的注意事项
不要将一个 socket 绑定到多个不同的 interface 上,这样会导致不可预料的后果。因为当你第一次绑定到一个interface 上发送数据后,在 route cache 中将会保存一个路由项,如果此时将该 socket 绑定到另一个 interface,那么从前一个 interface 上回来的数据包在路由的时候,就可能查不到对应的 socket ,从而被丢弃。