P2P通信標准協議(二)之TURN


上一篇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的簡介可以參照我之前的文章細說中間人攻擊(一),
首先TurnClient發送Allocation請求:

allocation

可以看到第一次requst被服務器拒絕,因為后者要求nonce驗證信息,服務器的返回中包含了nonce信息,
除此之外還包含了ERROR-CODE,SOFTWARE,FINGERPRINT屬性.

error-nonce

在下一次request請求中,客戶端加上了收到的nonce,以及USERNAME和REALM等屬性,再次發送到TurnServer:

allocation2

服務器接收到了正確的allocation請求,於是返回succcess response,可以看到在返回中帶有默認的lifetime為1800秒,
XOR-MAPPED-ADDRESS以及XOR-RELAY-ADDRESS等屬性:

allocation-success

前文也說過,若要和peer進行通信,必須先創建一個許可,因此Client向服務器發送CreatePermission請求,其中攜帶了peer的信息:

createpermission

服務器如果通過驗證,就會返回success response,隨后Client可以通過上文說到的兩種方法與Peer進行通訊,比如下面的Send indication方法:

send-indication

通過對TurnServer發送indication告知數據的接收方以及數據內容讓TurnServer進行轉發,從而間接地向對等端發送DATA.
而從對等端來看,就是收到一個從client的relay地址192.168.1.110:65315到目的地址192.168.1.106:59593(即peer地址)的UDP數據包.

后記

本來打算這篇介紹完TURN和ICE的,不過后來發現內容實在有點多,即便是只粗略介紹.因此只能把ICE協議的介紹留在下一篇來說了.
TURN協議因為是STUN的拓展,當然也沿襲了STUN的工具性質,只為穿越NAT提供方法,而不作為P2P通信的完整解決方案.一個比較適合
研究的TurnServer源代碼我也放到了這里,,而客戶端的實現則根據每個人的具體需求而不同,因此不再贅述.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM