如何实现P2P穿透

转: http://blog.csdn.net/lffy5267/article/details/1681530
iptables与stun 

Stun协议(Rfc3489、详 http://www.ietf.org/rfc/rfc3489.txt)将NAT粗略分为4种类型,即Full Cone、Restricted Cone、Port Restricted Cone和Symmetric。举个实际例子(例1)来说明这四种NAT的区别: 
A机器在私网(192.168.0.4) 
NAT服务器(210.21.12.140) 
B机器在公网(210.15.27.166) 
C机器在公网(210.15.27.140) 
现在,A机器连接过B机器,假设是 A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:8000)-> B(210.15.27.166:2000)。 
同时A从来没有和C通信过。 
则对于不同类型的NAT,有下列不同的结果: 
Full Cone NAT:C发数据到210.21.12.140:8000,NAT会将数据包送到A(192.168.0.4:5000)。因为NAT上已经有了192.168.0.4:5000到210.21.12.140:8000的映射。 
Restricted Cone:C无法和A通信,因为A从来没有和C通信过,NAT将拒绝C试图与A连接的动作。但B可以通过210.21.12.140:8000与A的192.168.0.4:5000通信,且这里B可以使用任何端口与A通信。如:210.15.27.166:2001 -> 210.21.12.140:8000,NAT会送到A的5000端口上。 
Port Restricted Cone:C无法与A通信,因为A从来没有和C通信过。而B也只能用它的210.15.27.166:2000与A的192.168.0.4:5000通信,因为A也从来没有和B的其他端口通信过。该类型NAT是端口受限的。 
Symmetric NAT:上面3种类型,统称为Cone NAT,有一个共同点:只要是从同一个内部地址和端口出来的包,NAT都将它转换成同一个外部地址和端口。但是Symmetric有点不同,具体表现在:只要是从同一个内部地址和端口出来,且到同一个外部目标地址和端口,则NAT也都将它转换成同一个外部地址和端口。但如果从同一个内部地址和端口出来,是到另一个外部目标地址和端口,则NAT将使用不同的映射,转换成不同的端口(外部地址只有一个,故不变)。而且和Port Restricted Cone一样,只有曾经收到过内部地址发来包的外部地址,才能通过NAT映射后的地址向该内部地址发包。 
现针对Symmetric NAT举例说明(例2): 
A机器连接过B机器,假使是 A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:8000)-> B(210.15.27.166:2000) 
如果此时A机器(192.168.0.4:5000)还想连接C机器(210.15.27.140:2000),则NAT上产生一个新的映射,对应的转换可能为A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:8001)-> C(210.15.27.140:2000)。此时,B只能用它的210.15.27.166:2000通过NAT的210.21.12.140:8000与A的192.168.0.4:5000通信, C也只能用它的210.15.27.140:2000通过NAT的210.21.12.140:8001与A的192.168.0.4:5000通信,而B或者C的其他端口则均不能和A的192.168.0.4:5000通信。 
通过上面的例子,我们清楚了Stun协议对NAT进行分类的依据。那么,我们现在根据上述分类标准(或例子),来简要分析一下iptables的工作原理(仅指MASQUERADE、下同),看看他又是属于哪种NAT呢? 
首先,我们去网上下载一个使用Stun协议检测NAT的工具,网址 http://sourceforge.net/projects/stun/,使用该工具对iptables的检测结果是Port restricted NAT detected。 
我们先不要急着接受这个检测结果,还是先来分析一下iptables的工作原理吧! 
iptables在转换地址时,遵循如下两个原则: 
1、尽量不去修改源端口,也就是说,ip伪装后的源端口尽可能保持不变。(即所谓的Preserves port number) 
2、更为重要的是,ip伪装后只需保证伪装后的源地址/端口与目标地址/端口(即所谓的socket)唯一即可。 
仍以前例说明如下(例3): 
A机器连接过B机器,假使是 A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:5000)-> B(210.15.27.166:2000)。(注意,此处NAT遵循原则1、故转换后端口没有改变) 
如果此时A机器(192.168.0.4:5000)还想连接C机器(210.15.27.140:2000),则NAT上产生一个新的映射,但对应的转换仍然有可能为A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:5000)-> C(210.15.27.140:2000)。这是因为NAT(转换后210.21.12.140:5000)-> B(210.15.27.166:2000)和NAT(转换后210.21.12.140:5000)-> C(210.15.27.140:2000)这两个socket不重复。因此,对于iptables来说,这既是允许的(第2条原则)、也是必然的(第1条原则)。 
在该例中,表面上看起来iptables似乎不属于Symmetric NAT,因为它看起来不符合Symmetric NAT的要求:如果从同一个内部地址和端口出来,是到另一个目标地址和端口,则NAT将使用不同的映射,转换成不同的端口(外部地址只有一个,故不变)。相反,倒是符合除Symmetric NAT外的三种Cone NAT的要求:从同一个内部地址和端口出来的包,NAT都将它转换成同一个外部地址和端口。加上iptables具有端口受限的属性(这一点不容置疑,后面举反例证明之),所以好多检测工具就把iptables报告为Port restricted NAT类型了。 
下面仍以前例接着分析(例4): 
在前例中增加D机器在A同一私网(192.168.0.5) 
A机器连接过B机器,假使是 A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:5000)-> B(210.15.27.166:2000) 
D机器连接过C机器,假使是 D(192.168.0.5:5000)-> NAT(转换后210.21.12.140:5000)-> C(210.15.27.140:2000) 
由iptables转换原则可知,上述两个转换是允许且必然的。 
如果此时A机器(192.168.0.4:5000)还想连接C机器(210.15.27.140:2000),则NAT上产生一个新的映射,但对应的转换则变为A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:5001)-> C(210.15.27.140:2000)。这是因为,如果仍然将其转换为210.21.12.140:5000的话,则其所构成的socket(210.21.12.140:5000->210.15.27.140:2000)将和D->C的socket一致,产生冲突,不符合iptables的第2条原则(注意,此处以5001表示转换后不同的端口,但事实上,iptables却并不按照内部端口+1的原则来产生新的端口)。在本例中我们注意到,从同一个内部地址和端口A(192.168.0.4:5000)出来,到不同的目标地址和端口,则NAT使用了不同的映射,转换成不同的端口。 
上面这个例子在实际环境中比较少见,我们再以QQ为例举一个真实且常见的例子(例5)。 
假设 
A(192.168.0.4)和 D(192.168.0.5)是同一NAT服务器(210.21.12.140)保护的两台私网机器,都运行了QQ客户端程序。 
B机器在公网(210.15.27.166),运行QQ服务器程序。 
C机器在公网(210.15.27.140),运行QQ客户端程序。 
A上QQ先登陆到B,按照原则1,使用如下映射: 
A(192.168.0.4:4000)-> NAT(转换后210.21.12.140:4000)-> B(210.15.27.166:8000)(原则1,端口不变) 
接着D上QQ也登陆到B,按照原则2,使用如下映射: 
D(192.168.0.5:4000)-> NAT(转换后210.21.12.140:4001)-> B(210.15.27.166:8000)(原则2,scoket不能有重复,此处4001仅表示转换后不同的端口,实际环境中决不是4001) 
然后D欲和公网C(210.15.27.140)上的QQ通信,按照iptables转换原则,使用如下映射: 
D(192.168.0.5:4000)-> NAT(转换后210.21.12.140:4000)-> C(210.15.27.140:4000) 
到此我们发现,和上例一样,从同一个内部地址和端口D(192.168.0.5:4000)出来,到不同的目标地址和端口,则NAT使用了不同的映射,转换成不同的端口。但和上例不一样的是,本例显然普遍存在于实际环境中。 
上面所举两例表明,结论刚好和例3相反,即iptables应该属于Symmetric NAT。 
为什么会出现彼此矛盾的情况呢?首先从NAT分类的定义上来看, Stun协议和iptables对映射的理解不同。Stun协议认为,一个映射的要素是:内部地址端口和NAT转换后地址端口的组合。而在iptables看来,一个映射的要素是:NAT转换后地址端口和外部目标地址端口的组合。另一方面则是Stun协议里Discovery Process给出的测试环境不够全面之故,他只考虑了NAT后面仅有一台私网机器的特例(例3),没有考虑NAT后面可以有多台私网机器的普遍例子(例5)。正是由于这两个原因,直接导致了上述矛盾的发生。所以,凡按照Stun协议标准设计的NAT分类检测工具对iptables的检测结果必然是Port restricted NAT。(事实上,在例3那样的特例下,iptables确实是一个标准的Port restricted NAT) 
那么,iptables究竟属于哪种NAT呢?我们再来回顾一下Stun协议对Cone NAT的要求:所有(或只要是)从同一个内部地址和端口出来的包,NAT都将它转换成同一个外部地址和端口。虽然iptables在部分情况下满足“从同一个内部地址和端口出来的包,都将把他转换成同一个外部地址和端口”这一要求,但它不能在所有情况下满足这一要求。所以理论上,我们就只能把iptables归为Symmetric NAT了。 
下面,我们再来分析一下iptables的端口受限的属性,我们举一个反例证明之(例6),仍以前例说明如下: 
A机器连接过B机器,假使是 A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:5000)-> B(210.15.27.166:2000) 
D机器连接过C机器,假使是 D(192.168.0.5:5000)-> NAT(转换后210.21.12.140:5000)-> C(210.15.27.140:2000) 
现假设iptables不具有端口受限的属性,则另一E机器在公网(210.15.27.153:2000)或C(210.15.27.140:2001)向210.21.12.140:5000发包的话,应该能够发到内部机器上。但事实是,当这个包到达NAT(210.21.12.140:5000)时,NAT将不知道把这个包发给A(192.168.0.4:5000)还是D(192.168.0.5:5000)。显然,该包只能丢弃,至此,足以证明iptables具有端口受限的属性。 
所以,iptables是货真价实的Symmetric NAT。 

附: 
1、Stun全称Simple Traversal of UDP Through NATs,所以本文所涉及的包,皆为UDP包。 
2、本文虽然是针对linux下iptables的分析,但倘若把本文中关键词“iptables”换成“Win2000下的ics或nat”,则本文的分析过程完全适用于Win2000下的ics或nat。即理论上Win2000下的ics或nat也是货真价实的Symmetric NAT,但实际上,凡按照Stun协议标准设计的NAT分类检测工具对其检测结果也必然是Port restricted NAT。其实,不光是linux下iptables、或者Win2000下的ics或nat、再或者任何其他NAT产品,只要他们遵循和iptables一样的两条转换原则,那么他们在Stun协议下的表现是完全一样的。 
3、虽然Win2000下的ics或nat在Stun协议下的表现和iptables完全一样,但其NAT时所遵循的原则1和iptables还是略有差别:iptables对内部私网机器来的所有源端口都将适用Preserves port number,除非确因原则2发生冲突而不得不更换源端口号,但在更换端口号时并不遵循内部端口+1原则(似乎没有规律)。Win2000下的ics或nat则仅对内部私网机器来的部分源端口(1025--3000)适用Preserves port number,对于那些超过3000的源端口号或者因原则2发生冲突的端口号,系统从1025开始重新按序分配端口,在此过程中,仍然遵循如前两个原则,只是原则1不再Preserves port number而已(在不与原则2发生冲突的前提下,尽量重复使用小的端口号,故使用1025的几率远远大于1026、1027…)。 

即将推出其姊妹篇----iptables下udp穿越实用篇----“iptables与natcheck”
iptables与natcheck 

Stun协议(Rfc3489、详 http://www.ietf.org/rfc/rfc3489.txt) 提出了4种NAT类型的定义及其分类,并给出了如何检测在用的NAT究竟属于哪种分类的标准。但是,具体到P2P程序如何应用Stun协议及其分类法穿越NAT,则是仁者见仁、智者见智。(因为Stun协议并没有给出也没有必要给出如何穿越NAT的标准) 
在拙作“iptables与stun”一文中,笔者花大幅精力阐述了iptables理论上属于Symmetric NAT而非Port Restricted Cone。对此,很多人(包括笔者最初学习Stun协议时)心中都有一个疑惑,即仅就Stun协议本身来说,Port Restricted Cone和Symmetric NAT的区别似乎不大,虽然两者的映射机制是有点不同,但他们都具有端口受限的属性。初看起来,这两者在穿越NAT方面的特性也差不多,尤其是对于外部地址欲往NAT内部地址发包的情况。既然如此,又为何有必要把iptables分得这么清呢,本文顺带解决了读者在这一方面的疑惑。 
网 http://midcom-p2p.sourceforge.net/给出了P2P程序具体如何穿越NAT的一个思路,并提供了一个P2P协议穿越NAT兼容性的测试工具natcheck。让我们仍旧用实例(例1)来说明这一思路吧! 
A机器在私网(192.168.0.4) 
A侧NAT服务器(210.21.12.140) 
B机器在另一个私网(192.168.0.5) 
B侧NAT服务器(210.15.27.140) 
C机器在公网(210.15.27.166)作为A和B之间的中介 
A机器连接过C机器,假使是 A(192.168.0.4:5000)-> A侧NAT(转换后210.21.12.140:8000)-> C(210.15.27.166:2000) 
B机器也连接过C机器,假使是 B(192.168.0.5:5000)-> B侧NAT(转换后210.15.27.140:8000)-> C(210.15.27.166:2000) 
A机器连接过C机器后,A向C报告了自己的内部地址(192.168.0.4:5000),此时C不仅知道了A的外部地址(C通过自己看到的210.21.12.140:8000)、也知道了A的内部地址。同理C也知道了B的外部地址(210.15.27.140:8000)和内部地址(192.168.0.5:5000)。之后,C作为中介,把A的两个地址告诉了B,同时也把B的两个地址告诉了A。 
假设A先知道了B的两个地址,则A从192.168.0.4:5000处同时向B的两个地址192.168.0.5:5000和210.15.27.140:8000发包,由于A和B在两个不同的NAT后面,故从A(192.168.0.4:5000)到B(192.168.0.5:5000)的包肯定不通,现在看A(192.168.0.4:5000)到B(210.15.27.140:8000)的包,分如下两种情况: 
1、B侧NAT属于Full Cone NAT 
则无论A侧NAT属于Cone NAT还是Symmetric NAT,包都能顺利到达B。如果P2P程序设计得好,使得B主动到A的包也能借用A主动发起建立的通道的话,则即使A侧NAT属于Symmetric NAT,B发出的包也能顺利到达A。 
结论1:只要单侧NAT属于Full Cone NAT,即可实现双向通信。 
2、B侧NAT属于Restricted Cone或Port Restricted Cone 
则包不能到达B。再细分两种情况 
(1)、A侧NAT属于Restricted Cone或Port Restricted Cone 
虽然先前那个初始包不曾到达B,但该发包过程已经在A侧NAT上留下了足够的记录:A(192.168.0.4:5000)->(210.21.12.140:8000)->B(210.15.27.140:8000)。如果在这个记录没有超时之前,B也重复和A一样的动作,即向A(210.21.12.140:8000)发包,虽然A侧NAT属于Restricted Cone或Port Restricted Cone,但先前A侧NAT已经认为A已经向B(210.15.27.140:8000)发过包,故B向A(210.21.12.140:8000)发包能够顺利到达A。同理,此后A到B的包,也能顺利到达。 
结论2:只要两侧NAT都不属于Symmetric NAT,也可双向通信。换种说法,只要两侧NAT都属于Cone NAT,即可双向通信。 
(2)、A侧NAT属于Symmetric NAT 
因为A侧NAT属于Symmetric NAT,且最初A到C发包的过程在A侧NAT留下了如下记录:A(192.168.0.4:5000)->(210.21.12.140:8000)-> C(210.15.27.166:2000),故A到B发包过程在A侧NAT上留下的记录为:A(192.168.0.4:5000)->(210.21.12.140:8001)->B(210.15.27.140:8000)(注意,转换后端口产生了变化)。而B向A的发包,只能根据C给他的关于A的信息,发往A(210.21.12.140:8000),因为A端口受限,故此路不通。再来看B侧NAT,由于B也向A发过了包,且B侧NAT属于Restricted Cone或Port Restricted Cone,故在B侧NAT上留下的记录为:B(192.168.0.5:5000)->(210.15.27.140:8000)->A(210.21.12.140:8000),此后,如果A还继续向B发包的话(因为同一目标,故仍然使用前面的映射),如果B侧NAT属于Restricted Cone,则从A(210.21.12.140:8001)来的包能够顺利到达B;如果B侧NAT属于Port Restricted Cone,则包永远无法到达B。 
结论3:一侧NAT属于Symmetric NAT,另一侧NAT属于Restricted Cone,也可双向通信。 
显然,还可得出另一个不幸的结论4,两个都是Symmetric NAT或者一个是Symmetric NAT、另一个是Port Restricted Cone,则不能双向通信。 
上面的例子虽然只是分析了最初发包是从A到B的情况,但是,鉴于两者的对称性,并且如果P2P程序设计得足够科学,则前面得出的几条结论都是没有方向性,双向都适用的。 
通过上述分析,我们得知,在穿越NAT方面,Symmetric NAT和Port Restricted Cone是有本质区别的,尽管他们表面上看起来相似。我们上面得出了四条结论,而natcheck网站则把他归结为一条:只要两侧NAT都属于Cone NAT(含Full Cone、Restricted Cone和Port Restricted Cone三者),即可双向通信。而且natcheck网站还建议尽量使用Port Restricted Cone,以充分利用其端口受限的属性确保安全性。目前,国内充分利用了上述思路的具有代表性的P2P软件是“E话通”(www.et66.com)。 
在对natcheck提供的思路进行详细分析后,开始探讨本文主题:iptables与natcheck。 
Natcheck脱胎于Stun协议,由拙作“iptables与stun”一文可知,其对iptables进行的穿越NAT兼容性测试结果必然是GOOD。此外,我在该文中还提到一句,如果在每个NAT后面仅有一个客户端这种特殊情况下,iptables就是一个标准的Port restricted Cone。根据前面natcheck的结论,这样两个iptables后面的客户端应该可以互相穿越对方的NAT。让我们来看一下实际情况(例2)呢? 
仍然参考前例,只是两侧都使用iptables来进行地址转换。(因为采用了iptables,故此处和前例稍有点区别,即转换后源端口不变) 
A与B通过C交换对方地址的初始化环节此处略去,我们从A(192.168.0.4:5000)向B(210.15.27.140:5000)(注意因使用iptables而导致端口和前例不一样)发包开始分析,因为在本例中,两侧均只有一个客户端,我们姑且把iptables简化成Port restricted Cone看待。 
如前例一样,从A(192.168.0.4:5000)到B(210.15.27.140: 5000)的第一个包必不能到达B,但其会在A侧iptables上留下记录,在这条记录没有超时之前(iptables下默认30秒),如果B也向A(210.21.12.140:5000)发包,如前所述,按理该包应该能够到达A,但事实上却是永远到不了。 
难道是natcheck的结论错了,或者是特殊情况下iptables并不是Port restricted Cone(即仍然是Symmetric NAT),我们还是别忙着再下结论,先来看看来两侧iptables上留下的记录吧: 
A侧:cat /proc/net/ip_conntrack | grep 192.168.0.4 | grep udp 
udp 17 18 src=192.168.0.4 dst=210.15.27.140 sport=5000 dport=5000 [UNREPLIED] src=210.15.27.140 dst=210.21.12.140 sport=5000 dport=5000 use=1 
B侧:cat /proc/net/ip_conntrack | grep 192.168.0.5 | grep udp 
udp 17 26 src=192.168.0.5 dst=210.21.12.140 sport=5000 dport=5000 [UNREPLIED] src=210.21.12.140 dst=210.15.27.140 sport=5000 dport=1026 use=1 
把两条记录翻译如下:(关于ip_conntrack文件的分析,请 http://www.sns.ias.edu/~jns/security/iptables/iptables_conntrack.html) 
A(192.168.0.4:5000)-> A侧NAT(转换后210.21.12.140:5000)-> B(210.15.27.140:5000) 
B(192.168.0.5:5000)-> B侧NAT(转换后210.15.27.140:1026)-> A(210.21.12.140:5000) 
奇怪,B到A的包在映射后源端口号怎么变了呢,按理不应该呀?因为按照iptables转换原则(详见“iptables与stun”),要求尽量保持源端口号不变,除非socket有重复。难道B侧NAT上还有重复记录,再cat一下呢? 
B侧:cat /proc/net/ip_conntrack | grep 210.21.12.140 | grep udp 
udp 17 10 src=210.21.12.140 dst=210.15.27.140 sport=5000 dport=5000 [UNREPLIED] src=210.15.27.140 dst=210.21.12.140 sport=5000 dport=5000 use=1 
udp 17 16 src=192.168.0.5 dst=210.21.12.140 sport=5000 dport=5000 [UNREPLIED] src=210.21.12.140 dst=210.15.27.140 sport=5000 dport=1026 use=1 
操!还果真有两条差不多的记录,第一条与NAT无关,是A到B的包在B侧iptables上留下的记录,产生时间上略早于第二条记录,其构成的socket是(210.21.12.140:5000,210.15.27.140:5000)。第二条即B到A的包产生的记录,其构成的socket是(210.15.27.140:1026,210.21.12.140:5000),如果其源端口不改动,即是(210.15.27.140:5000,210.21.12.140:5000),还真和第一条记录重复了呢,怪不得转换后需要修改源端口,也怪不得B发包到不了A。 为什么是这样的结果呢?我们知道,iptables是一个有状态的防火墙,他通过连接跟踪模块来实现状态检测的功能,该模块检查所有到来的数据包,也就是说,该模块不仅对NAT起作用,而且对普通的包过滤也起作用。显然,在上述例子里,A到B的包就是作为普通的包过滤而被记载在B侧iptables的连接跟踪表里,导致后来B到A的包为避免socket重复而不得不改换端口号,从而导致无法实现双向通信。看来,natcheck的结论并没有错,只是由于iptables具有状态检测的新特性导致即使在特殊情况下iptables又从Port restricted Cone变成了Symmetric NAT而已。 
那么,有办法解决这一问题吗?根据连接跟踪的特性,在iptables下,只要启用了NAT,就肯定要启用连接跟踪功能,而只要启用了连接跟踪功能,就必然顺带跟踪普通包过滤(启用连接跟踪后,似乎无法控制不让跟踪普通包过滤),也就是说,只要用NAT,就无法避免上述情况,真残酷!然而,这却是事实,即只要两端都采用了iptables作为NAT,则尽管两侧都通过了natcheck的兼容性测试,但iptables两侧永远也不能互相穿越。 
在“iptables与stun”一文中曾经附带提到,Win2000下的ics或nat在Stun协议下的表现和iptables是完全一样的。那么,在natcheck下,表现是否还一致呢?答案是否定的,虽然Win2000下的ics或nat也具有状态检测的功能,但该状态检测,仅对NAT起作用,不对普通包过滤起作用。所以在两侧都是Win2000下的ics或nat可以作为Port restricted Cone的特殊情况下,是允许被穿越的。另外,在一侧使用iptables,另一侧使用Win2000下的ics或nat,但两者都表现为Port restricted Cone的特殊情况下,从某个方向发起,最终是允许互相穿越的,但是这种穿越不具有对称性,即从另一个方向发起,则永远无法穿越,
为什么是这样的结果呢?我们知道,iptables是一个有状态的防火墙,他通过连接跟踪模块来实现状态检测的功能,该模块检查所有到来的数据包,也就是说,该模块不仅对NAT起作用,而且对普通的包过滤也起作用。显然,在上述例子里,A到B的包就是作为普通的包过滤而被记载在B侧iptables的连接跟踪表里,导致后来B到A的包为避免socket重复而不得不改换端口号,从而导致无法实现双向通信。看来,natcheck的结论并没有错,只是由于iptables具有状态检测的新特性导致即使在特殊情况下iptables又从Port restricted Cone变成了Symmetric NAT而已。 
那么,有办法解决这一问题吗?根据连接跟踪的特性,在iptables下,只要启用了NAT,就肯定要启用连接跟踪功能,而只要启用了连接跟踪功能,就必然顺带跟踪普通包过滤(启用连接跟踪后,似乎无法控制不让跟踪普通包过滤),也就是说,只要用NAT,就无法避免上述情况,真残酷!然而,这却是事实,即只要两端都采用了iptables作为NAT,则尽管两侧都通过了natcheck的兼容性测试,但iptables两侧永远也不能互相穿越。 
在“iptables与stun”一文中曾经附带提到,Win2000下的ics或nat在Stun协议下的表现和iptables是完全一样的。那么,在natcheck下,表现是否还一致呢?答案是否定的,虽然Win2000下的ics或nat也具有状态检测的功能,但该状态检测,仅对NAT起作用,不对普通包过滤起作用。所以在两侧都是Win2000下的ics或nat可以作为Port restricted Cone的特殊情况下,是允许被穿越的。另外,在一侧使用iptables,另一侧使用Win2000下的ics或nat,但两者都表现为Port restricted Cone的特殊情况下,从某个方向发起,最终是允许互相穿越的,但是这种穿越不具有对称性,即从另一个方向发起,则永远无法穿越,具体原理,读者可以参考例2自行分析。
iptables与socks5 

从“iptables和natcheck”一文可知,只要在两端都采用了iptables作NAT后,即使两侧都通过了natcheck的兼容性测试,但iptables两侧永远也不能互相穿越。 
怎么办呢,一种办法是在公网上添加中转服务器,两侧内网机器之间的UDP通讯都由中转服务器来中转(其实只要中转一侧足矣)。这种方法的好处是,因为中转服务器在公网,任何NAT后面的机器都可以和中转服务器建立连接,也就是说,不同内网之间的机器总是可以通过中转服务器实现双向通信的。然而,该办法的缺点是,对中转服务器的要求比较高,包括CPU处理能力和网络带宽两方面,而且客户机之间的通讯延迟也是不可避免的(目前网上最为盛行的Skype是个例外,他采用了分布式中转技术,直接挂在互联网上不在防火墙后面的Skype客户端都可以为他人提供中转服务,因此Skype在提供很高呼叫成功率的同时还能确保超高质量的语音效果)。还有一个更为重要的因素,即中转服务器的标准不统一,导致每种不同类型的P2P程序都需要一个专用的中转服务器。倘若这些中转服务器之间不能做到资源共享的话,必然存在资源浪费现象(标准的中转协议好像正在推出,名称为Traversal Using Relay NAT 即TURN)。 
另一种比较好的办法就是采用Socks5(Rfc1928)代理服务器取代专用的中转服务器,一是因为Socks5能够很好的支持UDP,二是Socks5代理服务器的品种以及在公网上部署的数量都比较多,而且最重要的是Socks5是一个已经标准化了的协议。客户端采用Socks5代理后,其UDP通信通过Socks5中转出去,在对方的P2P程序看来,使用Socks5代理后的客户就像直接连在公网上,也就是说,只要有一方使用Socks5 代理,则另一方不论采用何种NAT,都不会受Stun或natcheck的限制。因此,iptables和Socks5理论上应该合作愉快,但在实现Socks5代理时,如果对Socks5协议理解不够透彻,在和iptables合作时,还是有一些不愉快的。下文试举两例说明之。 
假设一端采用iptables,另一端采用Socks5,现在Socks5后面的QQ要向iptables后面的在线(不隐身)QQ发送消息。当QQ通过Socks5代理登陆QQ服务器时,首先要和Socks5服务器建立一个基于TCP连接的Socks5通道,用于控制QQ和Socks5服务器之间的后续UDP联结。一旦该通道成功建立,Socks5服务器将动态分配一个UDP端口为该QQ担任UDP中转的任务,当QQ给远端iptables后面在线QQ发送消息时,先将消息(UDP包)发送到Socks5服务器上先前分配的UDP端口,再由服务器将该UDP包转发到远端QQ。当UDP包到达远端QQ时,由iptables端口受限的属性可知,除非使用Socks5代理的QQ在线且在不久前(三分钟)曾经收到过iptables后面QQ主动发来的UDP包,否则这个包无法被转发到iptables后面的QQ上。 
那么这个UDP包最终又往哪里去了呢?我们来分析一下,该UDP包到达iptables所在那台Linux主机后的前进流程。当一个UDP包到达Linux时,先交由iptables处理,iptables则先看该UDP包在/proc/net/ip_conntrack文件里有无匹配项,如有,则进行匹配处理;如无,再看PREROUTING链里有无匹配规则,如有,则继续进行匹配处理;如无,由于该包目的地址就是Linux本身,所以iptables将其放入INPUT链。由于INPUT链的默认策略为ACCEPT,则该UDP包将被转交给Linux的本地进程处理,但事实上由于不存在这样的本地进程,结果是Linux向产生该UDP包的机器发回一个icmp-port-unreachable包(注意,这个包是Linux系统产生的,不是iptables的REJECT目标产生的)。如果INPUT链的策略为DROP,则该UDP包就被DROP。 
由上分析可知,一般情况下,最终iptables所在机器将向发送QQ方返回一个icmp-port-unreachable包。当Socks5服务器收到这个icmp-port-unreachable包后,不同的Socks5服务器有不同的处理方式。一些Socks5服务器(如Ccproxy带的Socks5)简单的忽略这个icmp-port-unreachable包,而QQ则由于没有发送成功(没有收到应答信息),继续重复上述过程若干次,但都无法发送成功,最后只能通过服务器中转,虽然由于中转造成发送速度很慢,但总是能够成功发送。而另外一些Socks5服务器(如Wingate带的Socks5)收到远端返回的icmp-port-unreachable包后,认为先前QQ发起的UDP联结无效,通过先前建立的Socks5通道(TCP)给客户端返回一个general SOCKS server failure(Reply Code‘01’),随后立即关闭该通道,同时还关闭了Socks5服务器上担任中转任务的UDP端口。然而QQ似乎并不知道它和Socks5服务器之间的联系已被切断,由于没有发送成功(没有收到应答信息),仍然向Socks5服务器上已关闭的UDP端口发送消息。此时由于Socks5服务器已关闭了相应的UDP端口,所以也向QQ返回一个icmp-port-unreachable包。QQ收到这个包后,继续重复上述过程若干次,最终因超时而失败,并且因为同样的原因也不能通过服务器中转。更为严重的是,即便到了此时,QQ仍然不知道它和Socks5服务器之间的联系已被切断,因此,即使当QQ向其他离线或隐身QQ或不在NAT后面的在线QQ发送消息时,也不能成功。 
上面这个例子表明,由于某些Socks5服务器和Socks5客户端对Socks5协议理解不够透彻,即使采用了Socks5,在和iptables互通时也会导致通讯不畅。 
另一个例子,考虑这样一种情形,Socks5服务器位于公网,内网客户端先通过iptables进行NAT,然后再去连接Socks5服务器。 
先引用Socks5协议(Rfc1928)里如下一段文字: 
The UDP ASSOCIATE request is used to establish an association within the UDP relay process to handle UDP datagrams. The DST.ADDR and DST.PORT fields contain the address and port that the client expects to use to send UDP datagrams on for the association. The server MAY use this information to limit access to the association. If the client is not in possesion of the information at the time of the UDP ASSOCIATE, the client MUST use a port number and address of all zeros. 
Socks5客户端在进行UDP ASSOCIATE时(通过TCP通道),要求用自己将来发送UDP包的地址和端口填充DST.ADDR和DST.PORT字段,以便Socks5服务器确保其分配的UDP端口仅为对应的Socks5客户端提供中转服务。然而,当Socks5客户端跨越NAT去连接Socks5服务器时,考虑到要经过NAT地址转换环节,Socks5客户端无法预知将来Socks5服务器看到的自己用来发送UDP包的地址和端口。针对这种情况,Rfc1928建议Socks5客户端使用0填充DST.ADDR和DST.PORT字段,这样一来,Socks5服务器就不会限制Socks5客户端对中转端口的使用了。然而,目前好多Socks5客户端似乎没有意识到这一点,像最通用的NEC公司的e-Border Driver,便使用了客户端的真实IP地址和端口填充DST.ADDR和DST.PORT字段;而其他一些P2P程序自带的Socks5客户端,仅用0填充DST.ADDR字段。鉴于跨越NAT后,IP地址必然发生变化,导致e-Border Driver永远不能用于跨越NAT的场合,而像iptables,由于具有Preserves port number的属性,所以,那些仅用0填充DST.ADDR字段的Socks5客户端,可以用于跨越iptables的场合。此外,跨越NAT还需要Socks5服务器的配合,遗憾的是,仍然有些Socks5服务器并没有充分理解Rfc1928的这一建议。在这点上,Wingate带的Socks5做得比较好,支持客户端用0填充DST.ADDR和DST.PORT字段;而Ccproxy带的Socks5,在客户端进行UDP ASSOCIATE时,虽然也支持客户端用0填充DST.ADDR和DST.PORT字段,并能在服务器上顺利为客户端分配一个UDP端口,但当后来该UDP端口和客户端通信时,不是发往客户端对应的UDP端口,而是发往客户端前述用0填充的DST.PORT,导致无法实现中转任务。至于NEC公司提供的广为流传的免费Socks5服务器,则干脆申明“if the destination port is 0, we will assume the same port as tcp's”,也将导致Socks5客户端无法跨越NAT。 
在这个例子里,iptables对Socks5代理没有任何影响,纯粹是因为Socks5本身的原因导致通讯不畅。相反,利用iptables的保护源端口特性,还能确保部分Socks5客户端能够顺利跨越用iptables实现的NAT去连接公网上的Socks5服务器。 
希望本文两个例子能对即将或已经实现Socks5代理的开发者有所帮助。  

你可能感兴趣的:(技术贴啊,P2P,穿透)