干货|更通用的 P2P 网络协议栈——Libp2p
Libp2p 是什么?
Libp2p 是用于构建 P2P 网络的模块化网络堆栈和库,源自开源项目 IPFS,模块化设计使它能够用来构建各种去中心化应用的 P2P 网络层。目前,知名区块链项目 Ethereum 2.0、Pokdot、BitXHub 都选择基于 Libp2p 库搭建系统网络层。Libp2p 作为 P2P 网络协议栈,它是通过解决实际问题不断成长的,可以认为是构建 P2P 网络经验的积累。
Libp2p 解决了哪些问题?
Libp2p 作为网络协议栈,主要解决两个问题:
节点发现
数据传输
本文主要讨论 Libp2p 解决数据传输问题方案,代码基于 go-libp2p v0.9.2。
Libp2p 是如何解决数据传输问题的?
传输层不可知
传输层不可知是指 Libp2p 支持多种传输层协议,例如 TCP、UDP、QUIC 等,应用程序开发者在使用 Libp2p 库时不需要知道完成数据传输使用的传输层协议,Libp2p 会根据远程节点地址信息自动完成协议选择。
地址定义
数据传输建立在节点连接的基础上,在可以拨号远程节点并建立连接之前,需要知道远程节点的监听地址。因为每种传输协议都有自己的地址格式,所以 Libp2p 使用一种称为“multiaddr”的编码方案来统一不同的协议的地址格式。
TCP/IP 传输协议“multiaddr”的描述如下:
/ip4/127.0.0.1/tcp/9999
UDP 传输协议“multiaddr”的描述如下:
/ip4/127.0.0.1/udp/9998
用这种描述方式来代替 127.0.0.1:9999 的好处是什么呢?“multiaddr”能更明确的描述使用的协议,如 127.0.0.1 属于 IPv4 协议,9999 属于 TCP 协议,9998 属于 UDP 协议。
以上为“multiaddr”描述的节点监听地址,当拨号一个节点时也是使用“multiaddr”,但需要加上远程节点的 ID,例如:
/ip4/192.168.100.100/tcp/9999/p2p/QmcEPrat8ShnCph8WjkREzt5CPXF2RwhYxYBALDcLC1iV6
这样就知道对方使用 IP4,地址:192.168.100.100,TCP 协议,端口:9999,是一个 P2P 节点,节点 ID:QmcEPrat8ShnCph8WjkREzt5CPXF2RwhYxYBALDcLC1iV6。
节点 ID 定义
节点 ID 是一串字符,由节点公钥的 hash 产生,并进行 base58 编码,节点 ID 是全网唯一的,拨号时使用节点 ID 可以有效解决中间人攻击问题。
支持多种传输协议
Libp2p 的 Swarm 模块负责将多个传输层组合到一个接口中,从而允许应用程序拨号节点,而不必指定要使用的传输层。它还负责协议协商、多路流复用、建立安全通信等接口升级操作。
Network 接口是 Libp2p 对外提供服务的接口,Swarm 是 Network 接口的具体实现。Libp2p 在 Swarm 中维护现有连接的状态,维护支持的传输层协议。Swarm 通过以下操作支持多传输协议:
为了支持多种传输层协议,新建节点时(NewNode)会将节点支持的传输层协议通过 AddTransport 加入 Swarm 的 transports 结构中。
当节点打开监听(Listen)时,Swarm 模块会调用 TransportForListening 获取监听地址对应的传输层协议,并调用相应的传输层协议的 Listen 函数。
当节点主动连接(Dial)其它节点时,Swarm 模块会调用 TransportForDialing 获取拨号地址使用的传输协议,并调用相应的传输层协议的 Dial 函数。
Libp2p 通过“multiaddr”的编码方案来统一不同协议的地址格式,在 Swarm 模块根据“multiaddr”解析协议并调用相应协议的接口完成具体操作,从而达到了应用层不需要关注使用的传输层协议的目的。
数据安全传输
以上过程在节点间建立了连接,Libp2p 是如何保证传输数据隐私的?这里以 TCP 协议为例进行展开介绍。
TcpTransport 是 TCP 的传输层实现模块,其中组合了 Upgrader 模块,Upgrader 负责把一个原始的 TCP 连接升级为支持加密,支持多路流复用的连接。secio 和 tls 是两个实现了 SecureTransport 接口的库,Libp2p 库默认使用 secio 加密库。
以上为 TCP 收到一个远程节点连接请求的调用流程,主动拨号远程节点调用过程类似。这里使用 secio 包的 runHandshakeSync 函数对连接进行加密。
secio 库密钥交换使用 Diffie-Hellman 密钥协商算法(secio 协议具体内容)。当然也可以使用 tls 对连接进行加密。
多路流复用
Libp2p 应用程序通常会在节点之间打开许多独立的通信流,并且可能会与某个远程节点同时打开多个并发流。多路流复用允许与远程节点建立一次连接即可完成整个生命周期的数据收发,同样只需要处理一次 NAT 操作,因为和同一个远程节点所有的流都共享相同的底层传输连接。在配置 Libp2p 时,会启用流复用模块,Swarm 将在拨号远程节点和侦听连接时使用它们。如果远程节点支持相同的多路流复用实现,则 Swarm 将在建立连接时选择并使用它;如果拨号 Swarm 已经与之建立连接的远程节点,则新建流将自动在现有连接上进行多路复用。
upgrader 的 muxer 模块负责将加密后的连接升级为支持多路流复用的连接。MuxedConn 为多路流复用操作接口,multiplex 为多路流复用操作具体实现,负责具体的流创建及管理。除了默认的 multiplex,Libp2p 还支持 yamux、spdy、muxado 等不同的多路复用器实现。
还是以监听为例,收到连接请求后,Upgrader 的 Secure 模块首先会将连接升级为加密连接。然后通过 NewMultiplex 创建多路复用器实例,多路复用器实例把接口升级为支持流复用的接口。
MuxedConn 的 OpenStream 接口用于向远程节点发送新建流请求,AcceptStream 接口用于接收远程节点创建流的请求。
由于节点间通信多个流使用的仍是同一个连接,为了区别不同的流,multiplex 模块实际上是对发送的数据添加了 header 字段。
header 高 61bits 为 stream id,每次新建流时 stream id 会自增。
header 低 3 位表示消息类型,总共定义了 4 种流消息:
newStreamTag = 0 // 创建流消息
messageTag= 2 // 数据消息
closeTag= 4 // 关闭流消息
resetTag= 6 // 重置流消息
Libp2p 通过 upgrader 的 muxer 模块将普通接口升级为支持流复用的接口,节点间数据传输时会在连接上通过流进行并行数据收发,而不是新建连接,从而减少了节点间新建连接的消耗。为了区别不同的流上的信息,流复用器对收发数据添加了表示流信息的 header 字段。
总结
从 Libp2p 解决数据传输问题能够看到 Libp2p 有很多小组件组成,解决相同问题小组件遵循相同接口,可以根据使用场景进行替换,各组件库可以单独开发升级,而不会对其它部分产生影响。“multiaddr”的地址编码方案使基于 Libp2p 的应用层开发不需要关注底层使用的传输层协议。节点间可以通过协议协商选择共同支持的加密模块对数据传输通道进行加密,从而保证数据隐私性。
版权声明: 本文为 InfoQ 作者【QTech】的原创文章。
原文链接:【http://xie.infoq.cn/article/d96c91cad94b7e690b288c37f】。文章转载请联系作者。
评论