NAT连通性测试工具以及Flash P2P中的NAT穿透原理

RTMFP connectivity Checker(NAT检查工具):
http://cc.rtmfp.net/


由于公网IP有限,NAT几乎是无处不在。比如我们在家里,牵一个ADSL,用Modem拨号得到一个公网IP,然后在Modem后面再接一个路由使得多个设备能同时上网。路由会有一个公网IP一个私网IP,然后家里的其它设备都用的是私网IP。此时路有器就要完成一个很重要的职责:对于进出它的包做网络地址转换。

NAT是在传输层及以上做的,传输层最主要的2个协议是TCP和UDP,下面只考虑UDP。对于UDP而言,每个包都有很基本的4个要素:src ip、src port、dst ip、dst port。根据在做NAT的时候是否保留src ip和src port,可以把NAT分为这么三种:

Cone: 将src ip映射到一个固定的IP,并且将src port映射到一个固定的Port,无论dst ip和dst port是什么。假如我从192.168.0.2:5000,通过路由器发一个UDP包给66.66.88.88:4000端口,而路由器把这个包的src ip和src port翻译成了173.245.73.182:5000。那么在此后一段时间内,无论我从192.168.0.2:5000往外面的任意IP、任意端口发包,src ip、src port都会被翻译成173.245.73.182:5000。

Single IP address, symmetric:将src ip映射到一个固定的IP,将src port映射到一个随机的port,但是保证对于相同的(dst ip,dst port), src port始终相同。(否则双方没法通话啊,回来的包回给哪个端口呢?)举例:假如我从192.168.0.2:5000,通过路由器发一个UDP包给66.66.88.88:4000端口,而路由器把这个包的src ip和src port翻译成了173.245.73.182:5000。那么在此后一段时间内,无论我从192.168.0.2:5000往外面的任意IP、任意端口发包,src ip都会被翻译成173.245.73.182,但是src port嘛,可就说不准了。
Multiple IP address, symmetric:与上面类似,但是src ip可能会被映射到多个IP中的一个。最典型的就是假如你的网关做了双线接入,那么你访问电信的资源就会走电信的那个IP出去,你访问网通的资源就会走网通的IP出去。这种策略对于做P2P来说简直就是恶梦啊!!

上面只说了发,下面说收。根据对收到的包的过滤限制,可能把Cone分为3种:

Full Cone: 不对收到的包的IP端口做任何限制. 这种NAT通常被称为static NAT,在外面看就像是一个透明代理一样。比如我在192.168.0.1上,用iptables把80端口映射到192.168.0.2的8080上。那么无论从哪来的包,都会被转发过去。

Restricted Cone: Restricted Cone会对收到的包的IP做限制。假如我从192.168.0.2:5000,通过路由器173.245.73.182:5000端口发给173.245.88.88:4000端口。然后对方从173.245.88.88:4000给173.245.73.182:5000回了一个包,那么毫无疑问我们的路由器应该接受这个包,并转发给192.168.0.2:5000。假设有另外一台我根本不认识的机器,比如66.66.99.99要给173.245.73.182:5000发包,那么我们的路由器就会丢弃这个包。

Port Restricted Cone: 它就是在Restricted Cone的基础上对端口也做了限制。假如我从192.168.0.2:5000,通过路由器173.245.73.182:5000端口发给173.245.88.88:4000端口。然后对方从173.245.88.88:4000给173.245.73.182:5000回了一个包,那么毫无疑问我们的路由器应该接受这个包。如果你换个端口,从173.245.88.88:6000给173.245.73.182:5000回包,我们的路由器就会丢弃。

对于Cone,可采用很简单的NAT穿透的方式建立P2P直连。

RTMFP中的P2P打洞过程:

假设一共三个角色:Server、Initiator(Peer1)、Target(Peer2)。Target已经与Server建立RTMFP连接。



上图中曲线代表NAT设备。

在连接建立之后,Target就有了一个唯一的PeerID,Server通过UDP包头可以得知这个Peer的公网IP和端口。

此外,在建立连接后,Target还会通过一个名为SetPeerInfo的RPC调用,将自己的IP、端口号汇报给Server。

例如:

2012-06-20 17:17:08 9640 (i)2581173 rtmfp send message, session: 0BC276C8 flow: 056A5C00 kMsgCmdEx idByte=17 streamId=0 time=84 trxId=0 kEncodingAMF0 setPeerInfo cmdData=( kNullType ) arg0=( kStringType "192.168.146.1:61920" ) arg1=( kStringType "192.168.15.1:61920" ) arg2=( kStringType "10.4.8.84:61920" )

然后Server就会维护一张映射表,key是PeerID,value是地址(IP和端口号)列表。

现在Initiator要连接Target。

Initiator首先向Server发InitiatorHello请求,其中带上Target的peerID。Initiator此时并不知道Peer2的地址,它只知道Target的PeerID。
Server向Initiator回复一个Redirect消息,里面包含Target的公网IP端口(通过UDP包头可以得到)、以及Target的所有内网IP端口, 如下面这条日志所示:
2012-06-20 17:17:17 9640 (d)0000000 core redirect { epd: 210fce584f9dcf7962d475af4128437d71233bb99f8debd2da8f9f558d60cf6071bb tag: 24dfff0da10b24d3b33af1931e0cf699 fromAddr: 173.245.73.182:61922 instanceInterfaceID: 2 } to { derived 66.66.99.99:61920;reported 192.168.146.1:61920;reported 192.168.15.1:61920;relay 66.66.88.88:19351;}
RTMFP Redirect消息就像HTTP的302一样,收到者(Initiator)需要向新地址重新建立连接,即发送InitiatorHello包。这里面所说的relay应该是经Server中转的意思,具体流程我还不明白。
同时,Server给Target发一个Forward Message,里面包含Peer1的公网IP端口。 如下面这条日志所示:
2012-06-20    17:17:17    9640    (d)0000000    core forward { epd: 210fce584f9dcf7962d475af4128437d71233bb99f8debd2da8f9f558d60cf6071bb tag: 24dfff0da10b24d3b33af1931e0cf699 fromAddr: 173.245.73.182:61922 instanceInterfaceID: 2 }    -
其中epd就是Initiator的peerID,173.245.73.182:61922 就是Initiator公网IP端口。
Forward Message就像servlet里面的forward一样,收到者(Target)需要直接给原始的请求者(Initiator)回下一个握手包(即Response Hello)。
对Initiator来说,哪个地址先回给它第一个Response Hello包,它就跟哪个地址继续握手。
假设Initiator和Target处于同一个NAT中,那么会尽量通过redirect消息进行直连,以后的交互就跟NAT没有关系。但是怎么控制这个的呢?我还不明白。

假设Initiator和Target处于不同的NAT之中,两个NAT类型都是Port Restricted Cone,那么Peer1收到Redirect消息的时候,然后向Peer2发送InitiatorHello的时候,就在Peer1的NAT上打了一个洞。虽然这个InitiatorHello也许会被Peer2的防火墙拦掉,但是没有关系,正因为有了这个洞,Peer2的ResponseHello才能进来。

假设Initiator是处于symmetric single IP NAT之后,那么Server给Target的地址其实是一个错误的地址(端口号不对),所以Target收到Forward消息后所作的那个ResponseHello包,Initiator根本就收不到(Initiator没有用那个端口给Target发过包)。但是假如Target是Restricted Cone,发完这个ResponseHello之后它就能接收来自Initiator的任何端口包。所以它们能接着Redirect消息之后的流程继续走下去。

如果很不幸,Target也是处于symmetric single IP NAT之后,那么Target在回复Forward消息的时候就要采用猜端口的方式,给Initiator多发几个ResponseHello包,如果有幸猜中了Initiator答复Redirect消息时所采用的端口,那么两者就可以连接起来了。不过Flash好像没有这么做。

NAT检查工具:http://cc.rtmfp.net/

Public UDP port number same as local UDP port number:这个是指NAT是否将src port做了转换。

Can receive from same IP address, same UDP port number:这个值应当永远是Yes。因为如果连这个检查都通不过,连接根本就建立不起来。

Can receive from same IP address, different UDP port number:如果这个值是true,说明是Port Restricted Cone,它会把出去的包的dst port记录下来,然后收到包时检查port number。

Can receive from different IP address, different UDP port number:如果这个值是true,说明是Restricted Cone,即它不对端口号做检查。

Can send to different IP address after server introduction:这个值应当永远是Yes。

Source IP address is preserved from original connection:这个是看server收到的包是否都来自于同一个IP。这个有一定的假阳性在里面,只要有一次为false,就说明用的是Multiple IP address symmetric NAT。

Source UDP port number is preserved from original connection:true说明是cone NAT,false说明是symmetric NAT。

我自制了一张连通性图:



Restricted Cone Port Restricted Cone symmetric single IP symmetric multiple IP
Restricted Cone Yes Yes Yes No
Port Restricted Cone Yes Yes No No
symmetric single IP Yes No No No
symmetric multiple IP No No No No

猜你喜欢

转载自angelguo.iteye.com/blog/1710916