P2P穿透UDP TCP原理.docx
《P2P穿透UDP TCP原理.docx》由会员分享,可在线阅读,更多相关《P2P穿透UDP TCP原理.docx(24页珍藏版)》请在冰点文库上搜索。
P2P穿透UDPTCP原理
P2P穿透UDP/TCP原理
前言
NAT技术的出现从某种意义上解决了IPv4的32位地址不足的问题,它同时也对外隐藏了其内部网络的结构。
NAT设备(NAT,一般也被称为中间件)把内部网络跟外部网络隔离开来,并且可以让内部的主机可以使用一个独立的IP地址,并且可以为每个连接动态地翻译这些地址。
此外,当内部主机跟外部主机通信时,NAT设备必须为它分配一个唯一的端口号并连接到同样的地址和端口(目标主机)。
NAT的另一个特性是它只允许从内部发起的连接的请求,它拒绝了所有不是由内部发起的来到外部的连接,因为它根本不知道要把这个连接转发给内部的哪台主机。
P2P网络已经日益流行。
尽管p2p文件共享软件引发了很多争夺站,比如Nepster和KaZaA之间,但是还是有很多有用的并且合法的P2P软件存在着,比如即时消息共享和文件共享。
另一个P2P程序是一个叫OpenHash的项目,它为公众提供了一个可用的分布式的哈希表,很多应用程序都在它的基础上开发了出来,比如很多的即时通信软件和可靠的CD标签库。
不幸的是,两个处于不同NAT后面的主机无法建立TCP连接,因为各自的NAT都只允许外出的连接。
NAT销售商在已经为NAT设备开发了端口映射的功能来解决这个问题。
NAT管理员可以使用端口映射来为那些需要接受那些不是从内部发起的连接请求的主机指定端口。
但是这种解决方法根据情况还需要很多其他的支持。
当有的服务器需要动态的分配端口的时候,这种方法就很受限了。
再说了,如果一般的用户没有权限或者不懂得如何进入NAT设备为他们指定端口映射,那这种方法就一点用处也没有了。
P2P协议对此已经阐述了少数通用的方法。
第一个可被p2p协议使用的技术是:
那些本来不能当作服务器的程序收到了来自请求者的消息后主动向请求者发起连接。
这种情况只适用于只有一方在NAT后面的情况。
第二种通用的方法是通过两个主机都可以连接得到的代理路由数据,但是这种方法对于两个NAT后面的主机来说效率太低了,因为所有的数据都必须经过代理。
其他相关的技术将在第三部分讨论。
我们努力的目标是找出一个可以让NAT后面的两个主机直接建立TCP连接的解决方案。
特别地,我们已经开发出几种方案可以用于那些支持端口分配地NAT和那些支持LSR路由的网络。
我们的方法是通过第三方提供建立直接连接需要的信息。
根据不同的环境,我们开发了几种不同的方案可以在可以预测和适当的时间的情况下建立连接。
这些技巧都需要把数据包的TTL值设置得很小,并且捕捉和分析外传的数据包以提供信息给第三方“媒人”。
并且人为地向网络发送一些数据包用来检测NAT所分配地端口。
补充一点,如果端口分配是随机地,我们就使用一种叫“birthdayparadox”的方法减少检测的次数。
这种方法需要的空间是直接的穷举所使用的空间的开方。
2.NAT的类型
NAT必须考虑路由器的三个重要的特性:
透明的地址分配、透明路由、ICMP包负载解析。
地址分配是指在一个网络会话开始的时候为内部不可以路由的地址建立一个到可路由地址的映射。
NAT必须为原地址和目标地址都进行这样的地址分配。
NAT的地址分配有静态的和动态的方式。
静态的地址分配必须预先在NAT中定义好,就比如每个会话都指派一对<内部地址,外部端口>映射到某对<外部地址,外部端口>。
相反地,动态的映射在每次会话的时候才定义的,它并不保证以后的每次会话都使用相同的映射。
一个相似的特性,NAT必须实现的是透明路由。
正如上面提到的,NAT是一种特殊的路由,它在它所路由的数据包中翻译地址。
这种转换基于数据流来改变相应的IP地址和端口。
其次,这种转换必须是设备透明的,这样才保证对现有网络的兼容性。
一个不是很明显的要求是,NAT必须保证内部网络的数据包不被发送到外部网络去。
最后一个NAT必须实现的特性是当收到ICMP错误包的时候,NAT使用正常的数据包做出同样的转换。
当在网络中发生错误时,比如当TTL过期了,一般地,发送人会收到一个ICMP错误包。
ICMP错误包还包含了尝试错误的数据包,这样发送者就可以断定是哪个数据包发生了错误。
如果这些错误是从NAT外部产生地,在数据包头部的地址将会被NAT分配的外部地址所代替,而不是内部地址。
因此,NAT还是有必要跟对ICMP错误一样,对在ICMP错误包中包含的数据包进行一个反向的转换。
虽然所有的NAT都实现了这三个特性,但是根据他们的特点和他们所支持的网络环境,他们还可以进入分类。
NAT可以分为四种:
Two-wayNATs,TwiceNATs,Multi-homedNATs和TraditionalNATs。
关于Two-wayNATs,TwiceNATs和Multi-homedNATs的特性讨论请看[12]。
Two-wayNATs,一般也叫双向NAT(BidirectionalNATs),在外部地址和内部地址间执行双向转换,尽管它个数据包只转换一个IP地址。
这种NAT是唯一一种允许从外部发起的连接请求。
相反地,TwiceNATs,它每路由一个数据包都对内部和外部的地址进行转换。
这种NAT用在那些外部地址和内部地址交叠的情况下。
Multi-homedNATs对于TwiceNATs来说,多了一个功能,它可以允许在内部使用不能路由的地址并且还可以有多个连接到外部网络。
之所以Multi-homedNATs能够这样做,是因为它跟另一个保持通信以确定他们的地址映射是不变的。
Multi-homedNATs允许大量的内部网络和增加冗余来允许多个连接到Internet。
到目前为止,最常用的NAT是传统NAT(TraditionalNATs),它可以分为基础NAT(BasicNATs)和NATP(NetworkAddressPortTranslation)两种。
基本NAT和NATP的区别它所能分配给内部地址的外部地址是否比内部地址多。
一个SimpleNAT用在那种所能分配的外部地址跟内部地址的数量相等或者更多的时候。
这种NAT进行端口分配,因为每个内部地址都可以分配到一个唯一的外部地址。
NATPS用于当NAT所能用来分配的外部地址的数量比内部地址少的时候,最常见的一种情况是很多的内部机器共享一个外部IP地址。
在这种情况下,NAT必须分配端口来补充IP用以消除网络传输的不明确的几率。
NAT和NATP共同的地方就是他们都阻止外来的连接,并且都可以进行静态和动态的地址分配。
NAPT是传统NAT中最普遍的一种,因为它允许很多的内部连接共享很少量的外部网络地址。
大部分为小型网络设计的商业NAT都是NAPT。
我们选择NATP作为我们研究的对象就是因为它的流行和它通过不允许外来连接限制了P2P协议。
后面我们就把NATPs简称为NATs了。
我们首先要做的是得到商业防火墙以确定他们的特性跟资料上记载的是否一样。
我们使用NatCheck这个程序对三个常用的NAT设备进行了测试:
NetgearMR814,LinksysBEFSR41,和LinksysBEFW11S4。
三个NAT都有相似的行为:
他们对UDP和TCP都进行了一致的转换,这表明在内部主机使用<内部IP:
内部端口>的时候,NAT是否直接将<内部IP:
内部端口>映射到<外部地址:
外部端口>,而不管它连接出去的目标主机<目标IP:
目标端口>是多少。
一致转换是静态NAT与动态NAT截然不同的一个特点,因为这不只是跟内部主机使用的地址有关,而且还跟端口有关。
RFC3002明确指出要支持一致转换。
没有一个NAT支持环路转换,不管是TCP还是UDP,这表明NAT是否可以正确的处理两个只知道对方外部地址的内部主机之间的连接。
在我们的项目中,我们假设两个主机是在不同的NAT后面的,所以这个测试跟我们的目标是无关的。
最后,所有的NAT都提供了对非主动请求的外来TCP和UDP包都进行过滤,这个测试可以表明NAT是否预防非主动恳求的外来包进入到内部网络。
非主动请求过滤发生在除了Two-wayNAT之外的所有NAT中,这也是在不同NAT后面的主机建立P2P连接最主要的障碍了。
3.相关研究工作
三个来自科内尔的作者独立于我们做了关于穿过NAT的TCP直接连接工作并且结果和我们的类似。
他们的被称为NUTSS[4]的框架为不同的NAT后的主机的UDP和TCP连接性作了准备,但是他们的TCP技术有一个重大的缺点。
协议依靠于为了能够TCP连接的欺骗包,这包在真实的网络作了限制。
许多ISP作了进入过滤以防止欺骗包进入他们的网络,这将导致作者的协议失败。
欺骗不能是真实连接主机的组成部分。
为了他们所信任的,作者提出了一个不依靠于欺骗的方案。
然而,这技术依靠于平台相关的TCP堆栈行为。
我们在这里所描述的技术在相当于NUTSS[4]环境中为连接性做真实的假设时避免了欺骗。
为了解决NAT给很多协议带来的困难,一种MIDCOM的架构被提了出来[13]。
MIDCOM是一种可以允许NAT或者防火墙后面的用户根据需要改变NAT行为的而允许连接的一种协议。
这个系统虽然在某些情况下是适用的,当它却不能保证每个时候都可以。
在那些用户没有办法控制NAT的情况下,这种方法对于P2P的连接还是行不通的。
很多时候NAT或者防火墙后面的用户通过代理服务器进行连接。
一种商业的代理解决方法是Hopster[6]提供的,Hopsetr的代理在连接方本地以隧道级别的传输在本地运行,它通过HTTPS(端口443)连接到Hopster自己的机器。
但是,因为Hopster的代理需要所有的传输都经过他们的机器,所有他们的方法跟我们的比起来就显得低效了,具体见第5部分。
为了能够进行直接的P2P连接,出现了针对UDP的解决方法。
UDP打洞技术[5]允许在有限的范围内建立连接。
STUN(TheSimpleTraversalofUserDatagramProtocolthroughNetworkAddressTranslators)协议实现了一种打洞技术可以在有限的情况下允许对NAT行为进行自动检测然后建立UDP连接[10]。
在UDP打洞技术中,NAT分配的外部端口被发送给协助直接连接的第三方。
在NAT后面的双方都向对方的外部端口发送一个UDP包,这样就在NAT上面创建了端口映射,双方就此可以建立连接。
一旦连接建立,就可以进行直接的UDP通信了。
在UDP打洞技术可以成功的情况下,我们在这篇文章中所用的有的技术也同样适用。
建立TCP连接比UDP连接更有优势。
首先,UDP连接不能够依赖建立好的连接,就是不能够持久连接。
UDP是无连接的并且没有对明确的通信。
一般地,NAT见了的端口映射如果一段时间不活动后就是过期。
为了保持UDP端口映射,必须每隔一段时间就发送UDP包,就算没有数据的时候,只有这样才能保持UDP通信正常。
第二,很多防火墙都配置成拒绝任何的外来UDP连接。
最后,一个单纯的TCP连接的实现更直观,并且现有的代码简单得修改就可以使用我们的技术。
目前的工作由bryanFord[2]完成,已经扩展了打洞技术,使能在正规的NAT后的不同主机间进行TCP连接。
方法类似于UDP打洞,由于一个映射在各个主机的NAT上被建立以使TCP直接连接能被创建,同步或同时TCP打开。
这工作的焦点在于开发一种工作于最多的被定义为其它NAT所需要而描述的NAT的技术来和TCP打洞协调一致。
我们工作的不同点在于我们所开发的方案能够在目前各类NAT行为上进行直接地TCP连接,包括不协调于TCP打洞的NAT。
Gnutella有一个能使在两个不同端点的TCP通信的方案[14],但这仅仅应用于一个端点在NAT后面的情形。
这方案被称为PushProxy并本质地建立多个能够推连接请求到NAT后的服务器的节点。
NAT后的服务器发送消息到端点询问他们是否愿意推为代理。
当NAT后的服务器指明它有一个文件和询问匹配,服务器包含了一列同意被推为代理的端点。
当端点想下载文件,它发送一个GnutellaPUSH消息到一个pushproxy,这代理允许消息通过到达NAT后的服务器。
NAT后的服务器就打开一个连接到这个发送PUSH消息的端点,所以文件可以被传送。
这方法使其更容易建立连接,这仅仅处理了一个端点在NAT之后的情况。
我们的方案解决的是当两个端点都在NAT之后的更困难问题。
Walfish[15]指出,采用间接的服务可以提供NAT或者防火墙后面主机之间的连通性,通过在两主机向间接服务器打开一个连接,并且服务器在他们之间传输所有的通信,在这篇文章中我们会完成一个不需要这样的间接服务器也能建立起这样得连接
总舵主(274197172)13:
11:
18
.问题陈述和假设
假设两个主机在不同的NAT后面,并且都知道对方的IP地址。
如果这些主机想直接发起TCP连接,那肯定失败。
在直接的TCP连接中,必须有一方是发起者(创建初始的SYN包),另一方必须监听。
在两个都在NAT后面的情况下,监听者将不可能收到SYN包,因为SYN包在到达NAT的时候就被丢弃了。
这是因为NAT或者防火墙不允许来自英特网的未请自来的数据包进入他们的网络内部。
所以,为了在不同NAT后面的主机之间建立直接的连接,必须让NAT以为这个连接是经过内部主机发起的。
我们可以通过让两边的主机都发起一个TCP连接,也就是创建一个SYN包,这样两边的NAT都会以为这个连接是从内部发起的,是经过内部请求的,因此,就可以允许后续的数据经过它的网络了。
注意,虽然两个端点都发送SYN包,但我们没有使用TCP的同时打开。
A的内部网络 B的内部网络
┌────────┐ ┌────────┐
│ 主机A │ │ 主机B |
│192.168.2.2:
400| │192.168.2.2:
500|
└────────┘ └────────┘
↑↑ ↑↑
↓\ /↓
┌────────┐ ┌────────┐
│ NATNA │ │ NATNB │
│128.2.4.1:
4000| │151.3.43.1:
5000|
└────────┘ └────────┘
↑ \_____/ ↑
\ 目标 /
↓ ↓
┌──────────┐
│ 第三方X │
│ 66.4.2.23:
8000 |
└──────────┘
英特网
图1:
我们所开发的技术环境
为了在两个端点间成功地建立一个TCP连接,每个端点必须知道它的伙伴在初始连接前的外部表面端口号。
一旦一个从NAT的内部网络请求被路由到外部网络的一个IP地址的包到达时,这些端口将被NAT所选择。
就像记帐一样,NAT用所选的外部端口号绑定到内部的IP地址和端口号。
我们把这绑定称为映射。
NAT不会向任何主机共享这个映射。
我们的技术展示了为何NAT映射可以被高效地确定。
一旦两个端点都知道他们伙伴的外部表面端口号,TCP连接就被两个端点初始化。
TCP序列号和应答号是TCP连接同步的完整组成部分。
序列号不能够指定,只能捕捉它。
我们的文章将会说明怎么协议管理这些参数以成功地在任何的环境下建立一个TCP连接。
在不同的位于NAT后的主机间建立直接的TCP连接是一个难题,因为NAT选择外部端口不是NAT后的主机能直接理解到的,并且因为成功的TCP连接需要序列号和应答号的协调。
没有工作于所有情况的单一方案。
NAT的行为依靠于它的实现,并且预测端口的能力依靠于内部网络活动的数量。
我们所做的两个假设在我们测试过的NAT中都是成立的。
第一个假设是我们假设主机都收不到来自外部网络的ICMPTTL过期包。
如果这些包被主机接收,那么TCP连接就会被中断。
我们的很多解决方法中都依赖于能够为发送SYN的数据包设置一个很低的TTL值。
当SYN包被路由丢弃的时候,TCMPTTL过期包会被发送回NAT。
用于我们实现的NAT必须没有转发收到的ICMPTTL过期包给内部网络。
如果NAT真的把这个包发送回内部网络,也可以配置防火墙来阻止这类包。
我们的另一个假设是NAT看到ICMPTTL过期包的时候映射的端口还不会失效。
作为另一个选择,我们可以不修改TTL值,那就必须让目标NAT不会产生TCPRST包。
而实际上这个选择是可行的,因为很多的NAT为了防止端口扫描一般都不发送TCPRST包。
5.技术
使用图1的模型,我们的目标是让在NA和NB后面的A和B建立直接的TCP连接。
我们已经开发出几种方法可以建立这样的连接,根据NAT和网络情况的不同,我们有对应的方法。
考虑以下的信息作为有序的三元组:
。
我们考虑下面几种情况:
情况1:
<可预测的,可预测的,LSR>
情况2:
<可预测的,可预测的,noLSR>
情况3:
<随机的,可预测的,LSR>
情况4:
<随机的,可预测的,noLSR>
情况5:
<随机的,随机的,LSR>
情况6:
<随机的,随机的,noLSR>
注意:
<随机的,可预测的,X>等同于<可预测的,随机的,X>。
5.1连接前诊断
作为协助者X还有两个端点A和的B,为了确定A和B将试图的连接属于下面的哪种情况,X必须首先对各个端点做一些诊断。
为了使用1,3,5的情况,两端必须确定在A和X还有B和X之间的LSR(松散源路由)是否是可用的。
loosesourcerouting(LSR)是一个允许IP包的创建者指定一列在数据包的路由中使用的托管IP地址的IP选项。
这个选项的结果是在路由表里的各个IP地址将按照路由表里指定的顺序收到数据包。
LSR选项引来了一个安全性风险,这是因为一个攻击者能监听到在路由表里的会话。
由于这一潜在的危险,许多路由器直接抛弃包含LSR选项的数据包。
为了确定从A穿过X到B时LSR是否可用,A可以简单的尝试用松散源路由穿过X连接到B,如果X收到这个包,这时LSR在A到B的第一半到X的行途是可用的。
如果在一段指定的超时后X还没收到任何包,这时可以假定LSR不可用。
由于X可以用这种方法得知从A到B的一半行途是否允许LSR可用,所以它必须检查也能收到从B来的LSR包。
如果也能收到,则X可以断定从A到B穿过X的LSR是可用的,任何的其它情况都必须假定LSR是无此选项。
为了确定是否NA可以随机或者可预测地分配端口,A可以用连续的端口打开两个到X的TCP连接。
如果X得到的这些连接端口是连续的,则X可以断定NA连续地分配了端口,同时是可预测的。
当连接到B时,A必须使用连续的下一个端口来确保NA继续按照X能预测的方式来分配端口。
如果NA没有连续地分配端口,但如果NA执行了一致地转换,这时仍然可以预测到A的端口。
A必须先打开一个在内端口PA到X的合法连接。
NA将分配给这个连接一个随机端口。
当包被发到X时,X可以非常清楚地看到NA所选择的端口。
A可以在相同端口打开第二个到X的不同端口的连接,这时X可以看到这两个连接是否包含了相同的外部端口。
如果是,则NA进行了一致性转换。
由于现在A必须用内部端口PA连接到B,所以X可以把NA所选的外部端口告诉B。
把A到X的连接维持到A被连接到B以使NA不会改变端口映射是很重要的。
如果尝试了两种端口预测方法后,X不能可靠地预测NA分配的端口,这时X必须假定NA是随机的分配端口。
当X完成对A的诊断,它可以同时用相同的方法对B进行诊断。
一旦X拥有了所需的信息,连接协议就可以开始了。
从这个诊断收集信息确保了详细情况的执行。
5.2序列号和应答号的协调
每个参与TCP连接者都维持两个变量,一个序列号和一个应答号。
在任何给定时刻,在任何主机的序列号是最后包发送的序列号。
另外,在任何给定时刻,在任何主机的应答号是下一个预期包的应答号。
通过三次握手的分步,初始的序列号和应答号被建立,如下:
1.在客户端发送SYN包后,
客户端的seq#(序列号):
P,ack#(应答号):
N/A
服务端的seq#(序列号):
N/A,ack#(应答号):
N/A
2.在服务端接收到SYN包和发送SYN+ACK后,
客户端的seq#(序列号):
P,ack#(应答号):
N/A
服务端的seq#(序列号):
Q,ack#(应答号):
P+1
3.在客户端接收到SYN+ACK和发送ACK后,
客户端的seq#(序列号):
P,ack#(应答号):
Q+1
服务端的seq#(序列号):
Q,ack#(应答号):
P+1
4.在服务端接收到ACK后,
客户端的seq#(序列号):
P,ack#(应答号):
Q+1
服务端的seq#(序列号):
Q,ack#(应答号):
P+1
在三次握手最后的状态必须被我们的方案所复制到,即使两个端点假定为客户的角色。
在每个方案的最后,各个端点的应答号必须是大于他们的伙伴的序列号。
我们的方案完成这个协调。
5.3低的TTL值确保
我们的方案某些是依赖于设置一个TCP包的TTL值,因此包将离开端点的内部网络,但没到达伙伴的NAT。
对不同的网络这个值将不同,因此它必须能被动态确定。
为了确定伙伴距离有多远,一个端点可以使用典型的路由追踪方法。
就是,发送从1开始而不断增加的TTL值的SYN包。
当TTL失效时各个包将导致ICMPTTL过期包被发回到端点。
通过分析返回的ICMPTTL过期包可以为连接中低的TTL值确定一个保险值。
许多NAT不将ICMPTTL过期包发回内部主机,所以一个端点可以议定当一个ICMPTTL过期包没有被返回时,用一个TTL值来引发一个包离开内部网。
同样地,在NAT返回ICMPTTL过期包,通过分析伙伴的NAT的消息,端点必须以发现的保险的TTL值为基础。
如果伙伴的NAT产生一个RST包,则端点可以使用一个比所产生的RST包小1的TTL值。
如果端点没有得到RST包但开始停止接收ICMPTTL过期包,则可以确定伙伴的NAT用了抛弃不请自来的消息而没有响应的保险行为。
事实上,这种情况和端点的NAT没有返回ICMPTTL过期包是一样的。
这个保险TTL值的确定不需要任何其它端点的参与。
因此,它可以在保险低的TTL值被用于连接之前就被确定。
5.4情况1:
<可预测的,可预测的,LSR>
A X B
|-------1a----->|<------1b------|
|<------2a------|-------2b----->|
|-------2b------|-------------->|
|<--------------|-------3a