原做者:黃日成,手Q遊戲中心後臺開發,騰訊高級工程師。從事C++服務後臺開發4年多,主要負責手Q遊戲中心後臺基礎系統、複雜業務系統開發,主導過手Q遊戲公會、企鵝電競App-對戰系統等項目的後臺系統設計,有豐富的後臺架構經驗。php
接本系列的上一篇《P2P技術詳解(二):P2P中的NAT穿越(打洞)方案詳解(基本原理篇)》,本篇將深刻分析各類NAT穿越(打洞)方案的技術實現原理和數據交互過程,但願能助你透徹理解它們。html
本文做者的其它幾篇文章或許你也感興趣:git
《 鮮爲人知的網絡編程(一):淺析TCP協議中的疑難雜症(上篇)》
《 鮮爲人知的網絡編程(二):淺析TCP協議中的疑難雜症(下篇)》
《 鮮爲人知的網絡編程(五):UDP的鏈接性和負載均衡》
《 鮮爲人知的網絡編程(六):深刻地理解UDP協議並用好它》
* 閱讀注意:本文屬高階文章,在你瞭解P2P基礎原理或還未讀過本系列前幾篇以前,請慎讀本篇,不然讀完要砸電腦,我也拉不住 ....程序員
(本文同步發佈於:http://www.52im.net/thread-2872-1-1.html)github
➊ 本文是《P2P理論詳解》系列文章中的第3篇,總目錄以下:面試
《 P2P技術詳解(一):NAT詳解——詳細原理、P2P簡介》
《 P2P技術詳解(二):P2P中的NAT穿越(打洞)方案詳解(基本原理篇)》
《 P2P技術詳解(三):P2P中的NAT穿越(打洞)方案詳解(進階分析篇)》(本文)
《 P2P技術詳解(四):P2P技術之STUN、TURN、ICE詳解》
➋ P2P相關的其它資源:算法
《 通俗易懂:快速理解P2P技術中的NAT穿透原理》(* 適合入門)
《 最新收集NAT穿越(p2p打洞)免費STUN服務器列表 [附件下載]》
《 一款用於P2P開發的NAT類型檢測工具 [附件下載]》
另外,若是你以爲本文對網絡通訊的基礎知識講的不夠系統話,可繼續看看下面這些精華文章大餐。編程
➌ 網絡編程基礎知識:緩存
《 TCP/IP詳解 - 第11章·UDP:用戶數據報協議》
《 TCP/IP詳解 - 第17章·TCP:傳輸控制協議》
《 TCP/IP詳解 - 第18章·TCP鏈接的創建與終止》
《 TCP/IP詳解 - 第21章·TCP的超時與重傳》
《 通俗易懂-深刻理解TCP協議(上):理論基礎》
《 通俗易懂-深刻理解TCP協議(下):RTT、滑動窗口、擁塞處理》
《 理論經典:TCP協議的3次握手與4次揮手過程詳解》
《 理論聯繫實際:Wireshark抓包分析TCP 3次握手、4次揮手過程》
《 計算機網絡通信協議關係圖(中文珍藏版)》
《 腦殘式網絡編程入門(一):跟着動畫來學TCP三次握手和四次揮手》
《 腦殘式網絡編程入門(二):咱們在讀寫Socket時,究竟在讀寫什麼?》
《 腦殘式網絡編程入門(三):HTTP協議必知必會的一些知識》
《 腦殘式網絡編程入門(四):快速理解HTTP/2的服務器推送(Server Push)》
《 腦殘式網絡編程入門(五):天天都在用的Ping命令,它究竟是什麼?》
《 腦殘式網絡編程入門(六):什麼是公網IP和內網IP?NAT轉換又是什麼鬼?》
《 腦殘式網絡編程入門(七):面視必備,史上最通俗計算機網絡分層詳解》
➍ 若是以爲上面的文章枯燥,則《網絡編程懶人入門》系列多是你的菜:安全
《 網絡編程懶人入門(一):快速理解網絡通訊協議(上篇)》
《 網絡編程懶人入門(二):快速理解網絡通訊協議(下篇)》
《 網絡編程懶人入門(三):快速理解TCP協議一篇就夠》
《 網絡編程懶人入門(四):快速理解TCP和UDP的差別》
《 網絡編程懶人入門(五):快速理解爲何說UDP有時比TCP更有優點》
《 網絡編程懶人入門(六):史上最通俗的集線器、交換機、路由器功能原理入門》
《 網絡編程懶人入門(七):深刻淺出,全面理解HTTP協議》
《 網絡編程懶人入門(八):手把手教你寫基於TCP的Socket長鏈接》
《 網絡編程懶人入門(九):通俗講解,有了IP地址,爲什麼還要用MAC地址?》
《 網絡編程懶人入門(十):一泡尿的時間,快速讀懂QUIC協議》
➎ 若是感到自已已經很牛逼了,《鮮爲人知的網絡編程》應該是你菜:
《 鮮爲人知的網絡編程(一):淺析TCP協議中的疑難雜症(上篇)》
《 鮮爲人知的網絡編程(二):淺析TCP協議中的疑難雜症(下篇)》
《 鮮爲人知的網絡編程(三):關閉TCP鏈接時爲何會TIME_WAIT、CLOSE_WAIT》
《 鮮爲人知的網絡編程(四):深刻研究分析TCP的異常關閉》
《 鮮爲人知的網絡編程(五):UDP的鏈接性和負載均衡》
《 鮮爲人知的網絡編程(六):深刻地理解UDP協議並用好它》
《 鮮爲人知的網絡編程(七):如何讓不可靠的UDP變的可靠?》
《 鮮爲人知的網絡編程(八):從數據傳輸層深度解密HTTP》
《 鮮爲人知的網絡編程(九):理論聯繫實際,全方位深刻理解DNS》
➏ 若是看完上面的文章仍是躁動不安,那看看《高性能網絡編程系列》吧:
《 高性能網絡編程(一):單臺服務器併發TCP鏈接數到底能夠有多少》
《 高性能網絡編程(二):上一個10年,著名的C10K併發鏈接問題》
《 高性能網絡編程(三):下一個10年,是時候考慮C10M併發問題了》
《 高性能網絡編程(四):從C10K到C10M高性能網絡應用的理論探索》
《 高性能網絡編程(五):一文讀懂高性能網絡編程中的I/O模型》
《 高性能網絡編程(六):一文讀懂高性能網絡編程中的線程模型》
網絡地址轉換(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。
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兩個節點間的直接通訊,在資源消耗和效率上都是比較好的。
目前常見的P2P通訊穿越NAT的技術、方法主要有:
1)應用層網關;
2)中間件技術;
3)打洞技術(Hole Punching);
4)Relay(服務器中轉)技術。
應用層網關(ALG)是解決NAT對應用層協議無感知的一個最經常使用方法,已經被NAT設備廠商普遍採用,成爲NAT設備的一個必需功能。
利用帶有ALG功能的NAT對特定應用層協議的支持和理解,在一個NAT網關檢測到新的鏈接請求時,須要判斷是否爲已知的應用類型,這一般是基於鏈接的傳輸層端口信息來識別的。在識別爲已知應用時,再調用相應功能對報文的深層內容進行檢查,當發現任何形式表達的IP地址和端口時,將會把這些信息同步轉換,而且爲這個新鏈接建立一個附加的轉換表項。
這樣,當報文到達公網側的目的主機時,應用層協議中攜帶的信息就是NAT網關提供的地址和端口。
例如:下圖,對於使用主動模式的FTP協議(PORT方式),就須要AGL的支持了。
因爲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是沒法鏈接上的)。
ALG技術是利用NAT自己的支持來進行NAT的穿越,這個方案有很大限制,主要的緣由是ALG都是爲特定協議的特定規範版本而開發的,然而無論是協議自己,仍是協議的數量都在變化,這就使得ALG適應性不強。
這是一種經過開發通用方法解決NAT穿越問題的努力。與前者不一樣之處是,AGL技術中NAT網關是這一解決方案的惟一參與者,而中間件技術中客戶端會參與網關公網映射信息的維護。UPnP就是這樣一種方法,UPnP中文全稱爲通用即插即用,是一個通用的網絡終端與網關的通訊協議,具有信息發佈和管理控制的能力。
NAT只要理解客戶端的請求並按照要求去分配響應的映射轉換表,不須要本身去分析客戶端的應用層數據。網關映射請求能夠爲客戶動態添加映射表項。
此時,NAT再也不須要理解應用層攜帶的信息,只轉換IP地址和端口信息。而客戶端經過控制消息或信令發到公網側的信息中,直接攜帶公網映射的IP地址和端口,接收端能夠按照此信息創建數據鏈接。NAT網關在收到數據或鏈接請求時,按照UPnP創建的表項只轉換地址和端口信息,不關心內容,再將數據轉發到內網。
這種方案須要網關、內部主機和應用程序都支持UPnP技術,且組網容許內部主機和NAT網關之間能夠直接交換UPnP信令才能實施。
Hole Punching技術是工做在運輸層的技術,能夠屏蔽上層應用層的差別,而且不須要NAT網關特定的支持,所以其通用性比較強,應用性也比較廣。
打洞技術的原理比較簡單,就是NAT內網的節點須要在NAT上創建本身的一條轉發映射關係(這就是所謂的在NAT上打下一個洞),而後外網的節點就經過這個」洞」來進行通訊。爲描述方便,咱們將一對IP地址和端口信息的組合稱之爲一個Endpoint。
打洞原理能夠簡化爲下面三個過程:
1)首先位於NAT後的Peer1節點須要向外發送數據包,以便讓NAT創建起內網Endpoint1(IP一、PORT1)和外網Endpoint2(IP二、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映射規則和對外來數據包的過濾規則呢?
由上面原理的討論咱們知道,要實現打洞穿越NAT,首先須要知道NAT的行爲規則(NAT的Endpoint映射規則和對外來數據包的過濾規則),這樣才能更好地實現打洞穿越。
那NAT有哪些行爲類型?有什麼辦法來偵測NAT的行爲呢?
8.2.1 NAT行爲類型與偵測方法
NAT的行爲類型和偵測方法是由STUN(首先在RFC3489中定義,英文全稱是Simple Traversal of UDP Through NATs)協議來描述的,STUN協議包括了RFC3489、RFC5389、RFC5780、RFC5769幾個系列文檔。
早期的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.2 NAT打洞過程
「打洞」方式穿越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的類型。
反過來讓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位於同一個局域網。
具體的穿越過程以下:
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位於獨立的局域網。
具體的穿越過程以下:
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可能的映射端口列表也就是可能的外網EndpointGA一、EndpointGA2 ... EndpointGAn,而後讓Client B都往這些外網EndpointGA一、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後面。
具體過程以下:
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進行服務器中轉了。
因爲進行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協議主要由RFC5766、RFC6062、RFC6156來描述,其中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丟棄。
首先介紹RFC5766,UDP協議的relay,主要有兩種方式:第一種是Send and Data methods,第二種是channels。下面分別介紹這兩個方式。
9.1.1 方式1、Send and Data methods,具體交互過程以下:
(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 方式2、channels,具體交互過程以下:
(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等額外的頭部信息,減小數據量。
TCP協議的relay是在RFC6062中描述,其中主要有兩種狀況下的relay:1. Client to peer 2. Client to client。下面分別介紹兩種狀況下relay。
9.2.1 狀況1、Client to peer,網絡拓撲以下:
在上面的網絡拓撲下,有兩種方式的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監聽的端口才行。具體交互過程以下:
(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後面,具體交互以下:
(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 狀況2、Client to client,網絡拓撲結構以下:
這種狀況下,RFC6062文檔中並無講到,估計是由於這種狀況是狀況一的一個特例而已,我這裏展開來說一下是但願能幫助你們更加深入理解協議自己。
TURN Client1和TURN Client2(1)-(6)步驟的交互狀況基本和上面的同樣,而且是比較獨立的,因此下面直接給出了。
TURN Client1 的(1)-(6)步驟交互狀況以下:
TURN Client2 的(1)-(6)步驟交互狀況以下:
從上面的交互能夠知道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是數據交互的發起者,具體交互過程以下:
(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協議,這個也有一個文檔系列:
RFC 5245 - ICE
RFC 5768 – ICE–SIP
RFC 6336 – ICE–IANA Registry
RFC 6544 – ICE–TCP
RFC 5928 - TURN Resolution Mechanism
這個文檔系列較爲複雜一些,有興趣的能夠閱讀一下。
[1]《 P2P技術詳解(一):NAT詳解——詳細原理、P2P簡介》
[2]《 P2P技術詳解(二):P2P中的NAT穿越(打洞)方案詳解(基本原理篇)》
[3] 《 Peer-to-Peer Communication Across Network Address Translators》
《 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請求嗎?》
《 知乎技術分享:知乎千萬級併發的高性能長鏈接網關技術實踐》
>> 更多同類文章 ……
(本文同步發佈於:http://www.52im.net/thread-2872-1-1.html)