UDP Punching
考虑下面的情况:
两台机器A和B分别在两个不同的局域网内,而这两个局域网分别在两个不同的NAPT后面,如果A和B之间进行通讯,则应该如何实现?
方案一:A和B直接通讯
方案二:A和B借助公网机器C进行间接通讯,C担任通讯转发角色。
现在我们想实现方案一,该怎么办?
局域网内的机器对外通讯是通过NAPT,举个例子:机器A是局域网内的机器,它在内网的IP地址是172.16.0.36,该局域网所属NAPT的公网IP是219.238.233.113,则机器A通过端口4000发送数据包到外网的机器C 219.238.233.220过程是:[172.16.0.36:4000] → [219.238.233.113:6000] → [219.238.233.220:8000]。
我们来分析一下这个过程:因为机器A是局域网内的机器,它的对外通信数据要通过NAPT,所以数据包经过NAPT时会变化:数据包的源地址由机器A的[172.16.0.36:4000]变为NAPT的[219.238.233.113:6000],然后发送出去,注意:[219.238.233.113:6000]的6000端口,这个端口是NAPT为机器A打开的!
当外网机器C发送数据包给机器A时,数据包的发送过程是:[219.238.233.220:8000] →[219.238.233.113:6000] → [172.16.0.36:4000],即数据包传给了NAPT的6000端口,NAPT查询6000端口对应于局域网内的机器A,然后就把数据包给了A。
我们再次思考一下:为什么外网的数据包能够发送到机器A呢?这个有一个前提条件:就是外网将数据包发送到NAPT的6000端口时,6000端口对机器A是开放的,那么这个6000端口是怎么生成的?由于机器A主动向NAPT申请的,这话怎么讲?因为机器A主动与外网通讯,所以NAPT为其打开了一个通道,对应于术语Session!这说明什么呢?这说明NAPT只为局域网内的机器开辟端口,即只对内部人员打开通道,因为它只认识自己管辖范围内的机器,它不会对外网机器开辟通道,如果它随便为外网机器开辟端口,那这个NAPT就不安全了!基于这样的原理,外网机器不能主动联系局域网内的机器!这个当中还有一个值得注意的:为什么来自机器[219.238.233.113:6000]的数据包被接受了,原因是:机器A主动跟[219.238.233.113:6000]通讯,NAPT记住了一点:来自[219.238.233.113:6000]的数据要送往机器A,也就是说NAPT记住了[219.238.233.113:6000]和[172.16.0.36:4000]的对应关系,注意:因为机器A主动跟[219.238.233.113:6000]通讯,所以NAPT记住了这样的对应关系,原因是机器A主动,哎,你主动一点吗,人家不就知道了吗?。
好了,以上是机器A主动与外网机器通讯,而这个外网具有公网IP地址,这时P2P有效。
如果外网机器主动与机器A通讯呢?上面已经分析了,外网的“主动”解决不了问题,因为你不能让机器A对应的NAPT记住不了对应关系,也没有那样的对应关系!
那么…?就让人通知机器A:机器A,有台外网机器想跟你通讯,他联系不上你,我将他的IP地址和Port端口告诉你,你联系他吧!好了,这样就又回到了机器A主动的情况!到底让谁呢,很显然,一个拥有公网IP的人,这就要求第三者参与!
好,这个过程我们也来分析一下:
同样机器A是局域网内的机器,它在内网的IP地址是172.16.0.36,该局域网所属NAPT的公网IP是219.238.233.113,外网机器B是[219.238.233.90],拥有公网IP[219.238.233.156]的第三方C,好了,机器A通过[172.16.0.36:4000],再通过NAPT的[219.238.233.113:6000],连接上C[219.238.233.156:8000],外网机器B通过[219.238.233.113:4000] 连接上C[219.238.233.156:8000],这样C知道了机器A的公网IP是[219.238.233.113:6000]、机器B的公网IP是[219.238.233.113:4000]!现在机器B要跟机器A通讯,则机器B告诉第三方C:我是[219.238.233.113:4000],我要和机器A通讯,我不能主动联系它,麻烦你叫它联系我吧!第三方C然后告诉机器A(慢着,为什么第三方可以和机器A联系,因为机器A已经主动连接上C了,机器A的NAPT记住了二者的对应关系!):机器A就联系机器B了!
在这里提一个问题:机器A联系第三方C时,NAPT为其开辟了端口号6000,那么机器A联系机器B时,NAPT会为其开辟新的端口号吗?当然,这个问题,在这儿不会产生什么影响,预做保留!
好了,回到考虑的问题:
两台机器A和B分别在两个不同的局域网内,而这两个局域网分别在两个不同的NAPT后面,如果A和B之间进行通讯,则应该如何实现?
满足怎样的条件,机器A和机器B才能通讯?
假设机器A用[172.16.0.36],机器B用[172.16.0.36],地址一样,但这不影响问题,因为A和B处于不同的局域网吗!
恩…自然我们想到第三方公网机器的参与!
同样分析一下这个过程:
同样机器A是局域网内的机器,它在内网的IP地址是172.16.0.36,该局域网所属NAPT的公网IP是219.238.233.100;机器B是另一个局域网内的机器,它在内网的IP地址是172.16.0.36,该局域网所属NAPT的公网IP是[219.238.233.200],以及拥有公网IP[219.238.233.150]的第三方C,好了,机器A通过[172.16.0.36:4000],再通过NAPT的[219.238.233.100:4000],连接上C[219.238.233.150:8000],机器B通过[172.16.0.36:4000] ,再通过[219.238.233.200:4000] 连接上C[219.238.233.150:8000],这样C知道了机器A的公网IP是[219.238.233.100:4000]、机器B的公网IP是[219.238.233.200:4000]!现在机器A和机器B处于对等状态,无论哪一方主动连接,过程都是一样的!
满足怎样的条件,双方才能通讯呢?关键是双方的NAPT要彼此记住对应关系!
假设机器A与机器B通过第三方C:知道了彼此的公网IP和端口Port,双方都开始主动!
机器B开始发送数据包通过自己的[172.16.0.36:4000],再通过NAPT的[219.238.233.200:X],到[219.238.233.100:4000],慢着…,通过NAPT的哪个端口呢?还是4000吗?NAPT会为其重新分配端口吗?
机器A开始发送数据包通过自己的[172.16.0.36:4000],再通过NAPT的[219.238.233.100:Y],到[219.238.233.200:4000],慢着…,通过NAPT的哪个端口呢?还是4000吗?NAPT会为其重新分配端口吗?
我们来看X、Y所代表的端口号,X、Y还是4000吗?它们会被重新分配吗?至此,这是一个问题!
如果机器B的NAPT:[219.238.233.200:X]的X不等于4000,比如说等于5000,那么NAPT记住的新的对应关系是 [219.238.233.100:X]和自身[219.238.233.200:4000]的对应关系,机器A发往[219.238.233.200:4000]的数据一定会被抛弃,因为机器B的NAPT记住的对应关系是第三方C[219.238.233.150:4000]和自己[219.238.233.156:4000]的对应关系,它只认识来自第三方C的数据包。同样的道理使用与机器A。
可见,只有X、Y都等于4000,即双方的NAPT不重新分配端口,则双方的NAPT才能记住对应NAPT的端口4000和对方[IP:Port]的对应关系,这样对方发送的数据包才能被接受到!
承接以上所说,谈谈NAPT为内网机器分配端口的情况!
NAPT被划分为两大种:Symmetric NAPT和Cone NAPT。
Symmetric NAPT: 对于到同一个IP地址,任意端口的连接分配使用同一个Session(与端口号对应);对于到不同的IP地址, 任意端口的连接使用不同的Session。我们称此种NAPT为 Symmetric NAPT。也就是只要本地绑定的UDP端口相同,发出的目的IP地址不同,则会建立不同的Session。
Cone NAPT: 对于到同一个IP地址,任意端口的连接分配使用同一个Session(与端口号对应)。对于到不同的IP地址,任意端口的连接也使用同一个Session。我们称此种NAPT为 Cone NAPT。也就是只要本地绑定的UDP端口相同,发出的目的地址不管是否相同,都使用同一个Session。
很显然,Cone NAPT是我们想要的!
现在绝大多数的NAPT属于后者,即Cone NAT。Win9x/2K/XP/2003系统自带的NAPT也是属于 Cone NAT的。值得庆幸,因为我们要做的UDP穿透只能在Cone NAT间进行,只要有一台不是Cone NAT,UDP穿透没有希望了,服务器转发吧。
至于NAPT具体实现,可以参考Linux中相应的源码实现。
--------------------
看看下面的情况:
Server S1 Server S2
18.181.0.31:1235 138.76.29.7:1235
| |
| |
+----------------------+----------------------+
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 155.99.25.11:62000 v | v 155.99.25.11:62000 v
|
Cone NAT
155.99.25.11
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 10.0.0.1:1234 v | v 10.0.0.1:1234 v
|
Client A
10.0.0.1:1234
接上面的例子,如果Client A的原来那个Socket(绑定了1234端口的那个UDP Socket)又接着向另外一个Server S2发送了一个UDP包,那么这个UDP包在通过NAT时会怎么样呢?
这时可能会有两种情况发生,一种是NAT再次创建一个Session,并且再次为这个Session分配一个端口号(比如:62001)。另外一种是NAT再次创建一个Session,但是不会新分配一个端口号,而是用原来分配的端口号62000。前一种NAT叫做Symmetric NAT,后一种叫做Cone NAT。
那么我们如果想从外部发送一个数据报给内网的计算机有什么办法呢?首先,我们必须在内网的NAT上打上一个“洞”(也就是前面我们说的在NAT上建立一个Session),这个洞不能由外部来打,只能由内网内的主机来打。而且这个洞是有方向的,比如从内部某台主机(比如:192.168.0.10)向外部的某个IP(比如:219.237.60.1)发送一个UDP包,那么就在这个内网的NAT设备上打了一个方向为219.237.60.1的“洞”,(这就是称为UDP Hole Punching的技术)以后219.237.60.1就可以通过这个洞与内网的192.168.0.10联系了。(但是其他的IP不能利用这个洞)。
这是一个Client A与Client B建立p2p连结的大概的流程:
Client A->Server S (Client A向Server S发送一个请求,请求信息是希望Client B向Client A方向打洞)
Server S->Client B (S要求B向A打洞)
Client B->Client A (打洞消息,这个消息Client A很可能不会收到,但是收不到没有关系,NAT B上的洞已经打好了)
Client A->Client B (发送正真的消息)
注意:以上过程只适合于Cone NAT的情况,如果是Symmetric NAT,那么当Client B向Client A打洞的端口已经重新分配了,Client B将无法知道这个端口(如果Symmetric NAT的端口是顺序分配的,那么我们或许可以猜测这个端口号,可是由于可能导致失败的因素太多,我们不推荐这种猜测端口的方法)。
阅读(1590) | 评论(2) | 转发(0) |