1. NAT穿透

在现实Internet网络环境中,大多数计算机主机都位于防火墙或NAT之后,只有少部分主机能够直接接入Internet。很多时候,我们希望网络中的两台主机能够直接进行通信,即所谓的P2P通信,而不需要其他公共服务器的中转。由于主机可能位于防火墙或NAT之后,在进行P2P通信之前,我们需要进行检测以确认它们之间能否进行P2P通信以及如何通信。这种技术通常称为NAT穿透(NAT Traversal)1

常见的NAT网络穿透方案有:

  1. STUN: 简单的用UDP穿透NAT,客户端获取STUN服务器信息后,通过TCP协议向STUN服务器发送共享秘钥请求,STUN服务器生产共享秘钥响应。客户端和STUN服务器间使用共享私密,用作捆绑请求和捆绑响应中的密匙。之后,客户使用UDP协议向STUN服务器发送捆绑请求,当捆绑请求消息到达服务器的时候,它可能经过了一个或者多个NAT。客户端之间的通信就可以通过STUN服务器获取对端网络的IP地址和端口绑定,彼此通信。最大的优点是支持TCP穿透。
  2. TURN: 使用中继穿透NA,是STUN方案的扩展。如果一个主机位于NAT的后面,在某些情况下它不能够与其他主机点对点直接连接。在这些情况下,它需要使用中间网点提供的中继连接服务。TURN协议就是用来允许主机控制中继的操作并且使用中继与对端交换数据。TURN与其他中继控制协议不同的是它能够允许一个客户端使用一个中继地址与多个对端连接。不支持TCP穿透。
  3. UPnP: 通用即插即用协议,STUN和TURN穿透方案都是通过“中间人”服务器实现点对点的通信。而UPnP不需要“中间人”服务器,通过UPnP-IGD协议实现点对点通信。但是需要在路由器网关等设备支持UPnP通信协议。

1.1 UPnP

UPnP 是各种各样的智能设备、无线设备和个人电脑等实现遍布全球的对等网络连接(P2P)的结构。UPnP 是一种分布式的,开放的网络架构。UPnP技术对即插即用进行了扩展,它简化了家庭或企业中智能设备的联网过程。在结合了UPnP技术的设备以物理形式连接到网络中之后,它们可以通过网络自动彼此连接在一起,而且连接过程无需用户的参与和使用中央服务器。
UPnP在控制指针和被控制设备之间提供通讯功能。而网络介质、TCP/IP协议、HTTP仅提供基本的连接和IP地址分配。整个工作过程需要处理六个方面的内容,即设备寻址、发现设备、对设备的描述、设备控制、设备事件、设备表达。

1.1.1 UPnP的NAT穿越技术

UPnP为NAT(网络地址转换)traversal带来了一个解决方案:IGD(Internet 网关设备) 协议。NAT traversal 允许UPnP数据包在没有用户交互的情况下,无障碍的通过路由器 或者 防火墙,(假如那个路由器 或者 防火墙 支持 NAT)。

1.1.2 UPnP网络的安全问题

虽然UPnP的端口映射可以使用户的某些上网行为更加流畅,但由于设备被暴露到公网,导致了设备可以被公网恶意扫描访问到,同时Shodan、Zoomeye等网站也可以爬取到此类设备,因此大大提升了内网设备被恶意攻击利用的风险。2 UPnP协议默认没有实现任何鉴权认证机制,因此UPnP设备必须另外实现设备防护服务或者设备安全服务。也就不存在标准解决方案拓展UPnP设备或应用程序的用户鉴权认证机制。不幸的是,好多UPnP设备实现没有鉴权机制,假设本地系统和用户是完全信任的。3 如果没有实现鉴权认证机制,路由器和防火墙在运行UPnP-IGD协议时就容易受到攻击。4

2. RLPx

RLP(Recursive Length Prefix encoding)递归长度前缀编码5,是Ethereum中对象序列化的一个主要的编码方式,其目的是对任意嵌套的二进制数据的序列进行编码。

RLPx: 是一个加密对等网络传输协议套件6 ,为应用程序通过p2p网络通信提供通用的传输通道和接口,旨在满足分散应用程序的要求。

特性:

  • 发现节点和网络的形成
  • 加密的握手
  • 加密传输
  • 协议Mux(框架)
  • 流控制
  • 对等优先策略
  • 同行的声誉
  • 安全
    • 身份验证的连接(ECDH + ECDHE AES128)
    • 经过验证发现协议(ECDSA)
    • 加密传输(AES256)
    • 协议共享一个连接提供了统一的带宽(框架)
    • 节点使用一个统一的网络拓扑
    • 对端可以统一连接到网络
    • 局部的同行的声誉模型

3. Kademlia算法

Kademlia是一种通过分散式杂凑表实现的协议算法,它是由Petar和David为非集中式P2P计算机网络而设计的。Kademlia规定了网络的结构,也规定了通过节点查询进行信息交换的方式。Kademlia网络节点之间使用UDP进行通讯。参与通讯的所有节点形成一张虚拟网(或者叫做覆盖网)。这些节点通过一组数字(或称为节点ID)来进行身份标识。节点ID不仅可以用来做身份标识,还可以用来进行值定位(值通常是文件的散列或者关键词)。其实,节点ID与文件散列直接对应,它所表示的那个节点存储着哪儿能够获取文件和资源的相关信息。当我们在网络中搜索某些值(即通常搜索存储文件散列或关键词的节点)的时候,Kademlia算法需要知道与这些值相关的键,然后分步在网络中开始搜索。每一步都会找到一些节点,这些节点的ID与键更为接近,如果有节点直接返回搜索的值或者再也无法找到与键更为接近的节点ID的时候搜索便会停止。

Kad网络中的每一个节点均拥有一个专属ID,该ID的具体形式与SHA1散列值类似,为一个长达160bit的整数,它是由节点自己随机生成的, 两个节点拥有同一ID的可能性非常之小,因此可以认为这几乎是不可能的。

P2P网络的演进:

  • 第一代P2P文件分享网络,像Napster,依赖于中央数据库来协调网络中的查询。
  • 第二代P2P网络,像Gnutella,使用泛滥式查询(query flooding)来查询文件,它会搜索网络中的所有节点。
  • 第三代p2p网络使用分散式杂凑表来查询网络中的文件,分散式杂凑表在整个网络中储存资源的位置,这些协议追求的主要目标就是快速定位期望的节点。7

Kademlia基于两个节点之间的距离计算,该距离是两个网络节点ID号的异或,计算的结果最终作为整型数值返回。关键字和节点ID有同样的格式和长度,因此,可以使用同样的方法计算关键字和节点ID之间的距离。节点ID一般是一个大的随机数,选择该数的时候所追求的一个目标就是它的唯一性(希望在整个网络中该节点ID是唯一的)。异或距离跟实际上的地理位置没有任何关系,只与ID相关。因此很可能来自德国和澳大利亚的节点由于选择了相似的随机ID而成为邻居。选择异或是因为通过它计算的距离享有几何距离公式的一些特征,尤其体现在以下几点:节点和它本身之间的异或距离是0;异或距离是对称的:即从A到B的异或距离与从B到A的异或距离是等同的;异或距离符合三角不等式:三个顶点A B C,AC异或距离小于或等于AB异或距离和BC异或距离之和。由于以上的这些属性,在实际的节点距离的度量过程中计算量将大大降低。Kademlia搜索的每一次迭代将距目标至少更近1 bit。一个基本的具有2的n次方个节点的Kademlia网络在最坏的情况下只需花n步就可找到被搜索的节点或值。

3.1 Kademlia路由表

Kademlia路由表由多个列表组成,每个列表对应节点ID的一位(例如:假如节点ID共有128位,则节点的路由表将包含128个列表),包含多个条目,条目中包含定位其他节点所必要的一些数据。列表条目中的这些数据通常是由其他节点的IP地址,端口和节点ID组成。每个列表对应于与节点相距特定范围距离的一些节点,节点的第n个列表中所找到的节点的第n位与该节点的第n位肯定不同,而前n-1位相同,这就意味着很容易使用网络中远离该节点的一半节点来填充第一个列表(第一位不同的节点最多有一半),而用网络中四分之一的节点来填充第二个列表(比第一个列表中的那些节点离该节点更近一位),依次类推。如果ID有128个二进制位,则网络中的每个节点按照不同的异或距离把其他所有的节点分成了128类,ID的每一位对应于其中的一类。随着网络中的节点被某节点发现,它们被逐步加入到该节点的相应的列表中,这个过程中包括向节点列表中存信息和从节点列表中取信息的操作,甚至还包括当时协助其他节点寻找相应键对应值的操作。这个过程中发现的所有节点都将被加入到节点的列表之中,因此节点对整个网络的感知是动态的,这使得网络一直保持着频繁地更新,增强了抵御错误和攻击的能力。

列表也称为K桶,其中K是一个系统变量,如20,每一个K桶是一个最多包含K个条目的列表,也就是说,网络中所有节点的一个列表(对应于某一位,与该节点相距一个特定的距离)最多包含20个节点。随着对应的bit位变低(即对应的异或距离越来越短),K桶包含的可能节点数迅速下降(这是由于K桶对应的异或距离越近,节点数越少),因此,对应于更低bit位的K桶显然包含网络中所有相关部分的节点。由于网络中节点的实际数量远远小于可能ID号的数量,所以对应那些短距离的某些K桶可能一直是空的(如果异或距离只有1,可能的数量就最大只能为1,这个异或距离为1的节点如果没有发现,则对应于异或距离为1的K桶则是空的)。

让我们看右边的那个简单网络,该网络最大可有2^3,即8个关键字和节点,目前共有7个节点加入,每个节点用一个小圈表示(在树的底部)。我们考虑那个用黑圈标注的节点6,它共有3个K桶,节点0,1和2(二进制表示为000,001和010)是第一个K桶的候选节点,节点3目前(二进制表示为011)还没有加入网络,节点4和节点5(二进制表示分别为100和101)是第二个K桶的候选节点,只有节点7(二进制表示为111)是第3个K桶的候选节点。图中,3个K桶都用灰色圈表示,假如K桶的大小(即K值)是2,那么第一个K桶只能包含3个节点中的2个。众所周知,那些长时间在线连接的节点未来长时间在线的可能性更大,基于这种静态统计分布的规律,Kademlia选择把那些长时间在线的节点存入K桶,这一方法增长了未来某一时刻有效节点的数量,同时也提供了更为稳定的网络。当某个K桶已满,而又发现了相应于该桶的新节点的时候,那么,就首先检查K桶中最早访问的节点,假如该节点仍然存活,那么新节点就被安排到一个附属列表中(作为一个替代缓存).只有当K桶中的某个节点停止响应的时候,替代cache才被使用。换句话说,新发现的节点只有在老的节点消失后才被使用。
DHT

3.2 Kademlia协议

  • PING 用来测试节点是否仍然在线
  • STORE 在某个节点中存储一个键值对
  • FIND_NODE 消息请求的接收者将返回自己桶中离请求键值最近的K个节点
  • FIND_VALUE 当请求的接收者存有请求者所请求的键的时候,它将返回相应键的值

4. P2P网络引导

在P2P网络的现实中,P2P网络节点在启动时,并不知道当前它要链接的P2P网络节点在哪里,在广袤的IP海洋中,通过组播去发现可能很长时间都不能发现一个节点,无法链接点这个P2P网络中,除非在所有的P2P应用程序启动时共同约定一个域名或者IP地址,从这个地址上至少获取一个P2P网络的活动节点,在从这个节点找到其他节点,一个P2P网络才能迅速加入到整个P2P网络当中。这个过程称之为P2P网络引导。