QQ视频与webrtc打洞对比分析



一、NAT

1. 含义

      NAT技术(Network Address Translation,网络地址转换)是一种把内部网络(简称为内网)私有IP地址转换为外部网络(简称为外网)公共IP地址的技术,它使得一定范围内的多台主机只利用一个公共IP地址连接到外网,可以在很大程度上缓解了公网IP地址紧缺的问题。

2. 实现方式

    静态转换、动态转换、端口多路复用。

    静态转换:是指将内部网络的私有IP地址转换为公有IP地址,IP地址对是一对一的,是一成不变的。通过静态配置,把一个固定的私网IP地址和端口关联到一个公网地址和端口,这种方式适用于在NAT网关上把一个知名服务(如HTTP)映射到一个内部主机上。

    动态转换:是指将内部网络的私有IP地址转换为公用IP地址时,IP地址对是不确定的,而是随机的,所有被授权访问Internet的私有IP地址可随机转换为任何指定的合法IP地址。也就是说,只要指定哪些内部地址可以进行转换,以及用哪些合法地址作为外部地址时,就可以进行动态转换。

    端口多路复用:是指改变外出数据包的源端口并进行端口转换,即端口地址转换(Port Address TranslationPAT)。采用端口多路复用方式,内部网络的所有主机均可共享一个合法外部IP地址实现对Internet的访问,从而最大限度地节约IP地址资源。同时,又可隐藏网络内部的所有主机,避免来自Internet的攻击。因此,目前网络中应用最多的就是端口多路复用方式。

3. 分类

     Full Cone NAT全锥形NAT、Restricted Cone NAT限制锥形NAT、Port Restricted Cone NAT端口限制锥形NAT、Symmetric NAT对称型NAT


      Full Cone NAT全锥形NAT:内网主机建立一个socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,任何外部主机只要知道这个(PublicIP:PublicPort)就可以发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包。

      Restricted Cone NAT限制锥形NAT:内网主机建立一个socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,如果任何外部主机想要发送数据给这个内网主机,只要知道这个(PublicIP:PublicPort)并且内网主机之前用这个socket曾向这个外部主机IP发送过数据。只要满足这两个条件,这个外部主机就可以用自己的(IP,任何端口)发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包。

      Port Restricted Cone NAT端口限制锥形NAT:内网主机建立一个socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,如果任何外部主机想要发送数据给这个内网主机,只要知道这个(PublicIP:PublicPort)并且内网主机之前用这个socket曾向这个外部主机(IP,Port)发送过数据。只要满足这两个条件,这个外部主机就可以用自己的(IP,Port)发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包。

      Symmetric NAT对称型NAT:内网主机建立一个socket(LocalIP,LocalPort),当用这个socket第一次发数据给外部主机1,NAT为其映射一个(PublicIP-1,Port-1),以后内网主机发送给外部主机1的所有数据都是用这个(PublicIP-1,Port-1),如果内网主机同时用这个socket给外部主机2发送数据,NAT会为其分配一个(PublicIP-2,Port-2), 以后内网主机发送给外部主机2的所有数据都是用这个(PublicIP-2,Port-2).如果NAT有多于一个公网IP,则PublicIP-1PublicIP-2可能不同,如果NAT只有一个公网IP,Port-1Port-2肯定不同,也就是说一定不能是PublicIP-1等于 PublicIP-2Port-1等于Port-2。此外,如果任何外部主机想要发送数据给这个内网主机,那么它首先应该收到内网主机发给他的数据,然后才能往回发送,否则即使他知道内网主机的一个(PublicIP,Port)也不能发送数据给内网主机

1. 类型检测

      前提条件:一个提供两个公网地址(通信地址分别设为:Endpoint1Endpoint2)的服务器S进行UDP端口数据监听并根据客户的要求给出响应;待检测的用户可以正常进行UDP通信。

步骤1.检测主机是否位于NAT

      为了检测IP地址是不是公网地址,主机A首先发送任意一个UDP数据包给服务器SEndpoint1),S收到包之后,用Endpoint1将接收到数据包头的IP地址和端口打成一个UDP反馈包发送给用户AA在收到反馈包之后,比较自身的Endpoint和反馈包中的Endpoint,如果一样则说明A不位于任何NAT之后,否则,就是位于NAT之后,这里并不能判断NAT的具体属于哪一种类型。

步骤2.检测NAT是否是全锥型

      为了检测所处的NAT是否是全锥型的,主机A向服务器SEndpoint1)发送UDP数据包后,服务器用Endpoint2将接收到数据包头的IP地址和端口打成一个UDP反馈包发送给用户A。另外,A在发送UDP数据包后,立即开始端口侦听,设定一个等待时间上限,防止无限堵塞(因为接收是一个While循环)。这样进行若干次,如果A每次都没有收到数据包,说明A所处的NAT类型不是全锥型的;相反,在这整个过程中只要收到一次服务的包,就说明A所处的NAT类型是全锥型的。

步骤3. 检测NAT是否是对称型

      为了检测所处的NAT是否是对称型的,主机A向服务器SEndpoint1)发送UDP数据包,服务器SEndpoint1)在收到数据包后,用Endpoint1将接收到数据包头的IP地址和端口打成一个UDP反馈包发送给用户A。另外,A在发送数据包后,开始侦听端口并接收数据,设定一个等待时间上限,防止无限堵塞(因为接收是一个While循环)。与此同时,主机A用同一个套接字向服务器Endpoint2)发送UDP数据,Endpoint2发送与上面类似的UDP回馈包。在A的整个数据接收过程中,如果收到的服务器反馈数据包中标识自身的IP地址和端口存在不相同的情况,就说明NAT是对称型的,否则就不是。

步骤4. 检测NAT是限制锥型的还是端口限制锥型

     最后,为了检测所处的NAT是限制锥型的还是端口限制锥型的,主机A向服务SEndpoint1发送UDP数据包,服务器用与Endpoint1相同的IP地址和不同的端号将带有APublic EndpointUDP反馈包发送给A。另外,A在发送数据包后,立即侦听端口和并进行数据接收,设定一个等待时间上限,防止无限堵塞(因为接收是一个While循环)。重复若干次。如过在整个过程中,用户A收到S发来的数据包,就说明NAT是限制型的;否则就说明NAT是端口限制型的。

 

二、打洞

      NAT技术虽然在一定程度上解决了IPv4地址短缺的问题,在构建防火墙、保证网络安全方面都发挥了一定的作用,却破坏了端到端的网络通信。NAT阻碍主机进行P2P通信的主要原因是NAT不允许外网主机主动访问内网主机,因为NAT设备上没有相关转发表项,要在NAT网络环境中进行有效的P2P通信,就必须寻找相应的解决方案——NAT穿越打洞。

      下面分各种NAT场景(如图2)来说明UDP打洞的过程。


 

     假设现在有AB通信双方,同时为了便于描述打洞过程,假定图2中的NAT类型均为Full Cone NAT:

   (aAB都处于公网下,无需打洞,AB可直接P2P直连。

   (bA处于公网,B在内网。若A去连B,由于B没有向A发送过数据,A的数据包会被BNAT1丢弃,所以即使A知道了B通过NAT1转换后的地址和端口,也是无法连接成功的。但是由于A在公网下,所以反过来B可以连接AP2P通信成功。
   (cAB都处于内网,分属不同的内网,它们彼此的内网地址在外网中是没有路由的,所以发往各自内网地址的UDP数据包会发送到错误的主机或者根本不存在的主机上。由于双方都不知道对方的公网IP和端口,就会无从下手,所以要在AB之间架设一台服务器S来为它们牵线,而且S是处在公网,以保证AB都能连接到SAB登录时都首先连接SS就会知道AB经过NAT后的IP和端口,当A想要连接B时,就向S发出请求,S会把B经过NAT后的IP和端口告诉A,同时SB发送A经过NAT后的IP和端口,并要求B发送数据给AB发送数据到达A时会被ANAT抛弃,但是BNAT会有B发送数据到A的记录,这时A再向B发送数据时就会被BNAT放行,因为B曾经向A的外网IP和端口发送过数据。AB的外网地址发送消息的过程就是“打洞”的过程,一旦AB都向对方的外网地址发送了数据包,就打开了AB之间的“洞”,一旦应用程序确认已经可以通过往对方的外网地址发送数据包的方式让数据包到达NAT后面的目的应用程序,程序会自动停止继续发送用于“打洞”的数据包,转而开始真正的P2P数据传输。这个过程可借助于网上的一个形象比喻来理解(见图3)。


 

   (dNAT1NAT2ISP(Internet Service Provider)提供的NAT设备,提供将多个用户节点映射到有限的几个公网IP的服务。NAT3作为NAT1的内网节点将把用户的家庭网络或内部网络接入NAT1的内网,然后用户的内部网络就可以经由NAT1访问公网了(NAT4同理)。从这种拓扑结构上来看,只有服务器S与NAT1NAT2是真正拥有公网可路由IP地址的设备,而NAT3和NAT 4所使用的公网IP地址,实际上是由ISP服务提供商设定的(相对于NAT1NAT2而言)内网地址(这个由ISP提供的内网地址相对于NAT1NAT2称之为“伪”公网地址)。现在假定AB希望通过UDP“打洞”完成P2P直连。最优化的路由策略是AB的“伪公网”IP上发送数据包。由于从服务器S的角度只能观察到真正的公网地址,也就是NAT3NAT4分别通过NAT1NAT2转换的外网地址,非常不幸的是AB是无法通过服务器S知道这些“伪”公网的地址,而且即使客户端AB通过某种手段可以得到NAT3和NAT4的“伪”公网地址,我们仍然不建议采用上述的“最优化”的打洞方式,这是因为这些地址是由ISP服务提供商提供的,或许会存在与客户端本身所在的内网地址重复的可能性,这样就会导致打洞数据包无法发出的问题。因此客户端别无选择,只能使用由服务器S观察到的AB的公网地址进行“打洞”操作,打洞过程与(c)类似。

   (eAB位于同一个NAT设备后面,并且处于同一局域网中,发出的数据包不需要路由,可进行速度较快的常规P2P通信。另外,AB发往对方公网地址的UDP数据包不一定会被对方收到,这取决于当前的NAT设备是否支持不同端口之间的UDP数据包传输(即Hairpin转换),就算支持,应用程序也应该优先选择上述常规的P2P直连,否则势必会造成数据包无谓地经过NAT设备,这是一种对资源的浪费。

  (f)由于AB不在一个内网中,所以相对于(e)而言,就不能不经过NATAB直接相连,这样情况下就需要NAT1支持Hairpin转换,虽然目前也有很多NAT设备不支持类似这样的“Hairpin转换”,但是已经有越来越多的NAT设备商开始加入对该转换的支持中来。同理(g)(h)。

二、webrtc打洞框架


 

      PeerSTUN服务器交互采用的是STUN协议,STUN服务器返回Peer的公网地址,地址形式为[IPport]

      LR都获取到了自己的局域网地址和公网地址后通过信令服务发送给对方,然后彼此都有了对方的地址。

      RFC5245定义这些地址为candidate,按照局域网 外网 > TURN地址的优先级顺序为candidates排序后进行打洞测试。

      打洞过程就是双方不停地往彼此的端口发包,既能收到对方发的包,也能收到自己发出去的回包,就说明打洞成功。失败的话,就通过TURN服务器进行转发。

注:以上部分描述和图挑选自网上资料与论文。

二、测试

1. 测试目的

      对比分析QQwebrtc的打洞情况。

2. 实验环境

      测试之初,我以为NAT在路由器中,试图通过对路由器的布置,可以设计出图2的各种场景,也以为NAT的四种类型可以在路由器中进行设置,这样就能全面地进行测试了。然而结果是,路由器上的NAT设置指的是静态转换、动态转换、端口多路复用的实现方式,查遍各种资料和咨询相关人员也没找到到底在哪里可以设置网络的Full Cone NAT、Restricted Cone NAT、Port Restricted Cone NAT、Symmetric NAT四种类型,最后得出一个结论:NAT功能通常被集成到路由器、防火墙、ISDN路由器或者单独的NAT设备中,或者由运营商决定吧,大概是我们触碰不到修改不了的吧,不确定。

      若欲将所有场景(图2)都测试完,同时又考虑到中国的多运营商情况,暂且只考虑铁通、移动、电信、联通四种,并且每种网络有Full con等四种类型,那么理论上的测试数量则为:组合数C4*42=120,再乘以8中场景(图2),最后为960种情况。然要齐全所有情况是相当困难的,实际实验室能提供的网络环境为:

    PC4-5Full cone 铁通

    PC4-6symmetric 联通

    PC4-7Port Restricted Cone 电信

    L-014-wifiFull cone  电信

    这些网分别只经过一个路由器接入,处于不同的内网。

    类型检测工具采用的是从网上下载的STUN_Client_app.exe


 

1. 实验方法

      本地内网IP通过利用http://www.ip138.com/检测当前网络的外网IP,采用抓包和相关日志分析方法来判断QQ视频和webrtc的打洞成败(即是否能建立P2P直连)情况。

      webrtc:源码中的STUN服务器Google公司的stun.l.google.com,为了便于在无VPN无FQ的情况下使用,需要将其改成能访问的地址,如荷兰的stun.voipbuster.comwebrtcpeerconnection_serverpeerconnection_client工程编译后的peerconnection_server.exepeerconnection_client.exe在源码\src\talk目录下。将peerconnection_server.exe放到公网下,两个客户端分别输入该公网地址连接上服务器后,其界面上会出现对方的PC名字,双击一方的名字,连接开始,若出现对方的画面表示P2P直连成功,否则失败,没有中转的情况。

     QQ:直接打开两个QQ进行视频通信(P2P失败时会使用中转服务器进行转发)。

2. 实验结果

    理论上各网络类型之间的P2P直连情况如下表:


 

     QQwebrtc的打洞测试结果:


1. 结果分析

      除了PC4-5,其余的结果与预期相符。

     其中,PC4-5的铁通网络,明明测出来是Full cone类型,却偏偏出现了与之特性不符的结果,究其原因是该铁通网具有多出口IP,以下面的简化模拟图来说明。

 


 

      现有两客户端AB,通过http://www.ip138.com/检测AB通过NAT后的外网地址分别为PublicIPA和PublicIPB。真正通信后,在A端抓包看到的对方地址为PublicIPB,则认为是直连,然而在B端抓包显示的对方地址却是一个陌生IP——PublicIPA2,看起来就像是A通过中转服务器似的,故而出现了一端直连一端中转的假象,之所以称之为假象,是因为在A端的抓包中并没有出现与PublicIPA2这个陌生IP进行通信的数据包。

     由于B是电信网,故而在B建立连接时,A从多IP中选择了电信的出口IP(不是说铁通就一定会选择与对方相同的运营商出口,只是说此时正好如此)。这种多ip出口就容易导致本来能够P2P的两端直连却失败。

    不清楚www.ip138.com获取的是不是真实运营商类型。webrtcSTUN服务器获得的客户端的NAT外网地址,我个人觉得是其所处的真实的运营商类型。因为STUN不在国内,故而不存在多运营商问题。在PC4-5 Full cone铁通和PC4-6 Symmetric联通下ping服务器STUN的地址stun.voipbuster.com过程如下:

(某市)铁通——>(省会)铁通——>()铁通——>中国铁通——>欧洲联盟——>荷兰

(某市)联通——>(省会)联通——>北京联通——>加拿大——>欧洲联盟——>德国——>欧洲联盟——>荷兰


转摘地址:http://www.mamicode.com/info-detail-1033776.html

<think>好的,我现在需要处理用户关于WebRTC打洞源码分析的请求。用户之前已经询问过WebRTC打洞技术的原理和实现,现在他想深入源码层面的分析,特别是涉及STUN和TURN的部分。首先,我需要回顾之前的对话和用户提供的引用资料,确保回答准确。 用户提供的引用中提到,WebRTC在三个地方触发连通性检查:本地Candidate收集、添加远端Candidate、收到STUN请求。此外,STUN协议的指纹机制和DNS发现服务器机制也被提及,还有TURN服务器的配置和部署示例。这些引用应作为分析源码的重要依据。 首先,我需要确定用户的知识水平。用户可能具备一定的网络和编程基础,但对WebRTC源码结构不熟悉。因此,回答需要结构清晰,分模块讲解,并给出源码路径和关键函数,方便用户查找。 接下来,要分解WebRTC打洞流程涉及的源码部分。根据引用[1],连通性检查的三个触发点对应源码中的不同处理逻辑。例如,收集本地Candidate可能位于ICE候选收集模块,处理远端Candidate可能在PeerConnection的AddIceCandidate方法,而处理STUN请求可能在网络传输层。 STUN协议部分,引用[2]提到指纹机制和DNS发现,这些在源码中可能体现为STUN消息的验证和处理逻辑。需要找到相关代码,如处理FINGERPRINT属性的部分,以及DNS查询STUN服务器的实现。 关于TURN,引用[3]和[4]涉及TURN服务器的配置和使用。源码中TURN候选的创建和连接管理是关键,特别是如何通过TURN中继传输数据。需要查看TurnPort类的实现,以及TURN消息的处理流程。 另外,用户可能需要了解ICE的状态机如何管理候选优先级和连通性检测。引用[1]中的三个触发点会影响ICE的状态转换,源码中的ICE模块需要详细分析,如ICEControlling和ICEControlled状态的处理。 需要注意,用户可能希望了解如何调试或修改源码,因此提供具体的代码文件和函数名会很有帮助。例如,在WebRTC的src/p2p目录下找到相关实现,或者指出处理STUN消息的类和方法。 最后,确保回答涵盖用户可能的问题,如连通性检查的具体实现、STUN/TURN消息的解析、ICE状态机的工作流程,以及如何结合配置文件和实际代码进行部署。同时,根据用户之前的问题,生成的相关问题应引导进一步学习,如调试技巧、协议扩展和性能优化。 总结,回答需要分模块解析源码结构,结合引用中的触发点和协议细节,提供具体的代码路径和函数示例,并确保语言简洁,适合源码分析的初学者。</think>### WebRTC 打洞源码分析(STUN/TURN/ICE 实现) 结合引用内容和源码实现逻辑,WebRTC 的 NAT 穿越核心代码主要集中在 `src/p2p` 目录下。以下是关键模块的源码分析框架: --- #### 一、ICE 候选收集优先级排序(源码路径:`src/p2p/base/port.cc`) 1. **候选类型分类** - **Host Candidate**:通过 `BasicPortAllocator` 收集本地网卡 IP 和端口 - **Server Reflexive Candidate**:通过 STUN 请求获取(`StunPort::PrepareAddress`) - **Relay Candidate**:通过 TURN 服务器分配中继地址(`TurnPort::CreateRelayPort`)[^1] 2. **优先级计算** 根据 RFC 5245 标准,候选优先级公式: $$ \text{priority} = (2^{24} \times \text{type\_pref}) + (2^{8} \times \text{local\_pref}) + (256 - \text{component\_id}) $$ 实现代码见 `src/p2p/base/ice_controller_interface.h` 中的 `CalculatePriority` 方法。 --- #### 二、STUN 消息处理流程(源码路径:`src/p2p/base/stun*.cc`) 1. **指纹校验机制** - 引用[2]中提到的 **FINGERPRINT 校验** 在 `StunMessage::ValidateFingerprint` 实现 - 通过 CRC32 校验码验证消息完整性,避免其他协议混淆: ```cpp bool StunMessage::ValidateFingerprint(const char* data, size_t size) { uint32_t fingerprint = STUN_FINGERPRINT_XOR ^ rtc::ComputeCrc32(data, size - 4); // 对比消息末尾的指纹值... } ``` 2. **DNS 服务器发现** - 引用[2]的 DNS 发现机制通过 `stun_servers` 和 `turn_servers` 列表配置 - 解析过程见 `src/p2p/client/basic_port_allocator.cc` 中的 `ResolveStunAddress` 方法。 --- #### 三、TURN 中继实现(源码路径:`src/p2p/base/turn_port.cc`) 1. **TURN 通道建立** - 发送 `Allocate Request` 到 TURN 服务器,源码逻辑在 `TurnPort::SendRequest` - 处理 `Allocate Success Response` 并绑定中继地址(`TurnPort::HandleAllocateSuccess`)[^4] 2. **数据转发机制** - 通过 `ChannelData` 消息实现中继传输(`TurnPort::HandleChannelData`) - 使用 `Permission` 机制控制数据流向: ```cpp void TurnPort::AddPermission(const rtc::IPAddress& peer_ip) { // 添加权限条目,允许特定 IP 发送数据 } ``` --- #### 四、ICE 连通性检查(源码路径:`src/p2p/base/ice_controller.cc`) 1. **触发检查的三个条件**(引用[1]) - **本地候选就绪**:`PortAllocatorSession` 触发 `CandidatesAllocationDone` 信号 - **远端候选添加**:`PeerConnection::AddIceCandidate` 调用 `JsepTransportController::AddRemoteCandidates` - **收到 STUN 请求**:`Connection::OnReadPacket` 处理传入的 Binding Request 2. **状态机流转** - **ICE 控制角色**:通过 `IceControllerInterface` 管理 `ICE_CONTROLLING` 或 `ICE_CONTROLLED` 状态 - **连通性检测策略**:在 `IceController::SelectConnectionToPing` 中实现优先级排序和超时重试机制。 --- #### 五、调试日志分析技巧 1. **启用详细日志** 编译时添加 GN 参数:`rtc_enable_logging = true`,运行时设置日志级别: ```bash export RTC_LOGLEVELS=verbose ``` 2. **关键日志标记** - STUN 消息交互:查找 `STUN-CLIENT` 或 `STUN-SERVER` 标签 - ICE 状态变更:关注 `ICE-CC`(连通性检查)和 `ICE-NW`(网络状态)日志。 --- ### 相关进阶问题 1. 如何通过修改 WebRTC 源码支持自定义 STUN 属性? 2. ICE 连通性检查失败时,如何通过源码调试定位网络策略冲突? 3. TURN 中继流量在源码中如何实现 QoS 优先级标记?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值