P2P通訊標準協議(三)之ICE

有價值炮灰發表於2015-12-20

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的文件進行仔細閱讀.這裡給出一些參考:

而具體的程式碼以及實現可以參考:

相關文章