两个处在不同内网的机器上的应用要想直接通信(不通过第三方来交换数据),由于互相并不知道彼此的外网IP,是做不到的。
当然如果是人为干预,你知道了彼此机器的外网IP,并做好端口映射的设置,两个是能够互相通信的,而这里说的,是在互不相知的情况下如何建立通信(这也符合生活常理,因为你较少的关注过你自己的外网IP,一些P2P应用也从来没要求过你先设置好端口映射才能够使用)。
为了能够让这样的两台不同内网中的P2P应用直接通信,首先能想到的就是,可以通过第三方的帮助来让着两台机器建立直接的通信。
如,有A、B、C三台机器
A机器
内网IP:10.10.0.123
外网IP:123.0.0.123
B机器:
内网IP:192.168.0.2
外网IP:211.0.0.211
C机器:222.0.0.222
那么A、B建立连接的过程应该为,A、B机器先与C机器建立会话,这样C就知道了A、B两台机器各自的外网IP,然后,A、B再利用对方的外网IP来互相连接。
上述过程假设很有道理,但实际上并没有想象的那么简单,因为,真正的映射不简简单单地是IP的映射,NAT所做是:将内网某个端口发出的会话映射为外网的一个端口,并且,不允许第三方插足这个会话,例如:A机器访问C机器的5000端口,即:
A机器: 10.10.1.123:1234 ---映射为---> 123.0.0.123:60000 会话到 C机器: 222.0.0.222:5000
那么,这个NAT之后,逆过程只允许C机器的5000端口到123.0.0.123:60000的通信,然后再由NAT到A机器的1234,第三方就无法再插足了。于是,上面的过程貌似行不通了。
然而,由于一个端口可以向多个服务端发出请求(建立多个对外的会话),而绝大多数NAT的实现都是映射端口而不是映射会话,让这个想法变得可以实现了,当然,有些NAT是映射会话的,这种情况下就实现不了上面的方法了。
所谓的映射端口而不是映射会话的意思是,映射端口只会为内网的某个端口分配一个外网端口来映射,而不管这个端口上建立的多少个会话。而映射会话的意思是,如果内网的一个端口建立了多个会话连接,那么NAT会为每一个会话都分配一个端口,如下图:
映射端口:
A:10.10.1.123:1234 ----映射为----> 123.0.0.123:60000 | ---会话到---> C机器
\ ---会话到---->B机器
映射会话:
A:10.10.1.123:1234 | ----映射为----> 123.0.0.123:60000 ----会话到---->C机器
\-----映射为----> 123.0.0.123:60001 ----会话到---->B机器
如果是这样,那么就可以借助于C机器来建立A和B的连接了。
A和B都连接到C,再由C告诉A、B对方的外网IP和端口号,然后A,B再向对方提出通信请求,这个过程就是打洞的过程。
为什么A、B再向对方提出通信请求就可以了呢?因为当A向B提出连接请求时,NAT就会将这个通信的逆过程合法化,即允许B的相应端口回访A的该端口。而此时B也做了同样的操作,导致了A也能够访问B的相应端口。于是A、B间不依赖于第三方的通信就建立了。
由上可知,有两个机制是很重要的,一个是一个端口可以建立多个会话,另一个是NAT映射的是端口而不是会话(只对会话做了不允许第三方插足的通信限制)。
还有,上面的过程显然是适用于UDP的,TCP需要一个客户端和服务端,而上面的过程要求通信的两个端口首先需要是客户端(因为连接了第三方服务端),其中一个再转为服务端是否能行得通呢?进一步,TCP应该如何打洞呢?