P2P技术详解-P2P中的NAT穿越(打洞)方案详解笔记

730 阅读10分钟

P2P介绍

P2P即点对点通信,或称为对等联网,与传统的服务器客户端模式有明显的区别,在即时通讯方案中应用广泛。

在P2P网络中,所有通讯节点的地位都是对等的,每个节点既是客户端又是服务端,节点之间通过直接通信实现文件信息、处理器运算能力、存储空间等资源的共享。

P2P网络具有分散性、可扩展性、健壮性等特点,这使得P2P在信息共享、即时通讯、协同工作、分布式计算、网路存储等领域有广阔的应用。


图1-经典的CS模式

图片.png

图2-P2P结构模型

图片.png

NAT与P2P

NAT技术和P2P技术作为经典的两项网络技术,在现在的网络上有广泛的应用。

NAT虽然在一定程度上解决IPv4地址短缺的问题,在构建防火墙,保证网络安全方面都发挥了一定的作用,却破坏了端到端的网络通信。

NAT阻碍主机进行P2P通信的主要原因是NAT不允许外网主机主动访问内网主机,但P2P技术要求通讯双方都发起访问,故想要在NAT网络环境中进行有效的P2P通信,就必须采用新的解决方案。

P2P作为一项实用的技术,有很大的优化空间,并且相对于网络设备,基于P2P的应用程序在实现上更为灵活。所以为了兼容NAT,基于P2P的应用程序在开发的时候大多会根据自身特点加入一些穿越NAT的功能以解决上述问题。

反向链接技术

此种情况是所有P2P场景中最简单的, 需要通信双方中只有一方位于NAT设备后。

如图,我们假设ClientB位于NAT之后,ClientB通过TCP端口连接到2345连接到服务器的TCP端口2346上,NAT设备重新分配TCP端口52000ClientA也通过TCP端口2345连接到服务器端口2346上。

由于ClientA拥有外网IP地址,故ClientB想要发起对ClientA的通信,可以直接通过TCP连接。 而ClientA无法通过TCP连接到ClientB进行P2P通信,NAT设备会拒绝这个连接请求。若想要喝ClientB通信,ClientA需要通过服务器ServerClientB转发一个连接请求,反过来请求ClientB连接到ClientA,即反向链接。ClientB在收到从服务器转发过来的请求以后,会主动向ClientA发起一个TCP请求。

通过反向链接的方式,在NAT设备上建立起关于这个连接的相关表项,从而实现ClientAClientB需要进行双向通信。

图片.png

基于UDP协议的P2P技术

UDP打洞技术是通过中间服务器的协助在各自的NAT网关上建立相关的表项,使P2P连接的双方发送的报文能够直接穿透对方的NAT网关,从而实现P2P客户端互连。

若两台位于NAT设备后的P2P客户端希望在自己的NAT网关上打洞,他们需要一个集中服务器作为协助者,还需要一种用于打洞的Session建立机制。

集中服务器

集中服务器本质上是一台被设置在公网上的服务器,P2P的双方都可以直接访问这台服务器。

集中服务器可以中转P2P双方各自的信息,另外也用于判断某个客户端是否在NAT网关之后: 一个客户端在集中服务器上登陆时,服务器会记录下该客户端的两对地址二元组信息{IP地址:UDP端口}

  • 第一对:客户端与集中服务器进行通信的自身IP地址及端口号,即客户端的内网IP地址和端口号
  • 第二对:集中服务器记录下的,由服务器观察到该客户端实际与自己通信所使用的IP地址喝端口号,即客户端经过NAT转换后的外网IP地址和端口号
P2P的Session建立原理

ClientAClientB发起直接连接,具体过程如下:

  • ClientA不知ClientB前是否有NAT网关,故直接向集中服务器发送消息,请求集中服务器帮组建立于ClientB的UDP的连接
  • 集中服务器将含有ClientB的外网及内网的两对地址二元组发送给ClientA,同时也将含有ClientA的内外网的两对地址二元组发送给ClientB,使得ClientAClientB互相知道对方的外网和内网的地址二元组信息
  • ClientA收到集中服务器发送的含有ClientB的外网及内网的两对地址二元组信息后,ClientA开始向ClientB发送UDP数据包,并且ClientA会锁定第一个给出响应的ClientB的地址二元组,同理ClientB接收到ClientA的地址二元组信息后也会对ClientA发送UDP数据包并锁定第一个得到回应的地址二元组

由于A与B互相向对方发送UDP数据包的操作是异步的,所以A和B发送数据包的时间先后并没有时序要求。

UDP在空闲状态下的超时问题

由于UDP转换协议提供的“洞”不是绝对可靠的,多数NAT设备内部都有一个UDP转换的空闲状态计时器,如果在一段时间内没有UDP数据通信,NAT设备会关掉由“打洞”过程打出来的“洞”。如果P2P应用程序希望“洞”的存活时间不受NAT网关的限制,就最好在穿越NAT以后设定一个穿越的有效期。

对于有效期目前没有标准值,它与NAT设备内部的配置有关,某些设备上最短的只有20秒左右。 在有效期内,无论是否有P2P数据包需要传输,应用程序都需要向对方发送心跳包以维持连接。心跳包需要双方应用程序都发送,只有一方发送无法保持另一方的Session正常工作。 为了维持连接,除了频繁发送心跳包外,还可以在当前有效期超时之前重新“打洞”,丢弃原有的“洞”。这是一种有效的方法。

基于TCP协议的P2P打洞技术

建立穿越NAT设备的P2P的TCP连接只比UDP复杂一点点,TCP协议的”“打洞”从协议层来看是与UDP的“打洞”过程非常相似的。在实际应用上,由于TCP协议的状态机给出了一种标准的方法来精确的获取某个TCP session的生命期,而UDP无法做到这一点,所以只要NAT设备支持,基于TCP的P2P技术的健壮性会比基于UDP技术的更强一些。

套接字与TCP端口的重用

TCP的套接字通常仅允许建立1对1的响应,即应用程序在将一个套接字绑定到本地的一个端口以后,任何试图将第二个套接字绑定到该端口的操作都会失败。

为了让TCP“打洞”顺利工作,需要使用本地TCP端口监听外部连接,并建立多个向外的TCP连接。主流操作系统支持“SO_REUSEADDR”参数,允许多个套接字绑定到同一本地地址(需设置该参数)。BSD系统引入了“SO_REUSEPORT”参数,区分端口和地址重用,这些系统需同时设置所有参数。

打开P2P的TCP流

若ClientA希望与ClientB建立TCP连接,我们会像通常一样假定A和B已与公网上的已知服务器建立了TCP连接,服务器如同IDP服务一般,记录下来每个客户端的公网内网的地址二元组

从协议层来看TCP打洞与UDP打洞是几乎相同的过程

而TCP打洞与UDP的不同点是: 使用UDP协议的每个客户端只需要一个套接字即可完成与服务器的通信,而TCP客户端必须处理多个套接字绑定到同一个本地TCP端口

客户端向彼此公网地址二元组发起连接的操作,会使得各自的NAT设备打开新的“洞”允许ClientAClientB的TCP数据通过。如果NAT设备支持TCP“打洞”操作的话,一个在客户端之间的基于TCP协议的流通道就会自动建立起来。如果ClientAClientB发送的第一个SYN包发到了ClientB的NAT设备,而ClientB在此前没有向A发送SYN包,ClientB的NAT设备会丢弃这个包,这会引起ClientA的“连接失败”或“无法连接”问题。而此时,由于ClientA已经向ClientB发送过SYN包,ClientB发往ClientA的SYN包将被看作是由ClientA发往ClientB的包的回应的一部分,所以ClientB发往ClientA的SYN包会顺利地通过ClientA的NAT设备,到达ClientA,从而建立起ClientAClientB的P2P连接。

从应用程序的角度来看TCP打洞

从应用程序的角度来看,TCP“打洞”是一种在NAT(网络地址转换)环境下建立P2P(点对点)连接的方法。具体过程如下:

  1. ClientA首先向ClientB发出SYN包,该包发往B的公网地址二元组,但被ClientB的NAT设备丢弃。

  2. ClientB发往ClientA的公网地址二元组的SYN包则通过A的NAT到达了ClientA

  3. 接下来会发生以下两种结果中的一种,具体取决于操作系统对TCP协议的实现:

    • 第一种结果:ClientA的TCP实现会发现收到的SYN包就是其发起连接并希望联入的ClientB的SYN包。此时,ClientAconnect()函数将成功返回,ClientAlisten()函数将没有任何反映。ClientA开始使用这个连接与ClientB进行P2P通信。ClientA会回应ClientB的SYN包,发送SYN-ACK包给ClientB,并使用先前ClientA发向ClientB的SYN包一样的序列号。ClientB收到后,发送ACK包给ClientA,两端建立起TCP连接。

    • 第二种结果:ClientA的TCP实现没有发现收到的SYN包就是其发起连接并希望联入的ClientB的SYN包。此时,ClientA通过listen()函数和accept()函数得到与ClientB的连接,而由ClientA发起的向B的公网地址二元组的连接会以失败告终。尽管ClientAClientB的连接失败,ClientA仍然得到了ClientB发起的向ClientA的连接,等效于ClientAClientB之间已经联通,ClientAClientB的基于TCP协议的P2P连接已经建立起来了。

第一种结果适用于基于BSD的操作系统对于TCP的实现,而第二种结果更加普遍一些,多数Linux和Windows系统都会按照第二种结果来处理。

总结

打洞技术看起来是一项近似乎蛮干的技术,却不失为一种有效的技术手段。在集中服务器的帮助下,P2P的双方利用端口预测的技术在NAT网关上打出通道,从而实现NAT穿越,解决了NAT对于P2P的阻隔,为P2P技术在网络中更广泛的推广作出了非常大的贡献。

原文连接:P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解