对于提供服务的程序,一般而言喜欢使用0.0.0.0这个地址,但是如果这个服务是UDP的,那么就有可能出现一些问题,比如如果某块网卡配置了多个IP地址,那么问题就可能重现。最近使用OpenVPN的时候就遇到了这样的问题。OpenVPN建议使用UDP协议,然而正是由于使用了UDP协议才出现了问题。以下几个要点帮助解决这样的问题,记之备忘:
-
udp服务没有bind到特定地址,而是0.0.0.0
-
没有bind地址的udp服务的返回包在路由后添加源地址
-
对于没有bind到特定地址的udp服务器的返回包,内核协议栈在路由后为其添加符合条件的网卡上的第一个最匹配的primary(非secondary)地址
-
Linux的Netfilter的OUTPUT挂接在路由后,并且1中所述的服务器的第一个返回包在此HOOK处初始化NAT使用的nf_conntrack数据结构(后面说原因)
-
udp客户端连接udp服务器的secondary地址,该连接包在进入udp服务器的PREROUTING这个HOOK的时候会初始化一个nf_conntrack(忽略port):
5.1 client address/server secondary address<->server secondary address/client address
-
udp服务器的返回包的源IP由于3,并没有被初始化成secondary address,因此在经过OUTPUT这个HOOK点时,由于没有找到既有的conntrack,又初始化了一条新的contrack:
6.1 server primary address/client address<->client address/server primay address
-
紧接着进入SNAT的规则链去匹配,匹配到了不该匹配到的条目,该条目本应该作用于主动外出的数据包的,对于进入的数据包,理应在PREROUTING的时候就已经匹配过了。
-
因此需要添加一条规则,将udp客户端连接的secondary地址定向到primary地址: iptables -t nat -A PREROUTING [一系列的udp匹配条件] -i eth3 -j DNAT –to-destination $primary_address 该条规则使得udp客户端的连接包在PREROUTING这个点上就能初始化nf_conntrack数据结构
-
unique_tuple回调函数中一般都有以下的一句话:
if (maniptype == IP_NAT_MANIP_DST)
return 0;
也就是说,对于DNAT来讲,不改变端口,而对于SNAT来讲,需要根据内核协议栈的一系列算法来重新选择一个源端口,该算法恨是简单,就是只要选择了端口后的一个tuple与其它的tuple不冲突即可,因此对于本例而言,由于目的端口大于1024,因此从1024开始选择,1024就没有被6.1这样的tunple使用,那么就是它了。
因此,千万不能将SNAT用作修改服务返回包,因此理论上,服务是被动连接的,在返回包发出之前,客户端的连接包早已该将nf_conntrack建立好才对,如果非要如此错误使用的话,得到的结果很可能就是服务器返回包的源端口被修改掉(UDP情况),好在OpenVPN还可以在客户端用float参数。