转自P2P通信原理与实现(C++)
这个博主关于网络和信息安全的文章写得很详细,推荐

1.简介

当今互联网到处存在着一些中间件(MIddleBoxes),如NAT和防火墙,导致两个(不在同一内网)中的客户端无法直接通信。这些问题即便是到了IPV6时代也会存在,因为即使不需要NAT,但还有其他中间件如防火墙阻挡了链接的建立。

当今部署的中间件大多都是在C/S架构上设计的,其中相对隐匿的客户机主动向周知的服务端(拥有静态IP地址和DNS名称)发起链接请求。大多数中间件实现了一种非对称的通讯模型,即内网中的主机可以初始化对外的链接,而外网的主机却不能初始化对内网的链接,除非经过中间件管理员特殊配置。在中间件为常见的NAPT的情况下(也是本文主要讨论的),内网中的客户端没有单独的公网IP地址,而是通过NAPT转换,和其他同一内网用户共享一个公网IP。这种内网主机隐藏在中间件后的不可访问性对于一些客户端

软件如浏览器来说并不是一个问题,因为其只需要初始化对外的链接,从某方面来看反而还对隐私保护有好处。

然而在P2P应用中,内网主机(客户端)需要对另外的终端(Peer)直接建立链接,但是发起者和响应者可能在不同的中间件后面,两者都没有公网IP地址。而外部对NAT公网IP和端口主动的链接或数据都会因内网未请求被丢弃掉。本文讨论的就是如何跨越NAT实现内网主机直接通讯的问题。

2.术语

防火墙(Firewall):
  防火墙主要限制内网和公网的通讯,通常丢弃未经许可的数据包。防火墙会检测(但是不修改)试图进入内网数据包的IP地址和TCP/UDP端口信息。

网络地址转换器(NAT):
  NAT不止检查进入数据包的头部,而且对其进行修改,从而实现同一内网中不同主机共用更少的公网IP(通常是一个)。

基本NAT(Basic NAT):
  基本NAT会将内网主机的IP地址映射为一个公网IP,不改变其TCP/UDP端口号。基本NAT通常只有在当NAT有公网IP池的时候才有用。

网络地址-端口转换器(NAPT):
  到目前为止最常见的即为NAPT,其检测并修改出入数据包的IP地址和端口号,从而允许多个内网主机同时共享一个公网IP地址。

锥形NAT(Cone NAT):
  在建立了一对(公网IP,公网端口)和(内网IP,内网端口)二元组的绑定之后,Cone NAT会重用这组绑定用于接下来该应用程序的所有会话(同一内网IP和端口),只要还有一个会话还是激活的。

例如,假设客户端A建立了两个连续的对外会话,从相同的内部端点(10.0.0.1:1234)到两个不同的外部服务端S1和S2。Cone NAT只为两个会话映射了一个公网端点(155.99.25.11:62000),确保客户端端口的“身份”在地址转换的时候保持不变。由于基本NAT和防火墙都不改变数据包的端口号,因此这些类型的中间件也可以看作是退化的Cone NAT。

对称NAT(Symmetric NAT)
  对称NAT正好相反,不在所有公网-内网对的会话中维持一个固定的端口绑定。其为每个新的会话开辟一个新的端口。如下图所示:
  
  其中Cone NAT根据NAT如何接收已经建立的(公网IP,公网端口)对的输入数据还可以细分为以下三类:

1) 全锥形NAT(Full Cone NAT)
  在一个新会话建立了公网/内网端口绑定之后,全锥形NAT接下来会接受对应公网端口的所有数据,无论是来自哪个(公网)终端。全锥NAT有时候也被称为“混杂”NAT(promiscuous NAT)。

2) 受限锥形NAT(Restricted Cone NAT)
  受限锥形NAT只会转发符合某个条件的输入数据包。条件为:外部(源)IP地址匹配内网主机之前发送一个或多个数据包的结点的IP地址。受限NAT通过限制输入数据包为一组“已知的”外部IP地址,有效地精简了防火墙的规则。

3) 端口受限锥形NAT(Port-Restricted Cone NAT)
  端口受限锥形NAT也类似,只当外部数据包的IP地址和端口号都匹配内网主机发送过的地址和端口号时才进行转发。端口受限锥形NAT为内部结点提供了和对称NAT相同等级的保护,以隔离未关联的数据。

3. P2P通信

根据客户端的不同,客户端之间进行P2P传输的方法也略有不同,这里介绍了现有的穿越中间件进行P2P通信的几种技术。

3.1 中继(Relaying)
  这是最可靠但也是最低效的一种P2P通信实现。其原理是通过一个有公网IP的服务器中间人对两个内网客户端的通信数据进行中继和转发。如下图所示:
  
  客户端A内网地址为10.0.0.1,且应用程序正在使用TCP端口1234。A和服务器S建立了一个链接,服务器的IP地址为18.181.0.31,监听1235端口。NAT A给客户端A分配了TCP端口62000,地址为NAT的公网IP地址155.99.25.11,作为客户端A对外当前会话的临时IP和端口。因此S认为客户端A就是155.99.25.11:62000。而B由于有公网地址,所以对S来说B就是138.76.29.7:1234。

当客户端B想要发起一个对客户端A的P2P链接时,要么链接A的外网地址155.99.25.11:62000,要么链接A的内网地址10.0.0.1:1234,然而两种方式链接都会失败。链接10.0.0.1:1234失败自不用说,为什么链接155.99.25.11:62000也会失败呢?来自B的TCP SYN握手请求到达NAT A的时候会被拒绝,因为对NAT A来说只有外出的链接才是允许的。

在直接链接A失败之后,B可以通过S向A中继一个链接请求,从而从A方向“逆向“地建立起A-B之间的点对点链接。

很多当前的P2P系统都实现了这种技术,但其局限性也是很明显的,只有当其中一方有公网IP时链接才能建立。越来越多的情况下,通信的双方都在NAT之后,因此就要用到我们下面介绍的第三种技术了。

3.3 UDP打洞(UDP hole punching)
  第三种P2P通信技术,被广泛采用的,名为“P2P打洞“。P2P打洞技术依赖于通常防火墙和cone NAT允许正当的P2P应用程序在中间件中打洞且与对方建立直接链接的特性。以下主要考虑两种常见的场景,以及应用程序如何设计去完美地处理这些情况。第一种场景代表了大多数情况,即两个需要直接链接的客户端处在两个不同的NAT之后;第二种场景是两个客户端在同一个NAT之后,但客户端自己并不需要知道。

3.3.1. 端点在不同的NAT之下
  假设客户端A和客户端B的地址都是内网地址,且在不同的NAT后面。A、B上运行的P2P应用程序和服务器S都使用了UDP端口1234,A和B分别初始化了与Server的UDP通信,地址映射如图所示:
  
  现在假设客户端A打算与客户端B直接建立一个UDP通信会话。如果A直接给B的公网地址138.76.29.7:31000发送UDP数据,NAT B将很可能会无视进入的数据(除非是Full Cone NAT),因为源地址和端口与S不匹配,而最初只与S建立过会话。B往A直接发信息也类似。

假设A开始给B的公网地址发送UDP数据的同时,给服务器S发送一个中继请求,要求B开始给A的公网地址发送UDP信息。A往B的输出信息会导致NAT A打开一个A的内网地址与与B的外网地址之间的新通讯会话,B往A亦然。一旦新的UDP会话在两个方向都打开之后,客户端A和客户端B就能直接通讯,而无须再通过引导服务器S了。

UDP打洞技术有许多有用的性质。一旦一个的P2P链接建立,链接的双方都能反过来作为“引导服务器”来帮助其他中间件后的客户端进行打洞,极大减少了服务器的负载。应用程序不需要知道中间件具体是什么(如果有的话),因为以上的过程在没有中间件或者有多个中间件的情况下也一样能建立通信链路。

3.3.2. 端点在相同的NAT之下
  现在考虑这样一种情景,两个客户端A和B正好在同一个NAT之后(而且可能他们自己并不知道),因此在同一个内网网段之内。客户端A和服务器S建立了一个UDP会话,NAT为此分配了公网端口62000,B同样和S建立会话,分配到了端口62001,如下图:
  
  假设A和B使用了上节介绍的UDP打洞技术来建立P2P通路,那么会发生什么呢?首先A和B会得到由S观测到的对方的公网IP和端口号,然后给对方的地址发送信息。两个客户端只有在NAT允许内网主机对内网其他主机发起UDP会话的时候才能正常通信,我们把这种情况称之为"回环传输“(lookback translation),因为从内部到达NAT的数据会被“回送”到内网中而不是转发到外网。例如,当A发送一个UDP数据包给B的公网地址时,数据包最初有源IP地址和端口地址10.0.0.1:1234和目的地址155.99.25.11:62001,NAT收到包后,将其转换为源155.99.25.11:62000(A的公网地址)和目的10.1.1.3:1234,然后再转发给B。即便NAT支持回环传输,这种转换和转发在此情况下也是没必要的,且有可能会增加A与B的对话延时和加重NAT的负担。

对于这个问题,解决方案是很直观的。当A和B最初通过S交换地址信息时,他们应该包含自身的IP地址和端口号(从自己看),同时也包含从服务器看的自己的地址和端口号。然后客户端同时开始从对方已知的两个的地址中同时开始互相发送数据,并使用第一个成功通信的地址作为对方地址。如果两个客户端在同一个NAT后,发送到对方内网地址的数据最有可能先到达,从而可以建立一条不经过NAT的通信链路;如果两个客户端在不同的NAT之后,发送给对方内网地址的数据包根本就到达不了对方,但仍然可以通过公网地址来建立通路。值得一提的是,虽然这些数据包通过某种方式验证,但是在不同NAT的情况下完全有可能会导致A往B发送的信息发送到其他A内网网段中无关的结点上去的。

3.3.3. 固定端口绑定
  UDP打洞技术有一个主要的条件:只有当两个NAT都是Cone NAT(或者非NAT的防火墙)时才能工作。因为其维持了一个给定的(内网IP,内网UDP)二元组和(公网IP, 公网UDP)二元组固定的端口绑定,只要该UDP端口还在使用中,就不会变化。如果像对称NAT一样,给每个新会话分配一个新的公网端口,就会导致UDP应用程序无法使用跟外部端点已经打通了的通信链路。由于Cone NAT是当今最广泛使用的,尽管有一小部分的对称NAT是不支持打洞的,UDP打洞技术也还是被广泛采纳应用。

TCP打洞
关于TCP打洞,有一点需要提的是,因为TCP是基于连接的,所以任何未经连接而发送的数据都会被丢弃,这导致在recv的时候是无法直接从peer端读取数据。
其实这对UDP也一样,如果对UDP的socket进行了connect,其也会忽略连接之外的数据,详见connect(2)

所以,如果我们要进行TCP打洞,通常需要重用本地的endpoint来发起新的TCP连接,这样才能将已经打开的NAT利用起来。具体来说,则是要设置socket的
SO_REUSEADDRSO_REUSEPORT属性,根据系统不同,其实现也不尽一致。
一般来说,TCP打洞的步骤如下:

  • A 发送 SYN 到 B (出口地址,下同),从而创建NAT A的一组映射
  • B 发送 SYN 到 A, 创建NAT B的一组映射
  • 根据时序不同,两个SYN中有一个会被对方的NAT丢弃,另一个成功通过NAT
  • 通过NAT的SYN报文被其中一方收到,即返回SYNACK, 完成握手
  • 至此,TCP的打洞成功,获得一个不依赖于服务器的链接

浅析P2P:两个没有公网IP的终端如何进行通信?相关推荐

  1. 域名与转发服务器ip指向不一致_域名映射到家庭网络(无公网IP)

    下面几段文字是我对内网穿透的理解和原理分析.如果只想了解如何配置,请直接跳到最下方图文配置介绍. 由于IPv4的地址已经全部分配完毕,某些运营商不给普通用户公网IP,你的路由器可能连接到运营商上一级路 ...

  2. 为什么会看到IP地址相同的两台电脑?附查询自己公网IP的方法

    为什么会看到IP地址相同的两台电脑? 我们会询问这个问题的原因是因为我们经常看到在两个不同的地方可以配置相同的类似于192.168.xxx.xxx的IP地址,或者在实验室机房里可以随意配置类似于192 ...

  3. 外网ip怎么查_无公网IP的情况下,搞定群晖并实现远程Nas访问

    前言: 我比较喜欢看电影,相比于现在的电影感觉还是老片好看,所以电脑里存了很多,硬盘容量就越来越少.刚开始不断换硬盘,从500到800再到1T,奈何现在很多重置老片动不动也有2/3G,这么换不是事,就 ...

  4. 【转载】无公网IP搞定群晖+ZEROTIER ONE实现内网穿透

    前言 最近刚开始折腾群晖,从5.2到6.0再到5.2再到6.1,期间过程曲折复杂,血泪交融,参考了无数文章,重启了无数次机器,拷贝了无数文件,以及损失了无数数据.再次提醒大家,数据一定要做好备份,一定 ...

  5. 考勤打卡定位问题解决办法------公网ip

    最近接到一个项目,有一个要求考勤打卡,就涉及到了定位的问题,作为一个没怎么接触过这方面的学生,最开始想到的方法是使用浏览器进行定位.然后根据打卡时的定位进行判断打卡成功还是失败.于是就有了第一次尝试: ...

  6. 无公网IP搞定群晖+ZEROTIER ONE实现内网穿透

    自己利用蜗牛星际DIY了一个黑群晖(安装系统参考这个链接https://zhuanlan.zhihu.com/p/60206902?edition=yidianzixun&utm_source ...

  7. linux lvs公网ip,Linux集群架构(2)LVS介绍、LVS的调度算法、NAT模式搭建、 DR模式、keepalive...

    负载均衡集群介绍 LVS介绍 1.LVS NAT模式:(,目标ip转发.适用于小型集群,机器数量不多10台左右) 2.LVS IP Tunnel模式(将目标ip进行更改) (在这个模式下的rs机器都是 ...

  8. e站host地址_Linux系统怎么使用命令行查询公网IP地址

    请关注本头条号,每天坚持更新原创干货技术文章. 如需学习视频,请在微信搜索公众号"智传网优"直接开始自助视频学习 1. 前言 本文主要讲解Linux系统怎么使用命令行查询公网IP地 ...

  9. 关于公司没有公网IP也没有动态IP,如何远程办公呢?

    2019独角兽企业重金招聘Python工程师标准>>> 迫于公司网络环境特殊,没有公网IP地址,也没有动态IP地址,其实就是园区分了一根内网固定IP的网线过来,这两天正巧有同事要外出 ...

最新文章

  1. python:未找到命令
  2. linux脚本 程序输入,[转]Linux中shell脚本如何自动输入…
  3. 罕见图像揭秘苹果Mesa数据中心
  4. 第四范式亮相世界智能大会 共探智能发展下人才培养路径
  5. highcharts 去掉Highcharts.com链接
  6. 【译】C#9的候选功能
  7. JAVA 手机号正则 工具类
  8. 程序员最常说的那些口头禅
  9. 【渝粤教育】国家开放大学2018年春季 0704-22T民法基础与实务 参考试题
  10. Node:根据开发环境配置axios默认路径
  11. mul ab 的执行结果是_实战总结:为xxljob定制化的 php 执行器
  12. Google guava之SortedMultiset简介说明
  13. Marshmallow 实现序列化和反序列化
  14. msp430发送pwm信号_MSP430F149学习之路——PWM信号
  15. 从童年回忆到“人人喊打“,好丽友做错了什么?
  16. eureka服务返回的数据是xml格式
  17. 斗图表情包爬虫(基于多线程)
  18. 一、多线程是什么?为什么要用多线程?
  19. 关于新浪微信瘦身的问题
  20. RecycleView 二级列表(多级列表)

热门文章

  1. Hive Show命令
  2. USB驱动分析(一)
  3. 项目一.认识Linux操作系统
  4. 阿里云点播 web 播放器
  5. win7系统开启snmp服务器配置,简单几招教你win7开启 snmp服务
  6. Galaxy 9300 刷机和获取root权限
  7. 微博互粉php,PHP+redis实现微博的推模型案例分析
  8. 关键词搜索商品(精准控价)
  9. 【直接下载】x86_64-8.1.0-release-win32-sjlj-rt_v6-rev0
  10. linux查看进程占用网速和流量使用情况