您的位置:首页 > 理论基础 > 计算机网络

穿透 NAT 的 P2P 通信

2016-10-21 00:00 232 查看
摘要: 翻译论文:Peer-to-Peer Communication Across Network Address Translators

摘要

网络地址转换(NAT)使得一个网段内包含的同位客户端无法被任何全球有效的 IPv4地址访问到,因此对 P2P 通信造成了广为人知的困难。目前有许多著名的 NAT 遍历技术,但是他们的文档很稀少,并且有关他们健壮性以及相关的优劣性的数据更是少之又少。这篇论文论证并分析了在诸多 NAT 遍历技术中最简单但是最健壮和实用的一个,也就是通常所说的打洞技术。打洞技术对于 UDP 通信可以有更好的理解,但是我们也会展示出它在基于 P2P TCP 流的可靠应用。在收集了诸多在各种 NAT 部署下的可靠性数据后,我们发现大约 82% NAT 测试支持 UDP 打洞,大约 64% 支持 TCP 流打洞。自从 NAT 供应商逐渐意识到 P2P 应用的需求,例如:IP 语音传输和在线游戏协议,对于打洞技术的支持也会在未来逐步提升。

简介

庞大的用户增长以及巨大的安全挑战这两者的压力以及使得互联网的发展让许多应用变得难以实现。在互联网传统统一的地址结构中,每个节点有一个全球唯一的 IP 地址并可以向其他每个节点进行直接通信,这种模式已经被一种新的更加实际的地址结构取代,一个全局地址域和许多私有地址域并由 NAT 内联起来组成了这一新模式。在这个新的地址结构中,如图1所示:



只有在 main 即全局地址域中的节点才能轻松的连接网络的任何地方,因为他们有唯一的、全球的可路由的 IP 地址。在私有网络中的节点可以连接其他在同一私有网段的其他节点并且他们可以经常打开 TCP 或者 UDP 连接在全局地址域的周知节点。NAT 是在路径中为出口连接分配临时公共结束点的,并转换组成字段中所有包的地址以及端口号,同时通常阻塞所有传入流除非另有专门配置。

这种互联网新的地址结构很适合典型条件下客户端-服务器的通信,当客户端是一个私有网段,服务器在全球公有网段地址域中。这种结构使得两个处在不同私有网段的节点的直接通信变得困难,但是,在视频会议以及在线游戏中这种 P2P 的通信协议也经常十分重要。我们很明显需要一种方式使得在现今的 NAT 下可以很平滑的是有这种 P2P 的协议方式。

众所周知,打洞技术是其中一个最有效的使得主机之间的 P2P 建立通信的方法。这种技术已经广泛的运用于基于 UDP 的应用中,但是重要的是这种技术同样适用于 TCP 模式。和他的名字所表示的相反,打洞技术并不会以牺牲私有网络的安全为代价,反而,打洞技术可以使应用能够遵循大多数 NAT 的默认的安全政策,到达 NAT 的 P2P 通信会话是路径有效信号是“请求性”的,因此这个信号是可以被 NAT 接受的。这篇论文论证了对于 UDP 以及 TCP 的打洞技术,并详细论述了应用程序以及 NAT 行为使得打洞技术正常运行的至关重要的方面。

不幸的是,没有一个遍历算法能适用于所有现存的 NAT,因为 NAT 行为并没有被完全标准化。这篇论文展示了一些实验性的结果以分析当前 NAT 对打洞技术的支持。,我们的数据是由许多用户通过
3ff0
互联网运行的我们的 NAT 检测的工具得到的运行结果而衍生出的,也基于了多种多样的 NAT 供应商。但是数据点是从“自愿”的用户社区收集而来,也许不能代表真正的部署在互联网上的 NAT 分布,结果只不过一般令人鼓舞。

在评估基础的打洞技术时,我们也会指出一种变化性,即是否可以使打洞技术在不同复杂成本下能够适用于现存的多种多样的 NAT。我们的主要焦点其实是发展出对于最简单的,能清晰、健壮的工作在一个现存良好工作的处于任意合理的网络拓扑中 NAT 的打洞技术。我们故意避免了一些过度巧妙的技巧因为这会在短期提升对于现存“损坏的”NAT 的兼容性,但是这也许只会在一时间内有效并且在长期中也许会导致额外的不可预测性和网络的脆弱性。

虽然 IPv6 这更大的地址空间能够最终降低对于 NAT 的需要,但是在短期而言 IPv6 会提升对于 NAT 的需求,因为 NAT 自身提供了一种 IPv4 与 IPv6 地址域之间简单的互用实现方式。此外,主机在私人网络的匿名性和不可触及性已经被普遍认为是涉及安全和隐私利益。防火墙是不太可能消失的即使你有足够多的 IP 地址:在默认情况下 IPv6 防火墙一般仍会阻碍主动的传入流量,使得打洞技术甚至对于 IIPv6 的应用程序也是有用的。

本论文剩余部分是按照一下组织的。第二部分介绍了基本术语以及 NAT 遍历的概念。第三部分详细描述了 UDP 打洞技术,第四部分介绍了 TCP 的打洞技术。第五部分总结了一个 NAT 要支持打洞技术必须具有的一些重要属性。第六部分展示了我们对于常用 NAT 打洞技术的实验结果,第七部分论述了相关工作,第八部分得出结论。

总体概念

这一部分介绍了在本篇论文中使用的基础的 NAT 术语,之后列出了应用于 UDP 以及 TCP 中总体的 NAT 遍历技术。

NAT 术语

这篇论文采用的 NAT 术语以及分类法是在 RFC2663 中定义的,也有些额外术语是近期在 RFC3489 中定义的。

特别重要的是“会话”的概念。对于 TCP 或者 UDP 而言一个会话的终点是一个(IP 地址,端口号)对,一个特定的会话由两个会话端点唯一确定。从一个主机的角度,一个会话由四个元组(本地 IP,本地端口,远端 IP,远端端口)有效的定义。一个会话的方向通常是发起会话的数据包的流向:TCP 中最初的同步(SYN)包,或者 UDP 中第一个用户数据报。

对于各种类型的 NAT,最常用的类型是传统的或者出站的 NAT,它提供了一个非对称私有网络和公共网络之间的桥梁。出站 NAT 在默认情况下只允许出站会话穿越NAT:传入的数据包被丢弃,除非 NAT 识别它们是从内部专用网络发起的现有会话的一部分。出站 NAT 是与 P2P 协议相矛盾的因为当等位主机想要去通信但是都藏在私有网段的不同 NAT 后面,无论哪个等位主机想要去发起一个会话都会被另一个等位主机的 NAT 拒绝。NAT 遍历就是需要使 P2P 会话让 NAT 看起来像“出站”会话。

出站 NAT 有两个子类型:基础 NAT,只转换 IP 地址;NAPT 转换整个会话终点。NAPT 是更综合的一种,已经变得十分常见因为它能使在私有网段的主机去共享同一个单一的公共 IP 地址。本文中我们假设使用 NAPT,但是原则和技术讨论同样适用(如果有时非常)基本 NAT。

中继转发

最有效但是最低效的穿透 NAT 的 P2P 通信方法是简单的通过中继转发让通信对于网络而言看起来像标准的 C/S 通信。假设两个客户端主机 A 和 B 每一个都发起了向一个周知服务器 S 的 TCP 或者 UDP 连接,S 的全球 IP 地址是18.181.0.31端口号是1234。如图2所示:



两个客户的位于单独的两个私有网段,他们各自的 NAT 阻止了两者中任何一个客户端直接对对方发起的连接。两个客户端都可以简单的使用服务器 S 在他们之间转发消息而不是试图直接连接。举个例子,向客户端 B 发送消息,客户端 A 简单的发送消息到服务器 S 只需沿着他们已经建立的 C/S 连接,然后服务器 S 将消息传达给客户端 B 使用它与 B 之间已经建立的 C/S 连接。

只要两个客户端都能连接到服务器那么中继转发是总可以正常工作的。这种模式的缺点是必须假设服务器提供电力和网络带宽,并且在等位客户端之间的通信潜伏期也可能增加即使服务器的连接很好。不过,当时没有在所有 NAT 中都能可靠工作的有效技术,如果你需要最高的鲁棒性中继转发算是一个有用的低效的策略。Turn 协议定义了一种相对安全的方式实现了中继转发的方法。

连接反转

一些 P2P 应用使用了一种直截了当但是有限的技术,就是我们知道的连接反转,他能使都能连接到一个周知服务器 S 两个主机但是只有一个主机在 NAT 之后的进行通信,就如图3所示:



如果 A 想要向 B 发起连接,然后后自动尝试一个直接的连接,因为 B 并不在 NAT 之后所以 A 的 NAT 解释这是一个外出对话的连接。如果 B 想要发起对 A 的连接,但是,任何直接试图连接 A 的连接都会被 A 的 NAT 阻隔。B 可以通过一个周知服务器 S 中继转发一个对 A 的连接请求来代替,要求 A 去发起一个对 B 的反转连接。尽管这个技术的限制很明显,但是他使用一个周知的集合服务器作为中继去帮助建立直接的 P2P 连接的中心思想是我们接下来要描述的更普通的打洞技术的基础。

UDP 打洞技术

UDP 打洞技术能够使两个客户端在集合服务器的帮助下直接建立 P2P 的 UDP 会话,即使两个客户端都在 NAT 之后。这个技术在 RFC3027的5.1部分提到过,并在 Web 上被更加完全的记录着,并且也被在近期的实验性网络协议中被使用。各种专用协议,比如在线游戏,也使用 UDP 打洞。

集合服务器

打洞技术假设两个客户端 A 和 B 已经和集合服务器 S 有激活的 UDP 会话。当一个客户端在 S 上注册的时候,服务器记录这个客户端的两个终点:一个是使客户端相信可以被使用的去进行对话的(IP 地址,UDP 端口)对,另一个是使服务器发现的能被使用的去进行对话的(IP 地址,UDP 端口)对。我们认为第一对作为客户端私有的终点,第二队作为客户端公共的终点。服务器可以从客户端注册消息体的一个域中获得私有终点,并且可以从客户端注册消息的 IP 和 UDP 头中的 IP 的地址以及 UDP 端口号域中获得公共终点。如果客户端没有在 NAT 之后,那么私有和共有终点一定是一致的。

我们知道的一些少数状态不佳的 NAT 会去扫描 UDP 数据报的主体在前4个字节域找出 IP 地址,并将他们转换为 IP 报头中的 IP 地址。为了增强鲁棒性抵制这种行为,应用也许希望去混淆在消息体中的 IP 地址,举个例子通过发送 IP 地址的补码而不是发送 IP 抵制本身,如果应用已经加密了消息,这种行为就不会是问题。

建立 P2P 会话

假设客户端 A 想要向客户端 B 建立 UDP 会话。打洞技术工作过程如下:

A 开始并不知道怎么到达 B,所以 A 询求服务器 S 的帮助去建立对 B 的 UDP 会话。

S 回复 A 一个包含 B 的公共和私有终点信息的消息。与此同时,S 使用对 B 的 UDP 会话去向 B 发送一个包含 A 的公有和私有信息的连接请求消息。一旦这些消息被接收,A 和 B 就知道了各自的公共和私有终点信息。

当 A 收到了从 S 发来的有关 B 的公共和私有终点信息,A 开始发送 UDP 包去这两个终点,随后锁定首先从 B 产生一个有效的反应的无论哪个端点。同样,当 B 在连接请求中收到 A 的公共和私有终点信息后,B 开始向 A 的所有终点发送 UDP 包,锁定第一个成功的终点。这些消息的顺序和时间不是关键的,由于他们是异步的。

我们现在开始考虑 UDP 打洞技术是如何处理一下这三种特殊网络场景的。在第一种情形下,是一种很简单的情况,两个客户端实际在同一个私有网段并在 NAT 之后。第二种情形,是更加普遍的情形,不同客户端在不同的 NAT 后面。第三种情形,客户端每个都处在一个两级 NAT 后面:一个公共的一级 NAT 假如是 ISP 配置的,和另一个分开的二级 NAT 就像消费者的对于家庭网络的 NAT 路由。

一般来说让应用程序本身来确定网络的确切物理层是困难甚至不可能的,因此这些场景(或许多其他可能的)实际上只适用于一段给定的时间。就像 STUN 等一些协议能够提供一些出现在通信路径上的 NAT 信息,但是这些信息也许不总是完整或者可靠的,尤其是当包含多个层级的 NAT 的时候。然而,打洞技术在以上几种场景都可以自动工作即使不需要应用能够知道特定的网络组织形式,只要包含的 NAT 的行为是合理的(所谓合理的 NAT 行为将在第五部分进行阐述)。

等位机在同一个 NAT 之后

首先我们考虑一种简单的情形就是两个客户端(互相不知晓)在同一个 NAT 之后,因此他们定位在同一个私有网段的 IP 地址域中,如图4所示:



客户端 A 已经和服务器 S 建立了 UDP 会话,公共的 NAT 已经为其分配了公开端口号为62000。客户端 B 同样和服务器 S 建立了会话,同样公共 NAT 为他分配了公开端口号62005.

假设客户端 A 使用打洞技术去和 B 建立 UDP 会话,将服务器 S 作为介绍人。客户端 A 向服务器 S 一个向 B 的连接请求。S 回应 A 以 B 的公共和私有终点,同样也将 A 的公共和私有终点信息发给 B。两个客户端然后试图通过这些终点直接向对方发送 UDP 数据报。发向公共终点的消息可能也或者不能到达他们的终点,这取决于 NAT 是否支持将在3.5节描述的 Hairpin 转换。发向私有终点的消息一定会到达他们的目的地,但是由于在私有网段内的直接路由会比通过 NAT 的非直接路由要快,客户端最可能去选择私有终点作为最终的正常通信。

通过假设 NAT 支持 Hairpin 转换,应用也许能免除复杂的尝试私有和公共终点,能够使得在同一 NAT 之后的本地通信的代价降低而不去通过穿越 NAT。在第6部分我们结果可以看到,Haipin 转换依旧不是在现有 NAT 之内更加普遍的 P2P 友好的 NAT 行为。因此,应用也许能通过同时使用私有和公共终点而大大受益。

等位机位于不同 NAT 之后

假设客户端 A 和 B 在不同 NAT 之后有私有 IP 地址,如图5所示:



A 和 B 各自都已经从他们的本地端口4321向服务器 S 的端口1234发起了 UDP 会话。为了处理这些出站会话,NAT A 在他自有的公共 IP 地址155.99.25.11下分配了端口62000给 A 对 S 的会话,同时 NAT B 在他的 IP 地址138.76.29.7下分配了端口31000给 A 对 S 的会话。

在 A 的注册信息发往 S 的过程中,A 对 S 报告了自己的私有终点10.0.0.1:4321,其中10.0.0.1是 A 在自己的私有网段下的 IP 地址。S 记录了 A 所报告的私有终点,同时也记录了 S 自己发现的 A 的公共终点。A 的公共终点在这个例子中是155.99.25.11:62000,这个临时的终点是由 NAT 分配给会话的。类似的,当客户端 B 注册的时候,S 记录了 B 的私有终点10.1.1.3:4321和 B 的公共终点138.76.29.7:31000。

现在 A 遵循上一节描述的打洞技术的过程去直接和 B 建立 UDP 会话。首先 A 发送一个请求到 S 寻求对 B 连接的帮助。在反馈中,S 发送 B 的公共和私有终点的信息给 A,同时发送 A 的公共和私有终点给 B。A 和 B 各自开始尝试直接发送 UDP 数据报给这些终点。

由于 A 和 B 处在不同的私有网段并且他们各自的 IP 地址并不是全局可路由的,这些发送到终点的消息将到达错误的主机或者根本没有到达主机。因为许多 NAT 也能完成 DHCP 服务器的行为,以一种相当确定的方式从私有地址池中分发 IP 地址通常是由 NAT 提供商默认决定,所以 A 发向 B 的私有终点的消息将到达一些错误的在 A 的私有网段中和 B 具有相同私有 IP 地址的主机的事情实际上是很有可能发生的。因此应用程序必须验证所有的消息用某种方式来强硬的过滤掉这种随意的交通。例如:这些消息可能需要包括特定于应用程序的名称或密码令牌,或者至少有一个预先通过 S 的随机声明。

现在考虑 A 的第一个发向 B 公共终点的消息,如上图所示。由于这个出站消息通过了 A 的 NAT,这个 NAT 注意到这是一个新的出站会话的第一个 UDP 包。这个新会话的源终点10.0.0.1:4321和已存在的 A 与 S 之间的会话的源终点是一致的,但是目的终点是不一样的。如果 NAT A 是正常工作的话,他保存了 A 的私有终点的信息,一致的将所有从私有源终点10.0.0.1:4321的出站会话的源终点转换为相关的公共源终点155.99.25.11:62000。因此 A 的第一个发向 B 公共终点的信息,实际上,在 A 的 NAT 中打通了一个洞给这个新的 UDP 会话,在 A 的私有网段中由(10.0.0.1:4321,138.76.29.7:31000)标定,在主网中由(155.99.25.11:62000,138.76.29.7:31000)标定。

如果 A 向 B 的公共终点发送的消息 到达 B 的 NAT 在 B 的第一个发向 A 的消息穿过 B 的 NAT之前,那么 B 的 NAT 可能会认为 A 的入站消息是未经过请求的进入消息然后丢弃他。但是 B 的第一个发向 A 的公共终点的消息,同样会在 B 的 NAT 中打开一个洞,以供新的 UDP 会话在 B 的私有网段由(10.1.1.3:4321,155.99.25.11:62000)标定,在主网由(138.76.29:31000,155.99.25.11:62000)标定。一旦第一个从 A 到 B 的消息穿过了他们各自的 NAT,孔洞就会在每个方向都打开并且 UDP 会话就可以正常运行。一旦客户端已经确定公共终点可以工作,他们可以选择停止发送消息向私有的终点了。

等位机位于不同层级的 NAT

在某些网络拓扑中包含了多个层级的 NAT 设备,如果没有特定拓扑结构的信息的话,两个客户端之间无法建立最理想的 P2P 路由。考虑一个最终的情形,如图6所描述:



假设 NAT C 是一个大型工业级的 NAT 由 ISP 所提供去在一个少量的 IP 地址空间中允许更多用户复用,NAT A 和 NAT B
3ff0
是小型的消费者 NAT 路由由两个 ISP 消费者独立提供去使得他们的私有家庭网络可以复用他们各自 ISP 提供的 IP 地址。只有服务器 S 和 NAT C 拥有全局可路由的 IP 地址;NAT A 和 NAT B 所使用的 IP 地址实际上属于 ISP 的私有地址空间,同时客户端 A 和客户端 B 的可路由 IP 地址是属于 NAT A 和 NAT B 的各自私有地址空间的。每个客户端预先都发起一个对服务器 S 的出站连接,使得 NAT A 和 NAT B 各自创建一个公共-私有的转换,NAT C 建立一个公共-私有的转换为每一个会话。

现在假设A 和 B 试图通过打洞技术建立一个直接的 P2P UDP 连接。最佳的路由策略将是客户端 A 发送消息到客户端 B 在 NAT B 中的半公开终点,10.0.1.2:55000 在 ISP 的地址域中,客户端 B 发送消息到 A 在 NAT B 中的半公开终点,10.0.1.1:45000。不幸的是,A 和 B 没有方式去知晓他们的地址,因为服务器 S 只能看到客户端实际的全局公开终点,155.99.25.11:62000 和 155.99.25.11:62005。即使 A 和 B 有一些方式去学习对方的地址,这里依旧没有保证这些地址是有用的,因为这些由 ISP 私有地址空间分配的地址也许会在非相关的私有网段的地址分配中产生冲突。(举个例子,客户端 A 中的 IP 地址在 NAT C 的地址域中也有可能出现 10.1.1.3,对于客户端 B 的私有 IP 地址在 NAT B 的地址域中也是一样)

客户端因此没有选择只能去使用它们能被服务器 S 看到的全局公共地址去建立 P2P 通信,并依赖于 NAT C 提供 Hairpin 或者 Loopback 转换。当 A 发送 UDP 数据报到 B 的全局终点 155.99.25.11:62005 时,NAT A 首先将数据报中的源终点从 10.0.0.1:4321 转换为 10.0.1.1:45000。现在数据报到达 NAT C,他发现数据报的终点地址是一个 NAT C 自己转换过的公共终点。如果 NAT C 是正常工作的话,之后他将数据报源和目的终点都转换,并将数据报回送到自己的私有网段中,现在源终点变为 155.99.25.11:62000,目的终点变为 10.0.1.2:55000。NAT B 最终转换数据报的终点地址并进入 B 的私有网段,之后数据报将到达 B。返回 A 的路径依旧是类似工作的。许多 NAT 也许现在还不支持 Hairpin 转换,但是对于 NAT 提供商而言他们将越来越注意到这一要点。

UDP 终端用户闲置超时

由于 UDP 传输协议使得 NAT 没有可靠性,应用程序自己需要去确定会话穿透 NAT 的声明周期,大多数 NAT 只是简单的设置了 UDP 转换的闲置计时器,如果孔洞在一些时间周期内没有数据传输使用的话就会关闭。不幸的是这里没有标准的计时器的值:有些 NAT 的超时时间至少20s。如果应用程序需要基于打洞技术在建立会话后保持闲置 UDP 会话存活,应用程序需要发送周期的心跳信号去保证相关的 NAT 转换没有消失。

不幸的是,许多 NAT 设置了基于特定终点对确定的 UDP 会话的 UDP 闲置计时器,所以在会话中发送心跳信号将无法保持会话存活即使所有的会话都从同一个私有终点创建。在许都不同的 P2P 会话中并不是发送心跳信号,应用程序能通过检测 UDP 会话是否存活来避免过度的心跳信号的流量,并在需要的时候重新运行打洞技术的过程。

TCP 打洞技术

在位于 NAT 之后的两个主机之间建立 P2P 的 TCP 连接要比 UDP 复杂一些,但是 TCP 打洞技术在协议层级上是类似的。由于这一技术还没有很好的理解,它现在只有很少一部分 NAT 支持。但是当 NAT 支持这一技术时, TCP 打洞技术要比 UDP 打洞技术更快而且可靠。P2P TCP 通信穿透良好运行的 NAT 实际上要比 UDP 通信更加健壮,因为不像 UDP 一样,TCP 协议状态机给与在路径上的 NAT 一个标准的方式去确定特定 TCP 会话的准确的声明周期。

Sockets 和 TCP 端口的再次使用

未完待续……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  p2pool 网络 NAT