一、P2P技术概述
1.1 什么是P2P技术
P2P的定义与特点
P2P(Peer-to-Peer,点对点)技术是一种网络通信模型,它允许网络中的参与者(节点)直接相互通信和共享资源,而无需依赖中央服务器作为中介。在P2P网络中,每个节点既可以是资源的提供者(服务器),也可以是资源的消费者(客户端)。
P2P网络的主要特点:
- 去中心化:没有中央控制节点,网络中的每个节点地位平等
- 自组织性:网络能够自动适应节点的加入和离开
- 资源共享:计算能力、存储空间和网络带宽等资源可以在节点间共享
- 可扩展性:随着节点数量增加,网络整体资源和服务能力也相应增加
- 冗余性:数据和服务通常在多个节点上复制,提高了系统的容错能力
与传统C/S架构的区别
特性 | 传统C/S(客户端/服务器)架构 | P2P(点对点)架构 |
---|---|---|
中心化程度 | 高度中心化,服务器作为核心 | 去中心化,节点平等 |
资源分布 | 资源集中在服务器端 | 资源分布在所有参与节点 |
可扩展性 | 受限于服务器性能,扩展成本高 | 随节点增加自然扩展,成本分摊 |
稳定性 | 服务器故障导致整体服务中断 | 单个节点故障影响有限 |
带宽利用 | 服务器带宽易成为瓶颈 | 带宽需求分散到各节点 |
实现复杂度 | 相对简单,架构清晰 | 较为复杂,需处理各种网络环境 |
安全控制 | 集中式安全控制,相对容易 | 分散式安全控制,挑战较大 |
P2P网络的优势与挑战
优势:
- 高效利用资源:充分利用边缘节点的计算能力、存储空间和网络带宽
- 降低成本:减少对中央服务器的依赖,降低基础设施和运维成本
- 提高可靠性:去中心化结构消除了单点故障风险
- 自然扩展:网络容量随着参与节点的增加而自然增长
- 抗审查性:分布式特性使得网络更难被审查或关闭
挑战:
- 网络穿透问题:NAT和防火墙环境下的连接建立困难
- 节点发现与管理:如何高效发现和管理动态变化的节点
- 安全与信任:缺乏中央权威,节点间的信任建立机制复杂
- 服务质量保障:节点性能和连接质量参差不齐,难以保证一致的服务质量
- 法律与合规:在某些地区面临法律监管挑战,特别是涉及版权内容时
- 资源不均衡:“搭便车"现象(部分节点只消费不贡献)影响网络效率
1.2 P2P技术的发展历程
早期P2P应用(1999-2005)
Napster时代(1999-2001):
- 1999年,Shawn Fanning创建了Napster,这是第一个广为人知的P2P文件共享应用
- Napster采用了中心化索引 + P2P传输的混合架构,用户通过中央服务器查找音乐文件,但文件传输直接在用户之间进行
- 2001年因版权诉讼而被迫关闭,但它开创了P2P文件共享的先河
去中心化探索(2000-2003):
- Gnutella(文件共享网络):2000年发布,首个完全去中心化的P2P网络,采用泛洪(Flooding)搜索机制
- Freenet:2000年推出,注重匿名性和抗审查能力的 P2P 网络
- Kazaa/FastTrack:2001年推出,引入了超级节点(Supernode)概念,形成两级架构
- eDonkey/eMule:2002年出现,引入了服务器网络和文件哈希技术
BitTorrent革命(2003-2005):
- 2001年,Bram Cohen设计了BitTorrent协议,2003年开始广泛应用
- 创新性地引入了分片下载、稀有优先和互惠机制(tit-for-tat)
- 显著提高了大文件分发效率,至今仍是最成功的P2P协议之一
现代P2P技术的演进(2005-2020)
结构化P2P网络(2005-2010):
- **分布式哈希表(DHT)**技术成熟:Kademlia、Chord、Pastry等算法广泛应用
- BitTorrent网络引入DHT,摆脱了对Tracker服务器的依赖
- 学术界和工业界对P2P网络路由和查找算法进行了深入研究
P2P流媒体时代(2008-2015):
- P2P技术在视频直播领域得到应用:PPTV、PPS影音等
- P2P-CDN混合架构出现,如迅雷看看、阿里云PCDN
- WebRTC(2011年发布)标准化了浏览器中的P2P通信能力
区块链与去中心化应用(2009-至今):
- 2009年比特币网络上线,将P2P技术与密码学、共识机制相结合
- 2015年以太坊推出,支持智能合约,催生了大量去中心化应用(DApps)
- IPFS(星际文件系统)在2015年推出,致力于构建去中心化的文件存储和访问系统
未来发展趋势
Web3.0与去中心化互联网:
- 基于区块链的去中心化身份和数据所有权
- 去中心化存储和计算平台的普及
- 用户直接控制和变现自己的数据和创作内容
边缘计算与P2P结合:
- 利用边缘设备的闲置计算资源构建分布式计算网络
- 降低云计算中心的负载和延迟
- 物联网设备间的直接P2P通信和协作
5G/6G网络中的应用:
- 高带宽、低延迟网络环境为P2P应用提供更好基础
- 设备直连(Device-to-Device,D2D)通信技术与P2P结合
- 移动边缘计算(MEC)中的P2P资源共享
安全与隐私增强:
- 零知识证明等密码学技术在P2P网络中的应用
- 去中心化隐私保护机制的发展
- 抗量子计算的P2P安全协议研究
二、NAT类型详解
3.1 NAT基础知识
NAT(Network Address Translation,网络地址转换)是一种在IP数据包通过路由器或防火墙时重写来源IP地址或目的IP地址的技术。它主要用于解决IPv4地址短缺问题,允许多个设备通过单个公网IP地址访问互联网。
NAT的主要作用:
- 地址复用:允许多个私有IP地址共享一个公网IP地址
- 网络安全:隐藏内部网络结构,防止外部直接访问内部主机
- 负载均衡:在某些实现中用于分发流量
- 端口复用:通过端口号区分不同内部主机的连接
NAT的工作原理
NAT通常部署在网络边界设备(如路由器)上,当内部主机向外部发送数据包时,NAT设备会:
- 来源地址转换:将私有来源IP和端口替换为公网IP和临时端口
- 维护转换表:记录转换关系(内部IP:端口 -> 公网IP:端口)
- 转发数据包:将修改后的数据包发送到外部网络
- 反向转换:当响应数据包返回时,根据转换表还原为内部IP和端口
基本NAT转换流程示例:
- 内部主机A (192.168.1.10:1234) 发送数据到外部服务器B (8.8.8.8:53)
- NAT设备将来源改为公网IP (203.0.113.1:5678)
- 响应从B返回到203.0.113.1:5678
- NAT根据表转换为192.168.1.10:1234并转发
公网IP与私网IP
公网IP(Public IP):
- 由IANA分配的全球唯一IP地址
- 可在互联网上直接路由
- 示例:203.0.113.1(IPv4),2001:db8::1(IPv6)
- 特点:唯一性、全球可达性、需注册分配
私网IP(Private IP): RFC 1918定义的保留地址段,不在互联网上路由
- 常见范围:
- 10.0.0.0/8 (10.0.0.0 - 10.255.255.255)
- 172.16.0.0/12 (172.16.0.0 - 172.31.255.255)
- 192.168.0.0/16 (192.168.0.0 - 192.168.255.255)
- 特点:可重复使用、仅限内部网络、需NAT转换才能访问互联网
公网IP资源有限,导致NAT的广泛应用,而私网IP允许多个网络独立使用相同地址空间。
3.2 NAT类型分类
3.3 NAT类型检测原理
四、不同网络环境下的P2P连接原理
4.1 公网 - 公网
在当前网络模型中,客户端 A 和客户端 B 都位于公网。客户端 A 和 客户端 B 通过以下步骤即可建立 P2P 连接:
- 客户端 A 向中心服务器上报自身信息,并获取客户端 B 信息;客户端 B 向中心服务器上报自身信息,并获取客户端 A 信息;(PS: 忽略两端获取对端信息时的时间差)
- 客户端 A 根据从中心服务器获取的信息发现 B 具有公网地址,于是 A 直接向 B 发起连接;(PS: 可以互换连接顺序)
- 客户端 B 根据从中心服务器获取的信息发现 A 具有公网地址,因此 B 等待 A 进行连接;
4.2 NAT - 公网
在当前网络模型中,客户端 B 位于公网,有公网 IP,客户端 A 位于任意 NAT 后。客户端 A 和 客户端 B 通过以下步骤即可建立 P2P 连接:
- 客户端 A 向中心服务器上报自身信息,并获取客户端 B 信息;客户端 B 向中心服务器上报自身信息,并获取客户端 A 信息;(PS: 忽略两端获取对端信息时的时间差)
- 客户端 B 根据从中心服务器获取的信息发现 A 位于 NAT 后,因此 B 等待 A 进行连接;
- 客户端 A 根据从中心服务器获取的信息发现 B 位于公网,于是 A 直接向 B 发起连接;
4.3 客户端位于同一NAT后
在当前网络模型中,客户端 A 和客户端 B 位于同一任意 NAT 后。客户端 A 和 客户端 B 通过以下步骤即可建立 P2P 连接:
- 客户端 A 向中心服务器上报自身信息,并获取客户端 B 信息;客户端 B 向中心服务器上报自身信息,并获取客户端 A 信息;(PS: 忽略两端获取对端信息时的时间差)
- 客户端 A 根据从中心服务器获取的信息发现 B 的公网地址和自身相同,猜测 B 可能与自己位于同一内网中,于是 A 尝试直接向 B 发起连接;(PS: 可以互换连接顺序)
- 客户端 B 根据从中心服务器获取的信息发现 A 的公网地址和自身相同,猜测 A 可能与自己位于同一内网中,因此 B 等待 A 进行连接;
4.4 客户端分属与不同NAT下
在当前网络模型中,客户端 A 和客户端 B 都位于 NAT 后。客户端 A 和 客户端 B 能否建立 P2P 连接和各自所属 NAT 类型有关。
4.4.1 任意 NAT - (Full Cone NAT或Restricted Cone NAT)
Full Cone NAT、Restricted Cone NAT和Port Restricted Cone NAT都有同样的映射规则:本地地址和端口不变时,映射到 NAT 上的端口不变。
当一端位于 Full Cone NAT或Restricted Cone NAT 下,另一端为任意 NAT 时,通过以下方式可以建立 P2P 连接:
- 客户端 A 向中心服务器上报自身信息,并获取客户端 B 信息;客户端 B 向中心服务器上报自身信息,并获取客户端 A 信息;(PS: 忽略两端获取对端信息时的时间差)
- 客户端 A 持续向客户端 B 在 NAT 网关2 上映射的公网地址和端口发送数据。对于 Restricted Cone NAT,由于NAT 网关2上还没有放开相应的过滤规则,因此前面客户端A发向客户端B的部分数据包会被丢失;
- 客户端 B 向客户端 A 的公网地址和端口发送数据,用以更新 NAT 网关2的过滤规则;
- 当NAT 网关2的过滤规则被刷新后,客户端 A 发向客户端B的数据便会被 NAT 网关2 接收,并转发给客户端 B;
- 接收数据时,客户端 B 就会知道客户端在 NAT 网关1上映射的端口和地址,此时客户端 B向NAT 网关1上映射的端口和地址发包,客户端A即可收到。此时客户端 A 和客户端 B 成功建立 P2P连接。
4.4.2 Easy NAT - Easy NAT
Easy NAT 代指 RFC3489 所定义的 Full Cone NAT、Restricted Cone NAT、Port Restricted Cone NAT。
当两端都位于 Easy NAT 下时,通过以下方式可以建立 P2P 连接(任意 NAT - (Full Cone NAT或Restricted Cone NAT) 流程类似):
- 客户端 A 向中心服务器上报自身信息,并获取客户端 B 信息;客户端 B 向中心服务器上报自身信息,并获取客户端 A 信息;(PS: 忽略两端获取对端信息时的时间差)
- 客户端 A 持续向客户端 B 在 NAT 网关2 上映射的公网地址和端口发送数据,由于NAT 网关2上还没有放开相应的过滤规则,因此前面客户端A发向客户端B的部分数据包会被丢失;
- 客户端 B 向客户端 A 的公网地址和端口发送数据,用以更新 NAT 网关2的过滤规则;
- 当NAT 网关2的过滤规则被刷新后,客户端 A 发向客户端B的数据便会被 NAT 网关2 接收,并转发给客户端 B;
- 接收数据时,客户端 B 就会知道客户端在 NAT 网关1上映射的端口和地址,此时客户端 B向NAT 网关1上映射的端口和地址发包,客户端A即可收到。此时客户端 A 和客户端 B 成功建立 P2P连接。
4.4.3 Symmetric NAT - Port Restricted Cone NAT
当客户端 A 位于 Symmetric NAT 下,客户端 B 位于 Port Restricted Cone NAT 时,通过以下方式可以建立 P2P 连接:
- 客户端 A 向中心服务器上报自身信息,并获取客户端 B 信息;客户端 B 向中心服务器上报自身信息,并获取客户端 A 信息;(PS: 忽略两端获取对端信息时的时间差)
- 客户端 A 持续向客户端 B 在 NAT 网关2 上映射的公网地址和端口发送数据。由于 NAT 网关2 没有放开相应的过滤规则,因此客户端 A 发往客户端 B 的数据包会被 NAT 网关2拦截,无法到达客户端 B;;
- 由于客户端 A 在NAT 网关1上映射的端点(IP:Port 中 Port 未知),因此客户端 B 无法向一个明确的端点发送数据包来更新 NAT 网关2 过滤规则。
此时就需要通过以下几种方案来让碰撞,让客户端 A 发向客户端 B 的包顺利通过 NAT 网关2。
全端口开放
虽然我们不知道客户端 A 在映射NAT 网关1上映射的端口是多少,但是我们知道,他映射的端口一定是 1024 - 65535 内其中一个,并且一定不是 A 连接中心服务器时使用的端口。
我们可以顺序构造目的端口为 1024 - 65535 的短 TTL 包(短 TTL 包可以让包不走到公网,仅仅用于打开防火墙规则,以免被识别为 Dos 攻击),让 NAT 网关2 开放所有可能端口(相当于将 Port Restricted Cone NAT 变为 Restricted Cone NAT)。
但是经过实际测试发现该方法效果不佳,主要有以下原因:
- 需要构造大量数据包:平均需要发包 32256 个包 才能碰撞到 客户端 A 在 NAT 网关1上映射的端口。假设客户端 B 的发包速率为 100 p/s,那么就需要五分半才能碰撞到端口;
- 容易触发运营商 QoS 限制:经过实际测试,发现一定时间内无效数据包过多时,运营商会对客户端 B 的包进行大量丢弃,导致丢包率上升。严重情况下运营商会直接全部丢弃客户端 B 的数据包。
端口预测
在部分 NAT 上,端口映射具有一定规律 。
比如发往目的 IP 1 时,映射的端口为 22001;发往目的 IP 2 时,映射的端口为 22002;那么我们可以猜测,发往目的 IP 3时使用的端口可能为 22003。
使用此种方案,需要客户端 A 向多个服务器请求来确认自身映射端口,从这些映射的端口中找到可能存在的端口变化规律。
此种方法具有一定可行性,但和 NAT 行为有关,不是一个通用解决方案。
生日攻击
在前面的“全端口开放”方法中,客户端 A 只在 NAT 网关1 上映射了一个端口。但是实际情况下,客户端 A 可以在 NAT 网关1 上映射多个端口。
根据概率论的 生日悖论,可以写发现客户端 A 映射的端口、客户端 B 发包数量与成功概率之间的关系,公式如下所示:
$$ \begin{array}{c} P_{success} = 1 - \frac{C_{64,512}^{ports} \times C_{64,512-ports}^{packets }}{C_{64,512}^{ports} \times C_{64,512}^{packets}} \end{array} $$根据上面的公式绘制出三维图如图所示:
根据函数图我们可以发现,当客户端 A 映射在公网上的端口越多时,建立连接所需的发包数越少,下表中列举了部分数据:
使用端口数\发包数 | 50% | 80% | 90% | 99% |
---|---|---|---|---|
10 | 4320 | 9590 | 13268 | 23807 |
50 | 888 | 2043 | 2903 | 5675 |
100 | 446 | 1030 | 1468 | 2902 |
200 | 223 | 517 | 738 | 1467 |
300 | 149 | 345 | 493 | 981 |
根据表中数据可以发现:假设客户端 A 占用100个端口,客户端 B 以 100 p/s 的速度进行探测,那么要达到50%的概率仅需 6s,达到 99% 也只需 29s!
根据上面的数据分析可以发现生日攻击是在 Symmetric NAT - Port Restricted Cone NAT 进行 P2P 打洞时一个较为优秀的方案。
4.4.4 Symmetric NAT - Symmetric NAT
当两端都是 Symmetric NAT 时,复杂度比 Symmetric NAT - Port Restricted Cone NAT 有了成倍的增长。
假设使用上面的“全端口开发”方案,那么就需要发包 4,161,798,144 次,耗费时间需要一年以上;
即使是使用“生日攻击”方案,一端打开 256 个端口,要达到 50% 的概率需要 54,000 次发包,按照 100 p/s 的发包速率需要9分钟;达到 99% 的成功率需要 170,000 次发包,时间上需要30分钟左右。
即使时间上可以忍受,但 NAT 网关却无法忍受这么多次的发包行为。因为每发一次包,就需要在 NAT 的 session 表上记录一条,想创建一条成功的连接,大部分情况下都会打爆 NAT 的session 表。
因此对于 Symmetric NAT - Symmetric NAT 网络类型,连接建立方案只能选择中继服务器模式。
4.5 客户端位于同一大 NAT 下,但不属于同一内网
在当前网络模型中,客户端 A 和客户端 B 位于同一任意大 NAT 后,但是分属于大 NAT 下的两个小子网中。客户端 A 和 客户端 B 需要通过以下步骤建立连接:
- 客户端 A 向中心服务器上报自身信息,并获取客户端 B 信息;客户端 B 向中心服务器上报自身信息,并获取客户端 A 信息;(PS: 忽略两端获取对端信息时的时间差)
- 客户端 A 根据从中心服务器获取的信息发现 B 的公网地址和自身相同,猜测 B 可能与自己位于同一内网中,于是 A 尝试直接向 B 发起连接。但是由于 A 和 B不在同一内网中,因此连接建立失败。客户端 A 通过中心服务器通知 B 连接建立失败,需要进行下一步尝试;
- 客户端 A 向 B 暴露的公网 IP 和端口直接发送请求。如果NAT 网关不支持 Hairpin 模式,那么这个数据包会被直接丢弃,导致数据包无法到达 NAT 网关2;如果 NAT 网关开启了 Hairpin 模式,A 发向 B 的流量会被 NAT 网关转发给 NAT 网关2,流程进入下一步;
- 当数据包到达 NAT 网关2后,下面的流程就和 “客户端分属与不同NAT下”时的打洞行为一致了。
4.6 多层 NAT
在网络中,存在设备在多层NAT后的情况。
对于这种多层NAT而言,真正有影响的是最靠近公网的那一个 NAT 网关和最靠近设备的那一个 NAT 网关,其余的 NAT 对于客户端和服务端来说都是不可见的,连接不会关心到底经过了多少层NAT。
但是多层 NAT 并非完全没有影响,准确来说,多层NAT 影响的是客户端的端口映射行为。客户端发出的端口映射请求,只有最靠近客户端的那层 NAT 设备会做出响应。其他的NAT设备不会收到客户端的端口映射请求。但是端口映射要产生作用的话,需要的是最靠近公网的 NAT 网关执行端口映射才行。
五、TCP P2P实现原理
5.1 TCP P2P 的优势
市面上实现 P2P 的产品主要都是以 UDP 协议为主。因为 UDP 是无连接的,能够往任意地址发包,便于实现 P2P 的能力。
但是 UDP 同时也是不可靠的,如果想要实现可靠传输得自己基于 UDP 去实现可靠传输协议,例如 QUIC、KCP、SCTP 等基于 UDP 实现的可靠连接。
但是基于 UDP 实现的可靠传输是位于应用层,运行在用户态的。
而 TCP 协议是操作系统网络栈原生支持的,而且经过这么多年在操作系统内核层面的优化,TCP 性能是十分能打的,如果我们能够基于 TCP 建立 P2P 连接,对于我们应用层来说就会省事很多了。
5.2 实现原理
要想实现基于 TCP 的 P2P,那么 TCP 也必须像 UDP 那样向不同地址建立连接时使用同一个 Src IP + Src Port。
要实现这个效果就需要使用 Linux 中的端口重用技术。端口重用技术运行我们使用同一个 Src IP + Src Port 向不同的目的地址发起 TCP 连接。
|
|
上面的 Demo 中,TCP dial 和 listen 在同一个 Src IP + Src Port 上,进行多次尝试之后就能达到与 UDP 一样的 P2P 打洞效果。