1. 什么是NAT
NAT(Network Address Translation,网络地址转换)是指一种网络技术,它允许多个设备通过一个公共IP地址连接到互联网。NAT通常被用在家庭或小型办公室的路由器上,以允许多台计算机共享一个互联网连接。这种做法可以节省IPv4地址空间,并且为内部网络提供了一定程度的安全性,因为外部网络无法直接访问内部网络中的设备。
许多路由器支持用于端口转发的自动配置协议,最常见的是UPnP(通用即插即用)或NAT-PMP(NAT端口映射协议)。
2. libp2p如何解决NAT问题
对于libp2p这样的点对点通信库来说,NAT是一个挑战,因为它使得两个位于不同私有网络后面的节点很难直接建立连接。libp2p需要处理NAT穿透问题,以便让这些节点能够相互通信。
为此,libp2p采用了多种技术和协议来解决这个问题,包括但不限于:
- STUN (Session Traversal Utilities for NAT): STUN是一种协议,用于发现NAT类型以及获取外部可见的IP地址和端口。
- TURN (Traversal Using Relays around NAT): 当直接的P2P连接不可行时,TURN服务器可以作为中继点来转发数据流。
- ICE (Interactive Connectivity Establishment): ICE是一种框架,结合了STUN和TURN的功能,用来尝试建立最佳的连接路径。
- Hole Punching: 这是一种技术,其中一个节点可以通过向另一个节点发送数据包来“打洞”穿越NAT,从而创建一条直接的UDP连接路径。
- UPnP (Universal Plug and Play) / NAT-PMP (NAT Port Mapping Protocol): 这些协议允许应用程序自动配置路由器上的端口映射,以便外部网络可以直接访问内部网络中的服务。
libp2p通过使用诸如STUN、TURN等技术来帮助节点发现它们对外部世界的可访问地址,从而避免了这个问题。此外,libp2p还利用自动NAT配置协议(如UPnP或NAT-PMP)来设置端口映射,以确保节点能够接收来自外部的入站连接。
3. AutoNAT
3.1 背景
虽然识别协议允许对等节点互相告知它们观察到的网络地址,但有时这些地址是不可访问的,因为该对等节点可能位于一个私有网络中(即,在NAT或防火墙之后)。
为了解决广播和尝试连接不可达地址的问题,libp2p实现了一个名为AutoNAT的协议。该协议允许节点判断它们是否位于NAT之后。通过这种方式,节点可以确定它们的公网可达性,并避免广播那些实际上无法被其他节点访问的地址,从而提高P2P网络的整体效率和稳定性。
3.2 协议介绍
AutoNAT允许一个节点通过拨打预定的公共地址来请求其他节点。
- 对于NAT背后的私有节点,强烈建议如下
- 不广播私有地址
- 通过NAT获得一个预留的中继的地址
- 广播这个中继地址
- 对于在公网上的节点
- 启动中继功能以帮助其他节点
- 考虑激活DHT服务器模式以改善与公网的连接
如果大多数拨号尝试都成功,则该节点可以合理地确定它不在 NAT 后面。另一方面,如果大多数拨号尝试都失败,则强烈表明 NAT 正在阻止传入连接。
3.3 协议实现
AutoNAT协议使用协议ID为/libp2p/autonat/1.0.0,涉及到Dial和DialResponse消息的交换。
启动该协议时,节点1发送一个Dial消息到一个的节点2,该消息包含一个列表的multiaddresses。节点2开始拨打这些地址。如果只少有一个dial是成功的,则节点2发送一个DialResponse消息给node1。消息的主体是ResponseStatus: SUCCESS。如果全部失败,则返回ResponseStatus: E_DIAL_ERROR。
节点1可以通过返回的消息判断自己是否在一个NAT后。
如果响应表明成功,则该节点可能不在NAT后面,并且不需要使用中继服务器来改善其连通性。如果响应指示错误,则该节点可能在NAT之后,可能需要使用中继服务器与网络中的其他节点进行通信。
发起方使用从多个对等体获得的响应来确定自己的NAT状态。如果超过3个节点报告一个成功拨号的地址,节点应该假设它不在NAT后面,并且是可公开访问的。另一方面,如果超过3个对等体报告拨号失败,则该节点应该假定它是不可公开访问的。
4. Circuit Relay
“电路中继”是一种传输协议,它通过第三方“中继”节点在两个对等节点之间路由流量。在许多情况下,对等节点无法穿越其网络地址转换(NAT)和/或防火墙,从而使其公开可访问。目前p2p实现了两个版本的协议,以下主要以v2版本为主。
4.1 p2p中的Circuit Relay
为了在面对NAT等连接障碍时实现点对点架构,libp2p定义了一种名为p2p-circuit的协议。当一个对等节点无法监听公共地址时,它可以拨号连接到一个中继对等节点,该节点将保持一个长期开放的连接。其他对等节点将能够使用p2p-circuit地址通过中继对等节点进行拨号,该地址将转发流量到其目的地。
电路中继协议的灵感来自于TURN,它是交互式连接建立(Interactive Connectivity Establishment)集合中的一部分NAT穿越技术。
中继协议的一个重要方面是它不是“透明的”。换句话说,源节点和目标节点都意识到流量正在被中继。这一点很有用,因为目标节点可以看到用于打开连接的中继地址,并且可能使用它来构建返回源节点的路径。它也不是匿名的——所有参与者都使用其对等ID进行标识,包括中继节点。
4.2 Relay addresses(中继地址)
中继电路是用一个多地址来标识的,这个多地址包括传输的对等体(侦听对等体或“中继目标”)的对等体ID。
假设我有一个libp2p节点,其Peer ID为QmAlice。我想把我的地址给我的朋友QmBob,但我在NAT后面,不让任何人直接拨给我。
这样的地址表示只能表面这个消息是通过电路中继和p2p协议试图连接到QmAlice这个唯一标识
/p2p-circuit/p2p/QmAlice
包括特定中继对等体QmRelay的标识。如果对等端已经知道如何打开与QmRelay的连接,他们将能够联系到我们。
/p2p/QmRelay/p2p-circuit/p2p/QmAlice
更好的方法是在地址中包含中继对等体的传输地址。假设我已经用对等体ID QmRelay建立了到特定中继的连接。他们通过识别协议告诉我,他们正在监听IPv4地址198.51.100.0的55555端口上的TCP连接。我可以构造一个地址,它描述了通过传输的特定中继给我的路径:
/ip4/198.51.100.0/tcp/55555/p2p/QmRelay/p2p-circuit/p2p/QmAlice
以上/p2p-circuit/之前的所有内容都是中继对等体的地址,其中包括传输地址和它们的对等体ID QmRelay。
/p2p-circuit/之后是线路另一端的对等体ID, QmAlice。
- 节点A位于NAT和/或防火墙后面,例如通过自动识别服务检测到。
- 因此,节点A向中继R请求预约,即节点A请求中继R代表它侦听传入的连接。
- 节点B想要与节点a建立连接。鉴于节点a没有发布任何直接地址,只有一个中继地址,节点B连接到中继R,要求中继R中继到a的连接。
- 中继R将连接请求转发给节点A,最终中继A和B发送的所有数据。
5. DCUtR
5.1 背景知识
使用中继作为代理来遍历nat,但是这在扩展和维护方面的成本可能很高,并且可能导致低带宽、高延迟的连接。穿孔是另一种通过使NAT后面的两个节点直接通信来实现NAT穿越的技术。然而,除了中继节点之外,它还需要另一个称为信令服务器的基础设施。libp2p提供了一个穿孔解决方案,它消除了对集中式信令服务器的需求,并允许使用分布式中继节点。
5.2 通过中继直接连接
libp2p DCUtR (Direct Connection Upgrade through Relay)是一种通过打孔在节点之间建立直接连接的协议,不需要信令服务器。DCUtR包括同步和打开到每个对等端预测的外部地址的连接。
DCUtR协议使用协议ID /libp2p/ DCUtR
,包括Connect
和Sync
消息的交换。
DCUtR协议支持TCP、QUIC等不同类型的连接,不同类型的连接建立过程不同。
6. Hole Punching
点对点网络上的节点可以分为公共和非公共两类。公共节点是那些可以不受阻碍地访问互联网的节点,而非公共节点位于某种防火墙后面。这适用于家庭和公司网络的大多数节点,以及移动电话。在大多数配置中,公共和非公共节点都可以拨号连接到其他公共节点。但是,不可能建立从公共互联网到非公共节点的连接。
6.1 如何拨打非公共节点
- 拨号非公共节点的方法:
- UPnP(通用即插即用):路由器和网络内计算机之间使用的协议。它允许计算机请求某些端口被并转发到该计算机。
- 端口转发:在路由器上手动配置端口转发。
- 限制
- 在许多情况下,UPnP被路由器或防火墙禁用。UPnP也可能无法工作,这取决于路由器的固件。
- 手动打开端口需要专业技术,并且不强制进行身份验证或授权。
6.2 可能的解决方案(hole punching)
6.2.1 Relaying overview
中继是一种用于在两端之间发送信息的机制。对于非公有节点:
节点A与中继节点R保持永久连接,当节点B想要连接到节点A时,它首先与节点R建立连接,节点R转发该连接上的所有数据包。中继增加了额外的延迟,并且是资源密集型的,因为节点R需要处理大量的流量。使用中继节点还需要技术专长。
6.2.2 如何利用节点R来促成节点A和B直接连接
在其他选择都不够的情况下,网络可以使用一种称为打孔的技术来与非公共节点建立连接。
每个节点连接到中继节点并共享其外部地址和端口信息。服务器临时存储节点的信息,并将每个节点的信息转发给其他节点。客户端可以使用这些信息建立彼此之间的直接连接。
以两个节点A和B为例,它们想要相互拨号:
- 两个节点的第一个数据包(例如,在TCP的情况下,一个SYN)通过它们各自的路由器。
- 路由器将一个5元组添加到路由器的状态表中。(修改路由器的表)
- PacketA和PacketB在各自路由器的防火墙上“打洞”。
- 两个包到达对方的路由器。
- 当A的数据包到达Router_B时,Router_B检查自己的状态表,发现一个5元组是在节点B发送的数据包中添加的。
- 路由器通过“打孔”将数据包转发给B。B的数据包也是如此;到达Router_A后,在Router_A的状态表中匹配一个5元组,将报文转发给a。
路由器状态表(路由表)是存储在路由器中的数据,它列出了到特定网络目的地的路由。5元组结构包括源IP地址、源端口、目的IP地址、目的端口和传输协议。
6.3 libp2p中的Hole punching
受ICE协议的启发,libp2p包括一个分散的穿孔功能,允许防火墙和NAT穿越,而不需要像STUN和TURN这样的中央协调服务器。
6.3.1 阶段一Preparation
6.3.1.1AutoNAT
确定节点是否可拨号,例如,发现节点是否位于NAT或防火墙后面。
- B向它所在网络上的Other_Peers(例如,引导节点)伸出手,并要求每个节点在一组它怀疑可以到达的地址上拨号。libp2p节点有多种发现其地址的方法,但最突出的是使用libp2p标识协议。
- Other_Peers尝试拨打B的每个地址,并将结果报告给B。
- 根据报告,B可以判断自己是否是公共节点,并确定是否需要打孔。
6.3.1.2 AutoRelay:动态发现和绑定网络中的中继节点。
IPFS通过Kademlia DHT使用查找方法发现k-最近的公共中继节点:/<RELAY_ADDR>/p2p-circuit/<PEER_ID_B>
B网络外的Other_Peers可以通过公共中继节点间接呼叫B。在IPFS的情况下,每个公共节点将充当一个中继。B将在Kademlia DHT上查找与其Peer ID最近的对等节点,或者选择已经连接到的公共节点的一个子集。
6.3.1.3 Circuit Relay:连接到发现的中继节点并请求预订。节点可以通过远程中继节点宣布自己可达。
- Relay可以通过Circuit Relay v2限制用于中继连接的资源(例如,通过连接数、时间和字节)。在IPFS的情况下,这允许网络中的每个公共节点充当中继,而不会消耗大量资源。
- 对于每一个被发现的中继
- B连接到远程节点,并请求中继节点代替它侦听连接,称为reservation;
- 如果Relay接受预订请求,则B可以宣布自己可通过Relay访问。
6.3.2 阶段二Hole punching
成功率(90%)
6.3.2.1 Circuit Relay(电路中继)
电路中继:通过公共中继节点建立安全的中继连接。节点A与中继节点建立直连。然后节点B通过中继节点请求到节点a的中继连接,创建一个双向通道,并使用TLS来保护该通道。
- A使用B的通告地址中包含的信息通过中继建立到B的中继连接。
- A首先建立到Relay的直接连接,然后从Relay请求到B的中继连接。
- Relay将请求转发给B并接受。
- 继电器将接收转发给A。
- A和B可以使用中继上的双向信道进行通信。
- A和B使用TLS等安全协议升级中继连接
6.3.2.2 DCUtR:使用DCUtR作为同步机构来协调冲孔。
- A通过Relay向B发送Connect消息。
- 连接包含a的地址。libp2p提供了多种机制来发现一个人的地址,例如,通过libp2p识别协议。
- B在中继连接上接收Connect消息,并回复一个包含其(非中继)地址的Connect消息。
- A测量发送消息和接收B消息之间的时间,从而确定A和B之间通过Relay的往返时间。
- 然后,A在中继连接上向B发送同步消息。
- A等待一半的往返时间,然后通过B的Connect接收到的地址直接拨打B。
- 一旦B收到A的Sync消息,它就直接使用A的Connect消息中提供的地址拨打A。
- 一旦A和B同时拨号,就会发生打孔。