精华内容
下载资源
问答
  • 最近在做基于UDP的P2P通信,实现了简单的通信程序,用STUN协议检测NAT并打洞穿透,效果不是很好,穿越成功率50%左右。查阅一些外文资料发现有很多实现这个功能的协议:old stun;stun-bits;TURN;UpnP;还有ICE; ...
  • P2P通信之TURN协议

    2020-12-01 00:01:05
    上一篇P2P通信标准协议(一)介绍了在NAT上进行端口绑定的通用规则,应用程序可以根据这个协议来设计网络以外的通信。但是,STUN/RFC5389协议里能处理的也只有市面上大多数的Cone NAT(关于NAT类型可以参照P2P通信原理...

    一、前言

    上一篇STUN协议中介绍了在NAT上进行端口绑定的通用规则,应用程序可以根据这个协议来设计网络以外的通信。但是,STUN/RFC5389协议里能处理的也只有市面上大多数的Cone NAT(关于NAT类型可以参照P2P通信原理与实现),对于Symmetric NAT,传统的P2P打洞方法是不适用的。因此为了保证通信能够建立,我们可以在没办法的情况下用保证成功的中继方法(Relaying),虽然使用中继会对服务器负担加重,而且也算不上P2P,但是至少保证了最坏情况下信道的通畅,从而不至于受NAT类型的限制。TURN/RFC5766就是为此目的而进行的拓展。

     

    二、TURN简介

    TURN的全称为Traversal Using Relays around NAT,是STUN/RFC5389的一个拓展,主要添加了Relay功能。如果终端在NAT之后, 那么在特定的情景下,有可能使得终端无法和其对等端(peer)进行直接的通信,这时就需要公网的服务器作为一个中继, 对来往的数据进行转发。这个转发的协议就被定义为TURN。TURN和其他中继协议的不同之处在于,它允许客户端使用同一个中继地址(relay address)与多个不同的peer进行通信。

    使用TURN协议的客户端必须能够通过中继地址和对等端进行通讯,并且能够得知每个peer的的IP地址和端口(确切地说,应该是peer的服务器反射地址)。 而这些行为如何完成,是不在TURN协议范围之内的。其中一个可用的方式是客户端通过email来告知对等端信息, 另一种方式是客户端使用一些指定的协议,如“introduction” 或 “rendezvous”,详见RFC5128.

    如果TURN使用于ICE协议中,relay地址会作为一个候选,由ICE在多个候选中进行评估,选取最合适的通讯地址。一般来说中继的优先级都是最低的。 TURN协议被设计为ICE协议(Interactive Connectivity Establishment)的一部分,而且也强烈建议用户在他们的程序里使用ICE,但是也可以独立于ICE的运行。 值得一提的是,TURN协议本身是STUN的一个拓展,因此绝大部分TURN报文都是STUN类型的,作为STUN的一个拓展,TURN增加了新的方法(method)和属性(attribute)。 因此阅读本章时最好先了解一下STUN协议

     

    三、操作概述

    在典型的情况下,TURN客户端连接到内网中,并且通过一个或者多个NAT到达公网,TURN服务器架设在公网中,不同的客户端以TURN服务器为中继和其他peer进行通信,如下图所示:

                                                Peer A
                                                Server-Reflexive    +---------+
                                                Transport Address   |         |
                                                192.0.2.150:32102   |         |
                                                    |              /|         |
                                  TURN              |            / ^|  Peer A |
            Client’s              Server            |           /  ||         |
            Host Transport        Transport         |         //   ||         |
            Address               Address           |       //     |+---------+
           10.1.1.2:49721       192.0.2.15:3478     |+-+  //     Peer A
                    |               |               ||N| /       Host Transport
                    |   +-+         |               ||A|/        Address
                    |   | |         |               v|T|     192.168.100.2:49582
                    |   | |         |               /+-+
         +---------+|   | |         |+---------+   /              +---------+
         |         ||   |N|         ||         | //               |         |
         | TURN    |v   | |         v| TURN    |/                 |         |
         | Client  |----|A|----------| Server  |------------------|  Peer B |
         |         |    | |^         |         |^                ^|         |
         |         |    |T||         |         ||                ||         |
         +---------+    | ||         +---------+|                |+---------+
                        | ||                    |                |
                        | ||                    |                |
                        +-+|                    |                |
                           |                    |                |
                           |                    |                |
                     Client’s                   |            Peer B
                     Server-Reflexive    Relayed             Transport
                     Transport Address   Transport Address   Address
                     192.0.2.1:7000      192.0.2.15:50000    192.0.2.210:49191
    

    在上图中,左边的TURN Client是位于NAT后面的一个客户端(内网地址是10.1.1.2:49721),连接公网的TURN服务器(默认端口3478)后, 服务器会得到一个Client的反射地址(Reflexive Transport Address, 即NAT分配的公网IP和端口)192.0.2.1:7000, 此时Client会通过TURN命令创建或管理ALLOCATION,allocation是服务器上的一个数据结构,包含了中继地址的信息。 服务器随后会给Client分配一个中继地址,即图中的192.0.2.15:50000,另外两个对等端若要通过TURN协议和Client进行通信,可以直接往中继地址收发数据即可,TURN服务器会把发往指定中继地址的数据转发到对应的Client,这里是其反射地址。

    Server上的每一个allocation都唯一对应一个client,并且只有一个中继地址,因此当数据包到达某个中继地址时,服务器总是知道应该将其转发到什么地方。但值得一提的是,一个Client可能在同一时间在一个Server上会有多个allocation,这和上述规则是并不矛盾的。

     

    四、传输

    在协议中,TURN服务器与peer之间的连接都是基于UDP的,但是服务器和客户端之间可以通过其他各种连接来传输STUN报文, 比如TCP/UDP/TLS-over-TCP. 客户端之间通过中继传输数据时候,如果用了TCP,也会在服务端转换为UDP,因此建议客户端使用UDP来进行传输. 至于为什么要支持TCP,那是因为一部分防火墙会完全阻挡UDP数据,而对于三次握手的TCP数据则不做隔离.

     

    五、分配(Allocations)

    要在服务器端获得一个中继分配,客户端须使用分配事务. 客户端发送分配请求(Allocate request)到服务器,然后服务器返回分配成功响应,并包含了分配的地址.客户端可以在属性字段描述其想要的分配类型(比如生命周期).由于中继数据实现了安全传输,服务器会要求对客户端进行验证,主要使用STUN的long-term credential mechanism.

    一旦中继传输地址分配好,客户端必须要将其保活.通常的方法是发送刷新请求(Refresh request)到服务端.这在TURN中是一个标准的方法.刷新频率取决于分配的生命期,默认为10分钟.客户端也可以在刷新请求里指定一个更长的生命期, 而服务器会返回一个实际上分配的时间. 当客户端想中指通信时,可以发送一个生命期为0的刷新请求.

    服务器和客户端都保存有一个成为五元组(5-TUPLE)的信息,比如对于客户端来说,五元组包括客户端本地地址/端口,服务器地址/端口, 和传输协议;服务器也是类似,只不过将客户端的地址变为其反射地址,因为那才是服务器所见到的. 服务器和客户端在分配 请求中都带有5-TUPLE信息,并且也在接下来的信息传输中使用,因此彼此都知道哪一次分配对应哪一次传输.

        TURN                                 TURN           Peer          Peer
        client                               server          A             B
          |-- Allocate request --------------->|             |             |
          |                                    |             |             |
          |<--------------- Allocate failure --|             |             |
          |                 (401 Unauthorized) |             |             |
          |                                    |             |             |
          |-- Allocate request --------------->|             |             |
          |                                    |             |             |
          |<---------- Allocate success resp --|             |             |
          |            (192.0.2.15:50000)      |             |             |
          //                                   //            //            //
          |                                    |             |             |
          |-- Refresh request ---------------->|             |             |
          |                                    |             |             |
          |<----------- Refresh success resp --|             |             |
          |                                    |             |             |
    

    如上图所示,客户端首先发送Allocate请求,但是没带验证信息,因此STUN服务器会返回error response,客户端收到错误后加上所需的验证信息再次请求,才能进行成功的分配.

     

    六、发送机制(Send Mechanism)

    client和peer之间有两种方法通过TURN server交换应用信息,第一种是使用SendData方法(method),第二种是使用通道(channels),两种方法都通过某种方式告知服务器哪个peer应该接收数据,以及服务器告知client数据来自哪个peer.

    Send Mechanism使用了Send和Data指令(Indication).其中Send指令用来把数据从client发送到server,而Data指令用来把数据从server发送到client.当使用Send指令时,客户端发送一个Send Indication到服务端,其中包含:

    • XOR-PEER-ADDRESS属性,指定对等端的(服务器反射)地址.
    • DATA属性,包含要传给对等端的信息.

    当服务器收到Send Indication之后,会将DATA部分的数据解析出来,并将其以UDP的格式转发到对应的端点去,并且在封装数据包的时候把client的中继地址作为源地址.从而从对等端发送到中继地址的数据也会被服务器转发到client上. 值得一提的是,Send/Data Indication是不支持验证的,因为长效验证机制不支持对indication的验证,因此为了防止攻击, TURN要求client在给对等端发送indication之前先安装一个到对等端的许可(permission),如下图所示,client到Peer B 没有安装许可,导致其indication数据包将被服务器丢弃,对于peer B也是同样:

        TURN                                 TURN           Peer          Peer
        client                               server          A             B
          |                                    |             |             |
          |-- CreatePermission req (Peer A) -->|             |             |
          |<-- CreatePermission success resp --|             |             |
          |                                    |             |             |
          |--- Send ind (Peer A)-------------->|             |             |
          |                                    |=== data ===>|             |
          |                                    |             |             |
          |                                    |<== data ====|             |
          |<-------------- Data ind (Peer A) --|             |             |
          |                                    |             |             |
          |                                    |             |             |
          |--- Send ind (Peer B)-------------->|             |             |
          |                                    | dropped     |             |
          |                                    |             |             |
          |                                    |<== data ==================|
          |                            dropped |             |             |
          |                                    |             |             |
    

    TURN支持两种方式来创建许可,比如其中一种就是发送CreatePermission request

     

    七、信道机制(Channels)

    对于一些应用程序,比如VOIP(Voice over IP),在Send/Data Indication中多加的36字节格式信息会加重客户端和服务端之间的带宽压力.为改善这种情况,TURN提供了第二种方法来让client和peer交互数据.该方法使用另一种数据包格式, 即ChannelData message,信道数据报文. ChannelData message不使用STUN头部,而使用一个4字节的头部,包含了一个称之为信道号的值(channel number).每一个使用中的信道号都与一个特定的peer绑定,即作为对等端地址的一个记号.

    要将一个信道与对等端绑定,客户端首先发送一个信道绑定请求(ChannelBind Request)到服务器,并且指定一个未绑定的信道号以及对等端的地址信息. 绑定后client和server都能通过ChannelData message来发送和转发数据.信道绑定默认持续10分钟,并且可以通过重新发送ChannelBind Request来刷新持续时间.和Allocation不同的是,并没有直接删除绑定的方法,只能等待其超时自动失效.

        TURN                                 TURN           Peer          Peer
        client                               server          A             B
          |                                    |             |             |
          |-- ChannelBind req ---------------->|             |             |
          | (Peer A to 0x4001)                 |             |             |
          |                                    |             |             |
          |<---------- ChannelBind succ resp --|             |             |
          |                                    |             |             |
          |-- [0x4001] data ------------------>|             |             |
          |                                    |=== data ===>|             |
          |                                    |             |             |
          |                                    |<== data ====|             |
          |<------------------ [0x4001] data --|             |             |
          |                                    |             |             |
          |--- Send ind (Peer A)-------------->|             |             |
          |                                    |=== data ===>|             |
          |                                    |             |             |
          |                                    |<== data ====|             |
          |<------------------ [0x4001] data --|             |             |
          |                                    |             |             |
    

    上图中0x4001为信道号,即ChannelData message的头部中头2字节,值得一提的是信道号的选取有如下要求:

    • 0x0000-0x3FFF : 这一段的值不能用来作为信道号
    • 0x4000-0x7FFF : 这一段是可以作为信道号的值,一共有16383种不同值在目前来看是足够用的
    • 0x8000-0xFFFF : 这一段是保留值,留给以后使用

    还是那句老话,关于协议具体的细节可以去翻阅RFC5766的草稿,其中每个属性以及其格式都介绍得很详细.

     

    八、实例

    在上一章也提到过,因为RFC是标准协议,因此实现上往往有良好的兼容性和拓展性.现存的开源P2P应用程序, 如果按照标准来设计,可以很容易与之对接.其中比较著名的就是PJSIP,PJSIP是一个开源的多媒体通信库,实现了许多标准协议,如SIP, SDP, RTP, STUN, TURN 和 ICE. 当然我们也能自己实现.比如GitHub上的TurnServer就是其中一个对TURN服务端的实现.下面在局域网环境下对TURN数据包进行简要分析.首先有如下机器情况:

    • TurnServer运行在192.168.1.110,使用默认端口3478,采用用户名和密码验证,其中用户名为pannzh,密码123456
    • TurnClient运行在192.168.1.106,为了方便,令peer也在192.168.1.106运行,端口为59593

    这里使用wireshark来抓包分析,关于wireshark的简介可以参照我之前的文章细说中间人攻击(一), 首先TurnClient发送Allocation请求:

    可以看到第一次requst被服务器拒绝,因为后者要求nonce验证信息,服务器的返回中包含了nonce信息, 除此之外还包含了ERROR-CODE,SOFTWARE,FINGERPRINT属性.

    在下一次request请求中,客户端加上了收到的nonce,以及USERNAME和REALM等属性,再次发送到TurnServer:

     

    服务器接收到了正确的allocation请求,于是返回succcess response,可以看到在返回中带有默认的lifetime为1800秒, XOR-MAPPED-ADDRESS以及XOR-RELAY-ADDRESS等属性:

     

    前文也说过,若要和peer进行通信,必须先创建一个许可,因此Client向服务器发送CreatePermission请求,其中携带了peer的信息:

    服务器如果通过验证,就会返回success response,随后Client可以通过上文说到的两种方法与Peer进行通讯,比如下面的Send indication方法:

    通过对TurnServer发送indication告知数据的接收方以及数据内容让TurnServer进行转发,从而间接地向对等端发送DATA. 而从对等端来看,就是收到一个从client的relay地址192.168.1.110:65315到目的地址192.168.1.106:59593(即peer地址)的UDP数据包.

     

    展开全文
  • P2P通信标准协议ICE

    2019-01-10 11:29:51
    P2P通信标准协议(二)中,介绍了TURN的基本交互流程,在上篇结束部分也有说到,TURN作为STUN 协议的一个拓展,保持了STUN的工具性质,而不作为完整的NAT传输解决方案,只提供穿透NAT的功能, 并且由具体的应用程序来使用....

    P2P通信标准协议(二)中,介绍了TURN的基本交互流程,在上篇结束部分也有说到,TURN作为STUN
    协议的一个拓展,保持了STUN的工具性质,而不作为完整的NAT传输解决方案,只提供穿透NAT的功能,
    并且由具体的应用程序来使用.虽然TURN也可以独立工作,但其本身就是被设计为ICE/RFC5245
    的一部分,本章就来介绍一下ICE协议的具体内容.


    SDP

    ICE信息的描述格式通常采用标准的SDP,其全称为Session Description Protocol,即会话描述协议.
    SDP只是一种信息格式的描述标准,不属于传输协议,但是可以被其他传输协议用来交换必要的信息,如SIP和RTSP等.

    SDP信息

    一个SDP会话描述包含如下部分:

    • 会话名称和会话目的
    • 会话的激活时间
    • 构成会话的媒体(media)
    • 为了接收该媒体所需要的信息(如地址,端口,格式等)

    因为在中途参与会话也许会受限制,所以可能会需要一些额外的信息:

    • 会话使用的的带宽信息
    • 会话拥有者的联系信息

    一般来说,SDP必须包含充分的信息使得应用程序能够加入会话,并且可以提供任何非参与者使用时需要知道的资源
    状况,后者在当SDP同时用于多个会话声明协议时尤其有用.

    SDP格式

    SDP是基于文本的协议,使用ISO 10646字符集和UTF-8编码.SDP字段名称和属性名称只使用UTF-8的一个子集US-ASCII,
    因此不能存在中文.虽然理论上文本字段和属性字段支持全集,但最好还是不要在其中使用中文.

    SDP会话描述包含了多行如下类型的文本:

    <type>=<value>

    其中type是大小写敏感的,其中一些行是必须要有的,有些是可选的,所有元素都必须以固定顺序给出.固定的顺序极大改善了
    错误检测,同时使得处理端设计更加简单.如下所示,其中可选的元素标记为* :

    会话描述:
         v=  (protocol version)
         o=  (originator and session identifier)
         s=  (session name)
         i=* (session information)
         u=* (URI of description)
         e=* (email address)
         p=* (phone number)
         c=* (connection information -- not required if included in
              all media)
         b=* (zero or more bandwidth information lines)
         One or more time descriptions ("t=" and "r=" lines; see below)
         z=* (time zone adjustments)
         k=* (encryption key)
         a=* (zero or more session attribute lines)
         Zero or more media descriptions
    
    时间信息描述:
         t=  (time the session is active)
         r=* (zero or more repeat times)
    
    多媒体信息描述(如果有的话):
         m=  (media name and transport address)
         i=* (media title)
         c=* (connection information -- optional if included at
              session level)
         b=* (zero or more bandwidth information lines)
         k=* (encryption key)
         a=* (zero or more media attribute lines)

    所有元素的type都为小写,并且不提供拓展.但是我们可以用a(attribute)字段来提供额外的信息.一个SDP描述的例子如下:

      v=0
      o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5
      s=SDP Seminar
      i=A Seminar on the session description protocol
      u=http://www.example.com/seminars/sdp.pdf
      e=j.doe@example.com (Jane Doe)
      c=IN IP4 224.2.17.12/127
      t=2873397496 2873404696
      a=recvonly
      m=audio 49170 RTP/AVP 0
      m=video 51372 RTP/AVP 99
      a=rtpmap:99 h263-1998/90000

    具体字段的type/value描述和格式可以去参考RFC4566.

    Offer/Answer模型

    上文说到,SDP用来描述多播主干网络的会话信息,但是并没有具体的交互操作细节是如何实现的,因此RFC3264
    定义了一种基于SDP的offer/answer模型.在该模型中,会话参与者的其中一方生成一个SDP报文构成offer,
    其中包含了一组offerer希望使用的多媒体流和编解码方法,以及offerer用来接收改数据的IP地址和端口信息.
    offer传输到会话的另一端(称为answerer),由answerer生成一个answer,即用来响应对应offer的SDP报文.
    answer中包含不同offer对应的多媒体流,并指明该流是否可以接受.

    RFC3264只介绍了交换数据过程,而没有定义传递offer/answer报文的方法,后者在RFC3261/SIP
    即会话初始化协议中描述.值得一提的是,offer/answer模型也经常被SIP作为一种基本方法使用.
    offer/answer模型在SDP报文的基础上进行了一些定义,工作过程不在此描述,需要了解细节的朋友可以参考RFC3261.

    ICE

    ICE的全称为Interactive Connectivity Establishment,即交互式连接建立.初学者可能会将其与网络编程的ICE
    弄混,其实那是不一样的东西,在网络编程中,如C++的ICE库,都是指Internate Communications Engine,
    是一种用于分布式程序设计的网络通信中间件.我们这里说的只是交互式连接建立.

    ICE是一个用于在offer/answer模式下的NAT传输协议,主要用于UDP下多媒体会话的建立,其使用了STUN协议以及TURN
    协议,同时也能被其他实现了offer/answer模型的的其他程序所使用,比如SIP(Session Initiation Protocol).

    使用offer/answer模型(RFC3264)的协议通常很难在NAT之间穿透,因为其目的一般是建立多媒体数据流,而且在报文中还
    携带了数据的源IP和端口信息,这在通过NAT时是有问题的.RFC3264还尝试在客户端之间建立直接的通路,因此中间就缺少
    了应用层的封装.这样设计是为了减少媒体数据延迟,减少丢包率以及减少程序部署的负担.然而这一切都很难通过NAT而完成.
    有很多解决方案可以使得这些协议运行于NAT环境之中,包括应用层网关(ALGs),Classic STUN以及Realm Specific IP+SDP
    协同工作等方法.不幸的是,这些技术都是在某些网络拓扑下工作很好,而在另一些环境下表现又很差,因此我们需要一个单一的,
    可自由定制的解决方案,以便能在所有环境中都能较好工作.

    ICE工作流程

    一个典型的ICE工作环境如下,有两个端点L和R,都运行在各自的NAT之后(他们自己也许并不知道),NAT的类型和性质也是未知的.
    L和R通过交换SDP信息在彼此之间建立多媒体会话,通常交换通过一个SIP服务器完成:

                     +-----------+
                     |    SIP    |
    +-------+        |    Srvr   |         +-------+
    | STUN  |        |           |         | STUN  |
    | Srvr  |        +-----------+         | Srvr  |
    |       |        /           \         |       |
    +-------+       /             \        +-------+
                   /<- Signaling ->\
                  /                 \
             +--------+          +--------+
             |  NAT   |          |  NAT   |
             +--------+          +--------+
               /                       \
              /                         \
             /                           \
         +-------+                    +-------+
         | Agent |                    | Agent |
         |   L   |                    |   R   |
         |       |                    |       |
         +-------+                    +-------+

    ICE的基本思路是,每个终端都有一系列传输地址(包括传输协议,IP地址和端口)的候选,可以用来和其他端点进行通信.
    其中可能包括:

    • 直接和网络接口联系的传输地址(host address)
    • 经过NAT转换的传输地址,即反射地址(server reflective address)
    • TURN服务器分配的中继地址(relay address)

    虽然潜在要求任意一个L的候选地址都能用来和R的候选地址进行通信.但是实际中发现有许多组合是无法工作的.举例来说,
    如果L和R都在NAT之后而且不处于同一内网,他们的直接地址就无法进行通信.ICE的目的就是为了发现哪一对候选地址的
    组合可以工作,并且通过系统的方法对所有组合进行测试(用一种精心挑选的顺序).

    为了执行ICE,客户端必须要识别出其所有的地址候选,ICE中定义了三种候选类型,有些是从物理地址或者逻辑网络接口继承
    而来,其他则是从STUN或者TURN服务器发现的.很自然,一个可用的地址为和本地网络接口直接联系的地址,通常是内网地址,
    称为HOST CANDIDATE,如果客户端有多个网络接口,比如既连接了WiFi又插着网线,那么就可能有多个内网地址候选.

    其次,客户端通过STUN或者TURN来获得更多的候选传输地址,即SERVER REFLEXIVE CANDIDATESRELAYED CANDIDATES,
    如果TURN服务器是标准化的,那么两种地址都可以通过TURN服务器获得.当L获得所有的自己的候选地址之后,会将其
    按优先级排序,然后通过signaling通道发送到R.候选地址被存储在SDP offer报文的属性部分.当R接收到offer之后,
    就会进行同样的获选地址收集过程,并返回给L.

    这一步骤之后,两个对等端都拥有了若干自己和对方的候选地址,并将其配对,组成CANDIDATE PAIRS.为了查看哪对组合
    可以工作,每个终端都进行一系列的检查.每个检查都是一次STUN request/response传输,将request从候选地址对的本地
    地址发送到远端地址. 连接性检查的基本原则很简单:

    1. 以一定的优先级将候选地址对进行排序.
    2. 以该优先级顺序发送checks请求
    3. 从其他终端接收到checks的确认信息

    两端连接性测试,结果是一个4次握手过程:

     L                        R
     -                        -
     STUN request ->             \  L's
               <- STUN response  /  check
    
                <- STUN request  \  R's
     STUN response ->            /  check

    值的一提的是,STUN request的发送和接收地址都是接下来进多媒体传输(如RTP和RTCP)的地址和端口,所以,
    客户端实际上是将STUN协议与RTP/RTCP协议在数据包中进行复用(而不是在端口上复用).

    由于STUN Binding request用来进行连接性测试,因此STUN Binding response中会包含终端的实际地址,
    如果这个地址和之前学习的所有地址都不匹配,发送方就会生成一个新的candidate,称为PEER REFLEXIVE CANDIDATE,
    和其他candidate一样,也要通过ICE的检查测试.

    连接性检查(Connectivity Checks)

    所有的ICE实现都要求与STUN(RFC5389)兼容,并且废弃Classic STUN(RFC3489).ICE的完整实现既生成checks(作为STUN client),
    也接收checks(作为STUN server),而lite实现则只负责接收checks.这里只介绍完整实现情况下的检查过程.

    1. 为中继候选地址生成许可(Permissions).

    2. 从本地候选往远端候选发送Binding Request.

    在Binding请求中通常需要包含一些特殊的属性,以在ICE进行连接性检查的时候提供必要信息.

    • PRIORITY 和 USE-CANDIDATE
      • 终端必须在其request中包含PRIORITY属性,指明其优先级,优先级由公式计算而得.
        如果有需要也可以给出特别指定的候选(即USE-CANDIDATE属性).
    • ICE-CONTROLLED和ICE-CONTROLLING
      • 在每次会话中,每个终端都有一个身份,有两种身份,即受控方(controlled role)和主控方(controlling role).
        主控方负责选择最终用来通讯的候选地址对,受控方被告知哪个候选地址对用来进行哪次媒体流传输,
        并且不生成更新过的offer来提示此次告知.发起ICE处理进程(即生成offer)的一方必须是主控方,而另一方则是受控方.
        如果终端是受控方,那么在request中就必须加上ICE-CONTROLLED属性,同样,如果终端是主控方,就需要ICE-CONTROLLING属性.
    • 生成Credential
      • 作为连接性检查的Binding Request必须使用STUN的短期身份验证.验证的用户名被格式化为一系列username段
        的联结,包含了发送请求的所有对等端的用户名,以冒号隔开;密码就是对等端的密码.

    3. 处理Response.

    当收到Binding Response时,终端会将其与Binding Request相联系,通常通过事务ID.随后将会将此事务ID与
    候选地址对进行绑定.

    • 失败响应
      • 如果STUN传输返回487(Role Conflict)错误响应,终端首先会检查其是否包含了ICE-CONTROLLED或ICE-CONTROLLING
        属性.如果有ICE-CONTROLLED,终端必须切换为controlling role;如果请求包含ICE-CONTROLLING属性,
        则必须切换为controlled role.切换好之后,终端必须使产生487错误的候选地址对进入检查队列中,
        并将此地址对的状态设置为Waiting.
    • 成功响应,一次连接检查在满足下列所有情况时候就被认为成功:
      • STUN传输产生一个Success Response
      • response的源IP和端口等于Binding Request的目的IP和端口
      • response的目的IP和端口等于Binding Request的源IP和端口

    终端收到成功响应之后,先检查其mapped address是否与本地记录的地址对有匹配,如果没有则生成一个新的候选地址.
    即对等端的反射地址.如果有匹配,则终端会构造一个可用候选地址对(valid pair).通常很可能地址对不存在于任何
    检查列表中,检索检查列表中没有被服务器反射的本地地址,这些地址把它们的本地候选转换成服务器反射地址的基地址,
    并把冗余的地址去除掉.

    后记

    本文介绍了一种完整的NAT环境通信解决方案ICE,并且对其中涉及到的概念SDP和offer/answer模型也作了简要介绍.
    ICE是使用STUN/TURN工具性质的最主要协议之一,其中TURN一开始也被设计为ICE协议的一部分.值的一提的是,
    本文只是对这几种协议作了概述性的说明,而具体工作过程和详细的属性描述都未包含,因此如果需要根据协议来
    实现具体的应用程序,还需要对RFC的文档进行仔细阅读.这里给出一些参考:

    而具体的代码以及实现可以参考:

    转自:https://www.cnblogs.com/pannengzhi/p/5061674.html

    展开全文
  • P2P通信之STUN协议

    2020-11-30 23:56:21
    一、前言 在P2P通信之原理篇中介绍了P2P打洞的基本原理和...本文就来介绍一下当前主要应用于P2P通信的几个标准协议,主要有STUN/RFC3489,STUN/RFC5389,TURN/RFC5766以及ICE/RFC5245。 二、STUN简介 在前言里我.

    一、前言

    在P2P通信之原理篇中介绍了P2P打洞的基本原理和方法,我们可以根据其原理为自己的网络程序设计一套通信规则,当然如果这套程序只有自己在使用是没什么问题的。可是在现实生活中,我们的程序往往还需要和第三方的协议(如SDP,SIP)进行对接,因此使用标准化的通用规则来进行P2P链接建立是很有必要的。本文就来介绍一下当前主要应用于P2P通信的几个标准协议,主要有STUN/RFC3489STUN/RFC5389TURN/RFC5766以及ICE/RFC5245

     

    二、STUN简介

    在前言里我们看到,RFC3489和RFC5389的名称都是STUN,但其全称是不同的。在RFC3489里,STUN的全称是Simple Traversal of User Datagram Protocol (UDP) Through Network Address Translators (NATs),即穿越NAT的简单UDP传输,是一个轻量级的协议,允许应用程序发现自己和公网之间的中间件类型,同时也能允许应用程序发现自己被NAT分配的公网IP。这个协议在2003年3月被提出,其介绍页面里说到已经被STUN/RFC5389所替代,后者才是我们要详细介绍的。

    RFC5389中,STUN的全称为Session Traversal Utilities for NAT,即NAT环境下的会话传输工具,是一种处理NAT传输的协议,但主要作为一个工具来服务于其他协议。和STUN/RFC3489类似,可以被终端用来发现其公网IP和端口,同时可以检测端点间的连接性,也可以作为一种保活(keep-alive)协议来维持NAT的绑定。和RFC3489最大的不同点在于,STUN本身不再是一个完整的NAT传输解决方案,而是在NAT传输环境中作为一个辅助的解决方法,同时也增加了TCP的支持。RFC5389废弃了RFC3489,因此后者通常称为classic STUN,但依旧是后向兼容的。

    而完整的NAT传输解决方案则使用STUN的工具性质,ICE就是一个基于offer/answer方法的完整NAT传输方案,如SIP

    STUN是一个C/S架构的协议,支持两种传输类型。一种是请求/响应(request/respond)类型,由客户端给服务器发送请求,并等待服务器返回响应;另一种是指示类型(indication transaction),由服务器或者客户端发送指示,另一方不产生响应。两种类型的传输都包含一个96位的随机数作为事务ID(transaction ID),对于请求/响应类型,事务ID允许客户端将响应和产生响应的请求连接起来;对于指示类型,事务ID通常作为debugging aid使用。

    所有的STUN报文信息都含有一个固定头部,包含了方法,类和事务ID。方法表示是具体哪一种传输类型(两种传输类型又分了很多具体类型),STUN中只定义了一个方法,即binding(绑定),其他的方法可以由使用者自行拓展;Binding方法可以用于请求/响应类型和指示类型,用于前者时可以用来确定一个NAT给客户端分配的具体绑定,用于后者时可以保持绑定的激活状态。类表示报文类型是请求/成功响应/错误响应/指示。在固定头部之后是零个或者多个属性(attribute),长度也是不固定的。

     

    三、STUN报文结构

    STUN报文和大多数网络类型的格式一样,是以大端编码(big-endian)的,即最高有效位在左边。所有的STUN报文都以20字节的头部开始,后面跟着若干个属性。下面来详细说说。

    1、STUN报文头部

    STUN头部包含了STUN消息类型,magic cookie,事务ID和消息长度,如下:

     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |0 0|     STUN Message Type     |         Message Length        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         Magic Cookie                          |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                                                               |
    |                     Transaction ID (96 bits)                  |
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

    最高的2位必须置零,这可以在当STUN和其他协议复用的时候,用来区分STUN包和其他数据包。

    STUN Message Type字段定义了消息的类型(请求/成功响应/失败响应/指示)和消息的主方法。虽然我们有4个消息类别,但在STUN中只有两种类型的事务,即请求/响应类型和指示类型。

    响应类型分为成功和出错两种,用来帮助快速处理STUN信息。Message Type字段又可以进一步分解为如下结构:

     0                 1
     2  3  4 5 6 7 8 9 0 1 2 3 4 5
    +--+--+-+-+-+-+-+-+-+-+-+-+-+-+
    |M |M |M|M|M|C|M|M|M|C|M|M|M|M|
    |11|10|9|8|7|1|6|5|4|0|3|2|1|0|
    +--+--+-+-+-+-+-+-+-+-+-+-+-+-+
    

    其中显示的位为从最高有效位M11到最低有效位M0,M11到M0表示方法的12位编码。C1和C0两位表示类的编码。比如对于binding方法来说,0b00表示request,0b01表示indication,0b10表示success response,0b11表示error response,每一个method都有可能对应不同的传输类别。拓展定义新方法的时候注意要指定该方法允许哪些类型的消息。

    Magic Cookie字段包含固定值0x2112A442,这是为了前向兼容RFC3489,因为在classic STUN中,这一区域是事务ID的一部分。另外选择固定数值也是为了服务器判断客户端是否能识别特定的属性。 还有一个作用就是在协议多路复用时候也可以将其作为判断标志之一。

    Transaction ID字段是个96位的标识符,用来区分不同的STUN传输事务。对于request/response传输,事务ID由客户端选择,服务器收到后以同样的事务ID返回response;对于indication则由发送方自行选择。事务ID的主要功能是把request和response联系起来,同时也在防止攻击方面有一定作用。服务端也把事务ID当作一个Key来识别不同的STUN客户端,因此必须格式化且随机在0~2^(96-1)之间。

    重发同样的request请求时可以重用相同的事务ID,但是客户端进行新的传输时,必须选择一个新的事务ID。

    Message Length字段存储了信息的长度,以字节为单位,不包括20字节的STUN头部。由于所有的STUN属性都是都是4字节对齐(填充)的,因此这个字段最后两位应该恒等于零,这也是辨别STUN包的一个方法之一。

    2、STUN属性

    在STUN报文头部之后,通常跟着0个或者多个属性,每个属性必须是TLV编码的(Type-Length-Value)。其中Type字段和Length字段都是16位,Value字段为为32位表示,如下:

     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |         Type                  |            Length             |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         Value (variable)                ....
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

    Length字段必须包含Value部分需要补齐的长度,以字节为单位。由于STUN属性以32bit边界对齐,因此属性内容不足4字节的都会以padding bit进行补齐。padding bit会被忽略,但可以是任何值。

    Type字段为属性的类型。任何属性类型都有可能在一个STUN报文中出现超过一次。除非特殊指定,否则其出现的顺序是有意义的:即只有第一次出现的属性会被接收端解析,而其余的将被忽略。 为了以后版本的拓展和改进,属性区域被分为两个部分。Type值在0x0000-0x7FFF之间的属性被指定为强制理解,意思是STUN终端必须要理解此属性,否则将返回错误信息;而0x8000-0xFFFF 之间的属性为选择性理解,即如果STUN终端不识别此属性则将其忽略。目前STUN的属性类型由IANA维护。

    这里简要介绍几个常见属性的Value结构:

    • MAPPED-ADDRESS

    MAPPED-ADDRESS同时也是classic STUN的一个属性,之所以还存在也是为了前向兼容。其包含了NAT客户端的反射地址,Family为IP类型,即IPV4(0x01)或IPV6(0x02), Port为端口,Address为32位或128位的IP地址。注意高8位必须全部置零,而且接收端必须要将其忽略掉。

     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |0 0 0 0 0 0 0 0|    Family     |           Port                |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                                                               |
    |                 Address (32 bits or 128 bits)                 |
    |                                                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
    • XOR-MAPPED-ADDRESS

    XOR-MAPPED-ADDRESS和MAPPED-ADDRESS基本相同,不同点是反射地址部分经过了一次异或(XOR)处理。对于X-Port字段,是将NAT的映射端口以小端形式与magic cookie的高16位进行异或,再将结果转换成大端形式而得到的,X-Address也是类似。之所以要经过这么一次转换,是因为在实践中发现很多NAT会修改payload中自身公网IP的32位数据,从而导致NAT打洞失败。

     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |x x x x x x x x|    Family     |         X-Port                |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                X-Address (Variable)
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
    • ERROR-CODE

    ERROR-CODE属性用于error response报文中。其包含了300-699表示的错误代码,以及一个UTF-8格式的文字出错信息(Reason Phrase)。

     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |           Reserved, should be 0         |Class|     Number    |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |      Reason Phrase (variable)                                ..
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

    另外,错误代码在语义上还与SIP和HTTP协议保持一致。比如:

    • 300:尝试代替(Try Alternate),客户端应该使用该请求联系一个代替的服务器。这个错误响应仅在请求包括一个USERNAME属性和一个有效的MESSAGE-INTEGRITY属性时发送;否则它不会被发送,而是发送错误代码为400的错误响应;
    • 400:错误请求(Bad Request),请求变形了,客户端在修改先前的尝试前不应该重试该请求。
    • 401:未授权(Unauthorized),请求未包括正确的资格来继续。客户端应该采用一个合适的资格来重试该请求。
    • 420:未知属性(Unknown Attribute),服务器收到一个STUN包包含一个强制理解的属性但是它不会理解。服务器必须将不认识的属性放在错误响应的UNKNOWN-ATTRIBUTE属性中。
    • 438:过期Nonce(Stale Nonce),客户端使用的Nonce不再有效,应该使用响应中提供的Nonce来重试。
    • 500:服务器错误(Server Error),服务器遇到临时错误,客户端应该再次尝试。

    此外还有很多属性,如USERNAME,NONCE,REALM,SOFTWARE等,具体可以翻阅RFC3489

     

    三、STUN 通信过程

    1. 产生一个Request或Indication

    当产生一个Request或者Indication报文时,终端必须根据上文提到的规则来生成头部,class字段必须是Request或者Indication,而method字段为Binding或者其他用户拓展的方法。属性部分选择该方法所需要的对应属性,比如在一些情景下我们会需要authenticaton属性或FINGERPRINT属性,注意在发送Request报文时候,需要加上SOFTWARE属性(内含软件版本描述)。

    2、发送Requst或Indication

    目前,STUN报文可以通过UDP,TCP以及TLS-over-TCP的方法发送,其他方法在以后也会添加进来。STUN的使用者必须指定其使用的传输协议,以及终端确定接收端IP地址和端口的方式,比如通过基于DNS的方法来确定服务器的IP和端口。

    2.1 通过UDP发送

    当使用UDP协议运行STUN时,STUN的报文可能会由于网络问题而丢失。可靠的STUN请求/响应传输是通过客户端重发request请求来实现的,因此,在UDP运行时,Indication报文是不可靠的。STUN客户端通过RTO(Retransmission TimeOut)来决定是否重传Requst,并且在每次重传后将RTO翻倍。具体重传时间的选取可以参考相关文章,如RFC2988。重传直到接收到Response才停止,或者重传次数到达指定次数Rc,Rc应该是可配置的,且默认值为7。

    2.2 通过TCP或者TCP-over-TLS发送

    对于这种情景,客户端打开对服务器的连接。在某些情况下,此TCP链接只传输STUN报文,而在其他拓展中,在一个TCP链接里可能STUN报文和其他协议的报文会进行多路复用(Multiplexed)。数据传输的可靠性由TCP协议本身来保证。值得一提的是,在一次TCP连接中,STUN客户端可能发起多个传输,有可能在前一个Request的Response还没收到时就再次发送了一个新的Request,因此客户端应该保持TCP链接打开,认所有STUN事务都已完成。

    3. 接收STUN消息

    当STUN终端接收到一个STUN报文时,首先检查报文的规则是否合法,即前两位是否为0,magic cookie是否为0x2112A442,报文长度是否正确以及对应的方法是否支持。如果消息类别为Success/Error Response,终端会检测其事务ID是否与当前正在处理的事务ID相同。如果使用了FINGERPRINT拓展的话还会检查FINGERPRINT属性是否正确。完成身份认证检查之后,STUN终端会接着检查其余未知属性。

    3.1 处理Request

    如果请求包含一个或者多个强制理解的未知属性,接收端会返回error response,错误代码420(ERROR-CODE属性),而且包含一个UNKNOWN-ATTRIBUTES属性来告知发送方哪些强制理解的属性是未知的。服务端接着检查方法和其他指定要求,如果所有检查都成功,则会产生一个Success Response给客户端。

    • 3.1.1 生成Success Response或Error Response

      • 如果服务器通过某种验证方法(authentication mechanism)通过了请求方的验证,那么在响应报文里最好也加上对应的验证属性。
      • 服务器端也应该加上指定方法所需要的属性信息,另外协议建议服务器返回时也加上SOFTWARE属性。
      • 对于Binding方法,除非特别指明,一般不要求进行额外的检查。当生成Success Response时,服务器在响应里加上XOR-MAPPED-ADDRESS属性。对于UDP,这是其源IP和端口信息,对于TCP或TLS-over-TCP,这就是服务器端所看见的此次TCP连接的源IP和端口。
    • 3.1.2 发送Success Response或Error Response

      • 发送响应时候如果是用UDP协议,则发往其源IP和端口,如果是TCP则直接用相同的TCP链接回发即可。

    3.2 处理Indication

    如果Indication报文包含未知的强制理解属性,则此报文会被接收端忽略并丢弃。如果对Indication报文的检查都没有错误,则服务端会进行相应的处理,但是不会返回Response。对于Binding方法,一般不需要额外的检查或处理。收到信息的服务端仅需要刷新对应NAT的端口绑定。

    由于Indication报文在用UDP协议传输时不会进行重传,因此发送方也不需要处理重传的情况。

    3.3 处理Success Response

    如果Success Response包含了未知的强制理解属性,则响应会被忽略并且认为此次传输失败。客户端对报文进行检查通过之后,就可以开始处理此次报文。

    以Binding方法为例,客户端会检查报文中是否包含XOR-MAPPED-ADDRESS属性,然后是地址类型,如果是不支持的地址类型,则这个属性会被忽略掉。

    3.4 处理Error Response

    如果Error Response包含了未知的强制理解属性,或者没有包含ERROR-CODE属性,则响应会被忽略并且认为此次传输失败。随后客户端会对验证方法进行处理,这有可能会产生新的传输。

    • 到目前为止,对错误响应的处理主要基于ERROR-CODE属性的值,并遵循如下规则:

      • 如果error code在300到399之间,客户端被建议认为此次传输失败,除非用了ALTERNATE-SERVER拓展;
      • 如果error code在400到499之间,客户端认为此次传输失败;
      • 如果error code在500到599之间,客户端可能会需要重传请求,并且必须限制重传的次数。

    任何其他的error code值都会导致客户端认为此次传输失败。

     

    四、后记

    上面只是介绍了STUN/RFC5389协议的基础部分,协议本身还包含了许多mechanism,如身份验证(Authentication),DNS Discovery,FINGERPRINT Mechanisms,ALTERNATE-SERVER Mechanism等, 身份验证又分为长期验证和短期验证,从而保证了传输的灵活性并减少服务器的负担。

     

     

    展开全文
  • P2P通信标准协议: TURN

    2016-10-25 14:54:01
    上一篇P2P通信标准协议(一)介绍了在NAT上进行端口绑定的通用规则,应用程序可以根据这个协议来设计网络以外的通信。 但是,STUN/RFC5389协议里能处理的也只有市面上大多数的Cone NAT(关于NAT类型可以参照P2P通信...

    上一篇P2P通信标准协议(一)介绍了在NAT上进行端口绑定的通用规则,应用程序可以根据这个协议来设计网络以外的通信。
    但是,STUN/RFC5389协议里能处理的也只有市面上大多数的Cone NAT(关于NAT类型可以参照P2P通信原理与实现),
    对于Symmetric NAT,传统的P2P打洞方法是不适用的。因此为了保证通信能够建立,我们可以在没办法的情况下用保证成功的中继方法(Relaying),
    虽然使用中继会对服务器负担加重,而且也算不上P2P,但是至少保证了最坏情况下信道的通畅,从而不至于受NAT类型的限制。TURN/RFC5766就是为此目的而进行的拓展。

    TURN简介

    TURN的全称为Traversal Using Relays around NAT,是STUN/RFC5389的一个拓展,主要添加了Relay功能。如果终端在NAT之后,
    那么在特定的情景下,有可能使得终端无法和其对等端(peer)进行直接的通信,这时就需要公网的服务器作为一个中继,
    对来往的数据进行转发。这个转发的协议就被定义为TURN。TURN和其他中继协议的不同之处在于,它允许客户端使用同一个中继地址(relay address)
    与多个不同的peer进行通信。

    使用TURN协议的客户端必须能够通过中继地址和对等端进行通讯,并且能够得知每个peer的的IP地址和端口(确切地说,应该是peer的服务器反射地址)。
    而这些行为如何完成,是不在TURN协议范围之内的。其中一个可用的方式是客户端通过email来告知对等端信息,
    另一种方式是客户端使用一些指定的协议,如“introduction” 或 “rendezvous”,详见RFC5128

    如果TURN使用于ICE协议中,relay地址会作为一个候选,由ICE在多个候选中进行评估,选取最合适的通讯地址。一般来说中继的优先级都是最低的。
    TURN协议被设计为ICE协议(Interactive Connectivity Establishment)的一部分,而且也强烈建议用户在他们的程序里使用ICE,但是也可以独立于ICE的运行。
    值得一提的是,TURN协议本身是STUN的一个拓展,因此绝大部分TURN报文都是STUN类型的,作为STUN的一个拓展,TURN增加了新的方法(method)和属性(attribute)。
    因此阅读本章时最好先了解一下STUN协议

    操作概述

    在典型的情况下,TURN客户端连接到内网中,并且通过一个或者多个NAT到达公网,TURN服务器架设在公网中,不同的客户端以TURN服务器为中继和其他peer进行通信,如下图所示:

                                            Peer A
                                            Server-Reflexive    +---------+
                                            Transport Address   |         |
                                            192.0.2.150:32102   |         |
                                                |              /|         |
                              TURN              |            / ^|  Peer A |
        Client’s              Server            |           /  ||         |
        Host Transport        Transport         |         //   ||         |
        Address               Address           |       //     |+---------+
       10.1.1.2:49721       192.0.2.15:3478     |+-+  //     Peer A
                |               |               ||N| /       Host Transport
                |   +-+         |               ||A|/        Address
                |   | |         |               v|T|     192.168.100.2:49582
                |   | |         |               /+-+
     +---------+|   | |         |+---------+   /              +---------+
     |         ||   |N|         ||         | //               |         |
     | TURN    |v   | |         v| TURN    |/                 |         |
     | Client  |----|A|----------| Server  |------------------|  Peer B |
     |         |    | |^         |         |^                ^|         |
     |         |    |T||         |         ||                ||         |
     +---------+    | ||         +---------+|                |+---------+
                    | ||                    |                |
                    | ||                    |                |
                    +-+|                    |                |
                       |                    |                |
                       |                    |                |
                 Client’s                   |            Peer B
                 Server-Reflexive    Relayed             Transport
                 Transport Address   Transport Address   Address
                 192.0.2.1:7000      192.0.2.15:50000     192.0.2.210:49191
    

    在上图中,左边的TURN Client是位于NAT后面的一个客户端(内网地址是10.1.1.2:49721),连接公网的TURN服务器(默认端口3478)后,
    服务器会得到一个Client的反射地址(Reflexive Transport Address, 即NAT分配的公网IP和端口)192.0.2.1:7000,
    此时Client会通过TURN命令创建或管理ALLOCATION,allocation是服务器上的一个数据结构,包含了中继地址的信息。
    服务器随后会给Client分配一个中继地址,即图中的192.0.2.15:50000,另外两个对等端若要通过TURN协议和Client进行通信,
    可以直接往中继地址收发数据即可,TURN服务器会把发往指定中继地址的数据转发到对应的Client,这里是其反射地址。

    Server上的每一个allocation都唯一对应一个client,并且只有一个中继地址,因此当数据包到达某个中继地址时,服务器总是知道应该将其转发到什么地方。
    但值得一提的是,一个Client可能在同一时间在一个Server上会有多个allocation,这和上述规则是并不矛盾的。

    传输

    在协议中,TURN服务器与peer之间的连接都是基于UDP的,但是服务器和客户端之间可以通过其他各种连接来传输STUN报文,
    比如TCP/UDP/TLS-over-TCP. 客户端之间通过中继传输数据时候,如果用了TCP,也会在服务端转换为UDP,因此建议客户端使用
    UDP来进行传输. 至于为什么要支持TCP,那是因为一部分防火墙会完全阻挡UDP数据,而对于三次握手的TCP数据则不做隔离.

    分配(Allocations)

    要在服务器端获得一个中继分配,客户端须使用分配事务. 客户端发送分配请求(Allocate request)到服务器,然后服务器
    返回分配成功响应,并包含了分配的地址.客户端可以在属性字段描述其想要的分配类型(比如生命周期).由于中继数据实现了
    安全传输,服务器会要求对客户端进行验证,主要使用STUN的long-term credential mechanism.

    一旦中继传输地址分配好,客户端必须要将其保活.通常的方法是发送刷新请求(Refresh request)到服务端.这在TURN
    中是一个标准的方法.刷新频率取决于分配的生命期,默认为10分钟.客户端也可以在刷新请求里指定一个更长的生命期,
    而服务器会返回一个实际上分配的时间. 当客户端想中指通信时,可以发送一个生命期为0的刷新请求.

    服务器和客户端都保存有一个成为五元组(5-TUPLE)的信息,比如对于客户端来说,五元组包括客户端本地地址/端口,服务器地址/端口,
    和传输协议;服务器也是类似,只不过将客户端的地址变为其反射地址,因为那才是服务器所见到的. 服务器和客户端在分配
    请求中都带有5-TUPLE信息,并且也在接下来的信息传输中使用,因此彼此都知道哪一次分配对应哪一次传输.

    TURN                                 TURN           Peer          Peer
    client                               server          A             B
      |-- Allocate request --------------->|             |             |
      |                                    |             |             |
      |<--------------- Allocate failure --|             |             |
      |                 (401 Unauthorized) |             |             |
      |                                    |             |             |
      |-- Allocate request --------------->|             |             |
      |                                    |             |             |
      |<---------- Allocate success resp --|             |             |
      |            (192.0.2.15:50000)      |             |             |
      //                                   //            //            //
      |                                    |             |             |
      |-- Refresh request ---------------->|             |             |
      |                                    |             |             |
      |<----------- Refresh success resp --|             |             |
      |                                    |             |             |
    

    如上图所示,客户端首先发送Allocate请求,但是没带验证信息,因此STUN服务器会返回error response,客户端收到错误后加上
    所需的验证信息再次请求,才能进行成功的分配.

    发送机制(Send Mechanism)

    client和peer之间有两种方法通过TURN server交换应用信息,第一种是使用SendData方法(method),第二种是使用
    通道(channels),两种方法都通过某种方式告知服务器哪个peer应该接收数据,以及服务器告知client数据来自哪个peer.

    Send Mechanism使用了Send和Data指令(Indication).其中Send指令用来把数据从client发送到server,而Data指令用来把数据从
    server发送到client.当使用Send指令时,客户端发送一个Send Indication到服务端,其中包含:

    • XOR-PEER-ADDRESS属性,指定对等端的(服务器反射)地址.
    • DATA属性,包含要传给对等端的信息.

    当服务器收到Send Indication之后,会将DATA部分的数据解析出来,并将其以UDP的格式转发到对应的端点去,并且在封装
    数据包的时候把client的中继地址作为源地址.从而从对等端发送到中继地址的数据也会被服务器转发到client上.
    值得一提的是,Send/Data Indication是不支持验证的,因为长效验证机制不支持对indication的验证,因此为了防止攻击,
    TURN要求client在给对等端发送indication之前先安装一个到对等端的许可(permission),如下图所示,client到Peer B
    没有安装许可,导致其indication数据包将被服务器丢弃,对于peer B也是同样:

    TURN                                 TURN           Peer          Peer
    client                               server          A             B
      |                                    |             |             |
      |-- CreatePermission req (Peer A) -->|             |             |
      |<-- CreatePermission success resp --|             |             |
      |                                    |             |             |
      |--- Send ind (Peer A)-------------->|             |             |
      |                                    |=== data ===>|             |
      |                                    |             |             |
      |                                    |<== data ====|             |
      |<-------------- Data ind (Peer A) --|             |             |
      |                                    |             |             |
      |                                    |             |             |
      |--- Send ind (Peer B)-------------->|             |             |
      |                                    | dropped     |             |
      |                                    |             |             |
      |                                    |<== data ==================|
      |                            dropped |             |             |
      |                                    |             |             |
    

    TURN支持两种方式来创建许可,比如其中一种就是发送CreatePermission request.

    信道机制(Channels)

    对于一些应用程序,比如VOIP(Voice over IP),在Send/Data Indication中多加的36字节格式信息会加重客户端和服务端
    之间的带宽压力.为改善这种情况,TURN提供了第二种方法来让client和peer交互数据.该方法使用另一种数据包格式,
    ChannelData message,信道数据报文. ChannelData message不使用STUN头部,而使用一个4字节的头部,包含了
    一个称之为信道号的值(channel number).每一个使用中的信道号都与一个特定的peer绑定,即作为对等端地址的一个记号.

    要将一个信道与对等端绑定,客户端首先发送一个信道绑定请求(ChannelBind Request)到服务器,并且指定一个未绑定的信道号以及对等端的地址信息.
    绑定后client和server都能通过ChannelData message来发送和转发数据.信道绑定默认持续10分钟,并且可以通过重新发送
    ChannelBind Request来刷新持续时间.和Allocation不同的是,并没有直接删除绑定的方法,只能等待其超时自动失效.

    TURN                                 TURN           Peer          Peer
    client                               server          A             B
      |                                    |             |             |
      |-- ChannelBind req ---------------->|             |             |
      | (Peer A to 0x4001)                 |             |             |
      |                                    |             |             |
      |<---------- ChannelBind succ resp --|             |             |
      |                                    |             |             |
      |-- [0x4001] data ------------------>|             |             |
      |                                    |=== data ===>|             |
      |                                    |             |             |
      |                                    |<== data ====|             |
      |<------------------ [0x4001] data --|             |             |
      |                                    |             |             |
      |--- Send ind (Peer A)-------------->|             |             |
      |                                    |=== data ===>|             |
      |                                    |             |             |
      |                                    |<== data ====|             |
      |<------------------ [0x4001] data --|             |             |
      |                                    |             |             |
    

    上图中0x4001为信道号,即ChannelData message的头部中头2字节,值得一提的是信道号的选取有如下要求:

    • 0x0000-0x3FFF : 这一段的值不能用来作为信道号
    • 0x4000-0x7FFF : 这一段是可以作为信道号的值,一共有16383种不同值在目前来看是足够用的
    • 0x8000-0xFFFF : 这一段是保留值,留给以后使用

    还是那句老话,关于协议具体的细节可以去翻阅RFC5766的草稿,其中每个属性以及其格式都介绍得很详细.

    实例

    在上一章也提到过,因为RFC是标准协议,因此实现上往往有良好的兼容性和拓展性.现存的开源P2P应用程序,
    如果按照标准来设计,可以很容易与之对接.其中比较著名的就是PJSIP,PJSIP是一个开源的多媒体
    通信库,实现了许多标准协议,如SIP, SDP, RTP, STUN, TURN 和 ICE. 当然我们也能自己实现.比如GitHub
    上的TurnServer就是其中一个对TURN服务端的实现.下面在局域网环境下对TURN数据包进行
    简要分析.首先有如下机器情况:

    • TurnServer运行在192.168.1.110,使用默认端口3478,采用用户名和密码验证,其中用户名为pannzh,密码123456
    • TurnClient运行在192.168.1.106,为了方便,令peer也在192.168.1.106运行,端口为59593

    这里使用wireshark来抓包分析,关于wireshark的简介可以参照我之前的文章

    后记

    本来打算这篇介绍完TURN和ICE的,不过后来发现内容实在有点多,即便是只粗略介绍.因此只能把ICE协议的介绍留在下一篇来说了.
    TURN协议因为是STUN的拓展,当然也沿袭了STUN的工具性质,只为穿越NAT提供方法,而不作为P2P通信的完整解决方案.一个比较适合
    研究的TurnServer源代码我也放到这里了,而客户端的实现则根据每个人的具体需求而不同,因此不再赘述.

    展开全文
  • P2P通信标准协议: STUN

    2016-10-25 14:57:24
    前一段时间在P2P通信原理与实现中介绍了P2P打洞的基本原理和方法,我们可以根据其原理为自己的网络程序设计一套通信规则, ...本文就来介绍一下当前主要应用于P2P通信的几个标准协议,主要有STUN/RFC3489,
  • P2P通信标准协议:SIP

    2016-10-25 17:08:59
    在前面几篇文章中我们介绍了建立p2p通信的一般协议(簇),以及一种完整的NAT传输解决方案ICE, 但是对于多用户的通信情况,还有一些通用协议来实现标准化的管理,如之前讲过的SDP和SIP等,SIP(Session Initiation ...
  • P2P通信标准协议: ICE

    2016-10-25 17:04:47
    P2P通信标准协议:STUN中,介绍了TURN的基本交互流程,在上篇结束部分也有说到,TURN作为STUN 协议的一个拓展,保持了STUN的工具性质,而不作为完整的NAT传输解决方案,只提供穿透NAT的功能, 并且由具体的应用程序来...
  • P2P通信标准协议(二)之TURN

    千次阅读 2016-11-16 11:05:50
    上一篇P2P通信标准协议(一)介绍了在NAT上进行端口绑定的通用规则,应用程序可以根据这个协议来设计网络以外的通信。 但是,STUN/RFC5389协议里能处理的也只有市面上大多数的Cone NAT(关于NAT类型可以参照P2P通信...
  • P2P通信标准协议(一)之STUN

    千次阅读 2016-11-16 11:04:59
    前一段时间在P2P通信原理与实现中介绍了P2P打洞的基本原理和方法,我们可以根据其原理为自己的网络程序设计一套通信规则, ...本文就来介绍一下当前主要应用于P2P通信的几个标准协议,主要有STUN/RFC3489
  • 对等式网络(peer-to-peer,简称P2P),又称点对点技术,是一种实现网络中...本文讨论如何跨越NAT实现网络中的主机直接通信的问题,研究与实现基于TCP/UDP的P2P网络通信协议。 关键词:P2P,点对点,网络通信,NAT...
  • P2P通信标准协议(三)之ICE

    千次阅读 2016-11-16 11:06:38
    P2P通信标准协议(二)中,介绍了TURN的基本交互流程,在上篇结束部分也有说到,TURN作为STUN 协议的一个拓展,保持了STUN的工具性质,而不作为完整的NAT传输解决方案,只提供穿透NAT的功能, 并且由具体的应用程序来使用...
  • 在前面几篇文章中我们介绍了建立p2p通信的一般协议(簇),以及一种完整的NAT传输解决方案ICE, 但是对于多用户的通信情况,还有一些通用协议来实现标准化的管理,如之前讲过的SDP和SIP等,SIP(Session Initiation Protocol...
  • Bitmessage是一种P2P通信协议,用于将加密的消息发送给其他人或许多订户。 它是分散的和不可信的,这意味着您不需要-固有地信任任何实体,例如根证书颁发机构。 它使用强身份验证,这意味着不能欺骗邮件的发件人。 ...
  • 基于UDP协议P2P通信技术的分析及实现 基于UDP协议P2P通信技术的分析及实现 基于UDP协议P2P通信技术的分析及实现
  • 针对各种基于流言机制的通信协议在构造非结构化P2P覆盖网络中,其适用范围缺乏广泛的实证问题,提出了一个通用的协议框架——基于流言机制的节点采样服务,以执行高效和可靠的节点采样服务。在这种协议框架下通过...
  • 通信协议,顾名思义就是在网络中终端直接进行通信时所要遵循的规则、要求,如连接到Internet有TCP/IP协议,在局域网中有IPX/SPX协议。在上一篇介绍了Socket通信方式,无论TCP还是UDP,都必须是一端先等待消息,接收...
  • 通过分析TNC架构的不足,提出一种增强的无线TNC证实模型并设计模型下的相关协议。通过分析,该模型有较高安全性和效率,具有一定的匿名性,适合于无线接入环境,同时能够兼容不含可信芯片的无线终端接入。
  • 比特币的P2P网络协议

    万次阅读 2019-04-18 09:32:37
    比特币的P2P网络协议 ... 一、五层网络模型 网络的五层模型可以分为:物理层、数据链路层、网络层、传输层、应用层。 ...网络的实际物理介质,同轴电缆、...其中以太网是局域网最通用的通信协议标准。 网络层: 也叫...
  • P2P通信算法

    2019-08-16 16:01:13
    2.协议:基于 TCP 构建,主网默认通信端口为 8333。 3.网络拓扑:比特币全节点组成的网络是一种全分布式的拓扑结构,节点与节点之间的传输过程更接近“泛洪算法”,即:交易从某个节点产生,接着广播到临近节点,...
  • 根据IP地址可在局域网中通信(TCP),传递消息。VS2008编写

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 939
精华内容 375
热门标签
关键字:

p2p通信协议