转载请注明出处:https://www.jzgwind.com/?p=973  by joey

一、背景

P2P网络的核心原理,是将分布在网络上各个节点的资源都利用起来,本着我为人人、人人为我的思想,达到更快的信息共享的目的。它的应用:
1、文件下载——BitTorrent、迅雷
2、语音通话——Skype、歪歪语音
3、视频共享——PPLive、快播

P2P网络:它和HTTP一样,是一套运行在应用层上的协议。

由于历史原因,IPV4地址的数量有限,不能满足所有的网络设备都分配独一无二的公网地址的需求,因此设计者通过NAT的方式巧妙的允许同一个内部网络中的多台设备共享一个外部网络地址。

但这种网络地址转换与访问方式是需要P2P去解决的问题之一,这种问题在IPV6的网络环境里仍然存在,因为在IPV6网络环境里,仍然需要NAT设备来兼容IPV4,即使NAT设备不再需要,P2P仍然需要去解决防火墙所带来的类似问题。

STUN算法是针对上述问题的解决方式之一(见本文第三部分)

二、一些定义:

防火墙:

A firewall restricts communication between a private internal network and the public Internet, typically by dropping packet that are deemed unauthorized. A firewall examines but does not modify the IP address and TCP/UDP port information in packets crossing the boundary.
防火墙可以限制内部私有网络和公网之间的通讯,典型的方式是丢弃没有被授权过数据包。
对于试图穿过网络边界的数据包,防火墙会检测其IP地址和TCP/UDP端口号,但并不修改其内容。

NAT(Network Address Translator):

A network address translator not only examines but also modifies the header information in packets flowing across the boundary, allowing many hosts behind the NAT to share the use of a smaller number of public IP addresses (often one).
NAT检查并修改穿过网络边界的数据包中的头信息,从而允许NAT下的许多主机节点能共享和使用数量较少的公有IP地址。

MiddleBox:

NAT设备和防火墙都统称为MiddleBox。

NAT与网关的区别:

网关是一个子网的出口,如果子网内一台主机请求的目标IP地址不在同一个子网内(通过子网掩码判断),则它会将目标MAC地址设置成网关的MAC地址(通过ARP协议获得IP地址对应MAC地址),由网关通过路由协议将请求转发到目标地址所在的网关。

三、NAT设备的分类

NAT刚开始只是路由器上的一个模块,目前家用的路由器都配置了NAPT模块,且默认都是开启状态的(有些路由器不允许关闭NAPT)。

图1: NAT设备分类

NAT可以分为以下两大类:

1、Basic NAT(Network Address Translation):

即网络地址转换,也叫“基本NAT”。

内网IP地址和端口向外部发送请求时(例如Socket请求),NAT设备只转换IP地址,端口保持不变。

Basic NAT设备已经不多见了(基本已淘汰)

2、NAPT(Network Address/Port Translator):

内网IP地址和端口向外部发送请求时,NAT设备同时将IP和端口映射为外部IP地址和外部端口。

NAPT又可以分为:

2-1、 Cone NAT——圆锥NAT

来自内部网络主机的请求(应用连接)的源IP和源端口被NAPT设备映射过一次后,以后来自同一个源IP地址和源端口的不同请求(应用连接),NAPT设备将会重复使用之前的映射关系。

当内部网络主机打开了一个通往外部主机的TCP/UDP会话(session)时,NAPT会给这个session分配一个公有IP和端口号,从而当外部主机响应TCP/UDP数据包返回时,它能被NAPT接收、翻译并转发给内部网络主机。

P2P应用在这个过程中的通讯往往用到了midcom protocol,且NAPT需要在这个过程中维持这个映射关系。

Cone NAT又分为以下三类:

(1)Full Cone NAT(全圆锥NAT)

只要内部网络主机的请求(应用连接)的源IP地址和源端口一样,NAT设备均映射(转换)到同一个外部IP地址和端口,且任何一个外部主机都可以通过这个外部IP和端口来访问内部主机。

(2)Restricted Cone NAT(限制型圆锥NAT)

对于限制型圆锥NAT,内部网络主机只接受指定IP地址传来的数据。

即只有先前该内部主机的已经请求(应用连接)过的目标IP地址对应的外部网络主机,才能向该内部主机发送数据。

注:
A restricted cone NAT effectively refines the firewall principle of rejecting unsolicited incoming traffic, by restricting incoming traffic to a set of
“known” external IP addresses.
限制型圆锥NAT有效地提炼了防火墙的设计原则:通过“限制只允许指定的已知IP地址的网络节点的请求”,从而拒绝未经授权(非指定IP)的主动请求。

(3)Port-Restricted Cone NAT(端口限制性圆锥NAT,隐含了IP和端口同时限制 )

端口限制型圆锥NAT又进一步对外部网络的主机的端口做了限制,内部网络主机只接受指定IP地址和指定端口的外部网络主机的请求。

即只有先前该内部主机的已经请求(应用连接)过的目标IP地址和目标端口对应的外部网络主机,才能用该端口作为源端口向该内部主机发送数据。

2-2、Symantric NAT——对称型NAT

来自同一个源IP地址和源端口的不同请求(应用连接),NAPT设备会映射分配不同的外部端口。

即当目的地址的IP和port有任何一个改变,那么NAPT设备都会重新分配一个外部端口使用

如果NAT设备是Symantric NAT,则P2P软件会失灵。幸运的是,现在绝大多数的NAT设备属于Cone NAT

注:端口限制性圆锥NAT提供了和对称型NAT一样的对内部主机的保护水平,但区别是端口限制性圆锥NAT会保留这个映射关系且可以重复使用,而对称型NAT则不会。

三、STUN算法

STUN(Simple Traversal of UDP Through NATs)是RFC3489规定的一种NAT穿透方式,它采用辅助的方法探测NAT的IP和端口,它对穿越早期的NAT起了巨大的作用。

虽然当网络中的NAT设备类型非常多时,RFC3489中的算法被证明了是不太可靠的。但STUN协议还将继续在NAT穿透中占有一席之地。

STUN协议和方法在RFC5389中已更新,RFC3489中的大部分的规格说明设计都被作为RFC5389中设计的子集得到保留,其余地被去除。

以下均介绍的是基于RFC3489介绍的STUN算法,具体可参见RFC3489里的第10部分:Use Cases

图2: NAT与STUN Server网络拓扑

A:NAT设备,内网IP地址是192.168.0.1,外网IP地址是202.187.45.3,假设请求的端口号为1234

B:内网主机,内网IP地址是192.168.0.20

C:STUN服务器,它有两个外网IP地址,分别是IPC1:187.34.1.55、IPC2:187.34.1.56

STUN服务器约定可以调用的两个端口是PORT1:40000和PORT2:40001

注:STUN服务器处于公网,需要有两个IP地址,具体原因见算法流程里的Step2。

测试1:请求的UDP包里参数(CHANGE-REQUEST属性)不作约定(等价于约定了相同的IP相同的端口),即响应RESPONSE返回的UDP对应的源IP和源端口,和请求UDP包里的目标地址和目标端口一致。

测试2:请求的UDP包里参数(CHANGE-REQUEST属性)约定了不同的IP不同的端口(即要求响应返回的UDP对应的源IP和源端口,和请求UDP包里CHANGE-REQUEST属性约定的IP和端口保持一致。如此一来,可以达到让“响应的源IP和源端口”和“请求的目标IP和目标端口”均不同的目的)。

测试2用来验证NAT设备是否是全锥型NAT(Full Cone NAT)

测试3:请求的UDP包里参数(CHANGE-REQUEST属性)约定了相同的IP不同的端口,即要求响应返回的UDP对应的源IP和源端口,和请求UDP包里CHANGE-REQUEST属性约定的IP和端口保持一致(即响应UDP包里的源IP和请求包里的目标IP一致,但响应UDP包里的源端口和请求包里的目标端口不相同)。

测试3用来验证NAT设备是“限制型圆锥NAT(Restricted Cone NAT)”还是“端口限制性圆锥NAT(Port-Restricted Cone NAT)”

图3: STUN协议算法流程图

Step1:

请求UDP包:

B向C的IPC1和PORT1(40000)发送UDP数据包:

192.168.0.20:1234 ——> 187.34.1.55:40000

如果NAT设备A存在,则C收到的源请求地址和端口将是A映射后的地址和端口:

202.187.45.3:62000(这里假设端口映射成62000)

即C实际收到的请求是:

202.187.45.3:62000——> 187.34.1.55:40000

响应UDP包:

C记下这一映射后的地址和端口并放到UDP包中,同时通过IPC1(187.34.1.55)和PORT1(40000)回复给B。
即:

187.34.1.55:40000——> 202.187.45.3:60000,同时把(202.187.45.3:60000)放到响应UDP包的数据段中。

B要么收到响应UDP包,要么收不到响应UDP包:

如果收不到,可能UAT设备不存在,且网络阻塞超时。

如果收不到,UAT设备又存在:则要么网络阻塞超时,要么约定的请求PORT1写错了,又或者是或者NAT设备坏了。

如果收到,则B会将UDP数据段里的IP地址(和端口号)取出来,与自己内网IP(和端口号)地址进行比较:

如果这两个地址(和端口号)一致,说明请求的源地址(和端口号)都没有被映射过,并没有NAT设备,则将会探测防火墙,判断是主机是处于公网还是处于“对称防火墙”内。如果这两个地址(和端口号)不一致,说明检测到了NAT设备,进入Step2。

注:symmetric UDP Firewall的解释:

Firewall that allows UDP out, and responses have to come back to the source of the request (like a symmetric NAT, but no translation. We call this a symmetric UDP Firewall)

Step2:

请求UDP包:

B向C的IPC1发送一个UDP包,192.168.0.20:1234 ——> 187.34.1.55:40000,并在该数据包的参数(CHANGE-REQUEST属性)里约定让C通过另外一个IPC2和PORT2(不同于SETP1的IP1)向B返回一个UDP数据包,即响应UDP包为:

响应UDP包:

187.34.1.56:40001——>202.187.45.3:60000

如果请求包的属性被使用,则在响应里的CHANGED-ADDRESS 属性也会用到,用来告诉B,响应里的源IP和源端口使用的是之前请求里CHANGE-REQUEST属性里要求的IP和端口。

B要么收到响应UDP包,要么收不到响应UDP包:

如果收到,则说明NAT设备是个全锥NAT设备(Full Cone NAT),任何外部IP和端口都可以从之前打过的洞(UDP Hole Punching)进行访问。

如果收不到,则进入Step3

Step3:

请求UDP包:

B向C的IPC2的PORT2发送一个UDP数据包:

192.168.0.20:1234 ——> 187.34.1.56:40001

响应UDP包:

C收到数据包后,把它收到包的源IP和PORT写到UDP包中,然后通过自己的IPC2和PORT2把此包发还给B。即

187.34.1.56:40001——> 202.187.45.3:PORT,同时把(202.187.45.3:PORT)放到响应UDP包的数据段中。

收到的PORT需要跟STEP1中收到的外网映射PORT(62000)作为比较:

如果PORT != 62000,则说明它是对称型NAT(Symmetric NAT)

如果PORT = 62000,则说明是圆锥NAT(Cone NAT)且不是全锥NAT,即进入Step4进一步判断是“限制型圆锥NAT”还是“端口限制型圆锥NAT”。

疑问:

Step2里,为什么不直接像Step3一样,请求用192.168.0.20:1234 ——> 187.34.1.56:40001呢?

这是因为这种方式,响应一定是能收到的,而Step2里,响应却不一定能收到,因为NAT设备会去校验RESPONSE的源地址和端口,是否和对应的请求的源地址和端口一致(Step3里的一定是一致的)。

Step4:

请求UDP包:

B向C的IP2的一个端口PORT2(40001)发送一个数据请求包:
192.168.0.20:1234 ——> 187.34.1.56:40001

响应UDP包:

上一个请求里,要求C用IP2和不同于请求包目标端口4001的另一个端口(假设为400003)作为源端口返回一个数据包给B。

即请求UDP包里的CHANGE-REQUEST属性约定使用修改后的40003作为响应UDP包里的源端口。
187.34.1.56:40003——> 202.187.45.3:port

如果B收到了,那也就意味着只要IP相同,即使port不同,NAT也允许UDP包通过。显然这是restrict cone NAT。

如果没收到,那么NAT设备就是端口限制型NAT(port restrict NAT)

一旦路经到达红色节点时,UDP的沟通是没有可能性的(peakflys注:准确来说除了包被防火墙blocked之外,其他情况也是有可能建立P2P的,只是代价太大,一般放弃)。

一旦通过黄色或是绿色的节点,就有连接的可能。

最终通过STUN服务器得到自己的NAT类型和公网IP、Port。

这一工作在真正的P2P输入之前完成,以后建立P2P时就非常容易了。

参考文献

P2P的原理和常见的实现方式(为libjingle开路) by peakflys

http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt(shootingstars)

STUN 维基百科