前言
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所分配地端口。补充一点,如果端口分配是随机地,我们就使用一种叫“birthday paradox”的方法减少检测的次数。这种方法需要的空间是直接的穷举所使用的空间的开方。
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-way NATs,Twice NATs,Multi-homed NATs和Traditional NATs。关于Two-way NATs,Twice NATs和Multi-homed NATs的特性讨论请看[12]。Two-way NATs,一般也叫双向NAT(Bidirectional NATs),在外部地址和内部地址间执行双向转换,尽管它个数据包只转换一个IP地址。这种NAT是唯一一种允许从外部发起的连接请求。相反地,Twice NATs,它每路由一个数据包都对内部和外部的地址进行转换。这种NAT用在那些外部地址和内部地址交叠的情况下。Multi-homed NATs对于Twice NATs来说,多了一个功能,它可以允许在内部使用不能路由的地址并且还可以有多个连接到外部网络。之所以Multi-homed NATs能够这样做,是因为它跟另一个保持通信以确定他们的地址映射是不变的。Multi-homed NATs允许大量的内部网络和增加冗余来允许多个连接到Internet。到目前为止,最常用的NAT是传统NAT(Traditional NATs),它可以分为基础NAT(Basic NATs)和NATP(Network Address Port Translation)两种。
基本NAT和NATP的区别它所能分配给内部地址的外部地址是否比内部地址多。一个Simple NAT用在那种所能分配的外部地址跟内部地址的数量相等或者更多的时候。这种NAT进行端口分配,因为每个内部地址都可以分配到一个唯一的外部地址。NATPS用于当NAT所能用来分配的外部地址的数量比内部地址少的时候,最常见的一种情况是很多的内部机器共享一个外部IP地址。在这种情况下,NAT必须分配端口来补充IP用以消除网络传输的不明确的几率。NAT和NATP共同的地方就是他们都阻止外来的连接,并且都可以进行静态和动态的地址分配。
NAPT是传统NAT中最普遍的一种,因为它允许很多的内部连接共享很少量的外部网络地址。大部分为小型网络设计的商业NAT都是NAPT。我们选择NATP作为我们研究的对象就是因为它的流行和它通过不允许外来连接限制了P2P协议。后面我们就把NATPs简称为NATs了。
我们首先要做的是得到商业防火墙以确定他们的特性跟资料上记载的是否一样。我们使用NatCheck这个程序对三个常用的NAT设备进行了测试:Netgear MR814,Linksys BEFSR41,和Linksys BEFW11S4。三个NAT都有相似的行为:他们对UDP和TCP都进行了一致的转换,这表明在内部主机使用<内部IP:内部端口>的时候,NAT是否直接将<内部IP:内部端口>映射到<外部地址:外部端口>,而不管它连接出去的目标主机<目标IP:目标端口>是多少。一致转换是静态NAT与动态NAT截然不同的一个特点,因为这不只是跟内部主机使用的地址有关,而且还跟端口有关。RFC3002明确指出要支持一致转换。没有一个NAT支持环路转换,不管是TCP还是UDP,这表明NAT是否可以正确的处理两个只知道对方外部地址的内部主机之间的连接。在我们的项目中,我们假设两个主机是在不同的NAT后面的,所以这个测试跟我们的目标是无关的。最后,所有的NAT都提供了对非主动请求的外来TCP和UDP包都进行过滤,这个测试可以表明NAT是否预防非主动恳求的外来包进入到内部网络。非主动请求过滤发生在除了Two-way NAT之外的所有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(The Simple Traversal of User Datagram Protocol through Network Address Translators)协议实现了一种打洞技术可以在有限的情况下允许对NAT行为进行自动检测然后建立UDP连接[10]。
在UDP打洞技术中,NAT分配的外部端口被发送给协助直接连接的第三方。在NAT后面的双方都向对方的外部端口发送一个UDP包,这样就在NAT上面创建了端口映射,双方就此可以建立连接。一旦连接建立,就可以进行直接的UDP通信了。在UDP打洞技术可以成功的情况下,我们在这篇文章中所用的有的技术也同样适用。建立TCP连接比UDP连接更有优势。首先,UDP连接不能够依赖建立好的连接,就是不能够持久连接。UDP是无连接的并且没有对明确的通信。一般地,NAT见了的端口映射如果一段时间不活动后就是过期。为了保持UDP端口映射,必须每隔一段时间就发送UDP包,就算没有数据的时候,只有这样才能保持UDP通信正常。第二,很多防火墙都配置成拒绝任何的外来UDP连接。最后,一个单纯的TCP连接的实现更直观,并且现有的代码简单得修改就可以使用我们的技术。
目前的工作由bryan Ford[2]完成,已经扩展了打洞技术,使能在正规的NAT后的不同主机间进行TCP连接。方法类似于UDP打洞,由于一个映射在各个主机的NAT上被建立以使TCP直接连接能被创建,同步或同时TCP打开。这工作的焦点在于开发一种工作于最多的被定义为其它NAT所需要而描述的NAT的技术来和TCP打洞协调一致。我们工作的不同点在于我们所开发的方案能够在目前各类NAT行为上进行直接地TCP连接,包括不协调于TCP打洞的NAT。
Gnutella有一个能使在两个不同端点的TCP通信的方案[14],但这仅仅应用于一个端点在NAT后面的情形。这方案被称为Push Proxy并本质地建立多个能够推连接请求到NAT后的服务器的节点。NAT后的服务器发送消息到端点询问他们是否愿意推为代理。当NAT后的服务器指明它有一个文件和询问匹配,服务器包含了一列同意被推为代理的端点。当端点想下载文件,它发送一个Gnutella PUSH消息到一个push proxy,这代理允许消息通过到达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|
└────────┘
└────────┘
↑↑
↑↑
↓ /
/ ↓
┌────────┐
┌────────┐
│
NAT NA │
│
NAT NB │
│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中都是成立的。第一个假设是我们假设主机都收不到来自外部网络的ICMP TTL过期包。如果这些包被主机接收,那么TCP连接就会被中断。我们的很多解决方法中都依赖于能够为发送SYN的数据包设置一个很低的TTL值。当SYN包被路由丢弃的时候,TCMP TTL过期包会被发送回NAT。用于我们实现的NAT必须没有转发收到的ICMP TTL过期包给内部网络。如果NAT真的把这个包发送回内部网络,也可以配置防火墙来阻止这类包。我们的另一个假设是NAT看到ICMP TTL过期包的时候映射的端口还不会失效。作为另一个选择,我们可以不修改TTL值,那就必须让目标NAT不会产生TCP RST包。而实际上这个选择是可行的,因为很多的NAT为了防止端口扫描一般都不发送TCP RST包。
5. 技术
使用图1的模型,我们的目标是让在NA和NB后面的A和B建立直接的TCP连接。
我们已经开发出几种方法可以建立这样的连接,根据NAT和网络情况的不同,我们有对应的方法。考虑以下的信息作为有序的三元组:<NA端口可预测性,NB端口可预测性,源地址可路由>。我们考虑下面几种情况:
情况 1: <可预测的, 可预测的, LSR>
情况 2: <可预测的, 可预测的, no LSR>
情况 3: <随机的, 可预测的, LSR>
情况 4: <随机的, 可预测的, no LSR>
情况 5: <随机的, 随机的, LSR>
情况 6: <随机的, 随机的, no LSR>
注意:<随机的, 可预测的,X>等同于<可预测的, 随机的,X>。
5.1 连接前诊断
作为协助者X还有两个端点A和的B,为了确定A和B将试图的连接属于下面的哪种情况,X必须首先对各个端点做一些诊断。
为了使用1,3,5的情况,两端必须确定在A和X还有B和X之间的LSR(松散源路由)是否是可用的。loose source routing(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 失效时各个包将导致 ICMP TTL 过期包被发回到端点。通过分析返回的 ICMP TTL 过期包可以为连接中低的 TTL 值确定一个保险值。
许多 NAT 不将 ICMP TTL 过期包发回内部主机,所以一个端点可以议定当一个 ICMP TTL 过期包没有被返回时,用一个 TTL 值来引发一个包离开内部网。
同样地,在 NAT 返回 ICMP TTL 过期包,通过分析伙伴的 NAT 的消息,端点必须以发现的保险的 TTL 值为基础。如果伙伴的 NAT 产生一个 RST 包,则端点可以使用一个比所产生的 RST 包小 1 的 TTL 值。如果端点没有得到 RST 包但开始停止接收 ICMP TTL 过期包,则可以确定伙伴的 NAT 用了抛弃不请自来的消息而没有响应的保险行为。事实上,这种情况和端点的 NAT 没有返回 ICMP TTL 过期包是一样的。
这个保险 TTL 值的确定不需要任何其它端点的参与。因此,它可以在保险低的 TTL 值被用于连接之前就被确定。
5.4 情况 1 : < 可预测的,可预测的, LSR>
A X B
|-------1a----->|<------1b------|
|<------2a------|-------2b----->|
|-------2b------|-------------->|
|<--------------|-------3a------|
|-------4a----->|<------4b------|
图 2 :情况 1
我们使用符号 “NA:4000 → NB : 5000 ,选项 / 负荷 ” 来表示在英特网上从 NA 到 NB 所传送的包内容。这符号意味着包有一个 NA 的 IP 源地址,源端口 4000 ,目的地址 NB 的 IP 地址,和目的端口 5000 。此外,在目的端口后是其它的任何重要选项或负荷值。选项包含 LSR:X 、 SYN:P 、 ACK:Q 和 SYN+ACK:R,S 。 LSR:X 表明包将可松散源路由到 X 。 SYN:P , ACK:Q 表明跟随的序列号或者应答号的 TCP 包的类型。 SYN+ACK:P,Q+1 表明包是一个序列号为 P 和应答号为 Q+1 的 TCP SYN+ACK 包。首先我们展示情况 1< 可预测的,可预测的, LSR> ,其中所使用的事件序号在图 2 中能找得到。
1.A 和 B 个发送一个可松散源路由的 SYN 包穿过协助者 X 到对方
( a ) NA:4000 → NB:5000,LSR:X,SYN:P
( b ) NB:5000 → NA:4000,LSR:X,SYN :Q
这两个 SYN 包由 TCP 的 connect() 函数调用产生。 SYN 在 NAT NA 和 NB 上都创建了预期的映射。在 NA 上的映射将允许后面被转播向 A 而来自 NB:5000 的通信并签准。
2.X 缓存两个包并向 A 和 B 各自发了对方所用的 ISN
( a ) X:1234 → NA:3999,B 刚用的 ISN Q
( b ) X:1235 → NB:4999,A 刚用的 ISN P
各个端点都需要他们的伙伴的 ISN ,这样他们才能构造出一个合法的 SYN+ACK 包。
3.A 和 B 各向对方发送一个 SYN+ACK 包
( a ) NB:5000 → NA:4000,LSR:X,SYN+ACK:Q,P+1
( b ) NA:4000 → NB:5000,LSR:X,SYN+ACK:P,Q+1
这两个 SYN+ACK 包是由运行于各个端点的独立线程所产生的。 A 和 B 重新使用了原来的序列号 P 和 Q 作为在 SYN+ACK 中的序列号,并保证了所复制的序列号和应答号的最后状态和 5.2 节讨论的真实的 TCP 连接一样。
4.A 和 B 各向对方发送一个 ACK 包
( a ) NA:4000 → NB:5000 ,LSR:X,ACK:Q+1
( b ) NB:5000 → NA:4000,LSR:X,ACK:P+1
一旦欺骗的 SYN+ACK 包被接收到, TCP 堆栈将自动地为我们做这一步。
5.X 抛弃两个到达的 ACK 包,因为没有任何一个端点期望接收到这个 ACK 。
图 2 假定了 A 和 B 知道他们的伙伴将使用哪个端口来工作;由于各个端点和它的伙伴都必须提前知道对方的信息,所以这一假设是合理的。首先第一步, X 必须完成对 A 和 B 的端口预测,因此 X 能预测到 NAT 设备选择的端口。 A 必须知道 NB 工作于 5000 端口,同时 B 必须知道 NA 工作于 4000 端口。为简单化,可以假定 X 自己没有在 NAT 后面,但唯一的假定前提是 X 必须预先直接连接到 A 和 B 。
情况 1 存在一种变种的方案。 X 可以发送在第 2 和第 3 步所需的 SYN+ACK 欺骗包,这似乎好于发送信息到 A 和 B 以使他们自己能够伪造 SYN+ACK 包。我们选择目前的方法是因为如果 X 发送 SYN+ACK 欺骗包,他们将被路由器抛弃的总比通过的多。此外,发送从 X 到 A 和 B 的 SYN+ACK 伪造包需要超级用户权限。而 A 和 B 为了其它的目的必须且已经运行于超级用户权限。
在我们的技术中,从步骤 2 到 5 是这样考虑的,我们将申明函数 Case1(integer extPortA, integer extPortB) 作为执行步骤 2 到 5 ,把参数 extPortA 和 extPortB 各自取代外部端口 4000 和 5000 。
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 失效时各个包将导致 ICMP TTL 过期包被发回到端点。通过分析返回的 ICMP TTL 过期包可以为连接中低的 TTL 值确定一个保险值。
许多 NAT 不将 ICMP TTL 过期包发回内部主机,所以一个端点可以议定当一个 ICMP TTL 过期包没有被返回时,用一个 TTL 值来引发一个包离开内部网。
同样地,在 NAT 返回 ICMP TTL 过期包,通过分析伙伴的 NAT 的消息,端点必须以发现的保险的 TTL 值为基础。如果伙伴的 NAT 产生一个 RST 包,则端点可以使用一个比所产生的 RST 包小 1 的 TTL 值。如果端点没有得到 RST 包但开始停止接收 ICMP TTL 过期包,则可以确定伙伴的 NAT 用了抛弃不请自来的消息而没有响应的保险行为。事实上,这种情况和端点的 NAT 没有返回 ICMP TTL 过期包是一样的。
这个保险 TTL 值的确定不需要任何其它端点的参与。因此,它可以在保险低的 TTL 值被用于连接之前就被确定。
5.4 情况 1 : < 可预测的,可预测的, LSR>
A X B
|-------1a----->|<------1b------|
|<------2a------|-------2b----->|
|-------2b------|-------------->|
|<--------------|-------3a------|
|-------4a----->|<------4b------|
图 2 :情况 1
我们使用符号 “NA:4000 → NB : 5000 ,选项 / 负荷 ” 来表示在英特网上从 NA 到 NB 所传送的包内容。这符号意味着包有一个 NA 的 IP 源地址,源端口 4000 ,目的地址 NB 的 IP 地址,和目的端口 5000 。此外,在目的端口后是其它的任何重要选项或负荷值。选项包含 LSR:X 、 SYN:P 、 ACK:Q 和 SYN+ACK:R,S 。 LSR:X 表明包将可松散源路由到 X 。 SYN:P , ACK:Q 表明跟随的序列号或者应答号的 TCP 包的类型。 SYN+ACK:P,Q+1 表明包是一个序列号为 P 和应答号为 Q+1 的 TCP SYN+ACK 包。首先我们展示情况 1< 可预测的,可预测的, LSR> ,其中所使用的事件序号在图 2 中能找得到。
1.A 和 B 个发送一个可松散源路由的 SYN 包穿过协助者 X 到对方
( a ) NA:4000 → NB:5000,LSR:X,SYN:P
( b ) NB:5000 → NA:4000,LSR:X,SYN :Q
这两个 SYN 包由 TCP 的 connect() 函数调用产生。 SYN 在 NAT NA 和 NB 上都创建了预期的映射。在 NA 上的映射将允许后面被转播向 A 而来自 NB:5000 的通信并签准。
2.X 缓存两个包并向 A 和 B 各自发了对方所用的 ISN
( a ) X:1234 → NA:3999,B 刚用的 ISN Q
( b ) X:1235 → NB:4999,A 刚用的 ISN P
各个端点都需要他们的伙伴的 ISN ,这样他们才能构造出一个合法的 SYN+ACK 包。
3.A 和 B 各向对方发送一个 SYN+ACK 包
( a ) NB:5000 → NA:4000,LSR:X,SYN+ACK:Q,P+1
( b ) NA:4000 → NB:5000,LSR:X,SYN+ACK:P,Q+1
这两个 SYN+ACK 包是由运行于各个端点的独立线程所产生的。 A 和 B 重新使用了原来的序列号 P 和 Q 作为在 SYN+ACK 中的序列号,并保证了所复制的序列号和应答号的最后状态和 5.2 节讨论的真实的 TCP 连接一样。
4.A 和 B 各向对方发送一个 ACK 包
( a ) NA:4000 → NB:5000 ,LSR:X,ACK:Q+1
( b ) NB:5000 → NA:4000,LSR:X,ACK:P+1
一旦欺骗的 SYN+ACK 包被接收到, TCP 堆栈将自动地为我们做这一步。
5.X 抛弃两个到达的 ACK 包,因为没有任何一个端点期望接收到这个 ACK 。
图 2 假定了 A 和 B 知道他们的伙伴将使用哪个端口来工作;由于各个端点和它的伙伴都必须提前知道对方的信息,所以这一假设是合理的。首先第一步, X 必须完成对 A 和 B 的端口预测,因此 X 能预测到 NAT 设备选择的端口。 A 必须知道 NB 工作于 5000 端口,同时 B 必须知道 NA 工作于 4000 端口。为简单化,可以假定 X 自己没有在 NAT 后面,但唯一的假定前提是 X 必须预先直接连接到 A 和 B 。
情况 1 存在一种变种的方案。 X 可以发送在第 2 和第 3 步所需的 SYN+ACK 欺骗包,这似乎好于发送信息到 A 和 B 以使他们自己能够伪造 SYN+ACK 包。我们选择目前的方法是因为如果 X 发送 SYN+ACK 欺骗包,他们将被路由器抛弃的总比通过的多。此外,发送从 X 到 A 和 B 的 SYN+ACK 伪造包需要超级用户权限。而 A 和 B 为了其它的目的必须且已经运行于超级用户权限。
在我们的技术中,从步骤 2 到 5 是这样考虑的,我们将申明函数 Case1(integer extPortA, integer extPortB) 作为执行步骤 2 到 5 ,把参数 extPortA 和 extPortB 各自取代外部端口 4000 和 5000 。
5.5
情况
2
:
<
可预测的,可预测的,
no LSR>
A X B
|-------2a----->|<------2b------|
|-------3a----->|<------3b------|
|<------4a------|-------4b----->|
|-------5b------|-------------->|
|<--------------|-------5a------|
|-------6a----->|<------6b------|
图 3 :情况 2
情况 1 依靠于可用的松散源路由。许多路由器目前设置了预防松散源路由,同时将具有代表性地抛弃请求服务的包。同样地,依靠于松散源路由的技术在实际中将有很高的概率会失败。如果松散源路由不可利用, SYN 序列号可以利用一个外出通道(他们预先连接到 X 的连接)和 X 通信,而不是物理性地让 X 看到包。注意图 2 的步骤 2 , X 知道 TCP 序列号 P 和 Q ,因为 X 正确地接收到两个 SYN 包。没了 LSR 就不是这种情况了。
为了初始连接,每端主机发送一个初始的 SYN 包到他们的伙伴,虽然他们知道将不能到达目的地。他们这时嗅探包离开网络,记录序列号,并报告这个信息给 X 。 X 需要这些包的 TCP 序列号,如此它将能够转播这些信息回到 A 和 B ,那样他们就能够产生 SYN+ACK 包。两条路线所发的包都不会到达他们所标明地址的目的地。
更简单的方案是每个端点各发送一个不被考虑的 SYN 到他们伙伴。在接收端适当的设置 NAT 和防火墙,他们将由于没有映射存在而不会向前发送这些包到内部网络主机。一些 NAT 和一些防火墙将发送 TCP 重置包 (RST) 到这个不请自来的包的源地址。如果 NAT 产生 RST 包, A 和 B 不能简单的像图 2 里步骤 1 的建议一样发送一个 SYN 包到对方,因为在接受到 RST , NA 和 NB 将终止所建立的洞。如果 NAT 没有产生 RST 包,打开的 TCP 连接将不会突然终止。
另一种确保 SYN 包将不会到达它的目的网络的方法是发送带有低于伙伴的 NAT 路径长度的 TTL 值的 SYN 包。这包将会在去目的地的路上被明确地抛弃,并且 TCP RST 包将不会被任何发送者看到。更确切地说,一个 ICMP 过期包将被看到,并且产生一个问题,因为 ICMP 过期包突然地终止一个 TCP 连接。然而,如果使用者能够设置他们本地的防火墙抛弃 ICMP 包或者如果 NAT 不会发送 ICMP 消息包到它的内部网络, TCP 连接的尝试将不会突然终止。
一个方案不能包括简单的欺骗源地址的 SYN 包,使发送者不会接收到 ICMP 包或者 RST 包。这样做会在中间件创建一个残废的映射。中间件在看到一个 SYN 包时,将创建一个从内部 IP 地址和端口到外部 IP 和端口的映射。然而,由于一个欺骗的 SYN 包有一个错误的源 IP 地址,映射将不会对应到内部网络的主机。此外,一个方案不能包括把 TTL 设置低到连中间件都看不到 SYN 包,因为这样做不会创建我们需要的允许外部后面发到内网的通信的映射。
假定一个 < 可预测的,可预测的, no LSR> 环境,这种连接就像我们现在在图 3 所描述的。
1.X 做了关于 5.1 节描述的端口预测。 X 预测到 NA 的下一端口是 4000 并预测到 NB 的下一端口是 5000 ,并经由已经存在的连接告知 A 和 B 。
2.A 和 B 各自发送一个 SYN 包到对方,他们知道这个包将会被对方的 NAT 抛弃或者由于 TTL 过期而被抛弃。
( a ) NA:4000 → NB:5000,SYN:P
( b ) NB:5000 → NA:4000,SYN:Q
这一点是在各个端点的真实 TCP 的 connect() 函数调用所产生的。 SYN 包由 TCP 堆栈产生。这在 NAT 上创建了允许后面的来自伙伴 IP 地址和端口的通信通过并到达端点的映射。
3.A 和 B 各自发送他们自己所留意的 ISN ( P 和 Q )到 X 。
( a ) NA:3999 → X:1234, 刚使用的 ISN 号 P
( b ) NB:4999 → X:1235, 刚使用的 ISN 号 Q
各个端点都需要它的伙伴的 ISN ,如此他们才能够构造合法的 SYN+ACK 包从而发到他们的伙伴。
4.X 发送 A 和 B 的对方各自所留意的 ISN 到 A 和 B
( a ) X:1234 → NA:3999,B 刚使用的 ISN 号 Q
( b ) X:1235 → NB:4999, A 刚使用的 ISN 号 P
5.A 和 B 各自发送一个 SYN+ACK 包到对方
( a ) NB:5000 → NA:4000,SYN+ACK:Q,P+1
( b ) NA:4000 → NB:5000,SYN+ACK:P,Q+1
这是三次握手的第二部分。此外, A 和 B 重用了他们原来的序列号 P 和 Q 作为在 SYN+ACK 中的序列号,并保证了所复制的序列号和应答号的最后状态和 5.2 节讨论的真实的 TCP 连接一样。
6.A 和 B 各自发送一个 ACK 包到对方,他们知道这包将会被对方的 NAT 抛弃或者由于 TTL 过期而被抛弃。
( a ) NA:4000 → NB:5000,ACK:Q+1
( b ) NB:5000 → NA:4000,ACK:P+1
TCP 堆栈将自动地为我们发送这些 ACK 包来完成三次握手。我们不想 ACK 包到达他们的目的地,由于没有任何一方在等待 ACK 包。
作为步骤 4 到 5 而和情况 1 很相似的另一种方法,是 X 可以欺骗 A 和 B 所需的 SYN+ACK 包。然而,我们为了某些类似在情况 1 中的原因选择了目前的方法。
由于步骤 2 到 6 在我们的技术中是可重复利用的,所以我们做了函数 Case2(integer extPortA, integer extPortB) 作为执行步骤 2 到 6 ,用参数 extPortA 和 extPortB 分别替代了外部端口 4000 和 5000 。
5.6 情况 3 : < 随机的,可预测的, LSR>
A X B
|-------2a----->| |
| |-------2b----->|
| |<------2c------|
| Case1|(m,5000) |
图 4 :情况 3
情况 3< 随机的,可预测的, LSR> 类似于图 2 所描述的情况 1 。然而, X 不能够预测到两个 NAT 中的一个的端口,比方说 NA 。 A 首先不得不发送它的 SYN 包来允许 X 查看 NA 所选的端口。 X 将报告这一信息给 B ,因此 B 能够发送它的 SYN 包到正确的目标 IP 地址和端口。这个情况 1 的修改版在图 4 中描述并解释如下。
1.X 做了关于 5.1 节描述的端口预测。 X 不能预测到 NA 的下一端口,但能够预测到 NB 的下一端口是 5000 ,并经由已经存在的连接告知 A 和 B 。
2.A 和 B 同步经由 X
( a ) NA:m → NB:5000,LSR:X,SYN:P
( b ) X 让 B 知道 NA 工作于端口 m
( c ) NB:5000 → NA:m , LSR:X , SYN:Q
这两个 SYN 包是由 TCP 的 connect() 函数调用所产生。
这两个 SYN 在 NAT 的 NA 和 NB 上各自创建了预期的映射。
3. 调用 Case1(m,5000)
5.7 情况 4 : < 随机的,可预测的, no LSR>
A X B
|-------2a----->| |
|-------------->| |
|-------------->| |
| ┆ | |
|---------- ---->|-------3------>|
|<--------------|-------4-------|
|<--------------|---------------|
|<--------------|---------------|
| | ┆ |
|<--------------|---------------|
|-------5------>| |
| |-------6------>|
| Case2|(m,5000) |
图 5 :情况 4
情况 4 的环境是 < 随机的,可预测的, no LSR> 。我们已经为这个依靠于随机的且不拒绝一个带了残废的且对应于在 NAT 后主机在前面的初始连接的 ACK 或者校验和域的 TCP 包的 NAT 环境开发了一个方案。这方案被呈现在图 5 中并解释如下。
1.X 做了关于 5.1 节描述的端口预测。 X 不能预测到 NA 的下一端口,但能预测到 NB 的下一端口是 5000 ,并经由已经存在的连接告知 A 和 B 。
2.A 发送 T 个 SYN 包到 B ,这些包不是被对方的 NAT 所抛弃就是由于 TTL 过期而被抛弃。
i = 0
While i < T
NA:rand → NB:5000, SYN:anything
i = i+1
End While
这会在 NAT NA 上创建 T 个映射, B 将最后用 SYN+ACK 猜到的就是其中一个。
3.X 通知 B 开始发送 SYN+ACK 包到 NA
4.B 发送许多 SYN+ACK 包到 NA 直到有一个到达 A
i = 1024
While A 还没报告成功
NB:5000 → NA:i,
SYN+ACK:,anything,anything, Payload:i
i = i+1
End While
5.A 报告穿过 NAT 的包负荷。
NA:3999 → X:1234, 工作于端口 m
A 通过监听来自 NB 的任何 SYN+ACK 包的数据报,将可看到这个残废的 SYN+ACK 包。
6.X 告诉 B 连接到 A 的端口 m
B 现在知道 SYN 包可以发向哪里。
7. 调用 Case2(m,5000)
在步骤 2 中 A 所发送的 T 个 SYN 包是独立于任何 TCP 的 connect() 函数调用。他们仅仅是使用在 NAT 上创建了 T 个映射的 libnet 库所产生的包。另一方面, Case2 调用的步骤 2 所产生的 SYN 包是由 A 和 B 的 TCP 的 connect() 函数调用所产生的。这情况 4 环境的方案依靠于随机生成端口的 NAT 的行为。这方案依靠于拒绝带有错误的如序列号或者校验和域的 TCP 包的中间件。
T 值能够被选择,像 B 在生成 T 个 SYN+ACK 到随机目的端口号后有 95% 的机会猜到一个正确的外部端口。其实, A 的 NAT 随机地选择 T 的数目(它的外部端口数),这时 B 必须一直维持猜测直到在 A 的 NAT 可选集中 B 选择到了一个。我们能够使用一个概率分析来构造一个高效的且最小工作量被 A 和 B 所预算的设想。设 PrG 是 B 猜到在 T 个实验中最少一个是正确的概率。价设 A 的 NAT 已经在 [1025 , 65535] 的范围中选择了 T 个不同的端口号,如果 B 选择 T 个不同的端口号,在 A 的 NAT 可选集中 B 不能选择到一个号的可能性是
Pr_G =n-T/n * n-1-T/n-1 * n-2-T/n-2 * . . . * n-(T -1)-T/n-(T -1)
其中 n 是可能选择的端口数( n=65535-1024=64511 )。
T-1
Pr_G =∏n-i-T/n-i
i=0
反之,在 T 个实验中猜到一个是正确的可能性是
PrG = 1-Pr_G
像之前的规定, T 可能被选择如
PrG > 95%
T-1
1-∏n-i-T/n-i> 95%
i=0
计算 T 的量为 T=439 的这个乘积。
439-1
1-∏64511-i-439/64511-i=0.9506> 95%
i=0
这结果说明如果 A 发送 439 个使在 A 的 NAT 外部端口得到不同的随机的映射的 SYN 包,并且 B 发送许多不同的随机的到目的端口的 SYN+ACK 包, B 在它发送第 440 个 AYN+ACK 包之前就有大于 95% 的机会正确地猜测到 439 个外部端口映射的其中一个。
仅仅发送 T 个 SYN 包的原因是这至少要使用两个资源,第一个是网络带宽的使用量,第二是在 NAT 上创建映射的数量。
A X B
|-------2a----->|<------2b------|
|-------3a----->|<------3b------|
|<------4a------|-------4b----->|
|-------5b------|-------------->|
|<--------------|-------5a------|
|-------6a----->|<------6b------|
图 3 :情况 2
情况 1 依靠于可用的松散源路由。许多路由器目前设置了预防松散源路由,同时将具有代表性地抛弃请求服务的包。同样地,依靠于松散源路由的技术在实际中将有很高的概率会失败。如果松散源路由不可利用, SYN 序列号可以利用一个外出通道(他们预先连接到 X 的连接)和 X 通信,而不是物理性地让 X 看到包。注意图 2 的步骤 2 , X 知道 TCP 序列号 P 和 Q ,因为 X 正确地接收到两个 SYN 包。没了 LSR 就不是这种情况了。
为了初始连接,每端主机发送一个初始的 SYN 包到他们的伙伴,虽然他们知道将不能到达目的地。他们这时嗅探包离开网络,记录序列号,并报告这个信息给 X 。 X 需要这些包的 TCP 序列号,如此它将能够转播这些信息回到 A 和 B ,那样他们就能够产生 SYN+ACK 包。两条路线所发的包都不会到达他们所标明地址的目的地。
更简单的方案是每个端点各发送一个不被考虑的 SYN 到他们伙伴。在接收端适当的设置 NAT 和防火墙,他们将由于没有映射存在而不会向前发送这些包到内部网络主机。一些 NAT 和一些防火墙将发送 TCP 重置包 (RST) 到这个不请自来的包的源地址。如果 NAT 产生 RST 包, A 和 B 不能简单的像图 2 里步骤 1 的建议一样发送一个 SYN 包到对方,因为在接受到 RST , NA 和 NB 将终止所建立的洞。如果 NAT 没有产生 RST 包,打开的 TCP 连接将不会突然终止。
另一种确保 SYN 包将不会到达它的目的网络的方法是发送带有低于伙伴的 NAT 路径长度的 TTL 值的 SYN 包。这包将会在去目的地的路上被明确地抛弃,并且 TCP RST 包将不会被任何发送者看到。更确切地说,一个 ICMP 过期包将被看到,并且产生一个问题,因为 ICMP 过期包突然地终止一个 TCP 连接。然而,如果使用者能够设置他们本地的防火墙抛弃 ICMP 包或者如果 NAT 不会发送 ICMP 消息包到它的内部网络, TCP 连接的尝试将不会突然终止。
一个方案不能包括简单的欺骗源地址的 SYN 包,使发送者不会接收到 ICMP 包或者 RST 包。这样做会在中间件创建一个残废的映射。中间件在看到一个 SYN 包时,将创建一个从内部 IP 地址和端口到外部 IP 和端口的映射。然而,由于一个欺骗的 SYN 包有一个错误的源 IP 地址,映射将不会对应到内部网络的主机。此外,一个方案不能包括把 TTL 设置低到连中间件都看不到 SYN 包,因为这样做不会创建我们需要的允许外部后面发到内网的通信的映射。
假定一个 < 可预测的,可预测的, no LSR> 环境,这种连接就像我们现在在图 3 所描述的。
1.X 做了关于 5.1 节描述的端口预测。 X 预测到 NA 的下一端口是 4000 并预测到 NB 的下一端口是 5000 ,并经由已经存在的连接告知 A 和 B 。
2.A 和 B 各自发送一个 SYN 包到对方,他们知道这个包将会被对方的 NAT 抛弃或者由于 TTL 过期而被抛弃。
( a ) NA:4000 → NB:5000,SYN:P
( b ) NB:5000 → NA:4000,SYN:Q
这一点是在各个端点的真实 TCP 的 connect() 函数调用所产生的。 SYN 包由 TCP 堆栈产生。这在 NAT 上创建了允许后面的来自伙伴 IP 地址和端口的通信通过并到达端点的映射。
3.A 和 B 各自发送他们自己所留意的 ISN ( P 和 Q )到 X 。
( a ) NA:3999 → X:1234, 刚使用的 ISN 号 P
( b ) NB:4999 → X:1235, 刚使用的 ISN 号 Q
各个端点都需要它的伙伴的 ISN ,如此他们才能够构造合法的 SYN+ACK 包从而发到他们的伙伴。
4.X 发送 A 和 B 的对方各自所留意的 ISN 到 A 和 B
( a ) X:1234 → NA:3999,B 刚使用的 ISN 号 Q
( b ) X:1235 → NB:4999, A 刚使用的 ISN 号 P
5.A 和 B 各自发送一个 SYN+ACK 包到对方
( a ) NB:5000 → NA:4000,SYN+ACK:Q,P+1
( b ) NA:4000 → NB:5000,SYN+ACK:P,Q+1
这是三次握手的第二部分。此外, A 和 B 重用了他们原来的序列号 P 和 Q 作为在 SYN+ACK 中的序列号,并保证了所复制的序列号和应答号的最后状态和 5.2 节讨论的真实的 TCP 连接一样。
6.A 和 B 各自发送一个 ACK 包到对方,他们知道这包将会被对方的 NAT 抛弃或者由于 TTL 过期而被抛弃。
( a ) NA:4000 → NB:5000,ACK:Q+1
( b ) NB:5000 → NA:4000,ACK:P+1
TCP 堆栈将自动地为我们发送这些 ACK 包来完成三次握手。我们不想 ACK 包到达他们的目的地,由于没有任何一方在等待 ACK 包。
作为步骤 4 到 5 而和情况 1 很相似的另一种方法,是 X 可以欺骗 A 和 B 所需的 SYN+ACK 包。然而,我们为了某些类似在情况 1 中的原因选择了目前的方法。
由于步骤 2 到 6 在我们的技术中是可重复利用的,所以我们做了函数 Case2(integer extPortA, integer extPortB) 作为执行步骤 2 到 6 ,用参数 extPortA 和 extPortB 分别替代了外部端口 4000 和 5000 。
5.6 情况 3 : < 随机的,可预测的, LSR>
A X B
|-------2a----->| |
| |-------2b----->|
| |<------2c------|
| Case1|(m,5000) |
图 4 :情况 3
情况 3< 随机的,可预测的, LSR> 类似于图 2 所描述的情况 1 。然而, X 不能够预测到两个 NAT 中的一个的端口,比方说 NA 。 A 首先不得不发送它的 SYN 包来允许 X 查看 NA 所选的端口。 X 将报告这一信息给 B ,因此 B 能够发送它的 SYN 包到正确的目标 IP 地址和端口。这个情况 1 的修改版在图 4 中描述并解释如下。
1.X 做了关于 5.1 节描述的端口预测。 X 不能预测到 NA 的下一端口,但能够预测到 NB 的下一端口是 5000 ,并经由已经存在的连接告知 A 和 B 。
2.A 和 B 同步经由 X
( a ) NA:m → NB:5000,LSR:X,SYN:P
( b ) X 让 B 知道 NA 工作于端口 m
( c ) NB:5000 → NA:m , LSR:X , SYN:Q
这两个 SYN 包是由 TCP 的 connect() 函数调用所产生。
这两个 SYN 在 NAT 的 NA 和 NB 上各自创建了预期的映射。
3. 调用 Case1(m,5000)
5.7 情况 4 : < 随机的,可预测的, no LSR>
A X B
|-------2a----->| |
|-------------->| |
|-------------->| |
| ┆ | |
|---------- ---->|-------3------>|
|<--------------|-------4-------|
|<--------------|---------------|
|<--------------|---------------|
| | ┆ |
|<--------------|---------------|
|-------5------>| |
| |-------6------>|
| Case2|(m,5000) |
图 5 :情况 4
情况 4 的环境是 < 随机的,可预测的, no LSR> 。我们已经为这个依靠于随机的且不拒绝一个带了残废的且对应于在 NAT 后主机在前面的初始连接的 ACK 或者校验和域的 TCP 包的 NAT 环境开发了一个方案。这方案被呈现在图 5 中并解释如下。
1.X 做了关于 5.1 节描述的端口预测。 X 不能预测到 NA 的下一端口,但能预测到 NB 的下一端口是 5000 ,并经由已经存在的连接告知 A 和 B 。
2.A 发送 T 个 SYN 包到 B ,这些包不是被对方的 NAT 所抛弃就是由于 TTL 过期而被抛弃。
i = 0
While i < T
NA:rand → NB:5000, SYN:anything
i = i+1
End While
这会在 NAT NA 上创建 T 个映射, B 将最后用 SYN+ACK 猜到的就是其中一个。
3.X 通知 B 开始发送 SYN+ACK 包到 NA
4.B 发送许多 SYN+ACK 包到 NA 直到有一个到达 A
i = 1024
While A 还没报告成功
NB:5000 → NA:i,
SYN+ACK:,anything,anything, Payload:i
i = i+1
End While
5.A 报告穿过 NAT 的包负荷。
NA:3999 → X:1234, 工作于端口 m
A 通过监听来自 NB 的任何 SYN+ACK 包的数据报,将可看到这个残废的 SYN+ACK 包。
6.X 告诉 B 连接到 A 的端口 m
B 现在知道 SYN 包可以发向哪里。
7. 调用 Case2(m,5000)
在步骤 2 中 A 所发送的 T 个 SYN 包是独立于任何 TCP 的 connect() 函数调用。他们仅仅是使用在 NAT 上创建了 T 个映射的 libnet 库所产生的包。另一方面, Case2 调用的步骤 2 所产生的 SYN 包是由 A 和 B 的 TCP 的 connect() 函数调用所产生的。这情况 4 环境的方案依靠于随机生成端口的 NAT 的行为。这方案依靠于拒绝带有错误的如序列号或者校验和域的 TCP 包的中间件。
T 值能够被选择,像 B 在生成 T 个 SYN+ACK 到随机目的端口号后有 95% 的机会猜到一个正确的外部端口。其实, A 的 NAT 随机地选择 T 的数目(它的外部端口数),这时 B 必须一直维持猜测直到在 A 的 NAT 可选集中 B 选择到了一个。我们能够使用一个概率分析来构造一个高效的且最小工作量被 A 和 B 所预算的设想。设 PrG 是 B 猜到在 T 个实验中最少一个是正确的概率。价设 A 的 NAT 已经在 [1025 , 65535] 的范围中选择了 T 个不同的端口号,如果 B 选择 T 个不同的端口号,在 A 的 NAT 可选集中 B 不能选择到一个号的可能性是
Pr_G =n-T/n * n-1-T/n-1 * n-2-T/n-2 * . . . * n-(T -1)-T/n-(T -1)
其中 n 是可能选择的端口数( n=65535-1024=64511 )。
T-1
Pr_G =∏n-i-T/n-i
i=0
反之,在 T 个实验中猜到一个是正确的可能性是
PrG = 1-Pr_G
像之前的规定, T 可能被选择如
PrG > 95%
T-1
1-∏n-i-T/n-i> 95%
i=0
计算 T 的量为 T=439 的这个乘积。
439-1
1-∏64511-i-439/64511-i=0.9506> 95%
i=0
这结果说明如果 A 发送 439 个使在 A 的 NAT 外部端口得到不同的随机的映射的 SYN 包,并且 B 发送许多不同的随机的到目的端口的 SYN+ACK 包, B 在它发送第 440 个 AYN+ACK 包之前就有大于 95% 的机会正确地猜测到 439 个外部端口映射的其中一个。
仅仅发送 T 个 SYN 包的原因是这至少要使用两个资源,第一个是网络带宽的使用量,第二是在 NAT 上创建映射的数量。
5.8
情况
5
:
<
随机的,随机的,
LSR>
┌─────┐ ┌─────┐
│ NA 端口了解 | │ NB 端口了解 |
└─────┘ └─────┘
│ ↑ ↑ │
│ / / │
│ // │
│ // │
↓ / / ↓
┌─────┐ ┌─────┐
│ A │ │ B │
└─────┘ └─────┘
图 6 :资源图表死锁
A X B
|-------2a----->|<------2b------|
|<------2c------|-------2c----->|
|-------3a----->|<------3b------|
| Case1|(m,n) |
图 7 :情况 5
在情况 5 中的环境是 < 随机的,随机的, LSR> 。为了允许 X 同步到 A 和 B , B 必须要知道 NA 预先发送它的 SYN 包时所选的端口号。为了确定 NA 选择的端口, X 不得不看 A 的 SYN 包。 A 的 SYN 包不能被发送直到 X 确定 NB 所选的端口号为止。这个死锁被制成图 6 的插图。 A 控制 NA 端口资源以致不外发 SYN 包,有效的预防了 X 知道 NA 所选的端口号。同样的, B 也控制 NB 端口资源。在他们能够释放所控制资源之前,每端都需要对方的端口。我们让 A 和 B 各发送两个 SYN 包可松散源路由穿过 X 且不连接到 TCP connect() 调用的方案来预防这个死锁。这两个 SYN 包在各自的 NAT 上创建了所需的映射并允许 X 获得两个资源,同时等同于情况 1 和 2 的类似方式连接。我们情况 5 的方案展示在图 7 中并解释如下。
1.X 做了关于 5.1 节述的端口预测。 X 不能预测到 NA 或者 NB 的下一端口并经由已经存在的连接告知 A 和 B 。
2.A 和 B 都发送一个 SYN 包松散源路由穿过 X
( a ) NA:m → NB:anything SYN:anything,LSR:X
( b ) NB:n → NA:anything SYN:anything,LSR:X
( c ) X 报告 m 给 B 并报告 n 给 A 。
这两个 SYN 将在各自的 NAT 上创建所需的映射。
3.A 和 B 发送一个 SYN 到对方松散源路由穿过 X
( a ) NA:m → NB:n,LSR:X,SYN:P
( b ) NB:n → NA:m,LSR:X,SYN:Q
因为一致转换,即使目标端口和之前的步骤有所不同, NAT 仍然为这个包利用所使用的相同映射(因此相同的外部端口号)。
4. 调用 Case1(m,n)
注意, SYN 包发送步骤 2 不是关联到任何 TCP 的 connect() 函数调用,而更合适的,步骤 3 的 SYN 包发送应归于一个 TCP 的 connect() 函数调用。同理, Case1 的调用中 SYN+ACK 包发送步骤 3 不绑定到 TCP 的 accept() 函数子程序。
5.9 情况 6 : < 随机的,随机的, no LSR>
A X B
|-------2------>|<------2-------|
|-------------->|<--------------|
|-------------->|<--------------|
| ┆ | ┆ |
|-------------->|<--------------|
|-------3-------|-------------->|
|<--------------|-------3-------|
|---------------|------- ------->|
|<--------------|---------------|
|---------------|-------------->|
|<--------------|---------------|
| ┆ | ┆ |
|---------------|-------------->|
|<--------------|---------------|
|-------4------>|<------4-------|
|<------5-------|-------5------> |
| Case2|(m,n) |
图 8 :情况 6
情况 6 的环境是 < 随机的,随机的, no LSR> 。回顾图 6 的资源图表死锁, A 和 B 从包不可松散源路由保存端口的资源信息。这情况的方案被画成图 8 并解释如下。
1.X 做了关于 5.1 节述的端口预测。 X 不能预测到 NA 或者 NB 的下一端口并经由已经存在的连接告知 A 和 B 。
2.A 发送 T 个 SYN 包到 B 同时 B 发送 T 个 SYN 包到 A ,这些包将被在另一端的 NAT 抛弃或者由于 TTL 到期而被抛弃。
i=0
While i<T
NA:rand → NB:rand,SYN:anything
NB:rand → NA:rand,SYN:anything
i=i+1
End While
这些 SYN 包在两边的 NAT 上各创建了 T 个映射。
3.B 和 A 发送许多 SYN+ACK 包到他们的伙伴的 NAT 直到有一个到达他们的伙伴那里。
i=1024
While A 还没报告成功
NB:rand → NA:i
SYN+ACK:,anything,anything,Payload:i
NA:rand → NB:i
SYN+ACK:,anything,anything,Payload:i
i=i+1
End While
4.A 和 B 报告通过 NAT 的包负荷。
NA:3999 → X:1234, 工作于端口 m
NB:4999 → X:1235, 工作于端口 n
5.X 告诉 B 连接到 A 的端口 m 并告诉 A 连接到 B 的端口 n 。
A 和 B 现在知道他们的伙伴的外部端口号。
6. 调用 Case2(m,n)
情况 6 远比情况 4 困难,因为各个端点必须在对方的 NAT 上正确地猜到一个完整的映射〈源端口,目的端口〉。在情况 4 中,在非随机的 NAT 后的端点能够正确地猜到目的端口。当其中一个 NAT 是可预测时,源端口就被固定了。情况 6 的搜索空间是情况 4 的平方,替代 64511 种可能的是 4161669121 种结合需要猜测。
┌─────┐ ┌─────┐
│ NA 端口了解 | │ NB 端口了解 |
└─────┘ └─────┘
│ ↑ ↑ │
│ / / │
│ // │
│ // │
↓ / / ↓
┌─────┐ ┌─────┐
│ A │ │ B │
└─────┘ └─────┘
图 6 :资源图表死锁
A X B
|-------2a----->|<------2b------|
|<------2c------|-------2c----->|
|-------3a----->|<------3b------|
| Case1|(m,n) |
图 7 :情况 5
在情况 5 中的环境是 < 随机的,随机的, LSR> 。为了允许 X 同步到 A 和 B , B 必须要知道 NA 预先发送它的 SYN 包时所选的端口号。为了确定 NA 选择的端口, X 不得不看 A 的 SYN 包。 A 的 SYN 包不能被发送直到 X 确定 NB 所选的端口号为止。这个死锁被制成图 6 的插图。 A 控制 NA 端口资源以致不外发 SYN 包,有效的预防了 X 知道 NA 所选的端口号。同样的, B 也控制 NB 端口资源。在他们能够释放所控制资源之前,每端都需要对方的端口。我们让 A 和 B 各发送两个 SYN 包可松散源路由穿过 X 且不连接到 TCP connect() 调用的方案来预防这个死锁。这两个 SYN 包在各自的 NAT 上创建了所需的映射并允许 X 获得两个资源,同时等同于情况 1 和 2 的类似方式连接。我们情况 5 的方案展示在图 7 中并解释如下。
1.X 做了关于 5.1 节述的端口预测。 X 不能预测到 NA 或者 NB 的下一端口并经由已经存在的连接告知 A 和 B 。
2.A 和 B 都发送一个 SYN 包松散源路由穿过 X
( a ) NA:m → NB:anything SYN:anything,LSR:X
( b ) NB:n → NA:anything SYN:anything,LSR:X
( c ) X 报告 m 给 B 并报告 n 给 A 。
这两个 SYN 将在各自的 NAT 上创建所需的映射。
3.A 和 B 发送一个 SYN 到对方松散源路由穿过 X
( a ) NA:m → NB:n,LSR:X,SYN:P
( b ) NB:n → NA:m,LSR:X,SYN:Q
因为一致转换,即使目标端口和之前的步骤有所不同, NAT 仍然为这个包利用所使用的相同映射(因此相同的外部端口号)。
4. 调用 Case1(m,n)
注意, SYN 包发送步骤 2 不是关联到任何 TCP 的 connect() 函数调用,而更合适的,步骤 3 的 SYN 包发送应归于一个 TCP 的 connect() 函数调用。同理, Case1 的调用中 SYN+ACK 包发送步骤 3 不绑定到 TCP 的 accept() 函数子程序。
5.9 情况 6 : < 随机的,随机的, no LSR>
A X B
|-------2------>|<------2-------|
|-------------->|<--------------|
|-------------->|<--------------|
| ┆ | ┆ |
|-------------->|<--------------|
|-------3-------|-------------->|
|<--------------|-------3-------|
|---------------|------- ------->|
|<--------------|---------------|
|---------------|-------------->|
|<--------------|---------------|
| ┆ | ┆ |
|---------------|-------------->|
|<--------------|---------------|
|-------4------>|<------4-------|
|<------5-------|-------5------> |
| Case2|(m,n) |
图 8 :情况 6
情况 6 的环境是 < 随机的,随机的, no LSR> 。回顾图 6 的资源图表死锁, A 和 B 从包不可松散源路由保存端口的资源信息。这情况的方案被画成图 8 并解释如下。
1.X 做了关于 5.1 节述的端口预测。 X 不能预测到 NA 或者 NB 的下一端口并经由已经存在的连接告知 A 和 B 。
2.A 发送 T 个 SYN 包到 B 同时 B 发送 T 个 SYN 包到 A ,这些包将被在另一端的 NAT 抛弃或者由于 TTL 到期而被抛弃。
i=0
While i<T
NA:rand → NB:rand,SYN:anything
NB:rand → NA:rand,SYN:anything
i=i+1
End While
这些 SYN 包在两边的 NAT 上各创建了 T 个映射。
3.B 和 A 发送许多 SYN+ACK 包到他们的伙伴的 NAT 直到有一个到达他们的伙伴那里。
i=1024
While A 还没报告成功
NB:rand → NA:i
SYN+ACK:,anything,anything,Payload:i
NA:rand → NB:i
SYN+ACK:,anything,anything,Payload:i
i=i+1
End While
4.A 和 B 报告通过 NAT 的包负荷。
NA:3999 → X:1234, 工作于端口 m
NB:4999 → X:1235, 工作于端口 n
5.X 告诉 B 连接到 A 的端口 m 并告诉 A 连接到 B 的端口 n 。
A 和 B 现在知道他们的伙伴的外部端口号。
6. 调用 Case2(m,n)
情况 6 远比情况 4 困难,因为各个端点必须在对方的 NAT 上正确地猜到一个完整的映射〈源端口,目的端口〉。在情况 4 中,在非随机的 NAT 后的端点能够正确地猜到目的端口。当其中一个 NAT 是可预测时,源端口就被固定了。情况 6 的搜索空间是情况 4 的平方,替代 64511 种可能的是 4161669121 种结合需要猜测。
6.
实现
我们已经在 LINUX 工作站,通过调用 libnet 和 libpcap 使用 C 实现了第 2 种和第 4 种情况。第 1 、 3 、 5 、 6 种情况没有实现。
协助者和端点都连接到由单独功能组成的库。协助者程序 natblaster_server() 只需提供其协助者所要监听的端口号。端点连接程序需要提供 7 个参数:( 1 )协助者的 IP 地址和( 2 )端口号,( 3 )本地端点外部 IP 地址,( 4 )内部 IP 地址,和( 5 )端口号,( 6 )伙伴外部 IP 地址,和( 7 )端口号。本地端点和伙伴的端口号必须由协助者帮助为试图的连接建立一个唯一标志符。三元组 < 本地外部 IP ,伙伴内部 IP ,伙伴内部端口 > 被用于在协助者上的唯一标志。库试图提供一个指定端口的套接字,然而,所返回的套接字不被保证是所指定的端口号。假设 natblaster_connect() 工作,库返回一个有效的套接字句柄。
为了测试我们的实现,我们在分离的网络上运行两个端点,每个都位于不同的且有效的 NAT 后面。第三部分的程序被运行于不在 NAT 之后的第三部计算机上。我们在英特网上做了比本地网络更多的测试,来确保测试更真实。
为了创建不会到达伙伴那里并不返回错误消息的包,我们设置了 TTL 值使其低到不会到达伙伴那里。设置低的 TTL 值由调用 IP_TTL 选项的 setsockopt() 系统调用完成。这选项也需要一个 TTL 值。这个值必须低于到伙伴的跳跃数,但必须要大于到外部最远的 NAT 的跳跃数。这套接字选项对于套接字的整个生存期来说必须不是持久不变的。例如,在三次握手成功之后, setsockopt() 应该被再一次调用来提高 TTL 值,这样后面的数据才确保能到达端点。依靠的是一个低的 TTL 只工作于是否一个 ICMP TTL 过期包不被端点的 TCP 堆栈看到,因为这过期包会导致在端点的套接字失败。我们所测试的 NAT 都不向前发送 ICMP TTL 过期包到内部网络。另一种方法是将发送普通包并希望伙伴的 NAT 默默地丢弃它们,然而,一些 NAT 可能发送 RST 包来响应那些未请自来的数据。这行为是详细的执行过程。我们没使用 5.3 节所描述的 TTL 决定技术;而是我们选择了我们知道是合适的并且低而普通的 TTL 值。
我们为连接前诊断实现了连续的端口分配方法,但没有实现一致转换。我们的实现没有使用一致转换。
我们的情况 2 和情况 4 的实现都非常成功并且能打开直接的 TCP 连接。情况 2 真实的打开了连接,而情况 4 有很高的几率是成功的(就是上面所讨论的,成功率取决于在预测相位的端口发送 SYN 和 SYNACK 的数量)。
由于 5.9 节最后所给的原因,我们没有实现情况 6 。我们没实现情况 1 , 3 和 5 是因为 LSR 在英特网上不是典型地可用的并且我们相信这在实践中会有较低的成功率。
如前面所论述的,我们使用了增加系统调用所需的标准的 Berkelet 网络实现。例如,当我们发送一个 SYN 包但需要知道序列号时,这个 SYN 包由使用标准的 connect() 调用所发送的,之后首先开始一个线程来为所发的 SYN 包观测数据报。这线程能报告所使用的序列号。
情况 2 和情况 4 必须有 root 的执行权,因为这是 libnet 和 libpcap 所要求的。由于无需欺骗或者探嗅,第三方可以以一般用户的权限运行。
7. 总结
我们已经证明了如何在两个不同的典型的 NAT 后面的主机之间建立直接的 TCP 连接,这些解决方案都没有以任何方式改变 TCP/IP 栈,而是为这些主机建立连接起到了杠杠作用。我们的方案可以为很多的程序所应用,从点对点的通信到即时消息通信。对于这个问题已经存在解决方法包括代理都没有有效地利用网络资源并且伸缩性不好。
随着 IPv6 的到来, NAT 也许就不再是个网站的组成了,但是,这些情况包括可预测 NAT 也可以被应用到使用状态的防火墙后面的主机。跟 NAT 相似, “ 状态防火墙 ” 有能力只允许从他们所包含的内部网络发起的连接。我们的解决方案双方都可以初始话一个这些防火墙都允许的 TCP 连接。我们的几种方案在配置有 IDSes 的场合是不可取的,因为在第四和第六种情况下,都使用了类似于端口扫描的技术。这种情况下最后关闭这样的网络监视设备。尽管如此,我们地解决方案对于大部分的网络来说一般是足够的,甚至对于那些可能都不存在的使用随机分配地址的 NAT 来说也是适用的。
8. 参考文献
[1] Bryan Ford. NatCheck: Hosted by the MIDCOM-P2P
project on SourceForge.
http://midcom-p2p.sourceforge.net.
[2] Bryan Ford, Pyda Srisuresh, and Dan Kegel. Peer-to-Peer
Communication Across Network Address Translators. In
USENIX Annual Technical Conference, Anaheim, CA, April
2005.
[3] Groove Networks. http://groove.net.
[4] Saikat Guha, Yutaka Takeday, and Paul Francis. NUTSS: A
SIP-based approach to UDP and TCP Network Connectivity.
In SIGCOMM 2004 Workshops, Aug 2004.
[5] M. Holdrege and P. Srisuresh. Protocol Complications with
the IP Network Address Translator. RFC 3027, Internet
Engineering Task Force, January 2001.
[6] Hopster: Bypass Firewall Bypass Proxy Software.
http://www.hopster.com.
[7] Information Sciences Institute. Transmission Control
Protocol (TCP). RFC 793, Internet Engineering Task Force,
September 1981.
[8] Brad Karp, Sylvia Ratnasamy, Sean Rhea, and Scott
Shenker. Spurring Adoption of DHTs with OpenHash, a
Public DHT Service. In Proceedings of the 3rd International
Workshop on Peer-to-Peer Systems, Feb 2004.
[9] Y. Rekhter, B. Moskowitz, D. Karrenberg, G. J. de Groot,
and E. Lear. Address Allocation for Private Internets. RFC
1918, Internet Engineering Task Force, February 1996.
[10] J. Rosenberg, J. Weinberger, C. Huitema, and R. Mahy.
STUN - Simple Traversal of User Datagram Protocol (UDP).
RFC 3489, Internet Engineering Task Force, September
2003.
[11] P. Srisuresh and K. Egevang. Traditional IP Network
Address Translator (Traditional NAT). RFC 3022, Internet
Engineering Task Force, January 2001.
[12] P. Srisuresh and M. Holdrege. IP Network Address
Translator (NAT) Terminology and Considerations. RFC
2663, Internet Engineering Task Force, August 1999.
[13] P. Srisuresh, J. Kuthan, J. Rosenberg, A. Molitor, and
A. Rayhan. Middlebox communication architecture and
framework. RFC 3303, Internet Engineering Task Force,
August 2002.
[14] Jason Thomas, Andrew Mickish, and Susheel Daswani. Push
Proxy: Protocol Document 0.6, June 2003.
[15] Michael Walfish, Jeremy Stribling, Maxwell Krohn, Hari
Balakrishnan, Robert Morris, and Scott Shenker.
Middleboxes No Longer Considered Harmful. In
Proceedings of USENIX Symposium on Operating Systems
Design and Implementation, December 2004.
我们已经在 LINUX 工作站,通过调用 libnet 和 libpcap 使用 C 实现了第 2 种和第 4 种情况。第 1 、 3 、 5 、 6 种情况没有实现。
协助者和端点都连接到由单独功能组成的库。协助者程序 natblaster_server() 只需提供其协助者所要监听的端口号。端点连接程序需要提供 7 个参数:( 1 )协助者的 IP 地址和( 2 )端口号,( 3 )本地端点外部 IP 地址,( 4 )内部 IP 地址,和( 5 )端口号,( 6 )伙伴外部 IP 地址,和( 7 )端口号。本地端点和伙伴的端口号必须由协助者帮助为试图的连接建立一个唯一标志符。三元组 < 本地外部 IP ,伙伴内部 IP ,伙伴内部端口 > 被用于在协助者上的唯一标志。库试图提供一个指定端口的套接字,然而,所返回的套接字不被保证是所指定的端口号。假设 natblaster_connect() 工作,库返回一个有效的套接字句柄。
为了测试我们的实现,我们在分离的网络上运行两个端点,每个都位于不同的且有效的 NAT 后面。第三部分的程序被运行于不在 NAT 之后的第三部计算机上。我们在英特网上做了比本地网络更多的测试,来确保测试更真实。
为了创建不会到达伙伴那里并不返回错误消息的包,我们设置了 TTL 值使其低到不会到达伙伴那里。设置低的 TTL 值由调用 IP_TTL 选项的 setsockopt() 系统调用完成。这选项也需要一个 TTL 值。这个值必须低于到伙伴的跳跃数,但必须要大于到外部最远的 NAT 的跳跃数。这套接字选项对于套接字的整个生存期来说必须不是持久不变的。例如,在三次握手成功之后, setsockopt() 应该被再一次调用来提高 TTL 值,这样后面的数据才确保能到达端点。依靠的是一个低的 TTL 只工作于是否一个 ICMP TTL 过期包不被端点的 TCP 堆栈看到,因为这过期包会导致在端点的套接字失败。我们所测试的 NAT 都不向前发送 ICMP TTL 过期包到内部网络。另一种方法是将发送普通包并希望伙伴的 NAT 默默地丢弃它们,然而,一些 NAT 可能发送 RST 包来响应那些未请自来的数据。这行为是详细的执行过程。我们没使用 5.3 节所描述的 TTL 决定技术;而是我们选择了我们知道是合适的并且低而普通的 TTL 值。
我们为连接前诊断实现了连续的端口分配方法,但没有实现一致转换。我们的实现没有使用一致转换。
我们的情况 2 和情况 4 的实现都非常成功并且能打开直接的 TCP 连接。情况 2 真实的打开了连接,而情况 4 有很高的几率是成功的(就是上面所讨论的,成功率取决于在预测相位的端口发送 SYN 和 SYNACK 的数量)。
由于 5.9 节最后所给的原因,我们没有实现情况 6 。我们没实现情况 1 , 3 和 5 是因为 LSR 在英特网上不是典型地可用的并且我们相信这在实践中会有较低的成功率。
如前面所论述的,我们使用了增加系统调用所需的标准的 Berkelet 网络实现。例如,当我们发送一个 SYN 包但需要知道序列号时,这个 SYN 包由使用标准的 connect() 调用所发送的,之后首先开始一个线程来为所发的 SYN 包观测数据报。这线程能报告所使用的序列号。
情况 2 和情况 4 必须有 root 的执行权,因为这是 libnet 和 libpcap 所要求的。由于无需欺骗或者探嗅,第三方可以以一般用户的权限运行。
7. 总结
我们已经证明了如何在两个不同的典型的 NAT 后面的主机之间建立直接的 TCP 连接,这些解决方案都没有以任何方式改变 TCP/IP 栈,而是为这些主机建立连接起到了杠杠作用。我们的方案可以为很多的程序所应用,从点对点的通信到即时消息通信。对于这个问题已经存在解决方法包括代理都没有有效地利用网络资源并且伸缩性不好。
随着 IPv6 的到来, NAT 也许就不再是个网站的组成了,但是,这些情况包括可预测 NAT 也可以被应用到使用状态的防火墙后面的主机。跟 NAT 相似, “ 状态防火墙 ” 有能力只允许从他们所包含的内部网络发起的连接。我们的解决方案双方都可以初始话一个这些防火墙都允许的 TCP 连接。我们的几种方案在配置有 IDSes 的场合是不可取的,因为在第四和第六种情况下,都使用了类似于端口扫描的技术。这种情况下最后关闭这样的网络监视设备。尽管如此,我们地解决方案对于大部分的网络来说一般是足够的,甚至对于那些可能都不存在的使用随机分配地址的 NAT 来说也是适用的。
8. 参考文献
[1] Bryan Ford. NatCheck: Hosted by the MIDCOM-P2P
project on SourceForge.
http://midcom-p2p.sourceforge.net.
[2] Bryan Ford, Pyda Srisuresh, and Dan Kegel. Peer-to-Peer
Communication Across Network Address Translators. In
USENIX Annual Technical Conference, Anaheim, CA, April
2005.
[3] Groove Networks. http://groove.net.
[4] Saikat Guha, Yutaka Takeday, and Paul Francis. NUTSS: A
SIP-based approach to UDP and TCP Network Connectivity.
In SIGCOMM 2004 Workshops, Aug 2004.
[5] M. Holdrege and P. Srisuresh. Protocol Complications with
the IP Network Address Translator. RFC 3027, Internet
Engineering Task Force, January 2001.
[6] Hopster: Bypass Firewall Bypass Proxy Software.
http://www.hopster.com.
[7] Information Sciences Institute. Transmission Control
Protocol (TCP). RFC 793, Internet Engineering Task Force,
September 1981.
[8] Brad Karp, Sylvia Ratnasamy, Sean Rhea, and Scott
Shenker. Spurring Adoption of DHTs with OpenHash, a
Public DHT Service. In Proceedings of the 3rd International
Workshop on Peer-to-Peer Systems, Feb 2004.
[9] Y. Rekhter, B. Moskowitz, D. Karrenberg, G. J. de Groot,
and E. Lear. Address Allocation for Private Internets. RFC
1918, Internet Engineering Task Force, February 1996.
[10] J. Rosenberg, J. Weinberger, C. Huitema, and R. Mahy.
STUN - Simple Traversal of User Datagram Protocol (UDP).
RFC 3489, Internet Engineering Task Force, September
2003.
[11] P. Srisuresh and K. Egevang. Traditional IP Network
Address Translator (Traditional NAT). RFC 3022, Internet
Engineering Task Force, January 2001.
[12] P. Srisuresh and M. Holdrege. IP Network Address
Translator (NAT) Terminology and Considerations. RFC
2663, Internet Engineering Task Force, August 1999.
[13] P. Srisuresh, J. Kuthan, J. Rosenberg, A. Molitor, and
A. Rayhan. Middlebox communication architecture and
framework. RFC 3303, Internet Engineering Task Force,
August 2002.
[14] Jason Thomas, Andrew Mickish, and Susheel Daswani. Push
Proxy: Protocol Document 0.6, June 2003.
[15] Michael Walfish, Jeremy Stribling, Maxwell Krohn, Hari
Balakrishnan, Robert Morris, and Scott Shenker.
Middleboxes No Longer Considered Harmful. In
Proceedings of USENIX Symposium on Operating Systems
Design and Implementation, December 2004.
相关测试代码请看
http://sourceforge.net/projects/natblaster
http://sourceforge.net/projects/natblaster