NAT实际上就是一个代理,代理NAT内部的所有机器,NAT的分类就是按照“如何代理”来讲的,不管是基于连接的NAT实现,还是基于包的NAT实现,都要处理好一个映射关系,那就是转换后的包的返回包如何转换回原始的。
Linux的NAT是对称NAT,而不是锥形NAT,虽然很多时候Linux的NAT看起来更像是一个锥形NAT。
只需要明白Linux实现NAT的本质,即使不明白NAT的分类也无所谓。Linux实现NAT的原则很简单:
用最小的代价保证一个tuple五元组在转换前的五元组和转换后的五元组中的唯一性。
注意,这两个五元组是分开存放的,它们在hash链表层面并没有任何关联。实际上这是ip_conntrack的实质,ip_conntrack在存放连接的时候是按照“两个方向”分别存放的, 因此需要一个五元组在两个方向的hash表中是唯一的才可以。有人说Linux可以实现锥形NAT,然而这个锥形NAT实际上是很脆弱的,考虑以下的连接:
(PC:1234->SERVER:80)
因此NAT上会出现下面的tuple(五元组):
正方向:(PC1:1234->SERVER:80)
反方向:(SERVER:80->NAT:1234)
如果此时PC2同样使用端口1234连接了SERVER2的80,那么NAT上将会出现新的两个tuple:
正方向:(PC2:1234->SERVER2:80)
反方向:(SERVER2:80->NAT:1234)
此时如果PC1也想用1234端口连接SERVER2的80,如下:
正方向:(PC1:1234->SERVER2:80)
反方向:?
为何是一个“?”呢?如果Linux NAT是锥形的NAT,那么根据锥形的含义,NAT需要将PC2的源组转换为(NAT:1234->SERVER2:80),这样的话,反方向的tuple就是(SERVER2:80->NAT:1234)了,和PC2的反方向tuple相冲突,这是不可以的,Linux自动将其源端口作了改变,转换为(NAT:不是1234->SERVER2:80),进而反方向tuple成了(SERVER2:80->NAT:不是1234)了,因此解决了唯一性问题,在“第三者的帮助下”,Linux被动成了对称NAT...
至于锥形NAT中的Port Restricted Cone,Restricted Cone,Full Cone这三类锥子,就不用讨论了,这三类锥子的目的在于限制反向流量的穿透性,一种分类上的清晰化会使策略实现起来更加方便,之所以将NAT分为锥形和对称的,并且锥形的又分为三个类别,这完全是为了实现RFC3489,而这个RFC是打洞技术的基石,也就是说,分类是为了描述打洞实现时更加方便简洁。反过来,NAT之所以可以被穿越(成功打洞),正是在于描述NAT本身的RFC没有规定其实现方式,也没有规定“不让穿越”,Linux只是按照RFC保证了NAT的结果,这也是实现之一而不是唯一的实现。
本文最开始谈到,NAT需要解决一个映射问题,而一个包携带的所有识别信息的交集就是五元组,因此NAT可以起到连接限制的作用,NAT设备能开放的所有连接数量由它所持有的公网IP地址以及可用的端口数量决定,比如下面的规则:
iptables -t nat -A POSTROUTING -p --dport 80 -j SNAT --to-source X.X.X.X:80
那么整个NAT后方,同时则只有一台机器的一个socket能连接到同一个目标的80端口,否则反方向的tuple就会冲突。可以通过/proc/net/stat/ip_conntrack文件的insert_failed那一列看到所有后续的连接反方向tuple由于和既有的反方向tuple相冲突而计数器递增。
Linux不区分以上这些由RFC3489而引发的XX,甚至都不区分锥形NAT和对称NAT,只保证NAT的结果即可。