默認
打賞 發表評論 1
想開發IM:買成品怕坑?租第3方怕貴?找開源自已擼?盡量別走彎路了... 找站長給點建議
P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)
閱讀(7940) | 評論(1 收藏4 淘帖1 2
微信掃一掃關注!

原作者:黃日成,手Q游戲中心后臺開發,騰訊高級工程師。從事C++服務后臺開發4年多,主要負責手Q游戲中心后臺基礎系統、復雜業務系統開發,主導過手Q游戲公會、企鵝電競App-對戰系統等項目的后臺系統設計,有豐富的后臺架構經驗。


1、引言


接本系列的上一篇《P2P技術詳解(二):P2P中的NAT穿越(打洞)方案詳解(基本原理篇)》,本篇將深入分析各種NAT穿越(打洞)方案的技術實現原理和數據交互過程,希望能助你透徹理解它們。

本文作者的其它幾篇文章或許你也感興趣:


* 閱讀注意:本文屬高階文章,在你了解P2P基礎原理或還未讀過本系列前幾篇之前,請慎讀本篇,否則讀完要砸電腦,我也拉不住 ....

2、《P2P技術詳解》系列文章


本文是《P2P理論詳解》系列文章中的第3篇,總目錄如下:


P2P相關的其它資源:


另外,如果你覺得本文對網絡通信的基礎知識講的不夠系統話,可繼續看看下面這些精華文章大餐。

➌ 網絡編程基礎知識:


➍ 如果覺得上面的文章枯燥,則《網絡編程懶人入門》系列可能是你的菜:


➎ 如果感到自已已經很牛逼了,《不為人知的網絡編程》應該是你菜:


➏ 如果看完上面的文章還是躁動不安,那看看《高性能網絡編程系列》吧:

3、NAT和NAPT


網絡地址轉換(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。

4、NAT帶來的問題


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兩個節點間的直接通信,在資源消耗和效率上都是比較好的。

5、P2P通信穿越NAT的技術、方法


目前常見的P2P通信穿越NAT的技術、方法主要有:

  • 1)應用層網關;
  • 2)中間件技術;
  • 3)打洞技術(Hole Punching);
  • 4)Relay(服務器中轉)技術。

6、NAT穿越技術1:應用層網關


應用層網關(ALG)是解決NAT對應用層協議無感知的一個最常用方法,已經被NAT設備廠商廣泛采用,成為NAT設備的一個必需功能。

6.1、原理


利用帶有ALG功能的NAT對特定應用層協議的支持和理解,在一個NAT網關檢測到新的連接請求時,需要判斷是否為已知的應用類型,這通常是基于連接的傳輸層端口信息來識別的。在識別為已知應用時,再調用相應功能對報文的深層內容進行檢查,當發現任何形式表達的IP地址和端口時,將會把這些信息同步轉換,并且為這個新連接創建一個附加的轉換表項。

這樣,當報文到達公網側的目的主機時,應用層協議中攜帶的信息就是NAT網關提供的地址和端口。

例如:下圖,對于使用主動模式的FTP協議(PORT方式),就需要AGL的支持了。

P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_1.png

由于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是無法連接上的)。

6.2、限制


ALG技術是利用NAT本身的支持來進行NAT的穿越,這個方案有很大限制,主要的原因是ALG都是為特定協議的特定規范版本而開發的,然而不管是協議本身,還是協議的數量都在變化,這就使得ALG適應性不強。

7、NAT穿越技術2:中間件技術


這是一種通過開發通用方法解決NAT穿越問題的努力。與前者不同之處是,AGL技術中NAT網關是這一解決方案的唯一參與者,而中間件技術中客戶端會參與網關公網映射信息的維護。UPnP就是這樣一種方法,UPnP中文全稱為通用即插即用,是一個通用的網絡終端與網關的通信協議,具備信息發布和管理控制的能力。

7.1、原理


NAT只要理解客戶端的請求并按照要求去分配響應的映射轉換表,不需要自己去分析客戶端的應用層數據。網關映射請求可以為客戶動態添加映射表項。

此時,NAT不再需要理解應用層攜帶的信息,只轉換IP地址和端口信息。而客戶端通過控制消息或信令發到公網側的信息中,直接攜帶公網映射的IP地址和端口,接收端可以按照此信息建立數據連接。NAT網關在收到數據或連接請求時,按照UPnP建立的表項只轉換地址和端口信息,不關心內容,再將數據轉發到內網。

7.2、限制


這種方案需要網關、內部主機和應用程序都支持UPnP技術,且組網允許內部主機和NAT網關之間可以直接交換UPnP信令才能實施。

8、NAT穿越技術3:打洞技術(Hole Punching)


Hole Punching技術是工作在運輸層的技術,可以屏蔽上層應用層的差異,并且不需要NAT網關特定的支持,因此其通用性比較強,應用性也比較廣。

8.1、原理


打洞技術的原理比較簡單,就是NAT內網的節點需要在NAT上建立自己的一條轉發映射關系(這就是所謂的在NAT上打下一個洞),然后外網的節點就通過這個”洞”來進行通信。為描述方便,我們將一對IP地址和端口信息的組合稱之為一個Endpoint。

打洞原理可以簡化為下面三個過程:

  • 1)首先位于NAT后的Peer1節點需要向外發送數據包,以便讓NAT建立起內網Endpoint1(IP1、PORT1)和外網Endpoint2(IP2、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映射規則和對外來數據包的過濾規則呢?

8.2、方法


由上面原理的討論我們知道,要實現打洞穿越NAT,首先需要知道NAT的行為規則(NAT的Endpoint映射規則和對外來數據包的過濾規則),這樣才能更好地實現打洞穿越。

那NAT有哪些行為類型?有什么辦法來偵測NAT的行為呢?

8.2.1NAT行為類型與偵測方法


NAT的行為類型和偵測方法是由STUN(首先在RFC3489中定義,英文全稱是Simple Traversal of UDP Through NATs)協議來描述的,STUN協議包括了RFC3489RFC5389RFC5780RFC5769幾個系列文檔。

早期的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.2NAT打洞過程


“打洞”方式穿越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的類型。

P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_2.png

反過來讓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位于同一個局域網。

P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_3.png

具體的穿越過程如下:

  • 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位于獨立的局域網。

P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_4.png

具體的穿越過程如下:

  • 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可能的映射端口列表也就是可能的外網EndpointGA1、EndpointGA2 ... EndpointGAn,然后讓Client B都往這些外網EndpointGA1、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后面。

P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_5.png

具體過程如下:

  • 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進行服務器中轉了。

9、NAT穿越技術4: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協議主要由RFC5766RFC6062RFC6156來描述,其中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丟棄。

P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_6-0.png

P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_6-1.png

9.1、UDP協議的Relay


首先介紹RFC5766,UDP協議的relay,主要有兩種方式:第一種是Send and Data methods,第二種是channels。下面分別介紹這兩個方式。

9.1.1方式一、Send and Data methods,具體交互過程如下:


P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_8-0.png

P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_8-1.png

P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_8-2.png

(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方式二、channels,具體交互過程如下:


P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_11-0.png

P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_11-1.png

P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_11-2.png

(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等額外的頭部信息,減少數據量。

9.2、TCP協議的Relay


TCP協議的relay是在RFC6062中描述,其中主要有兩種情況下的relay:1. Client to peer 2. Client to client。下面分別介紹兩種情況下relay。

9.2.1情況一、Client to peer,網絡拓撲如下:


P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_14.png

在上面的網絡拓撲下,有兩種方式的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監聽的端口才行。具體交互過程如下:
P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_15-0.png
P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_15-1.png
P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_15-2.png

(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后面,具體交互如下:
P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_16-1.png
P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_16-2.png
P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_16-3.png

(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情況二、Client to client,網絡拓撲結構如下:


P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_17.png

這種情況下,RFC6062文檔中并沒有講到,估計是因為這種情況是情況一的一個特例而已,我這里展開來講一下是希望能幫助大家更加深刻理解協議本身。

TURN Client1和TURN Client2(1)-(6)步驟的交互情況基本和上面的一樣,并且是比較獨立的,所以下面直接給出了。

TURN Client1 的(1)-(6)步驟交互情況如下:
P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_18-1.png
P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_18-2.png

TURN Client2 的(1)-(6)步驟交互情況如下:
P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_19-1.png
P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_19-2.png

從上面的交互可以知道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是數據交互的發起者,具體交互過程如下:
P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)_20.png

(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協議,這個也有一個文檔系列:


這個文檔系列較為復雜一些,有興趣的可以閱讀一下。

10、參考文獻


附錄:更多網絡編程相關文章


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請求嗎?
知乎技術分享:知乎千萬級并發的高性能長連接網關技術實踐
>> 更多同類文章 ……

即時通訊網 - 即時通訊開發者社區! 來源: - 即時通訊開發者社區!

上一篇:求助Android O上網絡通信socket recv 的長度為0的異常的問題下一篇:為什么Netty的ChannelOutboundHandler會聲明一個read方法

本帖已收錄至以下技術專輯

推薦方案
評論 1
太深了,看的云里霧里啊
打賞樓主 ×
使用微信打賞! 使用支付寶打賞!

返回頂部
股票配资平台都找股牛网