翻译了一下RFC5128,并为我拙劣的英语水准和汉语言表达技巧感到汗颜,如果有人不幸搜到这篇文章,英语不是太差的话就不要看了。
英文原版地址:http://tools.ietf.org/html/rfc5128
1.Introduction and Scope
当前的Internet已经普遍布署了网络地址转换器(NATs).存在各种各样的NAT设备和各种各样使用NAT设备布署的网络拓扑。由NAT设备创建的非对称的地址和连接形式,给一些p2p应用程序和协议带来了它们特有的问题,像电话会议,多媒体播放器,网络游戏,这些问题甚至有可能持续到IPv6世界,在向IPv6过渡的时期,有些NAT可能需要使只支持IPv4的节点跟只支持IPv6的节点进行通信,尽管这种使用NAT的合适的协议或方法尚未解决。即使是将来的纯IPv6世界可能仍然包括防火墙,它仍然拥有和NAT类似的过滤功能,但没有地址转换功能,这种过滤行为也影响着P2P应用程序的功能。出于这个原因,这篇文章中讨论的NAT转换所使用的技术同样也适用于和带有和NAT转换类似的过滤功能的某些防火墙。
当前部署的NAT设备主要围绕client/server这种模式设计的,在这种模式下匿名,私有网络中的匿名客户端设备使用固定的IP地址和DNS域名访问处于公有网络的服务器。中途遇到的NAT设备为客户端主机动态地分配地址。这种匿名性和NAT设备后面的主机的不可访问性对于像web浏览器这样的应用程序来说并不是问题,web浏览器只需要创建outgoint连接。这种匿名性和不可访问性有时候对于隐私的保护是有利的。
在p2p的模式中,Internet主机通常情况下被称为是“客户端”,这种客户端不仅能够向其它的peer节点发起会话,还能接受来自其它节点发起的会话。发起者和响应者可能处于不同的NAT设备的后面,它们可能都没有永久的IP地址或者其它形式的公有网络属性。举个例子来说,一个普通的网络游戏的结构中,所有参与游戏的应用程序主机和一台公网可寻址的公共服务器交互来进行注册,并且发现其它的peer主机。在与这台公共服务器进行通信之后,主机彼此之间建立直接的连接以在游戏期间提供快速高效的数据更新。类似的,一个文件共享程序可能跟一台公共服务器进行交互进行资源发现和搜索,但却在peer主机之间建立直接连接进行数据传输。NAT设备给p2p的连接带来了麻烦,因为NAT设备后面的主机在Internet上没有固定的公网可见端口,其它的peer也就无法对起发起Incoming TCP或UDP连接。RFC 3234[NAT-APPL]简单地强调了这个问题。
在这篇文章中,我们总结了当前熟知的一些方法,使应用程序工作在NAT设备环境中而不需要直接改变NAT设备。
2. Terminology and Conventions Used
2.1 端点(Endpoint)
一个Endpint是指在一台终端主机上的和会话相关的元组。对于每一种IP协议,endpoint的表示方式也可能不一样。比如,UDP或者TCP会话的endpoint是以(IP地址,UDP/TCP端口号)这种元组的形式表示的。
2.2 端点映射(Endpoint Mapping)
当私有网络中的主机通过NAT设备向公有网络发起一个outgoing的会话,NAT设备分配一个公有的endpoint来转换私有endpoint,于是随后来自外部主机的响应包就可以被NAT接收,转换,并且转发到私有endpoint去。NAT设备将私有endpoint转换成公有endpoint,或者反过来的这种分配就称为是端点映射。NAT使用端点映射在会话的过程中执行转换。
2.3 端点独立映射(Endpoint-Independent Mapping)
“端点独立映射”在 [BEH-UDP]中像下面这样定义:
对于从一个相同的内部IP地址和端口(X:x)发往任何外部IP地址和端口的随后的数据包,NAT会重用端口映射。
2.4 端点依赖性映射(Endpoint-Dependent Mapping)
“端点依赖性映射”是指“地址依赖性映射”和“地址和端口依赖性映射”的组合,在[BEH-UDP]中定义。
地址依赖性映射: (Address-Dependent Mapping)
对于接下来要从同一个内部IP地址和端口(X:x)发往同一个外部地址,不管外部端口号是多少,NAT都会重用端口映射。
地址和端口依赖性映射:(Address and Port-Dependent Mapping)
对于接下来要从同一个同部IP地址和端口(X:x)发往同一个外部地址和端口号的数据包,当映射依然处于活动状态的时候,NAT设备会重用端口映射。
2.5 端点独立的过滤(Endpoint-Independent Filtering)
“端点独立的过滤”在[BEH-UDP]中像下面这样定义:
不管外部IP地址和端口号(Z:z)是多少,NAT都只过滤不发往内部IP地址和端口号(X:x)的数据包。NAT会转发所有发往X:x的数据包。换句话说,从NAT内侧发信外部IP地址的数据包足以让任何数据包发回到内部endpoint。
使用“端点独立映射”和“端点独立过滤”的NAT设备可以接受任何位于公有网络的外部endpoint上的已经映射的端口的incoming流量。
2.6 端点依赖性过滤(Endpoint-Dependent Filtering)
“端点依赖性过滤”是指“地址依赖性过滤”和“地址和端口依赖性过滤”的组合,在[BEH-UDP]中定义
地址依赖性过滤(Address-Dependent Filtering)
NAT过滤不发往内部地址X:x的数据包。并且,如果X:x之前没有发送任何数据包给Y(和Y使用的端口无关),那么NAT设备就会过滤从Y:y发往X:x的数据包。换句话说,要从一个特定的外部endpoint接收数据包,内部endpoint必须先向特定的外部endpoint发送过数据包。
地址和端口依赖性过滤(Address and Port-Dependent Filtering)
NAT过滤不发往内部地址X:x的数据包。并且,如果X:x之前没有发送任何数据包给Y:y,那么NAT设备就会过滤从Y:y发往X:x的数据包。换句话说,要从一个特定的外部endpoint接收数据包,内部endpoint必须先向特定的外部endpoint发送过数据包。
(译者注:区别就仅仅是和外部端点的端口有没有关系)
使用“端点依赖性过滤”的NAT设备只会接受有限的一组公网外部端点的发往一个已经映射的公有端口的incoming流量。(这句话我认为我翻译的很失败,附上原文)
A NAT device employing “Endpoint-Dependent Filtering” will accept
incoming traffic to a mapped public port from only a restricted set
of external endpoints on the public network.
2.7 P2P程序(P2P Application)
P2P程序是指使用同一个endpoint向peer主机发起outgoing会话,并且接收来自peer主机的incoming会话的应用程序。P2P程序可以使用多endpoint进行p2p通信。
2.8 NAT友好的P2P应用程序(NAT-Friendly P2P Application)
NAT友好的P2P程序是指,即使peer节点处于由一个或者多个NAT连接不同的IP地址域中,也能够有效运行的p2p程序。
一种P2P程序建立peer会话并保持NAT友好的通用方法是使用一台公网可寻址的公共服务器用做注册和peer发现。
2.9 端点独立映射NAT(EIM-NAT)
EIM-NAT是使用端点独立映射的NAT设备。EMI-NAT可以有任何过滤行为。BEHAVE-comiliant NAT设备设备就是EIM-NAT设备的很好的例子。使用地址依赖映射的NAT设备就是非EIM-NAT设备的例子。
2.10 发夹(Hairpinning)
发夹在[BEH-UDP]中像下面这样定义:
如果两台主机(称为X1和X2)在同一个NAT设备之后,并且交换流量,NAT可以在它外面为X2分配地址,称为X2′x2′,如果X1向X2′x2′发送流量,它就会到达NAT,然后NAT再将流量从X1传递到X1,这被称为是发夹。
并不是目前所有的NAT设备都支持发夹。
3.P2P程序穿越NAT设备所用到的技术。
这一小节从软件设备者或协议设计者的角度上,详细地回顾了在现有的NAT设备基础上实现p2p通信的一些熟知的技术。
3.1中继
在存在NAT设备的环境中实现p2p通信,最可靠但效率最低的方法是让p2p通过中继,使用看上去像client/server的方式进行通信。考虑图表1中的场景。两个客户端主机,A和B,都向一台公共服务器ServerS发起了TCP或UDP连接,这台公共服务器有一个公网可寻址的IP地址,用来进行注册,发现和中继。NAT设备后面的主机向这台服务器注册。Peer可以发现NAT设备后面的主机,并且使用这台服务器中继端对端的消息。客户端驻留在各自的私有网络里面,它们各自的NAT设备阻止它们直接同对方发起连接。
Registry, Discovery
Combined with Relay
Server S
192.0.2.128:20001
|
+—————————-+—————————-+
| ^ Registry/ ^ ^ Registry/ ^ |
| | Relay-Req Session(A-S) | | Relay-Req Session(B-S) | |
| | 192.0.2.128:20001 | | 192.0.2.128:20001 | |
| | 192.0.2.1:62000 | | 192.0.2.254:31000 | |
| |
+————–+ +————–+
| 192.0.2.1 | | 192.0.2.254 |
| | | |
| NAT A | | NAT B |
+————–+ +————–+
| |
| ^ Registry/ ^ ^ Registry/ ^ |
| | Relay-Req Session(A-S) | | Relay-Req Session(B-S) | |
| | 192.0.2.128:20001 | | 192.0.2.128:20001 | |
| | 10.0.0.1:1234 | | 10.1.1.3:1234 | |
| |
Client A Client B
10.0.0.1:1234 10.1.1.3:1234
Figure 1: Use of a Relay Server to communicate with peers
两个客户端可以简单地通过Server S在他们之间传递消息,而不是试图进行直接连接。举个例子,要发送一条消息给Client B,Client A只需要在同Server S建立了cs连接之后将消息发给Server S即可,Server S可以通过它和Client B之间已经建立的cs连接将消息转发给Client B。
这种方法的优点在于,只要两个Client都能够连接到Server,这种方法就一直有效。中间的NAT设备也不需要是EIM-NAT.这种方法的明显缺点在于,它消息了Server的处理器能力和网络带宽,同时,即使Server拥有足够的I/O带宽并且处于正确良好的拓扑结构中,client之间的通信延迟也会有所增长。The TURN protocol
[TURN] defines a method of implementing application agnostic,
session-oriented, packet relay in a relatively secure fashion.
3.2 连接逆转(Connection Reversal)
接下来要讨论的进行直接通信的连接逆转技术只有在一个peer处在NAT设备的后面,而另一个peer不处在NAT设备后面时才有效。举个例子,考虑图表2中的场景。client A处在NAT设备的后面,而client B拥有一个公网可寻址的IP地址。公共server S有一个公网可寻址的IP地址,它用做注册和发现。处在NAT后面的设备通过server注册他们的endpoint,peer主机通过这台服务器发现处在NAT后面的主机的endpoint。
Registry and Discovery
Registry and Discovery
Server S
192.0.2.128:20001
|
+—————————-+—————————-+
| ^ Registry Session(A-S) ^ ^ Registry Session(B-S) ^ |
| | 192.0.2.128:20001 | | 192.0.2.128:20001 | |
| | 192.0.2.1:62000 | | 192.0.2.254:1234 | |
| |
| ^ P2P Session (A-B) ^ | P2P Session (B-A) | |
| | 192.0.2.254:1234 | | 192.0.2.1:62000 | |
| | 192.0.2.1:62000 | v 192.0.2.254:1234 v |
| |
+————–+ |
| 192.0.2.1 | |
| | |
| NAT A | |
+————–+ |
| |
| ^ Registry Session(A-S) ^ |
| | 192.0.2.128:20001 | |
| | 10.0.0.1:1234 | |
| |
| ^ P2P Session (A-B) ^ |
| | 192.0.2.254:1234 | |
| | 10.0.0.1:1234 | |
| |
Private Client A Public Client B
10.0.0.1:1234 192.0.2.254:1234
Figure 2: Connection reversal using Rendezvous server
client A拥有私有地址10.0.0.1,程序使用TCP端口1234. 这台client已经使用公有地址192.0.2.128,端口20001同server S建立了连接。NAT A在它的公有地址192.0.2.1上为client A分配了TCP端口62000作为client A同server S会话的临时公有端点地址。因此,server S认为client A的IP地址为192.0.2.1,使用端口62000。client B有它自己的固定IP地址192.0.2.254,client B上的程序正在监听端口1234以接收TCP连接。
现在假设client B希望同client A直接建立通信会话。B可能首先尝试连接A自己认为拥有的地址,也就是10.0.0.1:1234,或者连接由server S观察到的A的地址,也就是192.0.2.1:62000,在任何情况下连接都会失败。
在第一种情况下,发告往IP地址10.0.0.1的流量会直接被网络丢弃,因为10.0.0.1不是公网可路由的IP地址。在第二种情况下,从B发来的TCP SYN请求包将会到达NAT A并发送到端口62000,但NAT A会拒绝连接请求,因为NAT A只允许outgoint连接。
尝试和A直接建立连接并失败后,B可以使用server S传递一个请求给client A立一个到client B的“逆转”连接。client A在通过S收到这个中继请求后,向client B的公网地址和端口创建一个TCP连接,NAT A允许这个连接通过,因为这个连接是在防火墙的内部发起的,而client B也可以接收到这个连接,因为它不处在NAT设备的后面。
当前各种p2p程序都实现了这种技术。当然,它的最大的限制在于,它仅仅工作在只有一个通信的peer处在NAT设备后面。如果NAT设备是EIM-NAT,public client就可以联系外部的server S来确定可以接收从client A发起连接并允许这些连接的特定端点。如果NAT设备不是EIM-NAT,public client就不能确定哪些端点可以接收由client A发起的连接。在两个端点都处于NAT后面的情况越来越普遍的情况下,这种连接逆转的技术就会失败。连接逆转不是这种p2p连接问题的通用解决办法。如果前向连接和逆转连接都不能建立,程序只能退回去使用其它的机制,比如中继。
3.3 UDP 打孔技术
UDP打孔技术依赖于此EIM-NAT的一些属性,从而允许设计恰当的p2p程序在NAT设备上“打孔”来建立彼此之间的直接连接,即使两个客户端同时处于NAT设备之后也可以。当一台主机不是处在EIM-NAT之后时,peering主机不能获知要给哪个已经映射的endpoint发起连接。进一步说,处在非EIM-NAT设备后面的主机上的程序不能够使用已经建立的endpoint映射和外部目的地址进行通信,打孔技术将会失败。
我们将会考虑两个特定的场景,看看如何设计程序来优雅地处理这两种情况。第一个场景代表最常见的情况,处在两个不同的NAT设备后面的两个client希望直接建立p2p通信。第二种情况下,两个客户端事实上处在同一个NAT后面,但它们自己并不知道。
3.3.1 处在不同的NAT设备之后的Peers
看一下图表3中的场景。client A和client B都拥有私有地址,并且处在不同的NAT设备后面。公共服务器S拥有公网可路由的IP地址,用做注册,发现和有限制的中继。NAT后面的主机在这个Server上注册他们的公共endpoint。peer主机使用这台服务器来发现NAT后面主机的公共endpoint。不像3.1中描述的那样,peer主机只使用server传递连接初始控制信息,而是发送end-to-end信息。
运行在client A和client B上的p2p程序使用UDP端口1234.公共server使用UDP端口20001。A和B各自向server S创建了通信会话,从而分别使NAT A为clinet A和 serverA的会话分配了它的公共UDP端口62000,NAT B为clinet B和server S的会话分配了它的公共端口31000。
Registry and Discovery Combined
with Limited Relay
Server S
192.0.2.128:20001
|
+—————————-+—————————-+
| ^ Registry Session(A-S) ^ ^ Registry Session(B-S) ^ |
| | 192.0.2.128:20001 | | 192.0.2.128:20001 | |
| | 192.0.2.1:62000 | | 192.0.2.254:31000 | |
| |
| ^ P2P Session (A-B) ^ ^ P2P Session (B-A) ^ |
| | 192.0.2.254:31000 | | 192.0.2.1:62000 | |
| | 192.0.2.1:62000 | | 192.0.2.254:31000 | |
| |
+————–+ +————–+
| 192.0.2.1 | | 192.0.2.254 |
| | | |
| EIM-NAT A | | EIM-NAT B |
+————–+ +————–+
| |
| ^ Registry Session(A-S) ^ ^ Registry Session(B-S) ^ |
| | 192.0.2.128:20001 | | 192.0.2.128:20001 | |
| | 10.0.0.1:1234 | | 10.1.1.3:1234 | |
| |
| ^ P2P Session (A-B) ^ ^ P2P Session (B-A) ^ |
| | 192.0.2.254:31000 | | 192.0.2.1:62000 | |
| | 10.0.0.1:1234 | | 10.1.1.3:1234 | |
| |
Client A Client B
10.0.0.1:1234 10.1.1.3:1234
Figure 3: UDP Hole Punching to set up direct connectivity
现在假设client A想和client B直接建立UDP通信会话。如果A简单地发送一个UDP消息到B的公共endpoint 192.0.2.254:31000,B通常会丢弃这些incoming消息(除非它使用了端点独立(Endpoint-Indepentent)的过滤),因为这条消息的源地址和端口号不和S的匹配,而outgoing会话是和S建立的。同样,B简单地发送一个UDP消息到A的公共endpoint,那么NAT A也通常会丢弃这些消息。
假设A开始发送UDP消息到B的公共endpoint,同时通过server S向B传递请求,让B也发送UDP消息到A的公共endpoint。A直接发往B的公共endpoint(192.0.2.254:31000)的outgoint消息将会导致EIM-NAT A在A的私有endpoint和B的公共endpoint之间打开一个新的通信会话。同时,B发往A的公共endpoint(192.0.2.1:62000)的消息会导致EMI-NAT B在B的私有endpoint和B的公共endpoint之音打开一个新的通信会话。一旦在每个方向上都打开了UDP会话之后,client A和client B就可以直接和彼此进行通信,而不会进一步带来server S的负担。帮助NAT后面的peer节点传递连接初始化请求的server S最后被称为peer主机的“中介”节点。
UDP打孔技术有很多有用的属性。一旦两个处在NAT设备之后的peer主机建立了直接的p2p-udp连接,连接的每一部分都可以反过来充当“中介”来帮助其它的部分和其它的peer建立p2p连接,减少了初始中介服务器S的负载。既然上面的过程在即使任一个或者两个client恰好都不处于NAT设备之后的情况下,一样可以很好创建p2p的通信连接,那么程序也就不需要检测它前面的NAT设备的类型。UDP打孔技术甚至在多个NAT的情况下也可以自动地工作,这种情况下一个或者两个客户端在到达公有Internet网络时经过了两级或者更多的地址转换。
3.3.2 处于同一个NAT设备后面的peers
现在考虑这样一个场景:两个客户恰巧处于同一个EMI-NAT后面,因此也处于同一个私有地址空间中,像图表4描述的那样。公共服务器S拥有公网可路由的IP地址用于注册,发现和有限制地中继。处在NAT后面的主机向服务器注册。peer主机通过服务器发现NAT后面的主机,并且通过服务器传递消息。不像3.1节描述的那样,peer主机只传递控制信息,而是传递所有的点对点的消息。
client A已经和server S建立了UDP会话,EIM-NAT也已经给client A分配了公共端口号62000。同样client B也同server S建立了UDP会话,EIM-NAT也为它分配了公共端口号62001。
Registry and Discovery Combined
with Limited Relay
Server S
192.0.2.128:20001
|
^ Registry Session(A-S) ^ | ^ Registry Session(B-S) ^
| 192.0.2.128:20001 | | | 192.0.2.128:20001 |
| 192.0.2.1:62000 | | | 192.0.2.1:62001 |
|
+————–+
| 192.0.2.1 |
| |
| EIM-NAT |
+————–+
|
+—————————–+—————————-+
| ^ Registry Session(A-S) ^ ^ Registry Session(B-S) ^ |
| | 192.0.2.128:20001 | | 192.0.2.128:20001 | |
| | 10.0.0.1:1234 | | 10.1.1.3:1234 | |
| |
| ^ P2P Session-try1(A-B) ^ ^ P2P Session-try1(B-A) ^ |
| | 192.0.2.1:62001 | | 192.0.2.1:62000 | |
| | 10.0.0.1:1234 | | 10.1.1.3:1234 | |
| |
| ^ P2P Session-try2(A-B) ^ ^ P2P Session-try2(B-A) ^ |
| | 10.1.1.3:1234 | | 10.0.0.1:1234 | |
| | 10.0.0.1:1234 | | 10.1.1.3:1234 | |
| |
Client A Client B
10.0.0.1:1234 10.1.1.3:1234
Figure 4: Use of local and public endpoints to communicate with peers
假设A和B使用上面所描述的UDP打孔技术通过server S做为中介建立一个通信信道,然后A和B将会获取对方由server S所观察到的公共endpoint。只要NAT设备允许内网主机同其它的内网主机建立转换的UDP会话,而不仅仅是和外部主机建立,两台client就能够彼此之间进行通信。这种情况被称为“Hairpinning”,因为数据包从私有网络到达NAT之后被转换然后又环回到私有网络,而不是转发到公有网络。
举个例子,考虑上面的p2p session-try1。当A发送一个UDP包到B的公有endpoint时,这个包最初拥有源endpoint(10.0.0.1:1234)和目的endpoint(192.0.2.1:2001)。NAT收到这个包然后把它的源endpoint转换成192.0.2.1:62000,目的endpoint转换成10.1.1..3:1234,然后转发给B.
即便NAT设备支持hairpinning,在这种情况下转换和转发的步骤明显是没有必要的,并且增加了A和B之间会话的延迟,同时也增加了NAT的负载。解决的办法是直接转发。下面将会介绍:
当A和B最初通过公共服务器S交换地址信息的时候,他们包含他们自己所看到的IP地址和端口号,以及服务器S所看到的他们的公网IP地址和端口号。客户端同步地向它们知道的对方的任一个地址发送数据包,并且使用第一个连接成功的第址。如果两个客户端处理同一个NAT之后,那么直接发送到他们私有endpoint的数据包很可能先到达,从而实现了不需要NAT参与的直接通信信道。如果两个客户端处在不同的NAT后面,那么直接发往它们私有endpoint的数据包将不会到达,但可客户端将会按我们所期望的使用他们各自的公有endpoint建立连接。既然在不同的NAT的情况下,A发往B的私有endpoint的消息很有可能到达A的私有网络上的不相关的节点,反过来也也一样,那么这些对数据包在某种方式上经过认证是很重要的。
The [ICE] protocol employs this technique effectively, in that
multiple candidate endpoints (both private and public) are
communicated between peering end hosts during an offer/answer
exchange. Endpoints that offer the most efficient end-to-end
connection(s) are selected eventually for end-to-end data transfer.
3.3.3 被多个NAT分隔的peers
在某些包含NAT设备的拓扑中,在对拓扑结构没有确定的了解的情况下,很难在两个客户端之间建立“最佳的”p2p路由。举个例子,考虑一下图表5中的场景:
Registry and Discovery Combined
with Limited Relay
Server S
192.0.2.128:20001
|
^ Registry Session(A-S) ^ | ^ Registry Session(B-S) ^
| 192.0.2.128:20001 | | | 192.0.2.128:20001 |
| 192.0.2.1:62000 | | | 192.0.2.1:62001 |
|
+————–+
| 192.0.2.1 |
| |
| EIM-NAT X |
| (Supporting |
| Hairpinning) |
+————–+
|
+—————————-+—————————-+
| ^ Registry Session(A-S) ^ ^ Registry Session(B-S) ^ |
| | 192.0.2.128:20001 | | 192.0.2.128:20001 | |
| | 192.168.1.1:30000 | | 192.168.1.2:31000 | |
| |
| ^ P2P Session (A-B) ^ ^ P2P Session (B-A) ^ |
| | 192.0.2.1:62001 | | 192.0.2.1:62000 | |
| | 192.168.1.1:30000 | | 192.168.1.2:31000 | |
| |
+————–+ +————–+
| 192.168.1.1 | | 192.168.1.2 |
| | | |
| EIM-NAT A | | EIM-NAT B |
+————–+ +————–+
| |
| ^ Registry Session(A-S) ^ ^ Registry Session(B-S) ^ |
| | 192.0.2.128:20001 | | 192.0.2.128:20001 | |
| | 10.0.0.1:1234 | | 10.1.1.3:1234 | |
| |
| ^ P2P Session (A-B) ^ ^ P2P Session (B-A) ^ |
| | 192.0.2.1:62001 | | 192.0.2.1:62000 | |
| | 10.0.0.1:1234 | | 10.1.1.3:1234 | |
| |
Client A Client B
10.0.0.1:1234 10.1.1.3:1234
Figure 5: Use of Hairpinning in setting up direct communication
假设NAT X是由大型的ISP部署的一个EMI-NAT,用来将大量的用户复用到少数的公有IP地址上,NAT A和NAT B是两个ISP的用户独立部署的小型的用户NAT网关,用来将它们的私有家庭网络复用到他们分别由ISP提供的IP地址上。NAT A和NAT B所使用的”公有”IP地址对于ISP的地址域来说是私有的,而相应的,客户端A和客户端B的地址对于NAT A和NAT B来说也是私有的。像上一节一样,服务器S也是用来注册,发现和有限制的中继的。peer主机使用服务器传递连接初始化控制信息,而不是所有的点到点信息。
现在假设client A和client B要建立直接的p2p-udp连接。最佳方法是client A发送信息到client B在NAT B处的公有地址,这个地址在ISP的地址池中是192.168.1.2:31000,client B发送信息到client A在NAT A处的公有地址,也就是192.168.1.2:31000。不幸的是,A和B没有办法获取这些地址,因为server S只能观察到客户端的公有地址,192.0.2.1:62000和192.0.2.1:62001。即使A和B通过某种方式获取了这些地址,也不能保证这些地址一定可用,因为这些在ISP的私有地址池中分配的地址很有可能和客户端的私有地址池中分配的不相关的地址冲突。客户端因此别无选择只能使用它们由S观察到的全局公有endpoint来进行p2p通信,并且依赖NAT X来提供hairpinning。
3.4 TCP打孔技术
在这一节里,我们将会讨论“TCP打孔”技术,用来在一对都处在NAT设备后面的节点之间建立直接的TCP连接。和UDP打洞打孔技术一样,TCP打孔技术也依赖于EIM-NAT的一些属性,允许设计恰当的p2p程序通过在NAT设备上打孔来建立彼此之间的直接连接,即使通信的主机都处在NAT设备之后也一样奏效。这种技术有时也被叫做“TCP同时打开”。
很多TCP会话都以一个endpoint发送SYN数据包,接收方回复一个SYN-ACK数据包开始。然而两个endpoint同时打开TCP会话也是可以的,即同时发给对方一个SYN数据包,然后每一方收到后回复一个ACK数据包。这个过程被称为“TCP同时打开”。然而在很多系统上包括一些NAT设备上,“TCP同时打开”技术并不能正确的执行。如果一个NAT设备从私有网络外部收到一个试图创建incoming TCP连接的TCP SYN数据包,NAT设备通常丢弃这个SYN数据包,或者回复一个TCP RST数据包,以此来拒绝连接。在SYN超时或者连接重置的情况下,程序endpoint就会继续重新发送SYN包,直到对端peer和它做同样的事情。
我们考虑NAT设备支持“TCP同时打开”会话的情况。如果NAT设备认为一个SYN数据包的源endpoint和目的endpoint是和一个活动的TCP连接相关的,那么这个SYN数据包到达NAT设备的时候,NAT设备就会允许这个包通过。特别的,如果一个NAT设备已经看到并且使用相同的地址和端口号转换了一个outgoint连接,它就会认为这个会话是活动的,并且允许incoming SYN包通过。如果client A和client B各自向发起的outgoing TCP连接中另一方超时了,那么每个客户端的SYN包在到达对方的NAT设备之前都通过了自己本地的NAT设备,那么一个TCP连接就会建立起来。
出于下面的原因,这种技术可能并不会一直有效。如果任何一个SYN包到达远程NAT太快(在这个peer节点有机会发送SYN包之前),那么远程NAT设备可能会丢弃这个SYN包或者发送一个RST包来拒绝这个SYN包。这会导致本地NAT设备立即结束这个新的NAT会话,或者开始会话结束的超时等待,在超时结束的时候结束这个会话。即使peer节点继续同步地发起SYN重传尝试,如果一个NAT会话处于一种(end-of-session timeout)会话结束超时状态,一些远程的NAT主机可能允许incoming SYN通过,这就有可能会阻止TCP连接的建立。
事实上,大多数的NAT设备(超过50%)都支持端点独立(Endpoint-Indenptent)映射,并不会发送ICMP错误或者RST包来响应未经同意的incoming SYN包。结果是,对于大多数的TCP连接尝试,TCP同时打开技术在穿越NAT设备的时候并不会生效。
3.5 UDP端口预测
在一些支持端点独立映射的NAT存在的情况下,仍然有一种UDP打孔技术的变种可以用来创建p2p-UDP连接。这种方法有时候被称为”N+1″技术[BIDIR],Takeda[SYM-STUN]详细讨论了这种方法。这种方法分析NAT的行为并尝试预测在将来的会话中它将给分配的公网端口号。分配的公有端口号一般是可预测的,因为大多数的NAT映射端口的分配是按顺序来的。
考虑图表6中的避场景。两个客户端A和B,每一个都处于一个单独的NAT后面,并且同公共服务器S建立了单独的UDP连接。公共服务器有公网可寻址的IP地址,用来注册和发现。NAT后面的主机在服务器上注册他们的endpoint。peer主机通过这台服务器发现NAT后面主机的endpoint。
Registry and Discovery
Server S
192.0.2.128:20001
|
|
+—————————-+—————————-+
| ^ Registry Session(A-S) ^ ^ Registry Session(B-S) ^ |
| | 192.0.2.128:20001 | | 192.0.2.128:20001 | |
| | 192.0.2.1:62000 | | 192.0.2.254:31000 | |
| |
| ^ P2P Session (A-B) ^ ^ P2P Session (B-A) ^ |
| | 192.0.2.254:31001 | | 192.0.2.1:62001 | |
| | 192.0.2.1:62001 | | 192.0.2.254:31001 | |
| |
+———————+ +——————–+
| 192.0.2.1 | | 192.0.2.254 |
| | | |
| NAT A | | NAT B |
| (Endpoint-Dependent | | (Endpoint-Dependent|
| Mapping) | | Mapping) |
+———————+ +——————–+
| |
| ^ Registry Session(A-S) ^ ^ Registry Session(B-S) ^ |
| | 192.0.2.128:20001 | | 192.0.2.128:20001 | |
| | 10.0.0.1:1234 | | 10.1.1.3:1234 | |
| |
| ^ P2P Session (A-B) ^ ^ P2P Session (B-A) ^ |
| | 192.0.2.254:31001 | | 192.0.2.1:62001 | |
| | 10.0.0.1:1234 | | 10.1.1.3:1234 | |
| |
Client A Client B
10.0.0.1:1234 10.1.1.3:1234
Figure 6: UDP Port Prediction to set up direct connectivity
NAT A为A和S的通信会话分配了它的UDP端口62000,NAT B为B和S之间的会话分配了它的端口31000。通过和服务器S通信,A和B获取了对方由服务器S观察到的公有endpoint。client A现在向地址192.0.2.254的端口31001发送UDP消息(注意端口的增长),client B同时开始向地址192.0.2.1的端口62001发送UDP消息。如果NAT A和NAT B分新的会话连续地分端口,并且如果A-S和B-S的会话建立之后并没有经过太长时间,那么A和B之间的就会建立起来双向的可用通信信道。A发给B的消息将会导致NAT A打开一个新的会话,NAT A将会按我的期望地分配端口号62001,因为62001是它刚才分配给A和S的端口的下一个端口。同样,B发送给A的消息也会导致NAT B打开一个新的会话,NAT B为这个新的会话会配了端口号31001。如果两个客户端都正确地猜到了它们各自对应的NAT分配给新会话的端口号,那么一个双向的UDP通信信道就可以建立起来了。
很明显,很多因素会导致这种策略失败。如果任何一个NAT预测到的端口恰好已经被分配给了一个不相关的会话,那么NAT就会略过这个端口号,那么连接尝试就会失败。如果任何一个NAT有时候或者一直都不是按顺序地分配端口号,那么这种策略也会失败。如果NAT A后面的另一个客户端(不是A)在A同服务器S建立连接之后,并且还发送第一条消息给B之前,打开一个新的outgoing UDP会话到任何外部目的地,那么这个不相关的客户端就会不经意地“偷走”这个我们需要的端口号。因此这种方法在任何一个相关的NAT过载的情况下都可能不会生效。
既然实现了这种策略的程序即使有一个NAT使用了端点独立的映射时仍然需要能够运行,程序就需要预先知道每一个端点所涉及的NAT都是什么类型,并且相应地修改它们的行为,这就提高了算法的复杂度,同时也增大了网络的弱性。最后,端口预测方法在每一个客户端都处在两级或者更多级的NAT之后很少能够工作。
3.6 TCP端口预测
有一种“TCP打孔技术”的变种可以创建穿越依赖性映射NAT的p2p-TCP直接会话。不幸的是,这种方法相比如刚才介绍的UDP端口映射方法相比更加脆弱,对时间更加敏感。首先,预测到的一个NAT可能分配的端口可能是错的。其实,如果任何一个客户端的SYN包到达对方的NAT设备太快,那么远程NAT设备就可能丢掉SYN包或者发送一个RST包,导致本地的NAT设备转而关闭新的会话,接着徒劳地使用相同的端口继续进行失败的SYN重传。
4. Recent Work on NAT Traversal
5. Summary of Observations
5.1 TCP/UDP打孔(TCP/UDP Hole Punching)
TCP/UDP打孔看上去是现存的在两个都处于NAT后面的节点之间建立基于TCP/UDP的p2p通信的最有效方法。在很多种现存的NAT上都广泛使用了这项技术。然而程序在直接通信不能建立的情况下需要随时准备好回归到最简单的中继方式进行通信。
使用TCP/UDP打孔技术有一点需要注册,它只对转换NAT是EIM-NAT的情况生效。当参与路由的NAT设备不是EIM-NAT的时候,程序就不能够重用已经建立的端点映射和不同的外部目的地进行通信,这种技术就会失败。然而现在在Internet中部署的NAT大多数都是EIM-NAT,那就保证了TCP/UDP打孔技术广泛适用。不管怎样,仍然有大量的部署的NAT使用端点依赖性映射,但却不支持TCP/UDP打孔。
5.2 使用端点依赖性映射的NAT(NATs Employing Endpoint-Dependent Mapping)
端点依赖性映射的NAT对于像web浏览器这种cs程序来说并不是问题,它只需要创建outgoing连接。然而在近期像IM和VOIP这样的p2p程序现在广泛应用。使用端点依赖性映射的NAT对p2p程序的一些技术并不适用,像TCP/UDP打孔技术在穿越这些NAT设备的时候就会失败。
5.3 peer发现
应用程序peer可以出现在同一个NAT域内,也可以处于不同的NAT域内。为了让所有的peer发现程序的endpoint,一个程序可以选择把它自己的私有endpoint和公有endpoint到一台公共服务器上注册。
5.4 发夹(Hairpinning)
支持发夹技术有很大的好处,它可以允许EIM-NAT后面的主机和同一个NAT设备之后的其它主机通过它们的公有的,可能被转换的endpoints进行通信。支持发夹技术在多级NAT的场景中第一次部署了大容量的NAT的情况是非常有用的。像3.3.3节描述的那样。处在相同第一级NAT但不同第二级NAT后面的主机无法使用TCP/UDP打孔技术和彼此进行通信。
原创文章,转载请注明: 转载自basic coder
本文链接地址: http://basiccoder.com/rfc5128-chinese.html