原做者:黄日成,手Q游戏中心后台开发,腾讯高级工程师。从事C++服务后台开发4年多,主要负责手Q游戏中心后台基础系统、复杂业务系统开发,主导过手Q游戏公会、企鹅电竞App-对战系统等项目的后台系统设计,有丰富的后台架构经验。php
接本系列的上一篇《P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解(基本原理篇)》,本篇将深刻分析各类NAT穿越(打洞)方案的技术实现原理和数据交互过程,但愿能助你透彻理解它们。html
本文做者的其它几篇文章或许你也感兴趣:git
《 鲜为人知的网络编程(一):浅析TCP协议中的疑难杂症(上篇)》
《 鲜为人知的网络编程(二):浅析TCP协议中的疑难杂症(下篇)》
《 鲜为人知的网络编程(五):UDP的链接性和负载均衡》
《 鲜为人知的网络编程(六):深刻地理解UDP协议并用好它》
* 阅读注意:本文属高阶文章,在你了解P2P基础原理或还未读过本系列前几篇以前,请慎读本篇,不然读完要砸电脑,我也拉不住 ....程序员
(本文同步发布于:http://www.52im.net/thread-2872-1-1.html)github
➊ 本文是《P2P理论详解》系列文章中的第3篇,总目录以下:面试
《 P2P技术详解(一):NAT详解——详细原理、P2P简介》
《 P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解(基本原理篇)》
《 P2P技术详解(三):P2P中的NAT穿越(打洞)方案详解(进阶分析篇)》(本文)
《 P2P技术详解(四):P2P技术之STUN、TURN、ICE详解》
➋ P2P相关的其它资源:算法
《 通俗易懂:快速理解P2P技术中的NAT穿透原理》(* 适合入门)
《 最新收集NAT穿越(p2p打洞)免费STUN服务器列表 [附件下载]》
《 一款用于P2P开发的NAT类型检测工具 [附件下载]》
另外,若是你以为本文对网络通讯的基础知识讲的不够系统话,可继续看看下面这些精华文章大餐。编程
➌ 网络编程基础知识:缓存
《 TCP/IP详解 - 第11章·UDP:用户数据报协议》
《 TCP/IP详解 - 第17章·TCP:传输控制协议》
《 TCP/IP详解 - 第18章·TCP链接的创建与终止》
《 TCP/IP详解 - 第21章·TCP的超时与重传》
《 通俗易懂-深刻理解TCP协议(上):理论基础》
《 通俗易懂-深刻理解TCP协议(下):RTT、滑动窗口、拥塞处理》
《 理论经典:TCP协议的3次握手与4次挥手过程详解》
《 理论联系实际:Wireshark抓包分析TCP 3次握手、4次挥手过程》
《 计算机网络通信协议关系图(中文珍藏版)》
《 脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手》
《 脑残式网络编程入门(二):咱们在读写Socket时,究竟在读写什么?》
《 脑残式网络编程入门(三):HTTP协议必知必会的一些知识》
《 脑残式网络编程入门(四):快速理解HTTP/2的服务器推送(Server Push)》
《 脑残式网络编程入门(五):天天都在用的Ping命令,它究竟是什么?》
《 脑残式网络编程入门(六):什么是公网IP和内网IP?NAT转换又是什么鬼?》
《 脑残式网络编程入门(七):面视必备,史上最通俗计算机网络分层详解》
➍ 若是以为上面的文章枯燥,则《网络编程懒人入门》系列多是你的菜:安全
《 网络编程懒人入门(一):快速理解网络通讯协议(上篇)》
《 网络编程懒人入门(二):快速理解网络通讯协议(下篇)》
《 网络编程懒人入门(三):快速理解TCP协议一篇就够》
《 网络编程懒人入门(四):快速理解TCP和UDP的差别》
《 网络编程懒人入门(五):快速理解为何说UDP有时比TCP更有优点》
《 网络编程懒人入门(六):史上最通俗的集线器、交换机、路由器功能原理入门》
《 网络编程懒人入门(七):深刻浅出,全面理解HTTP协议》
《 网络编程懒人入门(八):手把手教你写基于TCP的Socket长链接》
《 网络编程懒人入门(九):通俗讲解,有了IP地址,为什么还要用MAC地址?》
《 网络编程懒人入门(十):一泡尿的时间,快速读懂QUIC协议》
➎ 若是感到自已已经很牛逼了,《鲜为人知的网络编程》应该是你菜:
《 鲜为人知的网络编程(一):浅析TCP协议中的疑难杂症(上篇)》
《 鲜为人知的网络编程(二):浅析TCP协议中的疑难杂症(下篇)》
《 鲜为人知的网络编程(三):关闭TCP链接时为何会TIME_WAIT、CLOSE_WAIT》
《 鲜为人知的网络编程(四):深刻研究分析TCP的异常关闭》
《 鲜为人知的网络编程(五):UDP的链接性和负载均衡》
《 鲜为人知的网络编程(六):深刻地理解UDP协议并用好它》
《 鲜为人知的网络编程(七):如何让不可靠的UDP变的可靠?》
《 鲜为人知的网络编程(八):从数据传输层深度解密HTTP》
《 鲜为人知的网络编程(九):理论联系实际,全方位深刻理解DNS》
➏ 若是看完上面的文章仍是躁动不安,那看看《高性能网络编程系列》吧:
《 高性能网络编程(一):单台服务器并发TCP链接数到底能够有多少》
《 高性能网络编程(二):上一个10年,著名的C10K并发链接问题》
《 高性能网络编程(三):下一个10年,是时候考虑C10M并发问题了》
《 高性能网络编程(四):从C10K到C10M高性能网络应用的理论探索》
《 高性能网络编程(五):一文读懂高性能网络编程中的I/O模型》
《 高性能网络编程(六):一文读懂高性能网络编程中的线程模型》
网络地址转换(NAT,全称Network Address Translation),早期的NAT指的是Basic NAT(静态NAT),它在技术上比较简单一点,仅支持地址转换,不支持端口映射。这就须要对每个当前链接都要对应一个IP地址,所以要维护一个公网的地址池。
咱们能够看出,Basic NAT一个比较明显的缺陷就是:同一时刻只能少许位于NAT后面的机器可以和外部交互(要看NAT有几个外网IP)。
后期的NAT基本都指的是NAPT(网络地址端口转换)了,这种方式支持端口的映射并容许多台主机共享一个公用IP地址,这样就能够支持同时多个位于NAT后面的机器和外部进行交互了。
支持端口转换的NAT又能够分为两类:
1)源地址转换(SNAT);
2)目的地址转换NAT(DNAT)。
下面说的NAT都是指NAPT。
NAT在缓解IPv4地址资源的紧张的同时,也带来了很多问题:
1)NAT使IP会话的保持时效变短;
2)NAT在实现上将多个内部主机发出的链接复用到一个IP上,这就使依赖IP进行主机跟踪的机制都失效了;
3)NAT工做机制依赖于修改IP包头的信息,这会妨碍一些安全协议的工做;
4)NAT限制了使用一些高层协议(FTP、Quake、SIP)的Peer两端的P2P通讯。
_对于问题1:_其主要缘由是,NAT设备创建的内网IP、端口到外网IP、端口的映射的表项是有一个保活期的。若是在一个超时时间内,该映射上没有实际数据的传输,那么NAT会过时并回收这个映射表项给其余通讯链路用(IP和端口资源有限,通讯的链路是无限)。为了不这种通讯链路提早被NAT中断的状况,不少应用层协议在设计的时候就考虑了一个链接保活的机制,即在一段时间没有数据须要发送时,主动发送一个NAT能感知到而又没有实际数据的保活消息,这么作的主要目的就是重置NAT的会话定时器。
_对于问题2:_其主要缘由是,对于NAT后面的N多主机,在外部看来都是同一个主机(NAT设备),因而来之同一个IP的数据包必定是来之同一个主机的前提判断就会不许确了,这样一下基于这个前提的机制(例如:TCP的TIME_WAIT的回收和重用)都会有问题。
_对于问题3:_其主要缘由是,NAT篡改了IP地址、传输层端口号和校验和。
_对于问题4:_其主要缘由是,通常状况下,NAT是不容许外部的Peer节点主动链接或发送数据包给NAT后面的主机的(这里的主动指的是,在一段时间内,首先发送数据包的一方为主动方)。
NAT表现出这样的行为,主要基于下面的几点考虑:
1)出于安全考虑,避免来自网络外部的攻击,隐藏并保护网络内部的计算机;
2)位于NAT后面的不少主机,对于主动进来的数据包,NAT通常不知道该路由给内部的哪一个主机(NAT设备上没有相关转发表项)。
因为NAT这种特性,那么在NAT环境下,实现P2P通讯的完整解决方案包括几个部分呢?相关的原理、方法、技术有哪些?
对于一个完整的P2P通讯解决方案,其实现包括下面两个步骤:
1)首先在Server的协助下,通讯两端Peer尝试相互链接,若是两端Peer在尝试互联不成功后,那么就将失败结果反馈给Server转入步骤2);
2)这个步骤比较简单粗暴了,就是relay(服务器中转),简单的来说就是Peer1将要发给Peer2的数据发给Server,而后由Server帮忙转发给Peer2,一样对于Peer2来讲也同样。
对于实现P2P通讯,步骤1是你们下功夫最多的,其缘由比较简单,就是步骤2须要消耗较多的服务器资源,成本比较高。步骤1实现P2P两个节点间的直接通讯,在资源消耗和效率上都是比较好的。
目前常见的P2P通讯穿越NAT的技术、方法主要有:
1)应用层网关;
2)中间件技术;
3)打洞技术(Hole Punching);
4)Relay(服务器中转)技术。
应用层网关(ALG)是解决NAT对应用层协议无感知的一个最经常使用方法,已经被NAT设备厂商普遍采用,成为NAT设备的一个必需功能。
利用带有ALG功能的NAT对特定应用层协议的支持和理解,在一个NAT网关检测到新的链接请求时,须要判断是否为已知的应用类型,这一般是基于链接的传输层端口信息来识别的。在识别为已知应用时,再调用相应功能对报文的深层内容进行检查,当发现任何形式表达的IP地址和端口时,将会把这些信息同步转换,而且为这个新链接建立一个附加的转换表项。
这样,当报文到达公网侧的目的主机时,应用层协议中携带的信息就是NAT网关提供的地址和端口。
例如:下图,对于使用主动模式的FTP协议(PORT方式),就须要AGL的支持了。
因为FTP协议通讯须要两个TCP链接,一个是命令链路,用来在FTP客户端与服务器之间传递命令;另外一个是数据链路,用来上传或下载数据。如上图,位于NAT后面的FTP client(192.168.1.2)首先发起一个TCP链接(命令链路)连上外网FTP Server(8.8.8.1),而后发送PORT报文(192.168.1.2,1084)说本身在1084端口接收数据,而后进过ALG处理PORT报文变成(8.8.8.1,12487),同NAT创建其一条(192.168.1.2,1084 <—>8.8.8.1,12487)映射。这样FTP Server发往(8.8.8.1,12487)的数据就会被转到(192.168.1.2,1084),从而实现数据传输(若是没通过ALG处理,那么FTP Server直接链接192.168.1.2,1084是没法链接上的)。
ALG技术是利用NAT自己的支持来进行NAT的穿越,这个方案有很大限制,主要的缘由是ALG都是为特定协议的特定规范版本而开发的,然而无论是协议自己,仍是协议的数量都在变化,这就使得ALG适应性不强。
这是一种经过开发通用方法解决NAT穿越问题的努力。与前者不一样之处是,AGL技术中NAT网关是这一解决方案的惟一参与者,而中间件技术中客户端会参与网关公网映射信息的维护。UPnP就是这样一种方法,UPnP中文全称为通用即插即用,是一个通用的网络终端与网关的通讯协议,具有信息发布和管理控制的能力。
NAT只要理解客户端的请求并按照要求去分配响应的映射转换表,不须要本身去分析客户端的应用层数据。网关映射请求能够为客户动态添加映射表项。
此时,NAT再也不须要理解应用层携带的信息,只转换IP地址和端口信息。而客户端经过控制消息或信令发到公网侧的信息中,直接携带公网映射的IP地址和端口,接收端能够按照此信息创建数据链接。NAT网关在收到数据或链接请求时,按照UPnP创建的表项只转换地址和端口信息,不关心内容,再将数据转发到内网。
这种方案须要网关、内部主机和应用程序都支持UPnP技术,且组网容许内部主机和NAT网关之间能够直接交换UPnP信令才能实施。
Hole Punching技术是工做在运输层的技术,能够屏蔽上层应用层的差别,而且不须要NAT网关特定的支持,所以其通用性比较强,应用性也比较广。
打洞技术的原理比较简单,就是NAT内网的节点须要在NAT上创建本身的一条转发映射关系(这就是所谓的在NAT上打下一个洞),而后外网的节点就经过这个”洞”来进行通讯。为描述方便,咱们将一对IP地址和端口信息的组合称之为一个Endpoint。
打洞原理能够简化为下面三个过程:
1)首先位于NAT后的Peer1节点须要向外发送数据包,以便让NAT创建起内网Endpoint1(IP一、PORT1)和外网Endpoint2(IP二、PORT2)的映射关系;
2)而后经过某种方式将映射后的外网Endpoint2通知给对端节点Peer2;
3)最后Peer2往收到的外网Endpoint2发送数据包,而后该数据包就会被NAT转发给内网的Peer1。
上面三个过程比较简单,然而细心的同窗会有些疑问:
1)步骤[1]中的映射关系的创建有什么规律的么?怎样才能获取到映射关系呢?
2)通知对端节点Peer2的方式通常是怎么样的?
3)步骤[3]必定能够实现么?也就是Peer2往收到的外网Endpoint2发送数据包,就必定可以被NAT转发给内网的Peer1吗?
对于疑问(3),若是所有会被转发给内网Peer1,那会不会太不安全了,只要知道内网Peer1的映射后的外网Endpoint2,就能够给穿透NAT给内网Peer1发送数据,这样内网Peer1不就很容易遭到攻击了?若是所有都不转发给内网Peer1,这样Peer1只能向外发数据,而没法收到外面的数据,严重影响Peer1的正常通讯。
那么,这就比较明了了,咱们须要的是一部分能够转发,另一部分不转发。这就涉及到NAT对外来数据包的一个过滤规则了,而疑问(1)提到的映射关系创建的规则,这涉及到NAT的Endpoint的映射规则。
那么问题来了,有什么方法能够知道NAT的Endpoint映射规则和对外来数据包的过滤规则呢?
由上面原理的讨论咱们知道,要实现打洞穿越NAT,首先须要知道NAT的行为规则(NAT的Endpoint映射规则和对外来数据包的过滤规则),这样才能更好地实现打洞穿越。
那NAT有哪些行为类型?有什么办法来侦测NAT的行为呢?
8.2.1 NAT行为类型与侦测方法
NAT的行为类型和侦测方法是由STUN(首先在RFC3489中定义,英文全称是Simple Traversal of UDP Through NATs)协议来描述的,STUN协议包括了RFC3489、RFC5389、RFC5780、RFC5769几个系列文档。
早期的STUN协议是由RFC3489(经典的STUN)来描述,其定义的NAT行为类型以下:
1)Full Cone NAT - 彻底锥形NAT:
全部从同一个内网IP和端口号Endpoint1发送过来的请求都会被映射成同一个外网IP和端口号Endpoint2,而且任何一个外网主机均可以经过这个映射的Endpoint2向这台内网主机发送包。也就是外网全部发往Endpoint2的数据包都会被NAT转发给Endpoint1。因为对外部请求的来源无任何限制,所以这种方式虽然足够简单,但却不安全。
2)Restricted Cone NAT - 限制锥形NAT:
它是Full Cone的受限版本:全部来自同一个内网Endpoint1的请求均被NAT映射成同一个外网Endpoint2,这与Full Cone相同。但不一样的是,只有当内网Endpoint1曾经发送过报文给外部主机(假设其IP地址为IP3)后,外部主机IP3发往Endpoint2的数据包才会被NAT转发给Endpoint1。这意味着,NAT设备只向内转发那些来自于当前已知的外部主机的数据包,从而保障了外部请求来源的安全性
3)Port Restricted Cone NAT - 端口限制锥形NAT:
它是Restricted Cone NAT的进一步受限版,与限制锥形NAT很类似,只不过它包括端口号PORT。只有当内网Endpoint1曾经发送过报文给外部Endpoint3(包括IP和端口了),Endpoint3发往Endpoint2的数据包才会被NAT转发给Endpoint1。端口号PORT这一要求进一步强化了对外部报文请求来源的限制,从而较Restrictd Cone更具安全性。
4)Symmetric NAT - 对称NAT:
上面的1)2)3)全部的Cone NAT中,映射关系只和内网的源Endpoint1相关,只要源Endpoint1不变其都会被映射成同一个Endpoint2。而对称NAT的映射关系不仅与源Endpoint1相关,还与目的Endpoint3相关。也就是源Endpoint1发往目的Endpoint30的请求被映射为Endpoint20,而源Endpoint1发往目的Endpoint31的请求,则被映射为Endpoint21了。此外,只有收到过内网主机发送的数据的外网主机才能够反过来向内网主机发送数据包。
经典 STUN 定义的 NAT 行为类型是将NAT的Mapping Behavior (映射规则)和Filtering Behavior(过滤规则)统一来归类的,这样对Symmetric NAT类型的归类过于笼统,使得许多 NAT 不彻底符合由它定义的类型。
因而后来,RFC3489被废弃并由RFC5389来替代,在RFC5389中,将Mapping Behavior (映射规则)和Filtering Behavior(过滤规则)分开来,定义了3种Mapping Behavior (映射规则)和3种Filtering Behavior(过滤规则),一共有9种组合。
为何是3种呢?其实理由很简单,对于一个特定的内网源Endpoint1,影响其映射关系的因素不外乎就4种状况:
1)目的IP和目的端口PORT都无关;
2)目的IP和目的端口PORT都相关;
3)仅仅目的IP相关;
4)仅仅目的PORT相关。
对于4仅仅考虑一下PORT信息有点鸡肋,基本和1差很少,因而把4去掉了。一样,对于过滤规则也同样。
3种Mapping Behavior (映射规则)和 Filtering Behavior(过滤规则)以下。
Mapping Behavior:
1)Endpoint-Independent Mapping:
对于一个内网的EndpointP,其映射的外网EndpointG是基本固定的,不会随着通讯外部主机的不一样而变化。
2)Address and Port-Dependent Mapping:
对于一个内网的EndpointP,若是与之通讯的外部为EndpointGB1,那么EndpointP就会被NAT映射成EndpointG1;若是与之通讯的外部为EndpointGB2,那么EndpointP就会被NAT映射成EndpointG2。也就是只要之通讯的外部为EndpointGB发生变化,那么映射的外网EndpointG就会变化。
3)Address-Dependent Mapping:
对于一个内网的EndpointP,若是与之通讯的外部为EndpointGB1,那么EndpointP就会被NAT映射成EndpointG1;若是与之通讯的外部为EndpointGB2(若是EndpointGB2的IP和EndpointGB1的相同),那么EndpointP一样会被NAT映射成EndpointG1,不然就会被NAT映射成EndpointG2。也就是只要之通讯的外部为EndpointGB的IP发生变化,那么映射的外网EndpointG就会变化。
Filtering Behavior:
1)Endpoint-Independent Filtering:
对于这种过滤类型,NAT在在本身的一个外网EndpointG1收到数据包,只要找到与之对应的内网EndpointP1,NAT就会转发这个数据包给相应的内网EndpointP1,无论这个数据包的来源是那里。(通常来讲,这样过滤规则的NAT是比较少的,由于这样的安全系数比较低)
2)Address and Port-Dependent Filtering:
对于这种过滤类型,NAT在本身的一个外网EndpointG1收到来源是EndpointGA1数据包,这个时候NAT要判断本身是否曾经经过本身的EndpointG1给EndpointGA1发送过数据包,若是曾经发过,那么NAT就容许该数据包经过NAT并路由给内网与之对于的内网EndpointP1;若是没发过,那么NAT会不容许该数据包经过NAT。
3)Address-Dependent Filtering:
对于这种过滤类型,NAT在本身的一个外网EndpointG1收到来源是EndpointGA1数据包,这个时候NAT要判断本身是否曾经经过本身的EndpointG1给和EndpointGA1的IP相同的机器发送过数据包(这里会忽略端口),若是曾经发过,那么NAT就容许该数据包经过NAT并路由给内网与之对于的内网EndpointP1;若是没发过,那么NAT会不容许该数据包经过NAT。
RFC5389只是定义了协议的相关属性、机制、报文结构以及一些相关的安全注意点等等,并有没对怎么进行完整的NAT类型侦测作介绍。而对完整NAT类型侦测过程主要由RFC5780这个文档来描述。完整的NAT类型侦测的过程主要在RFC5780文档的4.3和4.4节,主要分为NAT映射规则(Determining NAT Mapping Behavior)和NAT过滤规则(Determining NAT Filtering Behavior)。
下面对具体的侦测过程作介绍:
要进行NAT类型的侦测,须要一个具备双公网IP的服务器来协助侦测,咱们称该服务器为STUN Server。假设STUN Server的双IP分别为IP_SA(125.227.152.3)和IP_SB(125.227.152.4) 监听的两个端口分别为PORT_SA(4777)和PORT_SB(4888),客户端A的内网和端口分别为IP_CA(10.70.142.12)和PORT_CA(1234)。
1)客户端A以IP_CA: PORT_CA给STUN Server的IP_SA: PORT_SA发送一个bind请求,STUN server以IP_SA: PORT_SA给客户端A的IP_CA: PORT_CA回复响应,响应内容大致为:(NAT映射后的IP地址和端口为:IP_MCA1: PORT_MCA1,STUN Server的另一个IP地址和端口为:IP_SB: PORT_SB)。这个时候客户端判断,若是IP_CA: PORT_CA == IP_MCA1: PORT_MCA1,那么该客户端是拥有公网IP的,NAT类型侦测结束。
2)客户端A以IP_CA: PORT_CA给STUN server的IP_SB: PORT_SA(相对步骤1 ip改变了)发送一个bind请求,STUN server以IP_SB: PORT_SA给客户端A的IP_CA: PORT_CA回复响应,响应内容大致为:(NAT映射后的IP地址和端口为:IP_MCA2: PORT_MCA2)。这个时候客户端判断,若是IP_MCA1: PORT_MCA1 == IP_MCA2: PORT_MCA2,那么NAT是Endpoint Independent Mapping的映射规则,也就是一样的内网地址IP_CA: PORT_CA通过这种NAT映射后的IP_M: PORT_M是固定不变的;若是IP_MCA1: PORT_MCA1 != IP_MCA2: PORT_MCA2,那么就要进行下面的第3步测试。
3)客户端A以IP_CA: PORT_CA给STUN server的IP_SB: PORT_SB(相对步骤1 ip和port改变了)发送一个bind请求,STUN server以IP_SB: PORT_SB给客户端A的IP_CA: PORT_CA回复响应,响应内容大致为:(NAT映射后的IP地址和端口为:IP_MCA3: PORT_MCA3)。这个时候客户端判断,若是IP_MCA2: PORT_MCA2== IP_MCA3: PORT_MCA3,那么NAT是Address Dependent Mapping的映射规则,也就是只要是目的IP是相同的,那么一样的内网地址IP_CA: PORT_CA通过这种NAT映射后的IP_M: PORT_M是固定不变的;若是IP_MCA2: PORT_MCA2!= IP_MCA3: PORT_MCA3,那么NAT是Address and Port Dependent Mapping,只要目的IP和PORT中有一个不同,那么一样的内网地址IP_CA: PORT_CA通过这种NAT映射后的IP_M: PORT_M是不同的。
以上三个步骤是进行Mapping Behavior的侦测,下面两个步骤是进行Filtering Behavior侦测:
4)客户端A以IP_CA: PORT_CA给STUN server的IP_SA: PORT_SA发送一个bind请求(请求中带CHANGE-REQUEST attribute来要求stun server改变IP和PORT来响应),STUN server以IP_SB: PORT_SB给客户端A的IP_CA: PORT_CA回复响应。若是客户端A能收到STUN server的响应,那么NAT是Endpoint-Independent Filtering的过滤规则,也就是只要给客户端A的IP_CA: PORT_CA映射后的IP_MCA: PORT_MCA地址发送数据都能经过NAT到达客户端A的IP_CA: PORT_CA(这种过滤规则的NAT估计不多)。若是不能收到STUN server的响应,那么须要进行下面的第五步测试。
5)客户端A以IP_CA: PORT_CA给STUN server的IP_SA: PORT_SA发送一个bind请求(请求中带CHANGE-REQUEST attribute来要求stun server改变PORT来响应),STUN server以IP_SA: PORT_SB给客户端A的IP_CA: PORT_CA回复响应。若是客户端A能收到STUN server的响应,NAT是Address-Dependent Filtering的过滤规则,也就是只要以前客户端A以IP_CA: PORT_CA给IP为IP_D的主机发送过数据,那么在NAT映射的有效期内,IP为IP_D的主机以任何端口给客户端A的IP_CA: PORT_CA映射后的IP_MCA: PORT_MCA地址发送数据都能经过NAT到达客户端A的IP_CA: PORT_CA;若是不能收到响应,NAT是Address and Port-Dependent Filtering的过滤规则,也便是只有以前客户端A以IP_CA: PORT_CA给目的主机的IP_D: PORT_D发送过数据,那么在NAT映射的有效期内,只有以IP_D: PORT_D给客户端A的IP_CA: PORT_CA映射后的IP_MCA: PORT_MCA地址发送数据才能经过NAT到达客户端A的IP_CA: PORT_CA。
经过以上5个步骤就能完成完整的NAT类型侦测。
将NAT映射规则和过滤规则组合起来就造成9中不一样的NAT行为类型:
1)Endpoint Independent Mapping和Endpoint-Independent Filtering组合对应于RFC3489中的Full Cone NAT;
2)Endpoint Independent Mapping和Address-Dependent Filtering组合对应于RFC3489中的Restricted Cone NAT;
3)Endpoint Independent Mapping和Address and Port-Dependent Filtering组合对应于RFC3489中的Port Restricted Cone NAT;
4)Address and Port-Dependent Mapping和Address and Port-Dependent Filtering组合是RFC3489中所说的Symmetric NAT。
可见RFC3489只描述了9种NAT组合行为类型中的4种。最后一个文档rfc5769,定义了一些STUN协议的测试数据用于测试STUN server的正确性。
8.2.2 NAT打洞过程
“打洞”方式穿越NAT有两种形式:TCP”打洞”和UDP”打洞”。原理上,TCP”打洞”与UDP”打洞”是没有本质的区别的。
然而在实现上,TCP”打洞”的成功率远没UDP”打洞”的成功率高,其主要缘由有三:
1)有些NAT防火墙策略对TCP协议不是很友好:
有些NAT的防火墙策略不容许来路不明的外部向内网机器发起TCP链接。因为TCP是有链接的,NAT比较容易分清哪些是NAT 内网机器主动进行通讯的外部节点,这样防火墙策略比较明确。而UDP是无链接的,没有链接来标明一个数据流,协议比较简单,这样NAT支持的比较多。
2)TCP协议自己:
因为TCP的TIME_WAIT状态引发,同一个NAT后面的其余主机发起的链接被误判。具体能够看下面的文章:km.oa.com/group/25569/articles/show/246068 。
3)TCP协议的实现API:
由于标准的Berkeley sockets API是围绕C/S编程而设计的。这个API经过connect()容许一个TCP流套接字初始化一个向外的链接,经过listen()和 accept()监听一个外入的链接,一个套接字不能既用来监听又用来初始化向外的链接。更进一步讲, TCP套接字一般与本地主机上的TCP端口一一对应:一个套接字绑定到本地主机机上的某个端口后,另外一个套接字就不能再绑定到该端口。然而TCP打洞要成功,须要一个本地的TCP端口既能够监听外入的链接,同时又能够发起多个向外的链接。幸运的是,全部主流的操做系统都支持一个特殊的socket选项SO_REUSEADDR,它运行应用程序绑定多个设置了该选项的套接字到同一端口。BSD系统引入了SO_REUSEPORT选项来控制端口重用,从而把端口重用和地址重用相分离。在这样的系统中,两个选项都须要被设置。尽管如此,要进行TCP打洞须要进行TCP三次握手的同时打开,可是有些TCP/IP的实现,可能不支持这种同时打开的状况,这样也就没法创建TCP链接了。
下面就几种网络拓扑状况下,NAT打洞步骤进行逐一介绍。为了方便描述,假设通讯的两个节点分别为Client A和Client B,而辅助NAT穿越的STUN Server为Server S。下面的全部方法都要求Client A、Client B都与Server S保持一条长链接,或者周期性连上Server S,以便可以接收Server S的相关指令,咱们称这两个链接分别为ConnectA1,ConnectB1.
8.2.2.1)网络拓扑类型一:
以下图所示,Client A 位于NAT内网,而Client B是具备公网IP的机器。若是是Client A须要链接Client B那么Client A直接连Client B就能够了。若是Client B须要链接Client A,那么Client B直接Connect Client A通常是链接不上的。可是咱们能够反过来让Client A主动去连Client B不就能够了。下面所说的Client A或Client B的NAT类型指的是对于Server S能看到的Client的最外层的NAT的类型。
反过来让Client A主动去连Client B的技术就是所谓的:反向链接技术。
具体的穿越过程以下:
1)Client B经过ConnectB1向Server S发送请求,请求链接Client A;
2)Server S按需回复看是否须要启动Client B的NAT类型侦测。(这要看Server S是否已经缓存了Client B的相关NAT信息);
3)Server S经过[2]能够知道Client B具备公网IP,因而,Server S经过ConnectA1发送指令给Client A让Client A主动连Client B并告诉Client A目标Client B的IPB和监听端口PortB;
4)Client A收到Client B的IPB和监听端口PortB,而后发送链接请求连上Cient A并附带一下身份信息,因而二者就能够进行通讯。
下面为了描述简便,具体的NAT侦测步骤就省略了。
8.2.2.2)网络拓扑类型二:
以下图,Client A和Client B位于同一个NAT后面,这个时候Client A和 Client B位于同一个局域网。
具体的穿越过程以下:
1)Client A经过ConnectA1向Server S发送请求,请求链接Client B;
2)Server S发现Client A、B位于同一个NAT后面,因而返回Client A、Client B的公网EndpointGA、EndpointGB和内网EndpointPA、EndpointPB给Client A;
3)Client A收到后,知道Client B和本身位于同一个NAT里面,因而直接连上Client B的内网EndpointPB进行通讯。
8.2.2.3)网络拓扑类型三:
以下图,Client A和Client B分别位于不一样的NAT后面,这个时候Client A和 Client B位于独立的局域网。
具体的穿越过程以下:
1)Client A经过ConnectA1向Server S发送请求,请求链接Client B;
2)Server S发现Client A、B位于独立的NAT后面,也是经过ConnectA1返回给Client A、Client B的公网EndpointGA、EndpointGB和内网EndpointPA、EndpointPA给Client A。而且经过ConnectB1返回给Client A、Client B的公网EndpointGA、EndpointGB和内网EndpointPA、EndpointPB给Client B。
接下来的步骤和Client A、Client B的NAT类型密切相关,下面会分别就相应的组合进行介绍具体的过程步骤。
(1)Client A是任意类型NAT,Client B 是Full Cone NAT(Endpoint Independent Mapping和Endpoint-Independent Filtering)
Full Cone NAT通常是比较少的,由于这样的NAT安全性不好。
[3] Server S经过ConnectA1发送指令让Client A直接Connect Client B的外网EndpointGB,因为Client B的NAT是Full Cone,因而NAT无论三七二十一就把收到的包转发给Client B,因而它们就能够顺利通讯了。
(2)Client A是任意类型NAT,Client B 是Restricted Cone NAT(Endpoint Independent Mapping和Address-Dependent Filtering)
[3] Server S经过ConnectB1发送指令让Client B 先bind内网EndpointPB而后往Client A的外网EndpointGA发送Connect请求(因为Client B是Endpoint Independent Mapping,那么EndpointPB依旧是映射为EndpointGB),若是链接创建成功,那么它们就能够进行通讯了,反之失败的话,Client B将失败结果反馈给Server S,而后转入[4];
[4] Server S收到失败反馈,经过ConnectA1发送指令让Client A往Client B的外网EndpointGB发送Connect请求,因为在步骤[3],Client B已经往Client A发送过数据包,根据过滤规则(Address-Dependent Filtering),Client B的NAT会容许Client A的数据包经过NAT并转发给Client B。因而,它们就创建其链接进行通讯。
(3)Client A的NAT类型:映射规则是(Endpoint Independent Mapping)的,过滤规则任意;Client B 是Port Restricted Cone NAT(Endpoint Independent Mapping和Address and Port-Dependent Filtering)
[3] 该步骤和状况(2)中的步骤[3]彻底同样。
[4] Server S收到失败反馈,经过ConnectA1发送指令让Client A 先bind内网EndpointPA而后往Client B的外网EndpointGB发送Connect请求(因为Client A是Endpoint Independent Mapping,那么EndpointPA依旧是映射为EndpointGA),因为在步骤[3],Client B已经往Client A的EndpointGA发送过数据包,根据过滤规则(Address and Port-Dependent Filtering),Client B的NAT会容许Client A的EndpointGA的数据包经过NAT并转发给Client B。因而,它们就创建其链接进行通讯。
(4)Client A的NAT类型:映射规则是(非Endpoint Independent Mapping)的,过滤规则任意;Client B 是Port Restricted Cone NAT(Endpoint Independent Mapping和Address and Port-Dependent Filtering)
在这种状况下,在上面的步骤[4]的时候,因为Client A是非Endpoint Independent Mapping,那么EndpointPA就会映射为是EndpointGA_B而不是EndpointGA了。这样根据过滤 规则(Address and Port-Dependent Filtering),Client B的NAT将不会容许Client A的EndpointGA_B的数据包经过NAT。要想数据包能经过Client B的NAT,须要Client B曾经给EndpointGA_B发送过数据。可是,咱们没法经过直接的方法让Client B提早知道Client A的外网EndpointGA_B,难道就无能为力了吗?不,仍是有些方法的,虽然没法直接知道Client A的外网EndpointGA_B,可是咱们能够进行预测。
具体过程以下:
[3] 该步骤和状况(2)中的步骤[3]彻底同样。
[4] Server S收到失败反馈,经过ConnectA1发送指令让Client A 启动端口映射预测过程。端口映射预测能够简单、能够复杂,大致就是让Client A往Server的不一样端口、不一样ip发送数据包,以便Server收集到Client A的端口映射样本,以便可以根据样本的端口映射变化规律预测Client A的NAT的Mapping规则。
[5] Server S根据[4]的预测状况,经过ConnectB1发送给Client B接下来Client A可能的映射端口列表也就是可能的外网EndpointGA一、EndpointGA2 ... EndpointGAn,而后让Client B都往这些外网EndpointGA一、EndpointGA2 ... EndpointGAn发送数据包。
[6] 而后Server S经过ConnectA1发送指令让Client A 先bind内网EndpointPA而后往Client B的外网EndpointGB发送Connect请求(这个时候,假设预测算法有效的话,那么Client A的内网EndpointPA将会映射为EndpointGAi),因为在步骤[5],Client B已经往Client A的EndpointGAi发送过数据包,根据过滤规则(Address and Port-Dependent Filtering),Client B的NAT会容许Client A的EndpointGAi的数据包经过NAT并转发给Client B。因而,它们就创建其链接进行通讯。
[7] 若是在步骤[4]的预测失败,那么在步骤[6]将创建链接失败,而后Client B将失败结果反馈给Server S。这个时候Server S能够启动重试步骤[4][5][6]或直接判断Client A和Client B没法创建直接的P2P通讯了,因而进入Relay(服务器中转)环节。Realy部分在后面会单独介绍。
8.2.2.4)网络拓扑类型四:
以下图,Client A和 Client B位于多层NAT后面。
具体过程以下:
1)Client A经过ConnectA1向Server S发送请求,请求链接Client B;
2)Server S发现Client A、B位于同一个NAT后面,因而返回Client A、Client B的公网EndpointGA、EndpointGB和内网EndpointPA、EndpointPB给Client A;
3)Client A收到后,认为Client B和本身位于同一个NAT里面,因而往Client B的内网EndpointPB发送链接请求,固然是链接不上的;
4)在链接失败后,接着Client A尝试向Client B的外网EndpointGB发送链接请求,这个时候NAT C收到数据包后是否转发该数据包要看NAT C是否支持回环转换(hairpin translation),若是不支持那么就没法进行直连P2P通讯,须要就须要反馈给Server S开启Relay。
5)在步骤4)失败了,Client A是没法知道是由于NAT C不支持回环转换形成的失败,仍是内层NAT的行为形成的失败。因而Client A就假设NAT C是支持回环转换的,这个时候网络拓扑状况就变成网络拓扑类型三了,那么接下来的穿越步骤就和网络拓扑类型三的多种状况同样的了,这里就不重复了。
上图,只是给出了Client A、Client B位于两层NAT后面的一种状况,对于多层NAT的各类组合本文就不介绍了。对于多层NAT的组合,在穿透失败的时候,是比较难判断出究竟是哪层NAT的行为形成的。咱们只能用上面说过的全部方法进行逐一重试,若是仍是失败,那只能启动Relay进行服务器中转了。
因为进行P2P穿透是否成功与NAT的行为和防火墙策略有很大的关系,所以就算是一个P2P友好NAT也很难保证100%穿透成功。举个例子:8.2.2.4 网络拓扑类型四,假设NAT A、NAT B 、NAT C都是Full Cone NAT(彻底锥型),可是若是NAT C不支持回环转换(hairpin translation)那么也是没法穿透成功的。那么一个完整的P2P穿透的解决方案必不可少的一个部分就是relay了,relay部分主要TURN协议描述。做为STUN协议的一个补充,TURN协议主要由RFC5766、RFC6062、RFC6156来描述,其中RFC5766主要描述的是UDP协议的relay,RFC6062描述的是TCP协议(IPV4)的relay,而RFC6156描述的是IPV6的relay。下面主要介绍一下RFC5766和RFC6062两个文档中描述的较为重要的交互过程,具体的协议相关属性、报文结构等等,有兴趣的能够细读一下协议文档。
TURN协议简单的来说,以下图所示:client向turn server发送一个Allocation request请求一个分配(allocation),若是turn server接收请求就会给client分配一个relay地址(IP_RELAYA: PORT_RELAYA),每一个allocation都有一个有效期,过了有效期就不能使用了。在有效期内client能够发送refresh request来刷新延长有效期。Client A想给peer A发送数据须要建立权限,这个经过createPermission request请求来建立权限,权限建立成功后,client A就能够发送数据给turn server由turn server中转给peer A,同时peer A发送给turn server数据也会被turn server中转给client A。如图中所示,因为client 没有注册peer B的权限,那么client 发给peer B的数据会被turn server丢弃,同时peer B发给client 的数据也会被turn server丢弃。
首先介绍RFC5766,UDP协议的relay,主要有两种方式:第一种是Send and Data methods,第二种是channels。下面分别介绍这两个方式。
9.1.1 方式1、Send and Data methods,具体交互过程以下:
(1)首先client发送Allocate request 给TURN server 请求一个分配。其中携带的主要属性:
Transaction-Id=0xA56250D3F17ABE679422DE85 :事务ID用于标识一个交互过程
SOFTWARE="Example client, version 1.03" :无关紧要的属性
LIFETIME=3600 (1 hour) :请求分配的有效期,指望有效期
REQUESTED-TRANSPORT=17 (UDP) :将来数据传输采用的协议
DONT-FRAGMENT :请求不要将数据进行分割分包转发给PEER。
(2)TURN server回复一个Allocate error response响应,表示请求未经过受权,须要进行用户验证:
Transaction-Id=0xA56250D3F17ABE679422DE85 :事务ID要和(1)的同样
SOFTWARE="Example server, version 1.17" :无关紧要
ERROR-CODE=401 (Unauthorized) :错误码
REALM="example.com" :为了让客户端下次请求的时候要带上这个属性
NONCE="adl7W7PeDU4hKE72jdaQvbAMcr6h39sm" :为了让客户端下次请求的时候要带上这个属性
(3)Client收到响应后,发现是401错误响应,那么须要给TURN server提供用户名和密码进行验证。因而client从新发送Allocate request请求:
Transaction-Id=0xC271E932AD7446A32C234492 :另起一个事务,标识另一个请求过程
SOFTWARE="Example client 1.03" :同(1)
LIFETIME=3600 (1 hour) :同(1)
REQUESTED-TRANSPORT=17 (UDP) :同(1)
DONT-FRAGMENT :同(1)
USERNAME="George" :client的用户名
REALM="example.com" :(2)中TURN server响应给client的
NONCE="adl7W7PeDU4hKE72jdaQvbAMcr6h39sm" :(2)中TURN server响应给client的
MESSAGE-INTEGRITY=... :一些加密信息,用于验证client的
(4)TURN server 验证client经过后给client响应Allocate success response:
Transaction-Id=0xC271E932AD7446A32C234492 :事务ID要和(3)相同
SOFTWARE="Example server, version 1.17" :同(3)
LIFETIME=1200 (20 minutes) :该分配的有效期,实际有效期
XOR-RELAYED-ADDRESS=192.0.2.15:50000 :给client分配的relay地址
XOR-MAPPED-ADDRESS=192.0.2.1:7000 :client的经NAT后的映射地址
MESSAGE-INTEGRITY=... :一些加密信息
(5)收到TURN server的success响应后,client发送CreatePermission request来建立peer的权限:
Transaction-Id=0xE5913A8F460956CA277D3319 :另起一个事务,标识另一个请求过程
XOR-PEER-ADDRESS=192.0.2.150:0 :须要建立权限的peer的IP地址,权限只与IP地址相关,与端口无关
USERNAME="George"
REALM="example.com" :(2)中TURN server响应给client的
NONCE="adl7W7PeDU4hKE72jdaQvbAMcr6h39sm" :(2)中TURN server响应给client的
MESSAGE-INTEGRITY=... :一些加密的信息
(6)TURN server接受建立权限请求,发送CreatePermission success resp 响应给client:
Transaction-Id=0xE5913A8F460956CA277D3319 :事务ID要和(5)相同
MESSAGE-INTEGRITY=... :一些加密信息
(7)建立权限成功后,client就能够用Send indication来发送数据给TURN server而后由TURN server将数据relay给peer:
Transaction-Id=0x1278E9ACA2711637EF7D3328 :另起一个事务,标识另一个请求过程
XOR-PEER-ADDRESS=192.0.2.150:32102 :须要发送数据的peer监听的IP: PORT(注意IP必定要和注册权限的时候的IP同样,不然会被拒绝relay并响应错误)
DONT-FRAGMENT :请求TURN server不要将data数据分片发送
DATA=... :client须要发给peer的数据内容
(8)TURN server收到Send indication请求后,进行一些权限检查后,提取出协议包中的data属性中的数据内容,而后将数据内容用UDP协议从client的relay地址(源:192.0.2.15:50000)发送给peer(目的:192.0.2.150:32102):
-- UDP dgm ->
data=... : 发给peer的UDP 数据包
(9)peer收到UPD数据包后,若是有响应数据,那么就将响应数据用UDP发给TURN server的192.0.2.15:50000地址:
<- UDP dgm –
data=... :响应给TURN server的UDP数据包
(10)TURN server在client的relay地址(192.0.2.15:50000)那收到peer(192.0.2.150:32102)的UDP数据包,这时TURN server须要检测client是否注册了IP192.0.2.150的权限,若是没有就会丢弃该数据包。若是有那么就取出UDP数据包中的data部分,而后将data封装成TURN协议数据包,给client发送Data indication:
Transaction-Id=0x8231AE8F9242DA9FF287FEFF :协议并不要求这个事务ID要和(7)中的同样
XOR-PEER-ADDRESS=192.0.2.150:32102 :标识数据来自哪一个peer
DATA=... : peer 发给client的数据内容
以上是Send and Data methods方式的核心交互过程,较为完整交互过程能够查看一下协议文档。这里有个问题须要说明一下,就是每一个allocation都有一个有效期,client须要把握好有效期,及时在有效期内发送refresh request来刷新延长有效期。
9.1.2 方式2、channels,具体交互过程以下:
(1)--(6)交互过程和Send and Data methods方式是同样的,这里就不在重复了。
(7)权限建立成功后,client发送ChannelBind request给TURN server请求进行channel bind。
Transaction-Id=0x6490D3BC175AFF3D84513212 :事务ID
CHANNEL-NUMBER=0x4000 :client定义的bind channel ID
XOR-PEER-ADDRESS=192.0.2.210:49191 :peer B的IP和PORT
USERNAME="George" :同方式一
REALM="example.com" :同方式一
NONCE="adl7W7PeDU4hKE72jdaQvbAMcr6h39sm" :同方式一
MESSAGE-INTEGRITY=... :同方式一
(8)TURN server接受channelBind请求后,给client发送ChannelBind success response响应
Transaction-Id=0x6490D3BC175AFF3D84513212 :事务ID,和(7)相同
MESSAGE-INTEGRITY=... |
(9)client收到ChannelBind success response后就能够经过ChannelData来发送数据了。
Channel-number=0x4000 :(7)中定义bind channel ID
Data=... :client须要发给peer B的数据内容
(10)TURN server收到ChannelData后首先从TURN协议数据包中提取出Channel-number,接着查找Channel-number是否已经绑定peer,若是没有就返回错误并丢弃数据包;若是查找到有绑定peer,那么就提早出Data属性中的数据内容用UDP协议经过client的relay地址(源:192.0.2.15:50000)发送给peer B(目的:192.0.2.210:49191)。
--- UDP datagram --------->
Data=... 发给peer B的UDP 数据包
(11)peer收到UPD数据包后,若是有响应数据,那么就将响应数据用UDP发给TURN server的192.0.2.15:50000地址
<-- UDP datagram ----------
Data=... :peer 发给client的数据内容
(12)TURN server在client的relay地址(192.0.2.15:50000)那收到peer(192.0.2.210:49191)的UDP数据包,这时TURN server须要检测client是否注册了IP192.0.2.150的权限,若是没有就会丢弃该数据包。若是有注册权限,那就检查client是有channel绑定该peer,若是有那么就经过Channel Data 方式relay数据给client,不然就经过方式一中的Data indication 方式relay数据给client
Channel-number=0x4000 :(7)中定义bind channel ID
Data=... :peer B发给client的数据内容
以上是channels的核心交互过程,较为详细的过程能够查看协议文档。方式二比方式一多了一个channel Bind的步骤,这个步骤是为了告诉TURN server接下来以Channel-number标识的协议数据包是要发给谁的,这样才使得ChannelData中只要携带一个Channel-number头部信息就能够,而不用携带方式一中的Transaction-Id、XOR-PEER-ADDRESS等额外的头部信息,减小数据量。
TCP协议的relay是在RFC6062中描述,其中主要有两种状况下的relay:1. Client to peer 2. Client to client。下面分别介绍两种状况下relay。
9.2.1 状况1、Client to peer,网络拓扑以下:
在上面的网络拓扑下,有两种方式的relay:1. TURN Client 主动发起的relay 2. TURN Peer主动发起的relay。下面分别介绍这两种方式的交互过程。这里Turn Client表示可以理解TURN协议的主机,而Turn Peer表示普通的通常主机。
9.2.1.1)TURN Client 主动发起的relay:
在这种方式下,TURN server要可以直接链接上TURN Peer监听的端口才行。具体交互过程以下:
(1)--(6)交互过程和RFC5766的是基本同样的,这里就不在重复了。所不一样的是RFC5766中是UDP协议,而这里是TCP协议,而且(1)--(6)是在一个链接中完成,咱们称这个链接为control connection。
(7)client建立权限成功后,经过control connection发送Connect request给TURN server请求TURN server去链接Peer A
Transaction-Id=0x6490D3BC175AFF3D84513212 :事务ID
XOR-PEER-ADDRESS=192.0.2.210:49191 :Peer A监听的IP和端口
(8)TURN server收到Connect request后, 它会经过client的relay地址(源:192.0.2.15:50000)尝试TCP链接到Peer A的192.0.2.210:49191,若是链接不成功,那么给client响应错误码为447的错误。若是链接成功那么转入(9),咱们称这个链接为Peer data connection
(9)TURN server链接Peer A成功后,给client发送Connect success response
Transaction-Id=0x6490D3BC175AFF3D84513212 :事务ID,同(7)
CONNECTION-ID=0x123456787 :TURN server给client响应的标识,用于将两条TCP链接联系起来用的。
(10)client在control connection上收到Connect success response,那么client须要创建另一条TCP链接连上TURN server,咱们称这条链接为new connection。Client经过new connection给TURN server发送ConnectionBind request,请求将new connection和Peer data connection进行绑定。
Transaction-Id=0x6490D3BC175AFF3D84513212 :事务ID
CONNECTION-ID=0x123456787 :(9)中收到的CONNECTION-ID
(11)TURN server 收到ConnectionBind request后,进行一些操做,把new connection和Peer A connection两条TCP链接联系起来。
经过上面11个步骤之后,client和peer A就能分别经过new connection和Peer data connection两条TCP链接来发送数据了。Client经过new connection发送的数据到达TURN server,TURN server就会将数据原封不动经过Peer data connection转发给Peer A,一样对于Peer A也是同样的,TURN server就像进行端口转发同样了。这里有个问题是:Peer A connection这条TCP链接要比new connection这条TCP链接早一些创建起来的,这样在new connection创建起来以前peer A就开始发送数据的话,那么TURN server这个时候是没法将数据转发给client的,因此RFC6062协议要求,只要Peer data connection链接创建好了,那么TURN server就必须作好准备接收peer A的数据,并将接收到的数据buffer住,等new connection创建好后在转发给client。可是,有些开源实现并无这样作,因此这点要注意一下。
9.2.1.2)TURN Peer主动发起的relay:
在这种方式下,TURN Peer能够位于NAT后面,具体交互以下:
(1)--(6)交互过程和方式1的是同样的,这里就不在重复了。
(7)Peer A经过192.0.2.210:49191向client的relay地址192.0.2.15:50000发起TCP链接。TURN server 立刻accept这个TCP链接并作好buffer Peer A发送的数据流的准备。而后,TURN server检查 拥有relay地址192.0.2.15:50000的client是否已经注册了Peer A(192.0.2.210)的权限,若是没有,那么TURN server会立刻close刚刚accept的链接。若是有,那么转向(8),咱们把这个链接称为peer data connection
(8)TURN server 查找到拥有relay地址192.0.2.15:50000的client的control connection,经过control connection给client发送ConnectionAttempt indication。
Transaction-Id=0x6490D3BC175AFF3D84513212 :事务ID
XOR-PEER-ADDRESS=192.0.2.210:49191 :(7)中accept那个peer A的IP和端口
CONNECTION-ID=0x789465213545 :TURN server给client响应的标识,用于将两条TCP链接联系起来用的
(9)client收到ConnectionAttempt indication,若是接收这个peer的话,那么client会新起一个链接连上TURN server,咱们称这个链接为new connection,client经过new connection给TURN server发送ConnectionBind request,请求绑定peer data connection。
Transaction-Id=0x6490D3BC175AFF3D84513212 :事务ID
CONNECTION-ID=0x789465213545 : (8)中收到的CONNECTION-ID
(10)TURN server收到ConnectionBind request后会经过new connection给client发送ConnectionBind request success response。
经过上面10个步骤之后,client和peer A就能分别经过new connection和Peer data connection两条TCP链接来发送数据了。这个方式一样存在方式1中的数据buffer住问题。在这种状况下,在Peer A看来与之通讯的是Endpoint(client的relay地址192.0.2.15:50000),Peer A不须要知道真实的Client的地址。
9.2.2 状况2、Client to client,网络拓扑结构以下:
这种状况下,RFC6062文档中并无讲到,估计是由于这种状况是状况一的一个特例而已,我这里展开来说一下是但愿能帮助你们更加深入理解协议自己。
TURN Client1和TURN Client2(1)-(6)步骤的交互状况基本和上面的同样,而且是比较独立的,因此下面直接给出了。
TURN Client1 的(1)-(6)步骤交互状况以下:
TURN Client2 的(1)-(6)步骤交互状况以下:
从上面的交互能够知道TURN Client 1 的relay地址是:192.0.2.15:50000 ,NAT映射后的地址是:192.0.2.1:7000,而TURN Client2的relay地址是:192.0.2.150:40000 ,NAT映射后的地址是:192.0.2.2:7000。下面继续给出TURN Client 1和TURN Client 2的其余交互状况,因为它们和TURN server的交互带有必定的时序性,下面会交错给出它们和TURN server的交互步骤。TURN Client1和TURN Client2是对称,这里不妨假设TURN Client1是数据交互的发起者,具体交互过程以下:
(7)TURN Client1 首先经过control connection1发送Connect request给TURN server,请求链接TURN Client 2的relay地址192.0.2.150:40000。
Transaction-Id=0x6490D3BC175AFF3D84513212 :事务ID
XOR-PEER-ADDRESS=192.0.2.150:40000 : TURN Client2的relay地址
(8)TURN server收到Connect request后, 它会经过TURN Client1的relay地址(源:192.0.2.15:50000)尝试TCP链接到192.0.2.150:40000,这个链接通常都会成功,由于这个是TURN server给的relay地址,咱们称这个链接为peer data connection1
(9)TURN server链接192.0.2.150:40000成功后,给TURN Client1发送Connect success response
Transaction-Id=0x6490D3BC175AFF3D84513212:事务ID
CONNECTION-ID=0x123456787 :TURN server给client响应的标识,用于将两条TCP链接联系起来用的
(10)这个步骤和(9)几乎同时发生的,TURN server发现TURN Client2的relay地址192.0.2.150:40000有个TCP链接上来,那么TURN server立刻accept这个链接,咱们称这个链接是peer data connection2(其实就是peer data connection1);通过权限检查后,TURN server经过TURN Client2的control connection2给TURN Client2发送ConnectionAttempt indication
Transaction-Id=0x6490D3BC175AFF3D84511111 :事务ID
XOR-PEER-ADDRESS=192.0.2.15:50000 :TURN Client1的relay地址
CONNECTION-ID=0x789465213545 :TURN server给client响应的标识,用于将两条TCP链接联系起来用的
(11)TURN Client1 收到Connect success response后,另起一个TCP connection链接上TURN server,咱们称这个链接为new connection1。TURN Client1经过new connection1给TURN server 发送ConnectionBind request
Transaction-Id=0x6490D3BC175AFF3D84513212 :事务ID
CONNECTION-ID=0x123456787 : (9)中TURN server响应的CONNECTION-ID
(12)这个步骤几乎和(11)同时发生,TURN Client2 收到ConnectionAttempt indication,表示接受,而后它另起一个TCP connection链接上TURN server咱们称这个链接为new connection2。TURN Client2经过new connection2给TURN server发送ConnectionBind request。
Transaction-Id=0x6490D3BC175AFF3D84511111 :事务ID
CONNECTION-ID=0x789465213545 :(10)中TURN server响应的CONNECTION-ID
(13)和(14)TURN server分别经过new connection1和new connection2给TURN Client 1和TURN Client 2发送ConnectionBind request success response。
经过以上14个步骤,TURN Client 1就能借助new connection1和peer data connection1与TURN Client 2进行数据交互。而TURN Client 2借助new connection2和peer data connection2与TURN Client 1进行数据交互。
到这里,P2P通讯穿越NAT的相关原理、技术、方法的进阶分析基本介绍完毕,关于STUN和TURN协议,有个开源实现,有兴趣的同窗能够阅读一下源码:https://github.com/coturn/rfc5766-turn-server
另外还有一个ICE协议,这个也有一个文档系列:
RFC 5245 - ICE
RFC 5768 – ICE–SIP
RFC 6336 – ICE–IANA Registry
RFC 6544 – ICE–TCP
RFC 5928 - TURN Resolution Mechanism
这个文档系列较为复杂一些,有兴趣的能够阅读一下。
[1]《 P2P技术详解(一):NAT详解——详细原理、P2P简介》
[2]《 P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解(基本原理篇)》
[3] 《 Peer-to-Peer Communication Across Network Address Translators》
《 TCP/IP详解 - 第11章·UDP:用户数据报协议》
《 TCP/IP详解 - 第17章·TCP:传输控制协议》
《 TCP/IP详解 - 第18章·TCP链接的创建与终止》
《 TCP/IP详解 - 第21章·TCP的超时与重传》
《 技术往事:改变世界的TCP/IP协议(珍贵多图、手机慎点)》
《 通俗易懂-深刻理解TCP协议(上):理论基础》
《 通俗易懂-深刻理解TCP协议(下):RTT、滑动窗口、拥塞处理》
《 理论经典:TCP协议的3次握手与4次挥手过程详解》
《 理论联系实际:Wireshark抓包分析TCP 3次握手、4次挥手过程》
《 计算机网络通信协议关系图(中文珍藏版)》
《 UDP中一个包的大小最大能多大?》
《 P2P技术详解(一):NAT详解——详细原理、P2P简介》
《 P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解(基本原理篇)》
《 P2P技术详解(三):P2P中的NAT穿越(打洞)方案详解(进阶分析篇)》
《 P2P技术详解(四):P2P技术之STUN、TURN、ICE详解》
《 通俗易懂:快速理解P2P技术中的NAT穿透原理》
《 Java的BIO和NIO很难懂?用代码实践给你看,再不懂我转行!》
《 技术扫盲:新一代基于UDP的低延时网络传输层协议——QUIC详解》
《 让互联网更快:新一代QUIC协议在腾讯的技术实践分享》
《 现代移动端网络短链接的优化手段总结:请求速度、弱网适应、安全保障》
《 聊聊iOS中网络编程长链接的那些事》
《 移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”》
《 移动端IM开发者必读(二):史上最全移动弱网络优化方法总结》
《 IPv6技术详解:基本概念、应用现状、技术实践(上篇)》
《 IPv6技术详解:基本概念、应用现状、技术实践(下篇)》
《 从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路》
《 以网游服务端的网络接入层设计为例,理解实时通讯的技术挑战》
《 迈向高阶:优秀Android程序员必知必会的网络基础》
《 全面了解移动端DNS域名劫持等杂症:技术原理、问题根源、解决方案等》
《 美图App的移动端DNS优化实践:HTTPS请求耗时减少近半》
《 Android程序员必知必会的网络通讯传输层协议——UDP和TCP》
《 IM开发者的零基础通讯技术入门(一):通讯交换技术的百年发展史(上)》
《 IM开发者的零基础通讯技术入门(二):通讯交换技术的百年发展史(下)》
《 IM开发者的零基础通讯技术入门(三):国人通讯方式的百年变迁》
《 IM开发者的零基础通讯技术入门(四):手机的演进,史上最全移动终端发展史》
《 IM开发者的零基础通讯技术入门(五):1G到5G,30年移动通讯技术演进史》
《 IM开发者的零基础通讯技术入门(六):移动终端的接头人——“基站”技术》
《 IM开发者的零基础通讯技术入门(七):移动终端的千里马——“电磁波”》
《 IM开发者的零基础通讯技术入门(八):零基础,史上最强“天线”原理扫盲》
《 IM开发者的零基础通讯技术入门(九):无线通讯网络的中枢——“核心网”》
《 IM开发者的零基础通讯技术入门(十):零基础,史上最强5G技术扫盲》
《 IM开发者的零基础通讯技术入门(十一):为何WiFi信号差?一文即懂!》
《 IM开发者的零基础通讯技术入门(十二):上网卡顿?网络掉线?一文即懂!》
《 IM开发者的零基础通讯技术入门(十三):为何手机信号差?一文即懂!》
《 IM开发者的零基础通讯技术入门(十四):高铁上无线上网有多难?一文即懂!》
《 IM开发者的零基础通讯技术入门(十五):理解定位技术,一篇就够》
《 百度APP移动端网络深度优化实践分享(一):DNS优化篇》
《 百度APP移动端网络深度优化实践分享(二):网络链接优化篇》
《 百度APP移动端网络深度优化实践分享(三):移动端弱网优化篇》
《 技术大牛陈硕的分享:由浅入深,网络编程学习经验干货总结》
《 可能会搞砸你的面试:你知道一个TCP链接上能发起多少个HTTP请求吗?》
《 知乎技术分享:知乎千万级并发的高性能长链接网关技术实践》
>> 更多同类文章 ……
(本文同步发布于:http://www.52im.net/thread-2872-1-1.html)