Total Pageviews

Friday 17 August 2012

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


由于公网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。
  1. Initiator首先向Server发InitiatorHello请求,其中带上Target的peerID。Initiator此时并不知道Peer2的地址,它只知道Target的PeerID。
  2. 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中转的意思,具体流程我还不明白。
  3. 同时,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)。
  4. 对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 ConePort Restricted Conesymmetric single IPsymmetric multiple IP
Restricted ConeYesYesYesNo
Port Restricted ConeYesYesNoNo
symmetric single IPYesNoNoNo
symmetric multiple IPNoNoNoNo