在分析SNAT模块的代码时,就已经分析过函数get_unique_tuple与find_appropriate_src,那时对于为什么要调用find_appropriate_src函数使用已经转换的源ip、端口不清楚,最近听说这个功能可以用于udp打洞功能,就重新看了udp打洞的概念,就写下了这篇文档


1.SNAT转换的特殊处理

linux netfilter模块中,对于SNAT转换有什么特殊处理吗?

nat模块中,当对连接跟踪项进行NAT转换时,会调用get_unique_tuple获取一个唯一的nf_conntrack_tuple变量,而在该函数中,对于SNAT,会调用函数find_appropriate_src判断已进行过NAT转换的连接跟踪项中,是否存在源ip、端口与当前需要进行SNAT的连接跟踪项的原始方向的源ip、端口相同的连接跟踪项,若有,则使用此连接跟踪项已经转换后的源ip、源端口,作为当前需要转换的连接跟踪项的源ip、源端口,而不改变当前需要转换的连接跟踪项的目的ip、目的端口的值,最后还要对这个新得出的reply方向的nf_conntrack_tuple变量做唯一性判断,通过唯一性判断后即可以使用。

 

 

 

下面用实例说明一下:

即若有一个已转换的连接跟踪项A(lanpc1)

其原始方向的tuple值为:

original_tuple.src_ip=192.168.1.123 original_tuple.src_port=3001

original_tuple.dst_ip =68.192.10.11 original_tuple.dst_port=2009.

应答方向的tuple值为:

reply_tuple.src_ip=68.192.10.11 reply_tuple.src_port=2009,

reply_tuple.dst_ip=57.29.10.1 reply_tuple.dst_port=3001.

 

这时lanpc1(ip=192.168.1.123),又要进行一个新的连接,即三层ip分别为src_ip=192.168.1.123 src_port=3001,dst_ip=69.192.10.11,dst_port=2003的数据包,当该数据包经过NAT服务器时,则首先为该数据流创建连接跟踪项B,该连接跟踪项的originalreply方向的nf_conntrack_tuple变量的值如下:

new_original_tuple.src_ip=192.168.1.123 new_original_tuple.src_port=3001,

new_original_tuple.dst_ip=69.192.10.11 new_original_tuple.dst_port=2003.

new_reply_tuple.src_ip=69.192.10.11 new_reply_tuple.src_port=2003,

new_reply_tuple.dst_ip=192.168.1.123 new_original_tuple.dst_port=3001.

然后当数据进入POSTROUTING hook点后,就要进行SNAT转换,即会调用函数get_unique_tuple->find_appropriate_src,这次查找已经进行NAT转换的连接跟踪项后,发现连接跟踪项A的原始方向的nf_conntrack_tuple变量的src_ipsrc_port与连接跟踪项B的原始方向的nf_conntrack_tuple变量的src_ipsrc_port值相等,则经过find_appropriate_src后得出一个新的original_tuple变量记为N_original_tuple

N_original_tuple.src_ip=57.29.10.1 N_original_tuple.src_port=3001,

N_original_tuple.dst_ip=69.192.10.11 N_original_tuple.dst_port=2003.

N_original_tuple变量的reply方向的变量记为N_reply_tuple,值如下:

N_reply_tuple.src_ip=69.192.10.11 N_reply_tuple.src_port=2003,

N_reply_tuple.dst_ip=57.29.10.1 N_reply_tuple.dst_port=3001.

然后判断已确认的连接跟踪项中,有没有存在nf_conntrack_tuple变量为N_reply_tuple的,若不存在,则将连接跟踪项Breply方向的nf_conntrack_tuple变量设置为N_reply_tuple,此处我们假设不存在。则经过SNAT后的

连接跟踪项Boriginal 方向的nf_conntrack_tuple变量为:

new_original_tuple.src_ip=192.168.1.123 new_original_tuple.src_port=3001,

new_original_tuple.dst_ip=69.192.10.11 new_original_tuple.dst_port=2003.

 

reply方向的nf_conntrack_tuple变量为:

new_reply_tuple.src_ip=69.192.10.11 new_reply_tuple.src_port=2003,

new_reply_tuple.dst_ip=57.29.10.1 new_original_tuple.dst_port=3001.

 

对于lanpc1上相同源ip与源端口、不同目的ip与目的端口的两条数据流,

在经过允许linux系统的NAT服务器后,其经过SNAT映射后的源ip地址与端口

号也是相同的。

这就是linux netfilter对于SNAT转换中,对于相同源ip与端口、不同目的ip

端口的多条数据流进行SNAT映射时,尽量保证映射后的源ip、端口也是相同的。

 

2.UDP打洞

下面就分析下该特性能否实现udp打洞功能

所谓udp打洞即让处于私有网络中的Internet主机之间建立双向UDP连接的udp穿越方法,其基本原理是让位于NAT后的两台主机与处于公网地址空间的第三台服务器相连,

NAT设备建立好UDP状态信息就转为直接通信。

相应场景如下:

NAT-A:网关设备,支持NAT功能,使用linux netfilter模块实现NAT功能。外网地 址为78.27.19.12

client-A :NAT-A服务器后的lan侧设备,需要进行SNAT映射后才能与外网通信,ip:192.168.1.21

 

NAT-B:网关设备,支持NAT功能,使用linux netfilter模块实现NAT功能。外网地 址为62.11.29.18

client-B :NAT-B服务器后的lan侧设备,需要进行SNAT映射后才能与外网通信,ip:192.168.0.12

 

server-C:允许某种网络协议。ip:66.98.28.12

 

client-Aserver-C通信进行通信时,其ip地址转换如下:

192.168.1.21:8990->66.98.28.12:2800经过NAT-A后,变为

78.27.19.12:8990->66.98.28.12:2800.

client-Bserver-C通信进行通信时,其ip地址转换如下:

192.168.0.12:5801->66.98.28.12:3980经过NAT-B后,变为62.11.29.18:5801->66.98.28.12:3980.

 

当上述通信完成以后,server-C就拿到了AB的公网ip地址,这时,如果client-Aclient-B之间要进行通信时,server-C就把client-Aclient-B的公网ip地址告诉彼此,client-Aclient-B.

 

将报文发送到相应的公网地址即可。

client-A 要和client-B通信,则发送的数据为192.168.1.21:8990->62.11.29.18:5801

client-B 要和client-A通信,则发送的数据为 192.168.0.12:5801->78.27.19.12:8990

如果要实现上述功能,就要保证client-A的数据192.168.1.21:8990->62.11.29.18:5801在经过NAT-A后,应该映射为78.27.19.12:8990->62.11.29.18:5801client-B的数据192.168.0.12:5801->78.27.19.12:8990在经过NAT-B后,应该映射为62.11.29.18:5801->78.27.19.12:8990

 

3.SNAT模块支持UDP打洞吗

现在我们把linux netfilterSNAT模块的特殊处理与UDP打洞的应用场景结合起来,发现linux netfilterSNAT对于同一个源ip与不同端口、不同目的ip地址的多个数据流,刚好能够尽量保证经SNAT转换后的源ip与端口号相同,这是不是说明linux netfilterSNAT模块是支持UDP打洞的呢?


貌似是可以的,但是需要详细分析一下,还以上面udp打洞的实例为准,继续分析。

 

我们假设现在client-A先发生到client-B的数据包,即192.168.1.21:8990->62.11.29.18:5801经过NAT-A后,变为

78.27.19.12:8990->62.11.29.18:5801,此时数据从NAT-A发出,到达NAT-BNAT-B首先会建立一个连接跟踪项C,其original方向的nf_conntrack_tuple变量的值为:

C_original_tuple.src_ip=78.27.19.12,C_original_tuple.src_port=8990

    C_original_tuple.dst_ip=62.11.29.18 ,C_original_tuple.dst_port=5801

original方向的nf_conntrack_tuple变量的值为:

C_reply_tuple.src_ip=62.11.29.18 C_reply_tuple.src_port=5801,

C_reply_tuple.dst_ip=78.27.19.12 C_reply_tuple.dst_port=8990

然后进入PRE_ROUTING链,由于没有找到相关的DNAT操作,则会进入NAT-B协议栈处理,由于NAT-B 没有任何socket78.27.19.12:8990发送过数据包,则数据包会被丢弃掉。但是,请注意,此时在连接跟踪项的hash确认链表中已经把该连接跟踪项添加进行了。

此时client-B开始向client-A发送数据包,即192.168.0.12:5801->78.27.19.12:8990,数据进入NAT-B后,也会创建一个连接跟踪项,经过协议栈路由查找后,会进入POSTROUTING链,此时会进行SNAT操作,

按照我们对linux netfilter SNAT的分析,在进行SNAT操作时,会调用get_unique_tuple->find_appropriate_src,由于存在相同源ip、端口的连接跟踪项(client-B->server-C),则通过find_appropriate_src获取的reply方向的nf_conntrack_tuple变量的值为

B_N_reply_tuple.src_ip=78.27.19.12 B_N_reply_tuple.src_port=8990

B_N_reply_tuple.dst_ip=62.11.29.18 B_N_reply_tuple.dst_port=5801.

接着就会调用nf_nat_used_tuple查找当前已确认的连接跟踪项中是否有使用与B_N_reply_tuple变量相同的值,

接着就找到了client-A刚才发过来且被丢弃的数据包对应的连接跟踪项,这样话就需要B_N_reply_tuple变量的端口号了,即修改B_N_reply_tuple.dst_port的值,即不能设置为5801了,这样的话,client-B的数据包在经过NAT-B后就被修改为62.11.29.18:X->78.27.19.12:8990(X不等于5801),这样的数据发送到NAT-A后同样会被丢弃掉的,因NAT-A现在只会将62.11.29.18:5801->78.27.19.12:8990的数据转发给client-A

看来linux netfilterSNAT特殊处理并不能支持UDP打洞呢,主要就是因为netfilter是以连接跟踪为主的,不管数据是否被丢掉,都会为该数据流创建一个连接跟踪项,这也就是client-A先发数据到NAT-B后,client-B再发送到client-A的数据进入NAT-B后,导致源端口需要重新映射的原因。

 

综上,linux netfilterSNAT模块虽然尽量保证不修改源端口值,但是由于netfilter是以连接跟踪为基础的,导致无法实现udp打洞功能。

 

 

对于udptcp穿越功能,可以通过upnp igd实现端口映射(其实其端口映射功能就是使用netfilterDNAT功能,实现公网数据到私网的映射,上面我们分析的udp打洞功能没有实现的case,如果也添加一个DNAT操作,肯定也是能够实现client-Aclient-B的通信的),从而实现client-Aclient-B的直接通信。

upnp igd实现的端口映射功能,要求lanpc与网关均需要安装upnp相关的客户端与服务器。然后就能实现端口映射了。目前的网关设备基本上都支持upnp 端口映射功能。

Logo

更多推荐