本文是《P2P理論詳解》系列文章中的第2篇,總目錄以下:
php
➋ P2P相關的其它資源:
html
另外,若是你以爲本文對網絡通訊的基礎知識講的不夠系統話,可繼續看看下面這些精華文章大餐。
➊ 網絡編程基礎知識:
編程
➋ 若是以爲上面的文章枯燥,則《網絡編程懶人入門》系列多是你的菜:
安全
➌ 若是感到自已已經很牛逼了,《鮮爲人知的網絡編程》應該是你菜:
服務器
➍ 若是看完上面的文章仍是躁動不安,那看看《高性能網絡編程系列》吧:
cookie
在現實Internet網絡環境中,大多數計算機主機都位於防火牆或NAT以後,只有少部分主機可以直接接入Internet。不少時候,咱們但願網絡中的兩臺主機可以直接進行通訊,即所謂的P2P通訊,而不須要其餘公共服務器的中轉。因爲主機可能位於防火牆或NAT以後,在進行P2P通訊以前,咱們須要進行檢測以確認它們之間可否進行P2P通訊以及如何通訊。這種技術一般稱爲NAT穿透(NAT Traversal)。最多見的NAT穿透是基於UDP的技術,如RFC3489中定義的STUN協議。
STUN,首先在RFC3489中定義,做爲一個完整的NAT穿透解決方案,英文全稱是Simple Traversal of UDP Through NATs,即簡單的用UDP穿透NAT。
在新的RFC5389修訂中把STUN協議定位於爲穿透NAT提供工具,而不是一個完整的解決方案,英文全稱是Session Traversal Utilities for NAT,即NAT會話穿透效用。RFC5389與RFC3489除了名稱變化外,最大的區別是支持TCP穿透。
TURN,首先在RFC5766中定義,英文全稱是Traversal Using Relays around NAT:Relay Extensions to Session Traversal Utilities for NAT,即便用中繼穿透NAT:STUN的擴展。簡單的說,TURN與STURN的共同點都是經過修改應用層中的私網地址達到NAT穿透的效果,異同點是TURN是經過兩方通信的「中間人」方式實現穿透。
ICE跟STUN和TURN不同,ICE不是一種協議,而是一個框架(Framework),它整合了STUN和TURN。網絡
瞭解STUN以前,咱們須要瞭解NAT的種類。
NAT對待UDP的實現方式有4種,分別以下:
併發
STUN(Simple Traversal of User Datagram Protocol Through Network Address Translators),即簡單的用UDP穿透NAT,是個輕量級的協議,是基於UDP的完整的穿透NAT的解決方案。它容許應用程序發現它們與公共互聯網之間存在的NAT和防火牆及其餘類型。它也可讓應用程序肯定NAT分配給它們的公網IP地址和端口號。STUN是一種Client/Server的協議,也是一種Request/Response的協議,默認端口號是3478。(IETF官方文檔RFC3489/STUN點此進入)
負載均衡
【Ø 消息頭】
全部的STUN消息都包含20個字節的消息頭,包括16位的消息類型,16位的消息長度和128位的事務ID。
<ignore_js_op>
消息類型許可的值以下:
框架
消息長度,是消息大小的字節數,但不包括20字節的頭部。事務ID,128位的標識符,用於隨機請求和響應,請求與其相應的全部響應具備相同的標識符。
【Ø 消息屬性】
消息頭以後是0或多個屬性,每一個屬性進行TLV編碼,包括16位的屬性類型、16位的屬性長度和變長屬性值。
<ignore_js_op>
屬性類型定義以下:
具體的ERROR-CODE(響應號),與它們缺省的緣由語句一塊兒,目前定義以下:
屬性空間分爲可選部分與強制部分,值超過0x7fff的屬性是可選的,即客戶或服務器即便不認識該屬性也可以處理該消息;值小於或等於0x7fff的屬性是強制理解的,即除非理解該屬性,不然客戶或服務器就不能處理該消息。
<ignore_js_op>
STUN協議的完整交互過程如上,下面咱們來介紹具體實現步驟。
通常狀況下,客戶會配置STUN服務器提供者的域名,該域名被解析爲IP地址和SRV過程的端口號。服務器名是「stun」,使用UDP協議發送捆綁請求,使用TCP協議發送共享私密請求。STUN協議的缺省端口號爲3478。
若要提供完整性檢查,STUN在客戶和服務器間使用128位的共享私密,做爲在捆綁請求和捆綁響應中的密匙。
首先,客戶經過發現過程得到它將與之創建TCP鏈接的IP地址和端口號。客戶打開該地址和端口的鏈接,開始TLS協商,驗證服務器的標識。客戶發送共享私密請求。該請求沒有屬性,只有頭。服務器生成響應。
客戶會在該鏈接上生成多個請求,但在得到用戶名和密碼後關閉該鏈接。
服務器收到共享私密請求,驗證從TLS鏈接上到達的該請求;若是不是經過TLS收到的請求,則生成共享私密錯誤響應,並設置ERROR-CODE屬性爲響應號433;這裏區分兩種狀況:若經過TCP收到請求,則錯誤響應經過收到請求的相同鏈接發送;若經過UDP收到請求,則錯誤響應發送回請求送出的源IP和端口。
服務器檢查請求中的任何屬性,當其中有不理解的小於或等於0x7fff的值,則生成共享私密錯誤響應,設置ERROR-CODE屬性爲響應號420,幷包括UNKNOWN-ATTRIBUTE屬性,列出它不理解的小於或等於0x7fff的屬性的值。該錯誤響應經過TLS鏈接發送。
若請求正確,服務器建立共享私密響應,包含與請求中相同的事務ID,幷包含USERNAME和PASSWORD屬性。用戶名在10分鐘內有效。
共享私密響應經過與收到請求的相同的TLS鏈接發送,服務器保持鏈接打開狀態,由客戶關閉它。
接着,客戶發送捆綁請求,攜帶的屬性包括:
客戶發送捆綁請求,經過客戶重傳來提供可靠性。客戶開始用100ms的間隔重傳,每次重傳間隔加倍,直至1.6秒。之間間隔1.6秒的重傳繼續,直到收到響應或總共已經發送了9次。所以,若9500ms後,還未收到響應,客戶認爲傳輸已經失敗。
服務器檢查捆綁請求的MESSAGE-INTEGRITY屬性,不存在則生成捆綁錯誤響應,設置ERROR-CODE屬性爲響應號401;若存在,計算請求的HMACKey值。
服務器檢查USERNAME屬性,不存在則生成捆綁錯誤響應,設置ERROR-CODE屬性爲響應號432;若存在,但不認識該USERNAME的共享私密(例如,它超時了),生成捆綁錯誤響應,設置ERROR-CODE屬性爲響應號430。
若服務器知道該共享私密,但所計算的HMAC與請求的不一樣,生成捆綁錯誤響應,設置ERROR-CODE屬性爲響應號431。
假設消息完整性檢查經過了,服務器檢查請求中的任何屬性的值,若遇到不理解的小於或等於0x7fff的值,生成捆綁錯誤響應,設置ERROR-CODE屬性爲響應號420,該響應包含UNKNOWN-ATTRIBUTE屬性,並列出不理解的小於或等於0x7fff的屬性。
若請求正確,服務器生成單個捆綁響應,包含與捆綁請求相同的事務ID。服務器在捆綁響應中加入MAPPED-ADDRESS屬性,該屬性的IP地址和端口號爲捆綁請求的源IP地址和端口號。
捆綁響應的源地址和端口號取決於捆綁請求中CHANGE-REQUEST屬性的值及捆綁請求收到的地址和端口號相關。總結以下:
<ignore_js_op>
服務器在捆綁響應中加入SOURCE-ADDRESS屬性,包含用於發送捆綁響應的源地址和端口號;加入CHANGED-ADDRESS屬性,包含源IP地址和端口號。
若是捆綁請求中包含了USERNAME和MESSAGE-INTEGRITY屬性,則服務器在捆綁響應中加入MESSAGE-INTEGRITY屬性。
若是捆綁請求包含RESPONSE-ADDRESS屬性,則服務器在捆綁響應中加入REFLECTED-FROM屬性:若是捆綁請求使用從共享私密請求得到的用戶名進行認證,則REFLECTED-FROM屬性包含共享私密請求到達的源IP地址和端口號;若請求中的用戶名不是使用共享私密分配的,則REFLECTED-FROM屬性包含得到該用戶名的實體的源IP地址和端口號;若請求中沒有用戶名,且服務器願意處理該請求,則REFLECTED-FROM屬性包含請求發出的源IP地址和端口號。
服務器不會重傳響應,可靠性經過客戶週期性地重發請求來保障,每一個請求都會觸發服務器進行響應。
客戶端判斷響應的類型是捆綁錯誤響應仍是捆綁響應。捆綁錯誤響應一般在請求發送的源地址和端口收到;捆綁響應一般在請求中的RESPONSE-ADDRESS屬性的地址和端口收到,若沒有該屬性,則捆綁響應將在請求發送的源地址和端口號收到。
客戶經過帶外方式得到STUN服務器信息後,就打開對應的地址和端口的鏈接,並開始與STUN服務器進行TLS協商。一旦打開了鏈接,客戶就經過TCP協議發送共享私密請求,服務器生成共享私密響應。STUN在客戶和服務器間使用共享私密,用做捆綁請求和捆綁響應中的密匙。以後,客戶使用UDP協議向STUN服務器發送捆綁請求,當捆綁請求消息到達服務器的時候,它可能通過了一個或者多個NAT。結果是STUN服務器收到的捆綁請求消息的源IP地址被映射成最靠近STUN服務器的NAT的IP地址,STUN服務器把這個源IP地址和端口號複製到一個捆綁響應消息中,發送回擁有這個IP地址和端口號的客戶端。
當STUN客戶端收到捆綁響應消息以後,它會將本身發送捆綁請求時綁定的本地IP地址和端口號同捆綁響應消息中的IP地址和端口號進行比較,若是不匹配,就表示客戶端正處於一個或者多個NAT的前面。
在Full-Cone NAT的狀況下,在捆綁響應消息中的IP地址和端口是屬於公網的,公網上的任何主機均可以使用這個IP地址和端口號向這個應用程序發送數據包,應用程序只須要在剛纔發送捆綁請求的IP地址和端口上監聽便可。
固然,客戶可能並不在一個Full-Cone NAT的前面,實際上,它並不知道本身在一個什麼類型的NAT的前面。爲了肯定NAT的類型,客戶端使用附加的捆綁請求。具體過程是很靈活的,但通常都會像下面這樣工做:客戶端再發送一個捆綁請求,此次發往另外一個IP地址,可是使用的是跟上一次同一個源IP地址和源端口號,若是返回的數據包裏面的IP地址和端口號和第一次返回的數據包中的不一樣,客戶端就會知道它是在一個對稱NAT的前面。客戶端爲了確認本身是否在一個徹底錐形NAT的前面,客戶端能夠發送一個帶有標誌的捆綁請求,這個標誌告訴服務器使用另外一個IP地址和端口發送捆綁響應。換句話說,若是客戶端使X/Y的IP地址端口對向A/B的IP地址端口對發送捆綁請求,服務器就會使用源IP地址和源端口號爲C/D的地址端口對向X/Y發送捆綁響應。若是客戶端收到了這個響應,它就知道它是在一個Full-Cone NAT前面。
STUN協議容許客戶端請求服務器從收到捆綁請求的IP地址往回發捆綁響應,可是要使用不一樣的端口號。這能夠用來檢查客戶端是否在Port Restricted Cone NAT的前面仍是在Restricted Cone NAT的前面。
STUN協議在RFC5389中被從新命名爲Session Traversal Utilities for NAT,即NAT會話穿透效用。在這裏,NAT會話穿透效用被定位爲一個用於其餘解決NAT穿透問題協議的協議。它能夠用於終端設備檢查由NAT分配給終端的IP地址和端口號。同時,它也被用來檢查兩個終端之間的鏈接性,比如是一種維持NAT綁定表項的保活協議。STUN能夠用於多種NAT類型,並不須要它們提供特殊的行爲。
STUN自己再也不是一種完整的NAT穿透解決方案,它至關因而一種NAT穿透解決方案中的工具。這是與RFC3489/STUN版本相比最重要的改變。
目前定義了三種STUN用途:
【Ø 消息頭】
STUN消息頭爲20字節,後面緊跟0或多個屬性。STUN頭部包含一STUN消息類型、magic cookie、事務ID和消息長度。
<ignore_js_op>
每一個STUN消息的最高位前2位必須爲0。當STUN協議爲多個協議多路複用時若使用的是同一個端口,這能夠用於與其餘協議區分STUN數據包。消息類型肯定消息的類別(如請求、成功迴應、失敗迴應、標誌)。雖然這裏有四種消息類型,但能夠分爲2類事務:請求/響應事務、標誌事務。
消息類型字段可進一步劃分爲下面結構:
<ignore_js_op>
消息類型定義以下:
魔術字域必須包含固定的值0x2112A442。在RFC3489中,該域是事務ID的一部分。配置魔術字容許服務器檢測客戶是否理解某些在改進的版本中增長的屬性。另外,還可用於STUN多路複用時與其餘協議的包進行區分。
96位的事務ID用於惟一的識別STUN事務。對於請求/響應事務,事務ID由STUN客戶端來選擇;對於標誌事務,由代理(代理指支持STUN的客戶端或服務器)來選擇併發送。它主要服務於與請求相關的響應,所以它也扮演着一個幫助阻止肯定類型的攻擊的角色。服務器使用事務ID來惟一的標識出全部客戶端的每個事務。事務ID自己必須是惟一的,而且隨機的從0到2的96-1次方中選擇。從新發送相同的請求時,也必須使用新的事務ID。成功或錯誤響應必須攜帶與相對應的請求相同的事務ID。
消息長度字段不包括20字節的STUN頭部。全部的STUN屬性必須填充爲4字節的倍數。消息長度字段的最後2位老是爲0,這爲區分STUN包與其餘協議的包提供了另一種方法。
【Ø 消息屬性】
STUN頭以後是0或多個屬性。每一個屬性都採用TLV編碼,16位的類型、16位的長度及可變長度的值。每一個STUN屬性必須是4字節邊界對齊。
<ignore_js_op>
屬性空間被劃分爲2個範圍。屬性的類型值在0x0000到0x7fff是強制理解屬性,這意味着除非STUN代理可以理解這些屬性,不然將不能正常處理包含該屬性的消息;屬性的類型值在0x8000到0xffff範圍是可選理解屬性,這意味着若是STUN代理不能理解它們的話這些屬性能夠被忽略。
STUN屬性類型集由IANA維護,具體定義詳見IETF官方文檔 RFC5389。
RFC5389與RFC3489的不一樣點以下:
FINGERPRINT機制是一種可選的用於其餘協議多路複用STUN時發送給相同的傳輸地址時區分STUN數據包的機制,該機制不支持與RFC3489相兼容。
在一些用途中,基於相同的傳輸地址時多個協議會多路複用STUN消息,例如RTP協議。STUN消息必須首先和應用報文分離開。目前,在STUN報頭中有3種固定的字段能夠用於該目的。儘管如此,在一些案例中,三種固定字段仍然不能充分的區別開。
當擴展的指紋機制被使用時,STUN代理在發送給其餘STUN代理的消息中包括FINGERPRINT屬性。當其餘STUN代理收到時,除基本的檢查以外,還將檢查是否包含FINGERPRINT屬性及它是否包含正確的值,至此,它將相信這是一個STUN消息。指紋機制幫助STUN代理檢查其餘協議那些看起來像是STUN消息的消息。
STUN客戶端可使用DNS來發現STUN服務器的IP地址和端口。客戶端必須知道服務器的域名。
當客戶端但願找出服務器在公網上的位置就採用捆綁請求/響應事務,SRV(資源記錄表)中服務器名稱是「stun」。當經過TLS會話採用捆綁請求/響應事務,SRV中服務器名稱爲「stuns」。STUN用戶能夠定義額外的DNS資源記錄服務名稱。
STUN請求的默認端口是3478,用於TCP和UDP。STUN在TLS上的默認端口是5349。服務器可以在TLS上運行STUN與STUN在TCP上時使用相同的端口,只有服務器軟件支持決定初始消息是不是TLS或STUN消息。
若是SRV中沒有記錄可查,客戶端執行A或AAAA記錄查找域名。結果將會是1張IP地址表,每個均可以使用TCP或UDP採用默認端口號鏈接。一般要求使用TLS,客戶端使用STUN在TLS上的默認端口號鏈接其中一個IP地址。
短時間證書機制
短時間證書機制假設在STUN事務以前,客戶端和服務器已經使用了其餘協議來交換了證書,以username和password形式。這個證書是有時間限制的。例如,在ICE用途中,兩個終端使用帶外方式交換信息來對username和password達成一致,並在媒體會話期間使用。這個證書被用來進行消息完整性檢查,用於每一個請求和多個響應中。與長期證書機制相比,沒有挑戰和響應方式,所以,這種證書的時間限制特性的優勢是能夠阻止重播。
長期證書機制
長期證書機制依賴於一個長期證書,username和password在客戶端和服務器中是共用的。這個證書從它提供給 用戶開始將一直是有效的,直到該用戶再也不是該系統的用戶。這本質上是一個提供給用戶username和password的傳統的登入方式。
客戶端初始發送一個請求,沒有提供任何證書和任何完整性檢測。服務器拒絕這個請求,並提供給用戶一個範圍(用於指導用戶或代理選擇username和password)和一個nonce。這個nonce提供重放保護。它是一個cookie,由服務器選擇,以這樣一種方式來標示有效時間或客戶端身份是有效的。客戶端重試這個請求,此次包括它的username和realm和服務器提供的nonce來回應。服務器確認這個nonce和檢查這個message integrity。若是它們匹配,請求則經過認證。若是這個nonce再也不有效,即過時了,服務器就拒絕該請求,並提供一個新的nonce。
在隨後的到同一服務器的請求,客戶端從新使用這個nonce、username和realm,和先前使用的password。這樣,隨後的請求不會被拒絕直到這個nonce變成無效的。須要注意的是,長期證書機制不能用來保護Indications,因爲Indications不能被改變,所以,使用Indications時要麼使用短時間證書,要麼就省略認證和消息完整性。由於長期證書機制對離線字典攻擊敏感,部署的時候應該使用很難猜想的密碼。
服務器使用加強的重定向功能將一個客戶端轉向另外一個服務器,經過迴應一個錯誤響應號爲300(嘗試備份)的錯誤響應。服務器在錯誤響應中攜帶一個ALTERNATE-SERVER屬性。
客戶端收到錯誤響應號爲300的錯誤響應後,在該響應中查找ALTERNATE-SERVER屬性。若找到一個,客戶端就會將當前的事務做廢,並從新嘗試發送請求到該屬性中列出的服務器。請求報文若已經經過認證,則必須使用與先前發送給執行重定向操做的服務器一樣的證書。若是客戶端在最後5分鐘裏已經重試發送請求時已經重定向到了一個服務器,它必須忽略重定向操做並將當前的事務做廢,這是爲了防止無限的重定向循環。
在RFC3489中:
客戶端想要與RFC3489的服務器互操做,應發送一個使用綁定方法的請求消息,不包含任何消息,使用UDP協議發送給服務器。若是成功,將收到服務器發回的包含MAPPED-ADDRESS屬性而不是XOR-MAPPED-ADDRESS屬性的成功響應。客戶端試圖與基於RFC3489的應用服務器互操做必須準備好接收任意一個屬性。此外,客戶端必須忽略任何在響應中出現的保留的強制理解的屬性。RFC3489中規定保留屬性中的0x000二、0x000四、0x0005和0x000B可能出如今綁定響應中。
服務器可以察覺由RFC3489中的客戶端發送的攜帶有不正確的魔術字的捆綁請求消息。當服務器察覺到RFC3489中的客戶端,它應該將捆綁請消息中魔術字域中的值拷貝到捆綁響應中的魔術字字段中,而且插入一個MAPPED-ADDRESS屬性代替XOR-MAPPED-ADDRESS屬性。
客戶端在極少的環境下可能包括RESPONSE-ADDRESS或CHANGE-REQUEST屬性中的一個。在這些狀況下,服務器把這些屬性看作是一個不認識的強制理解的屬性,並回應一個錯誤響應。RFC3489版本中的STUN缺乏魔術字和指紋屬性這兩種可以高可靠性的正確標識其餘協議多路複用時的STUN消息。所以,STUN執行與RFC3489兼容時不該該被用於多個協議。
TURN,在RFC5766中定義,英文全稱Traversal Using Relays around NAT(TURN):Relay Extensions to Session Traversal Utilities for NAT(STUN),即便用中繼穿透NAT:STUN的中繼擴展。簡單的說,TURN與STUN的共同點都是經過修改應用層中的私網地址達到NAT穿透的效果,異同點是TURN是經過兩方通信的「中間人」方式實現穿透。
若是一個主機位於NAT的後面,在某些狀況下它不可以與其餘主機點對點直接鏈接。在這些狀況下,它須要使用中間網點提供的中繼鏈接服務。TURN協議就是用來容許主機控制中繼的操做而且使用中繼與對端交換數據。TURN與其餘中繼控制協議不一樣的是它可以容許一個客戶端使用一箇中繼地址與多個對端鏈接。
TURN協議被設計爲ICE的一部分,用於NAT穿越,雖然如此,它也能夠在沒有ICE的地方單獨使用。
<ignore_js_op>
在一個典型組網中,一個TURN客戶端鏈接在一個私有網絡中,經過一個或多個NAT來鏈接到公網。在公網中有一個TURN服務器。在因特網的別處有一個或多個對端是這個TURN客戶端但願通信的。這些對端也有多是在一個或多個NAT的後面。該客戶端使用服務器做爲一箇中繼來發送數據包 到這些對端去,而且從這些對端接收數據包。
客戶端經過一個IP地址和端口的組合來與服務器創建會話。客戶端使用TURN命令在服務器上建立和操做一個ALLOCATION。一旦這個allocation建立好了,客戶端可以在數據發往哪一個對端的指示下發送應用數據到這個服務器,服務器將中繼這些數據到合適的對端。客戶端發送的應用數據包含在TURN消息中,服務器將數據提取出來,並以UDP數據包方式發送給對端。反向上,對端以UDP數據包方式發送應用數據到這個allocation提供的中繼傳輸地址。由於TURN消息老是包含客戶端與哪些對端通信的指示,客戶端可以使用單一的allocation來與多個對端通信。
具體協議細節,詳見IETF官方文檔:RFC5766.
以上圖爲例進行講解,每一個消息中,多個屬性包含在消息中並顯示它們的值。爲了方便閱讀,值以人們可讀的格式來顯示。
<ignore_js_op>
客戶端使用10.1.1.2:49271做爲傳輸地址向服務器的傳輸地址發送Allocate請求。客戶端隨機選擇一個96位的事務ID。該Allocate請求消息包括SOFTWARE屬性來提供客戶端的軟件版本信息;包括LIFETIME屬性,指明客戶端但願該allocation具備1小時的生命期而非缺省的10分鐘;包括REQUESTED-TRANSPORT屬性來告訴服務器與對端之間採用UDP協議來傳輸;包括DONT-FRAGMENT屬性由於客戶端但願在隨後的Send indications中使用DON’T-FRAGMENT屬性。
服務器須要任何請求必須是通過認證的,所以服務器拒絕了該最初的Allocation請求,而且迴應了攜帶有錯誤響應號爲401(未受權)的Allocate錯誤響應;該響應包括一個REALM屬性,指明認證的域;還包括一個NONCE屬性和一個SOFTWARE屬性。
客戶端收到了錯誤響應號爲401的Allocate錯誤響應,將從新嘗試發送Allocate請求,此時將包括認證屬性。客戶端在新的請求中從新選擇一個新的事務ID。客戶端包括一個USERNAME屬性,使用從服務器那收到的realm值來幫助它決定使用哪一個值;請求還包括REALM和NONCE屬性,這兩個屬性是從收到的錯誤響應中拷貝出來的。最後,客戶端包括一個MESSAGE-INTEGRITY屬性。
服務器收到認證的Allocate請求後,檢查每一個屬性是否正確;而後,產生一個allocation,並給客戶端迴應Allocate成功響應。服務器在該成功響應中攜帶一個LIFETIME屬性,本例中服務器將客戶端請求的1小時生命期減少爲20分鐘,這是由於這個特定的服務器可能不容許超過20分鐘的生命期;該響應包括XOR-RELAYED-ADDRESS屬性,值爲該allocation的中繼傳輸地址;該響應還包括XOR-MAPPED-ADDRESS屬性,值爲客戶端的server-reflexive地址;該響應也包含一個SOFTWARE屬性;最後,包括一個MESSAGE-INTEGRITY屬性來證實該響應,確保它的完整性。
<ignore_js_op>
接着,客戶端爲了準備向對端A發送一些應用數據而建立一個permission。這裏經過一個CreatePermission請求來作到。該請求攜帶XOR-PEER-ADDRESS屬性包含有肯定的請求的IP地址,這裏爲對端A的地址;須要注意的是,屬性中地址的端口號被設置爲0在CreatePermission請求中,而且客戶端使用的是對端A的server-reflexive地址而不是它的主機地址(私網地址);客戶端在該請求中攜帶與以前的Allocate請求中同樣的username、realm和nonce值,所以該請求被服務器承認。此時在該請求中,客戶端沒有攜帶SOFTWARE屬性。
服務器收到該CreatePermission請求,產生一個相應的許可,並以CreatePermission成功響應來回應。該響應中只包含了Transaction-ID和MESSAGE-INTEGRITY屬性。
<ignore_js_op>
如今客戶端使用Send indication來發送應用數據到對端A。對端的server-reflexive傳輸地址包含在XOR-PEER-ADDRESS屬性中,應用數據包含在DATA屬性中。客戶端已經在應用層上執行了路徑MTU發現功能,所以經過DON’T-FRAGMENT屬性來告知服務器當經過UDP方式來向對端發送數據時應設置DF位。Indications不能使用長期證書機制來認證,因此該消息中沒有MESSAGE-INTEGRITY屬性。
服務器收到Send indication後,提取出應用數據封裝成UDP格式發給對端A;UDP報文的源傳輸地址爲中繼傳輸地址,並設置DF位。
對端A迴應它本身的包含有應用數據的UDP包給服務器。目的地址爲服務器的中繼傳輸地址。當服務器收到後,將生成Data indication消息給客戶端,攜帶有XOR-PEER-ADDRESS屬性。應用數據包含在DATA屬性中。
<ignore_js_op>
客戶端如今若要綁定一個通道到對端B,將指定一個空閒的通道號(本例中爲0x4000)包含在CHANNEL-NUMBER屬性中,對端B的傳輸地址包含在XOR-PEER-ADDRESS屬性中。與之前同樣,客戶端再次利用上次請求中的username、realm和nonce。
當服務器收到該請求後,服務器綁定這個對端的通道號,爲對端B的IP地址安裝一個permission,而後給客戶端迴應一個ChannelBind成功響應消息。
<ignore_js_op>
客戶端如今發送一個ChannelData消息給服務器,攜帶有發送給對端B的數據。這個消息不是一個STUN消息,所以沒有事務ID。它之有3個字段:通道號、數據、數據長度;服務器收到後,檢查通道號後發現當前已經綁定了,就以UDP方式發送數據給對端B。
接着,對端B發送UDP數據包迴應給服務器的中繼傳輸地址。服務器收到後,迴應給客戶端ChannelData消息,包含UDP數據包中的數據。服務器知道是給哪一個客戶端發送ChannelData消息,這是由於收到的UDP數據包中的目的地址(即服務器的中繼傳輸地址),而且知道使用的是哪一個通道號,這是由於通道已經與相應的傳輸地址綁定了。
<ignore_js_op>
有時候,20分鐘的生命期已經到了,客戶端須要刷新allocation。此時經過發送Refresh請求來進行。該請求包含最後一次使用的username、realm和nonce,還包含SOFTWARE屬性。當服務器收到這個Refresh請求時,它注意到這個nonce值已經超期了,則給客戶端迴應一個錯誤響應號爲438(過時Nonce)的Refresh錯誤響應,並提供一個新的nonce值。可護端將重試該請求,此時攜帶新的nonce值。若第二次嘗試被接受,服務器將回應一個成功響應。須要注意的是,此時客戶端在請求中沒有攜帶LIFETIME屬性,因此服務器刷新客戶端的allocation時採用缺省的10分鐘生命期。
ICE的全稱Interactive Connectivity Establishment(互動式鏈接創建),由IETF的MMUSIC工做組開發出來的,它所提供的是一種框架,使各類NAT穿透技術能夠實現統一。ICE跟STUN和TURN不同,ICE不是一種協議,而是一個框架(Framework),它整合了STUN和TURN。
<ignore_js_op>
如上圖所示,若是A想與B通訊,那麼其過程以下:
因爲該技術是創建在多種NAT穿透協議的基礎之上,而且提供了一個統一的框架,因此ICE具有了全部這些技術的優勢,同時還避免了任何單個協議可能存在的缺陷。所以,ICE能夠實如今未知網絡拓撲結構中實現的設備互連,並且不須要進行對手配置。另外,因爲該技術不須要爲VoIP流量手動打開防火牆,因此也不會產生潛在的安全隱患。
在現實Internet網絡環境中,大多數計算機主機都位於防火牆或NAT以後,只有少部分主機可以直接接入Internet。不少時候,咱們但願網絡中的兩臺主機可以直接進行通訊(即所謂的P2P通訊),而不須要其它公共服務器的中轉。因爲主機可能位於防火牆或NAT以後,在進行P2P通訊以前,咱們須要進行檢測以確認它們之間可否進行P2P通訊以及如何通訊。這種技術一般被稱爲NAT穿透(NAT Traversal)。
RFC3489中定義的STUN,即簡單地用UDP穿過NAT(STUN)是個輕量級的協議。它容許應用發現它們與公共互聯網之間存在的NAT和防火牆及其餘類型。它還爲應用提供判斷NAT給它們分配的公共網際協議(IP)地址。STUN可工做在許多現存NAT上,而且不須要它們作任何特別的行爲。它容許普遍的各種的應用穿越現存的NAT設施。
RFC5389中對STUN協議進行了修訂,將其定位於爲穿透NAT提供工具,即NAT會話穿透效用是一個用於其餘解決NAT穿透問題協議的協議。它能夠用於終端設備檢查由NAT分配給終端的IP地址和端口號。同時,它也被用來檢查兩個終端之間的鏈接性,比如是一種維持NAT綁定表項的保活協議。STUN自己並非一種完整的NAT穿透解決方案。它至關因而一種NAT穿透解決方案中的工具。這是與先前的版本相比最重要的改變。以前的RFC3489中定義的STUN是一個完整的穿透NAT解決方案。此外,最大的區別是支持TCP穿透。
RFC5766中對STUN協議再次進行了擴展,即中繼穿透NAT:STUN的擴展。TURN與STUN的共同點都是經過修改應用層中的私網地址達到NAT穿透的效用,異同點是TUN採用了兩方通信的「中間人」方式實現穿透,突破了原先STUN協議沒法在兩臺主機不可以點對點直接鏈接下提供做用的限制。
技術無止境,NAT穿透技術仍在不斷更新中,這裏只對STUN/TURN協議做了簡單的介紹,具體細節請參考RFC3489/5389/5766。
(原文連接:點此進入,內容有修改和刪節)
《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中一個包的大小最大能多大?》
《Java新一代網絡編程模型AIO原理及Linux系統AIO介紹》
《NIO框架入門(一):服務端基於Netty4的UDP雙向通訊Demo演示》
《NIO框架入門(二):服務端基於MINA2的UDP雙向通訊Demo演示》
《NIO框架入門(三):iOS與MINA二、Netty4的跨平臺UDP雙向通訊實戰》
《NIO框架入門(四):Android與MINA二、Netty4的跨平臺UDP雙向通訊實戰》
《P2P技術詳解(一):NAT詳解——詳細原理、P2P簡介》
《P2P技術詳解(二):P2P中的NAT穿越(打洞)方案詳解》
《P2P技術詳解(三):P2P技術之STUN、TURN、ICE詳解》
《高性能網絡編程(一):單臺服務器併發TCP鏈接數到底能夠有多少》
《高性能網絡編程(二):上一個10年,著名的C10K併發鏈接問題》
《高性能網絡編程(三):下一個10年,是時候考慮C10M併發問題了》
>> 更多同類文章 ……