http://www.ppcn.net/n4690c38.aspx### 穿越NAT的p2p通信方法研究
內(nèi)容概述:在p2p通信領(lǐng)域中,由NAT(Network Address Translation,網(wǎng)絡(luò)地址轉(zhuǎn)換)引起的問(wèn)題已經(jīng)眾所周知了,它會(huì)導(dǎo)致在NAT內(nèi)部的p2p客戶端在無(wú)論以何種有效的公網(wǎng)ip都無(wú)法訪問(wèn)的問(wèn)題。雖然目前已經(jīng)發(fā)展出多種穿越NAT的技術(shù),但相關(guān)的技術(shù)文檔卻很少,用來(lái)證明這些技術(shù)的穩(wěn)定性和優(yōu)點(diǎn)的實(shí)際數(shù)據(jù)更少。本文的目的在于描述和分析在實(shí)際中運(yùn)用得最廣泛、最可靠同時(shí)也是最簡(jiǎn)單的一種NAT穿越技術(shù),該技術(shù)通常被稱為“打洞”技術(shù)。目前,“打洞”技術(shù)已經(jīng)在UDP通信領(lǐng)域中得到了廣泛的理解和應(yīng)用,在此,也將討論如何利用它實(shí)現(xiàn)可靠的p2p的TCP流通信。在收集了大量的“打洞”技術(shù)可以穿越的NAT設(shè)備和網(wǎng)絡(luò)的數(shù)據(jù)以后,我們發(fā)現(xiàn)82%的已測(cè)NAT設(shè)備支持UDP形式的“打洞”穿越,64%的已測(cè)NAT設(shè)備支持TCP流形式的“打洞”穿越。由于重量級(jí)p2p應(yīng)用程序(如,VOIP、BT、在線游戲等)的用戶需求量持續(xù)上升,并且該事實(shí)也已經(jīng)引起了NAT設(shè)備生產(chǎn)廠商的廣泛關(guān)注,因此,我們認(rèn)為未來(lái)會(huì)有越來(lái)越多的NAT設(shè)備提供對(duì)“打洞”穿越技術(shù)的支持。
1、介紹
用戶量高速增長(zhǎng)以及大量安全問(wèn)題的巨大壓力迫使Internet技術(shù)不斷向前發(fā)展,但是這些新興的技術(shù)很大程度地增加了應(yīng)用程序開發(fā)的成本和復(fù)雜性。Internet最初的地址體系是每個(gè)節(jié)點(diǎn)有一個(gè)唯一不變的全局地址,可以通過(guò)該地址直接與任何其它的節(jié)點(diǎn)進(jìn)行通信,而現(xiàn)如今,該地址體系已經(jīng)被新的實(shí)際上廣泛使用的地址體系所替換,新的地址體系是由全局地址域和通過(guò)NAT接入全局地址域的大量私有地址域組成。在新的地址體系中(如圖1所示),只有在“main”全局地址域中的節(jié)點(diǎn)可以在網(wǎng)絡(luò)中很容易地與任何其它的擁有全局地址的節(jié)點(diǎn)通信,因?yàn)樵摴?jié)點(diǎn)擁有全局的、唯一的、可路由的地址。在私有網(wǎng)絡(luò)中的節(jié)點(diǎn)可以與在同一個(gè)私有網(wǎng)絡(luò)中的其它節(jié)點(diǎn)進(jìn)行通信,并且在通常情況下可以向全局地址中的某個(gè)“著名”的節(jié)點(diǎn)發(fā)起TCP連接或發(fā)送UDP數(shù)據(jù)包。NAT設(shè)備在此扮演的角色就是為從內(nèi)網(wǎng)向公網(wǎng)發(fā)起的連接的節(jié)點(diǎn)分配臨時(shí)的轉(zhuǎn)發(fā)session,將來(lái)自內(nèi)網(wǎng)的數(shù)據(jù)包的地址和端口轉(zhuǎn)換為公網(wǎng)的地址和端口,將來(lái)自公網(wǎng)的數(shù)據(jù)包的地址和端口轉(zhuǎn)換為內(nèi)網(wǎng)的端口和地址,同時(shí)NAT將屏蔽所有未經(jīng)授權(quán)的來(lái)自公網(wǎng)的數(shù)據(jù)包。
新的Internet地址體系非常適合于“客戶端/服務(wù)器”這樣的通信模式,一個(gè)典型的C/S通信模式是:客戶端在內(nèi)網(wǎng)(私有地址域),服務(wù)器在公網(wǎng)(全局地址域),通過(guò)NAT將內(nèi)網(wǎng)和公網(wǎng)連接起來(lái)。這種地址體系使得在不同內(nèi)網(wǎng)(私有地址域)中的兩個(gè)節(jié)點(diǎn)很難直接通信,而這恰恰是p2p應(yīng)用(如,電話會(huì)議或在線游戲)中最基本的要求。很顯然,我們需要一種方法即使在NAT設(shè)備存在的前提下,仍然能夠無(wú)障礙地實(shí)現(xiàn)p2p通信。
在不同內(nèi)網(wǎng)的兩個(gè)節(jié)點(diǎn)之間建立p2p連接的最有效的方法就是“打洞”。該技術(shù)在基于UDP的應(yīng)用程序中得到了廣泛的應(yīng)用,同樣的,該技術(shù)也可以用于基于TCP的應(yīng)用程序。有趣的是,與“打洞”字面上的意思剛好相反,該技術(shù)不會(huì)影響到內(nèi)網(wǎng)的安全。事實(shí)上,“打洞”技術(shù)使得p2p軟件的絕大部分功能都在NAT設(shè)備默認(rèn)的安全策略的控制之下,這些都由NAT設(shè)備建立的session來(lái)管理。本文闡述了適用于UDP和TCP的“打洞”技術(shù),并詳細(xì)描述了重要“打洞”過(guò)程中,應(yīng)用程序和NAT設(shè)備之間的行為。
不幸的是,由于NAT設(shè)備的響應(yīng)和行為不是標(biāo)準(zhǔn)的,所以沒有任何技術(shù)可以穿越現(xiàn)有的所有NAT設(shè)備。本文提供了一些在現(xiàn)有NAT設(shè)備上進(jìn)行“打洞”的實(shí)驗(yàn)結(jié)果。我們收集的數(shù)據(jù)來(lái)自于互聯(lián)網(wǎng)上使用了“NAT Check”工具并在大量不同生產(chǎn)廠商的NAT設(shè)備上進(jìn)行“打洞”實(shí)驗(yàn)的用戶。由于數(shù)據(jù)是來(lái)自于一個(gè)叫做“self-selecting”的用戶社區(qū),或許不會(huì)完全代表在Internet上真正部署和使用的NAT設(shè)備,但是結(jié)果無(wú)論如何還是很令人興奮的。
在做基本的“打洞”操作評(píng)估的時(shí)候,我們應(yīng)該指出在現(xiàn)有的NAT設(shè)備“打洞”的復(fù)雜度上,不同的復(fù)雜度會(huì)有不同的結(jié)果。但目前我們把討論的重點(diǎn)集中于開發(fā)最簡(jiǎn)單的,可以應(yīng)用于任何網(wǎng)絡(luò)拓?fù)浣Y(jié)構(gòu)的、穩(wěn)定的、有正確NAT響應(yīng)的NAT設(shè)備上的“打洞”技術(shù)。我們有意避免使用一些“聰明的小把戲”通過(guò)欺騙某些NAT設(shè)備來(lái)達(dá)到短期內(nèi)穿越較多的NAT設(shè)備,但從長(zhǎng)期來(lái)看會(huì)引起網(wǎng)絡(luò)未知錯(cuò)誤的技術(shù)。
盡管引入IPv6會(huì)極大地增加互聯(lián)網(wǎng)的地址空間,從而減少對(duì)于NAT設(shè)備的需求量,但短期內(nèi)IPv6確實(shí)增加了對(duì)NAT設(shè)備的需求量,因?yàn)镹AT設(shè)備本身提供了一種方便的方法進(jìn)行IPv4與IPv6地址域轉(zhuǎn)換。另外私有網(wǎng)絡(luò)上建立匿名和加密訪問(wèn)節(jié)點(diǎn)也有利于組織機(jī)構(gòu)的安全性以及不受外界干擾,這些都意味著NAT還將存在相當(dāng)長(zhǎng)的一段時(shí)間。同樣,防火墻技術(shù)也不會(huì)由于有了足夠的ip地址而消失,IPv6的防火墻仍然會(huì)默認(rèn)丟掉所有未經(jīng)授權(quán)的數(shù)據(jù)包,仍然可以讓在IPv6環(huán)境下工作的應(yīng)用程序“打洞”。
本文接下來(lái)的部分按照如下的方式組織:第二章介紹基本的NAT穿越概念和術(shù)語(yǔ);第三章介紹UDP“打洞”過(guò)程;第四章介紹TCP“打洞”過(guò)程;第五章介紹支持“打洞”的NAT設(shè)備必須具有那些特性;第六章介紹我們?cè)谀壳傲餍械腘AT設(shè)備上的“打洞”實(shí)驗(yàn)結(jié)果;第七章討論相關(guān)的網(wǎng)絡(luò)問(wèn)題;第八章全文總結(jié)以及結(jié)束語(yǔ)。
2、基本概念本節(jié)介紹了本文使用到的基本的NAT術(shù)語(yǔ),著重描述了適用于UDP和TCP兩種協(xié)議的通用的NAT穿越技術(shù)。
2.1、NAT術(shù)語(yǔ)
本文絕大部分術(shù)語(yǔ)和分類來(lái)自于RFC 2663定義,另外一些來(lái)自于較新的RFC 3489中的定義。
理解session是很重要的。一個(gè)TCP或UDP的session endpoint是由一個(gè)IP地址,端口號(hào)組成,每個(gè)session是由兩個(gè)session endpoint構(gòu)成。從內(nèi)網(wǎng)節(jié)點(diǎn)的角度來(lái)看,一個(gè)session由4部分組成分別為:本地IP,本地端口,遠(yuǎn)端IP,遠(yuǎn)端端口。session的方向通常代表了數(shù)據(jù)包的初始流動(dòng)的方向;對(duì)于TCP來(lái)說(shuō)就是SYN包的流向,對(duì)于UDP來(lái)說(shuō)就是第一個(gè)用戶數(shù)據(jù)包的流向。
NAT有很多種,但最普遍的一種類型叫做“傳統(tǒng)”NAT,或者“向外”NAT。他們?cè)趦?nèi)網(wǎng)和公網(wǎng)之間提供了一個(gè)“不對(duì)稱”橋的映射。“向外”NAT在默認(rèn)情況下只允許向外的session穿越NAT:
從外向內(nèi)的的數(shù)據(jù)包都會(huì)被丟棄掉,除非NAT設(shè)備事先已經(jīng)定義了這些從外向內(nèi)的數(shù)據(jù)包是已存在的內(nèi)網(wǎng)session的一部分。
“外向”NAT會(huì)造成p2p協(xié)議的混亂,因?yàn)楫?dāng)p2p的雙方?jīng)Q定向在不同NAT后面的對(duì)方開始通信的時(shí)候,無(wú)論哪一方試圖初始化一個(gè)session,另一方的NAT都會(huì)拒絕這個(gè)請(qǐng)求。NAT穿越的核心思想就是讓p2p的雙方的NAT看上去都是“向外”的NAT。
“向外”NAT有兩種類型:(1)“基礎(chǔ)”NAT,該NAT只轉(zhuǎn)換IP地址,不轉(zhuǎn)換端口號(hào)。(2)NAPT(Network Address/Port Translation)NAPT轉(zhuǎn)換整個(gè)session endpoints。由于NAPT允許內(nèi)網(wǎng)的多個(gè)節(jié)點(diǎn)通過(guò)共享的方式使用同一個(gè)的公共的IP地址,因此,支持NAPT的NAT設(shè)備才會(huì)越來(lái)越多。盡管本文通篇討論的內(nèi)容都是基于支持NAPT的NAT設(shè)備的,但這些規(guī)律和技術(shù)同樣適用于“基礎(chǔ)”NAT。
2.2 轉(zhuǎn)發(fā)方式
最可靠但同時(shí)也是效率最低的p2p穿越NAT進(jìn)行通信的方法是采用類似C/S方式的轉(zhuǎn)發(fā)。假定兩個(gè)節(jié)點(diǎn)A和B每個(gè)節(jié)點(diǎn)都有向外的TCP或UDP連接,聯(lián)入公共的已知服務(wù)器S,S的公網(wǎng)IP地址是18.181.0.31,端口號(hào)是1234(如圖2所示),每個(gè)客戶端位于不同的私有內(nèi)網(wǎng)中,并且它們的NAT設(shè)備妨礙了客戶端之間直接的p2p連接。做為對(duì)直連方案的替代方案,兩個(gè)客戶端可以利用公共的服務(wù)器S進(jìn)行消息的轉(zhuǎn)發(fā)。例如,A為了將消息送給B,A只需將消息發(fā)給S,然后由S轉(zhuǎn)發(fā)給B,這一過(guò)程將使用A與B事先與S建立好的連接。
轉(zhuǎn)發(fā)方式通常只能在雙方客戶端都連接到服務(wù)器的時(shí)候有效。這種方式的缺點(diǎn)在于,它假定服務(wù)器的處理能力和網(wǎng)絡(luò)帶寬以及通信延遲都是理想的情況下,不會(huì)受到客戶端個(gè)數(shù)的影響。但是,由于沒有其它的方法能夠像轉(zhuǎn)發(fā)方式那樣,可以穿越現(xiàn)存的所有NAT設(shè)備,因此在構(gòu)建高可靠性的p2p系統(tǒng)的時(shí)候,通過(guò)服務(wù)器轉(zhuǎn)發(fā)的方式依舊是一個(gè)非常有用的保證系統(tǒng)可靠性的方法。TURN協(xié)議定義了如何實(shí)現(xiàn)安全的轉(zhuǎn)發(fā)方式。
2.3 反向連接方式
一些p2p的應(yīng)用程序采用了直接但是有所限制的技術(shù)來(lái)實(shí)現(xiàn)NAT穿越,該技術(shù)叫做“反向連接”,這是用于當(dāng)兩個(gè)節(jié)點(diǎn)聯(lián)入服務(wù)器S的時(shí)候,只有一個(gè)一個(gè)節(jié)點(diǎn)在NAT設(shè)備的后面(如圖3所示)。如果A希望建立與B的連接,那么A可以直接聯(lián)入B,因?yàn)锽是在公網(wǎng)中存在的,沒有經(jīng)過(guò)NAT轉(zhuǎn)換,而且A的NAT設(shè)備也允許A直接由內(nèi)網(wǎng)發(fā)起向外網(wǎng)的連接。如果B希望建立與A的連接,很不幸,A的NAT設(shè)備會(huì)阻止該操作,此時(shí),B可以借助于轉(zhuǎn)發(fā)服務(wù)器S,向A發(fā)送“反向連接”請(qǐng)求,由A“主動(dòng)”連接B,從而達(dá)到A與B的p2p通信的目的。
盡管該技術(shù)的局限性非常明顯,但是使用已知的服務(wù)器做為中介輔助p2p客戶端雙方進(jìn)行p2p連接的思想已經(jīng)成為了更加通用的“打洞”技術(shù)的基本思想。
3 UDP打洞方式
即使兩個(gè)p2p客戶端都位于NAT設(shè)備后面,UDP打洞方式也能夠通過(guò)已知的服務(wù)器實(shí)現(xiàn)p2p客戶端直連。該技術(shù)在RFC 3027的第5.1節(jié)中曾有所提及,在網(wǎng)絡(luò)上可以找到對(duì)其較模糊的描述,在最近的IP協(xié)議實(shí)驗(yàn)中得到應(yīng)用,在多種在線游戲協(xié)議中得到了應(yīng)用。
3.1 集中服務(wù)器
打洞技術(shù)假定客戶端A和B可以與公網(wǎng)內(nèi)的已知的集中服務(wù)器建立UDP連接(可以互發(fā)UDP數(shù)據(jù)包)。當(dāng)一個(gè)客戶端在S上登陸的時(shí)候,服務(wù)器記錄下該客戶端的兩個(gè)endpoints(IP地址,UDP端口),一個(gè)是該客戶端確信自己是通過(guò)該ip和端口與服務(wù)器S進(jìn)行通信的,另一個(gè)是服務(wù)器S記錄下的由服務(wù)器“觀察”到的該客戶端實(shí)際與自己通信所使用的ip和端口。我們可以把前一個(gè)endpoint看作是客戶端的內(nèi)網(wǎng)ip和端口,把后一個(gè)endpoint看作是客戶端的內(nèi)網(wǎng)ip和端口經(jīng)過(guò)NAT轉(zhuǎn)換后的公網(wǎng)ip和端口。服務(wù)器可以從客戶端的登陸消息的消息體中得到該客戶端的內(nèi)網(wǎng)endpoint相關(guān)信息,可以通過(guò)對(duì)登陸消息的IP或UDP頭得到該客戶端的公網(wǎng)endpoint。如果該客戶端不是位于NAT設(shè)備后面,那么采用上述方法得到的兩個(gè)endpoint的值應(yīng)該完全相同。
也有一些“弱智”的NAT設(shè)備會(huì)掃描UDP數(shù)據(jù)包的包體,尋找4字節(jié)的位域,看上去很像IP地址的位域,并且把它們改為與IP頭一樣的地址。為了避免這種行為的NAT設(shè)備對(duì)UDP數(shù)據(jù)包包體的修改,應(yīng)用程序可以采用直接對(duì)IP地址的值進(jìn)行加密的方式騙過(guò)NAT設(shè)備的檢查。
3.2 建立p2p的session
假定A要發(fā)起對(duì)B的直接連接,“打洞”過(guò)程如下所示:(endpoint指ip地址和端口的配對(duì))(1)A最初不知道如何向B發(fā)起連接,于是A向服務(wù)器S發(fā)送消息,請(qǐng)求S幫助建立與B的UDP連接。
(2)S將含有B的公網(wǎng)和內(nèi)網(wǎng)的endpoint發(fā)給A,同時(shí),S將含有A的公網(wǎng)和內(nèi)網(wǎng)的endpoint的用于請(qǐng)求連接的消息也發(fā)給B。一旦這些消息順利到達(dá),A與B就都知道了對(duì)方的公網(wǎng)和內(nèi)網(wǎng)的endpoint。
(3)當(dāng)A收到由S發(fā)來(lái)的包含B的公網(wǎng)和內(nèi)網(wǎng)endpoint的消息,A開始向這些B的endpoint發(fā)送UDP數(shù)據(jù)包,并且A會(huì)自動(dòng)鎖定第一個(gè)給出響應(yīng)的B的endpoint。同理,當(dāng)B收到由S發(fā)來(lái)的A的公網(wǎng)和內(nèi)網(wǎng)endpoint以后,也會(huì)開始向A的公網(wǎng)和內(nèi)網(wǎng)的endpoint發(fā)送UDP數(shù)據(jù)包,并且自動(dòng)鎖定第一個(gè)得到A的回應(yīng)的endpoint。由于A與B的互相向?qū)Ψ桨l(fā)送UDP數(shù)據(jù)包的操作是異步的,所以A和B發(fā)送數(shù)據(jù)包的時(shí)間先后并沒有嚴(yán)格的時(shí)序要求。
下面我們就來(lái)看一下這三個(gè)角色之間是如何進(jìn)行UDP“打洞”的。在這里我們分為三種具體情景來(lái)討論:第一種也是最“簡(jiǎn)單”的一種情景,兩個(gè)客戶端都位于同一個(gè)NAT設(shè)備后面,位于同一個(gè)內(nèi)網(wǎng)中;第二種也是最普遍的一種情景,兩個(gè)客戶端分別位于不同的NAT設(shè)備后面,分屬不同的內(nèi)網(wǎng);第三種是客戶端位于兩層NAT設(shè)備之后,通常最上層的NAT是由ISP網(wǎng)絡(luò)提供商,第二層的NAT是家用的NAT路由器之類的設(shè)備。
通常情況下由應(yīng)用程序自身確定的網(wǎng)絡(luò)物理層連接方式是很困難的,有時(shí)甚至是不可能的,即使是上述的若干種情景下可以穿越NAT,也只是代表在一定時(shí)期內(nèi)有效,而不是永久有效的。諸如STUN之類的網(wǎng)絡(luò)協(xié)議或許可以提供必要的NAT信息,但在遇到多層NAT設(shè)備的時(shí)候,通常這些信息也不是完全完整和有效的。盡管如此,只要NAT設(shè)備的響應(yīng)是“合理”的,在通常情況下“打洞”技術(shù)還是能夠在應(yīng)用程序?qū)W(wǎng)絡(luò)狀況一無(wú)所知的前提下自動(dòng)適用于多數(shù)場(chǎng)合。(“合理”的NAT響應(yīng)將在第五章中詳細(xì)討論)
3.3 p2p客戶端位于同一個(gè)NAT設(shè)備后面
首先假設(shè)兩個(gè)客戶端位于同一個(gè)NAT設(shè)備后面,并且位于相同的內(nèi)網(wǎng)(相同的私有IP地址域)如圖4所示。A與S建立了UDP連接,經(jīng)過(guò)NAT轉(zhuǎn)換后,A的公網(wǎng)端口被映射為62000。B同樣與S建立了UDP連接,公網(wǎng)端口映射為62005。
(圖4)
假設(shè)A想通過(guò)服務(wù)器S做為介紹人,發(fā)起對(duì)B的連接。A向S發(fā)出消息請(qǐng)求與B進(jìn)行連接。S將B的公網(wǎng)endpoint(即公網(wǎng)ip和port)以及內(nèi)網(wǎng)endpoint(即內(nèi)網(wǎng)ip和port)發(fā)給A,同時(shí)把A的公網(wǎng)、內(nèi)網(wǎng)的endpoints發(fā)給B。由A和B發(fā)往對(duì)方公網(wǎng)endpoint的UDP數(shù)據(jù)包能否被對(duì)方收到,這取決于當(dāng)前的NAT是否支持“發(fā)夾”轉(zhuǎn)換(hairpin轉(zhuǎn)換,也就是同一臺(tái)設(shè)備,不同端口之間的UDP數(shù)據(jù)包能否到達(dá),詳見3.5節(jié))。但是A與B往對(duì)方內(nèi)網(wǎng)endpoint發(fā)送的UDP數(shù)據(jù)包是一定可以到達(dá)的,無(wú)論如何,內(nèi)網(wǎng)數(shù)據(jù)包不需要路由,并且速度更快。A與B有很大的可能性采用內(nèi)網(wǎng)的endpoint進(jìn)行常規(guī)的p2p通信。
假定NAT設(shè)備支持“發(fā)夾”轉(zhuǎn)換,應(yīng)用程序也忽略由內(nèi)網(wǎng)endpoint的連接,那么A、B會(huì)采用公網(wǎng)endpoint做為p2p通信的連接,這勢(shì)必會(huì)造成數(shù)據(jù)包無(wú)謂地經(jīng)過(guò)NAT設(shè)備,這是一種對(duì)資源的浪費(fèi)。我們會(huì)在第六節(jié)討論這種情況,畢竟支持“發(fā)夾”轉(zhuǎn)換的NAT設(shè)備還遠(yuǎn)沒有對(duì)“打洞”技術(shù)支持的NAT設(shè)備多。就目前的網(wǎng)絡(luò)情況而言,應(yīng)用程序在“打洞”的時(shí)候,最好還是把公網(wǎng)endpoint和內(nèi)網(wǎng)endpoint都實(shí)驗(yàn)一下。
3.4 p2p客戶端位于不同的NAT設(shè)備后面
假定A與B在不同的NAT設(shè)備后面,分屬不同的內(nèi)網(wǎng),如圖5所示。A與B都經(jīng)由各自的NAT設(shè)備與服務(wù)器S建立了UDP連接,A與B的本地端口號(hào)均為4321,服務(wù)器S的公網(wǎng)端口號(hào)為1234。在“向外”session中,A的公網(wǎng)IP被映射為155.99.25.11,公網(wǎng)端口為62000,B的公網(wǎng)IP被映射為138.76.29.7,公網(wǎng)端口為31000。
如下所示:客戶端A-->本地IP:10.0.0.1,本地端口:4321,公網(wǎng)IP:155.99.25.11,公網(wǎng)端口:62000客戶端B-->本地IP:10.1.1.3,本地端口:4321,公網(wǎng)IP:138.76.29.7,公網(wǎng)端口:31000
(圖 5)
在A向服務(wù)器S發(fā)送的登陸消息體中,會(huì)包含A的內(nèi)網(wǎng)endpoint信息,即10.0.0.1:4321;服務(wù)器S會(huì)記錄下A的內(nèi)網(wǎng)endpoint,同時(shí)會(huì)把自己觀察到的A的公網(wǎng)endpoint記錄下來(lái),即155.99.25.11:62000。同理,服務(wù)器S會(huì)記錄下B的內(nèi)網(wǎng)endpoint,10.1.1.3:4321和由S觀察到的B的公網(wǎng)endpoint,138.76.29.7:31000。無(wú)論A與B二者任何一方向S發(fā)送p2p連接請(qǐng)求,服務(wù)器都會(huì)將其記錄下來(lái)的上述的公網(wǎng)、內(nèi)網(wǎng)endpoint發(fā)送給A、B。
由于A、B分屬不同的內(nèi)網(wǎng),它們彼此的內(nèi)網(wǎng)endpoint無(wú)法在公網(wǎng)中路由,所以發(fā)往各自內(nèi)網(wǎng)endpoint的UDP數(shù)據(jù)包會(huì)發(fā)送到錯(cuò)誤的主機(jī)或者根本不存在的主機(jī)。因此應(yīng)用程序?qū)τ谑盏降南⒈仨毥?jīng)過(guò)授權(quán)和過(guò)濾,只有通過(guò)授權(quán)的的消息才能是從對(duì)方的endpoint發(fā)出來(lái)的,例如,可以在消息中加入對(duì)方的程序名稱、加密算法,或者至少是一個(gè)雙方都從服務(wù)器S上的預(yù)先得到的隨機(jī)數(shù)字。
現(xiàn)在假定A的第一個(gè)消息將發(fā)往B的公網(wǎng)endpoint,如圖5所示。該消息途經(jīng)A的NAT設(shè)備,并在該設(shè)備上生成了一個(gè)“向外”的session。新的session源endpoint是10.0.0.1:4321該endpoint和A與服務(wù)器S的建立連接的時(shí)候NAT生成的源endpoint一樣,但它的目的endpoint不同。如果A的NAT設(shè)備給出的響應(yīng)是“友好”的,那么A的NAT設(shè)備將保留A的內(nèi)網(wǎng)endpoint,并且所有來(lái)自A的源endpoint(10.0.0.1:4321)的數(shù)據(jù)包都沿用A與S事先建立起來(lái)的session,公網(wǎng)endpoint均為(155.99.25.11:62000)。A向B的公網(wǎng)endpoint發(fā)送消息的過(guò)程就是“打洞”的過(guò)程,從A的內(nèi)網(wǎng)的角度來(lái)看應(yīng)為從(10.0.0.1:4321)發(fā)往(138.76.29.7:31000),從A的在其NAT設(shè)備上建立的session來(lái)看,是從(155.99.25.11:62000)發(fā)到(138.76.29.7:31000)。
如果A發(fā)給B的公網(wǎng)endpoint的消息包在B向A發(fā)送消息包之前到達(dá)B的NAT設(shè)備,B的NAT會(huì)認(rèn)為A發(fā)過(guò)來(lái)的消息是未經(jīng)授權(quán)的公網(wǎng)消息,會(huì)丟棄掉該數(shù)據(jù)包。B發(fā)往A的消息包根上述的過(guò)程一樣,會(huì)在B的NAT上建立一個(gè)(10.1.1.3:4321,155.99.25.11:62000)的session(通常也會(huì)沿用B與S連接時(shí)建立的session,只是該session現(xiàn)在不光可以接受由S發(fā)給B的消息,還可以接受從A的NAT設(shè)備-155.99.25.11:6200發(fā)來(lái)的消息)
一旦A與B都向?qū)Ψ降腘AT在公網(wǎng)上的endpoint發(fā)送了數(shù)據(jù)包,就打開了A與B之間的“洞”,A與B向?qū)Ψ降墓W(wǎng)endpoint發(fā)送數(shù)據(jù),等效為向?qū)Ψ降目蛻舳酥苯影l(fā)送UDP數(shù)據(jù)包了。一旦應(yīng)用程序確認(rèn)已經(jīng)可以通過(guò)往對(duì)方的公網(wǎng)endpoint發(fā)送數(shù)據(jù)包的方式讓數(shù)據(jù)包到達(dá)NAT后面的目的應(yīng)用程序,程序會(huì)自動(dòng)停止繼續(xù)發(fā)送用于“打洞”的數(shù)據(jù)包,轉(zhuǎn)而開始真正的p2p數(shù)據(jù)傳輸。
3.5 p2p客戶端位于多層NAT設(shè)備后面
有的網(wǎng)絡(luò)拓?fù)浣Y(jié)構(gòu)包含了多個(gè)NAT設(shè)備,如果沒有掌握該拓?fù)浣Y(jié)構(gòu)的詳細(xì)信息,兩個(gè)客戶端之間是無(wú)法建立“最優(yōu)化”的p2p路由的。現(xiàn)在我們來(lái)討論最后一種情況,如圖6所示。假定NAT C是由ISP(Internet Service Provider)提供的工業(yè)級(jí)的NAT設(shè)備,NAT C提供將多個(gè)下屬的用戶NAT或用戶節(jié)點(diǎn)映射到有限的幾個(gè)公網(wǎng)IP的服務(wù),NAT A和NAT B做為NAT C的內(nèi)網(wǎng)節(jié)點(diǎn)將把用戶的家庭網(wǎng)絡(luò)或內(nèi)部網(wǎng)絡(luò)接入NAT C的內(nèi)網(wǎng),然后用戶的內(nèi)部網(wǎng)絡(luò)就可以經(jīng)由NAT C訪問(wèn)公網(wǎng)了。從這種拓?fù)浣Y(jié)構(gòu)上來(lái)看,只有服務(wù)器S與NAT C是真正擁有公網(wǎng)可路由IP地址的設(shè)備,而NAT A和NAT B所使用的“公網(wǎng)”IP地址,實(shí)際上是由ISP服務(wù)提供商設(shè)定的(相對(duì)于NAT C而言)內(nèi)網(wǎng)地址(本位的后續(xù)部分我把這個(gè)由ISP提供的內(nèi)網(wǎng)地址相對(duì)于NAT A和NAT B稱之為“偽”公網(wǎng)地址),同理隸屬于NAT A與NAT B的客戶端,相對(duì)與NAT A,NAT B而言,它們處于NAT A,NAT B的內(nèi)網(wǎng),以此類推,客戶端可以放到到多層NAT設(shè)備后面。客戶端A和客戶端B發(fā)起對(duì)服務(wù)器S的連接的時(shí)候,就會(huì)依次在NAT A和NAT B上建立向外的session,而NAT A、NAT B要聯(lián)入公網(wǎng)的時(shí)候,會(huì)在NAT C上再建立向外的session。
(圖 6)
現(xiàn)在假定客戶端A和B希望通過(guò)UDP“打洞”完成兩個(gè)客戶端的p2p直連。最優(yōu)化的路由策略是客戶端A向客戶端B的“偽公網(wǎng)”IP上發(fā)送數(shù)據(jù)包,即ISP服務(wù)提供商指定的內(nèi)網(wǎng)IP,NAT B的“偽”公網(wǎng)endpoint,10.0.1.2:55000。由于從服務(wù)器S的角度只能觀察到真正的公網(wǎng)地址,也就是NAT A,NAT B在NAT C建立的session的真正的公網(wǎng)地址155.99.25.11:62000以及155.99.25.11:62005,所以非常不幸,客戶端A與客戶端B是無(wú)法通過(guò)服務(wù)器S知道這些“偽”公網(wǎng)的地址的。而且即使客戶端A和B通過(guò)某種手段可以得到NAT A和NAT B的“偽”公網(wǎng)地址,我們?nèi)匀徊唤ㄗh采用上述的“最優(yōu)化”的打洞方式,這是因?yàn)檫@些地址是由ISP服務(wù)提供商提供的或許會(huì)存在與客戶端本身所在的內(nèi)網(wǎng)地址重復(fù)的可能性。(例如:NAT A的內(nèi)網(wǎng)的IP地址域恰好與NAT A在NAT C的“偽”公網(wǎng)IP地址域重復(fù),這樣就會(huì)導(dǎo)致打洞數(shù)據(jù)包無(wú)法發(fā)出的問(wèn)題)
因此客戶端別無(wú)選擇,只能使用由公網(wǎng)服務(wù)器S觀察到的A,B的公網(wǎng)endpoint進(jìn)行“打洞”操作,用于“打洞”的數(shù)據(jù)包將由NAT C進(jìn)行轉(zhuǎn)發(fā),這里NAT C是否支持“發(fā)夾”轉(zhuǎn)換或“環(huán)路”轉(zhuǎn)換非常重要,否則數(shù)據(jù)包將無(wú)法由NAT C轉(zhuǎn)發(fā)給NAT A和NAT B,進(jìn)而無(wú)法到達(dá)客戶端A和B。當(dāng)客戶端A向客戶端B的公網(wǎng)endpoint(155.99.25.11:62005)發(fā)送UDP數(shù)據(jù)包的時(shí)候,NAT A首先把數(shù)據(jù)包的源endpoint由A的內(nèi)網(wǎng)endpoint(10.0.0.1:4321)轉(zhuǎn)換為“偽”公網(wǎng)endpoint(10.0.1.1:45000),現(xiàn)在數(shù)據(jù)包到了NAT C,NAT C應(yīng)該可以識(shí)別出來(lái)該數(shù)據(jù)包是要發(fā)往自身轉(zhuǎn)換過(guò)的公網(wǎng)endpoint,如果NAT C可以給出“合理”響應(yīng)的話,NAT C將把該數(shù)據(jù)包的源endpoint改為155.99.25.11:62000,目的endpoint改為10.0.1.2:55000,即NAT B的“偽”公網(wǎng)endpoint,NAT B最后會(huì)將收到的數(shù)據(jù)包發(fā)往客戶端B。同樣,由B發(fā)往A的數(shù)據(jù)包也會(huì)經(jīng)過(guò)類似的過(guò)程。也有很多NAT設(shè)備不支持類似這樣的“發(fā)夾”轉(zhuǎn)換,但是已經(jīng)有越來(lái)越多的NAT設(shè)備生產(chǎn)廠商開始加入對(duì)該轉(zhuǎn)換的支持。
3.6 UDP在空閑狀態(tài)下的超時(shí)問(wèn)題
由于UDP轉(zhuǎn)換協(xié)議提供的“洞”不是絕對(duì)可靠的,多數(shù)NAT設(shè)備內(nèi)部都有一個(gè)UDP轉(zhuǎn)換的空閑狀態(tài)計(jì)時(shí)器,如果在一段時(shí)間內(nèi)沒有UDP數(shù)據(jù)通信,NAT設(shè)備會(huì)關(guān)掉由“打洞”操作打出來(lái)的“洞”,做為應(yīng)用程序來(lái)講如果想要做到與設(shè)備無(wú)關(guān),就最好在穿越NAT的以后設(shè)定一個(gè)穿越的有效期。很遺憾目前沒有標(biāo)準(zhǔn)有效期,這個(gè)有效期與NAT設(shè)備內(nèi)部的配置有關(guān),最短的只有20秒左右。在這個(gè)有效期內(nèi),即使沒有p2p數(shù)據(jù)包需要傳輸,應(yīng)用程序?yàn)榱司S持該“洞”可以正常工作,也必須向?qū)Ψ桨l(fā)送“打洞”維持包。這個(gè)維持包是需要雙方應(yīng)用都發(fā)送的,只有一方發(fā)送不會(huì)維持另一方的session正常工作。除了頻繁發(fā)送“打洞”維持包以外,還有一個(gè)方法就是在當(dāng)前的“洞”有效期過(guò)期之前,p2p客戶端雙方重新“打洞”,丟棄原有的“洞”,這也不失為一個(gè)有效的方法。
4 關(guān)于TCP打洞技術(shù)建立穿越NAT設(shè)備的p2p的TCP連接只比UDP復(fù)雜一點(diǎn)點(diǎn),TCP協(xié)議的“打洞”從協(xié)議層來(lái)看是與UDP的“打洞”過(guò)程非常相似的。盡管如此,基于TCP協(xié)議的打洞至今為止還沒有被很好的理解,這也造成了對(duì)其提供支持的NAT設(shè)備不是很多。在NAT設(shè)備支持的前提下,基于TCP的“打洞”技術(shù)實(shí)際上與基于UDP的“打洞”技術(shù)一樣快捷、可靠。實(shí)際上,只要NAT設(shè)備支持的話,基于TCP的p2p技術(shù)的健壯性將比基于UDP的技術(shù)的更強(qiáng)一些,因?yàn)門CP協(xié)議的狀態(tài)機(jī)給出了一種標(biāo)準(zhǔn)的方法來(lái)精確的獲取某個(gè)TCP session的生命期,而UDP協(xié)議則無(wú)法做到這一點(diǎn)。4.1 套接字和TCP端口的重用實(shí)現(xiàn)基于TCP協(xié)議的p2p“打洞”過(guò)程中,最主要的問(wèn)題不是來(lái)自于TCP協(xié)議,而是來(lái)自于來(lái)自于應(yīng)用程序的API接口。這是由于標(biāo)準(zhǔn)的伯克利(Berkeley)套接字的API是圍繞著構(gòu)建客戶端/服務(wù)器程序而設(shè)計(jì)的,API允許TCP流套接字通過(guò)調(diào)用connect()函數(shù)來(lái)建立向外的連接,或者通過(guò)listen()和accept函數(shù)接受來(lái)自外部的連接,但是,API不提供類似UDP那樣的,同一個(gè)端口既可以向外連接,又能夠接受來(lái)自外部的連接。而且更糟的是,TCP的套接字通常僅允許建立1對(duì)1的響應(yīng),即應(yīng)用程序在將一個(gè)套接字綁定到本地的一個(gè)端口以后,任何試圖將第二個(gè)套接字綁定到該端口的操作都會(huì)失敗。為了讓TCP“打洞”能夠順利工作,我們需要使用一個(gè)本地的TCP端口來(lái)監(jiān)聽來(lái)自外部的TCP連接,同時(shí)建立多個(gè)向外的TCP連接。幸運(yùn)的是,所有的主流操作系統(tǒng)都能夠支持特殊的TCP套接字參數(shù),通常叫做“SO_REUSEADDR”,該參數(shù)允許應(yīng)用程序?qū)⒍鄠€(gè)套接字綁定到本地的一個(gè)endpoint(只要所有要綁定的套接字都設(shè)置了SO_REUSEADDR參數(shù)即可)。BSD系統(tǒng)引入了SO_REUSEPORT參數(shù),該參數(shù)用于區(qū)分端口重用還是地址重用,在這樣的系統(tǒng)里面,上述所有的參數(shù)必須都設(shè)置才行。4.2 打開p2p的TCP流假定客戶端A希望建立與B的TCP連接。我們像通常一樣假定A和B已經(jīng)與公網(wǎng)上的已知服務(wù)器S建立了TCP連接。服務(wù)器記錄下來(lái)每個(gè)聯(lián)入的客戶端的公網(wǎng)和內(nèi)網(wǎng)的endpoints,如同為UDP服務(wù)的時(shí)候一樣。從協(xié)議層來(lái)看,TCP“打洞”與UDP“打洞”是幾乎完全相同的過(guò)程。1、客戶端A使用其與服務(wù)器S的連接向服務(wù)器發(fā)送請(qǐng)求,要求服務(wù)器S協(xié)助其連接客戶端B。2、S將B的公網(wǎng)和內(nèi)網(wǎng)的TCP endpoint返回給A,同時(shí),S將A的公網(wǎng)和內(nèi)網(wǎng)的endpoint發(fā)送給B。3、客戶端A和B使用連接S的端口異步地發(fā)起向?qū)Ψ降墓W(wǎng)、內(nèi)網(wǎng)endpoint的TCP連接,同時(shí)監(jiān)聽各自的本地TCP端口是否有外部的連接聯(lián)入。4、A和B開始等待向外的連接是否成功,檢查是否有新連接聯(lián)入。如果向外的連接由于某種網(wǎng)絡(luò)錯(cuò)誤而失敗,如:“連接被重置”或者“節(jié)點(diǎn)無(wú)法訪問(wèn)”,客戶端只需要延遲一小段時(shí)間(例如延遲一秒鐘),然后重新發(fā)起連接即可,延遲的時(shí)間和重復(fù)連接的次數(shù)可以由應(yīng)用程序編寫者來(lái)確定。5、TCP連接建立起來(lái)以后,客戶端之間應(yīng)該開始鑒權(quán)操作,確保目前聯(lián)入的連接就是所希望的連接。如果鑒權(quán)失敗,客戶端將關(guān)閉連接,并且繼續(xù)等待新的連接聯(lián)入。客戶端通常采用“先入為主”的策略,只接受第一個(gè)通過(guò)鑒權(quán)操作的客戶端,然后將進(jìn)入p2p通信過(guò)程不再繼續(xù)等待是否有新的連接聯(lián)入。

(圖 7)
與TCP不同的是,使用UDP協(xié)議的每個(gè)客戶端只需要一個(gè)套接字即可完成與服務(wù)器S通信,并同時(shí)與多個(gè)p2p客戶端通信的任務(wù),而TCP客戶端必須處理多個(gè)套接字綁定到同一個(gè)本地TCP端口的問(wèn)題,如圖7所示。現(xiàn)在來(lái)看更加實(shí)際的一種情景,A與B分別位于不同的NAT設(shè)備后面,如圖5所示,并且假定圖中的端口號(hào)是TCP協(xié)議的端口號(hào),而不是UDP的端口號(hào)。圖中向外的連接代表A和B向?qū)Ψ降膬?nèi)網(wǎng)endpoint發(fā)起的連接,這些連接或許會(huì)失敗或者無(wú)法連接到對(duì)方。如同使用UDP協(xié)議進(jìn)行“打洞”操作遇到的問(wèn)題一樣,TCP的“打洞”操作也會(huì)遇到內(nèi)網(wǎng)的IP與“偽”公網(wǎng)IP重復(fù)造成連接失敗或者錯(cuò)誤連接之類的問(wèn)題。客戶端向彼此公網(wǎng)endpoint發(fā)起連接的操作,會(huì)使得各自的NAT設(shè)備打開新的“洞”允許A與B的TCP數(shù)據(jù)通過(guò)。如果NAT設(shè)備支持TCP“打洞”操作的話,一個(gè)在客戶端之間的基于TCP協(xié)議的流通道就會(huì)自動(dòng)建立起來(lái)。如果A向B發(fā)送的第一個(gè)SYN包發(fā)到了B的NAT設(shè)備,而B在此前沒有向A發(fā)送SYN包,B的NAT設(shè)備會(huì)丟棄這個(gè)包,這會(huì)引起A的“連接失敗”或“無(wú)法連接”問(wèn)題。而此時(shí),由于A已經(jīng)向B發(fā)送過(guò)SYN包,B發(fā)往A的SYN包將被看作是由A發(fā)往B的包的回應(yīng)的一部分,所以B發(fā)往A的SYN包會(huì)順利地通過(guò)A的NAT設(shè)備,到達(dá)A,從而建立起A與B的p2p連接。4.3 從應(yīng)用程序的角度來(lái)看TCP“打洞”從應(yīng)用程序的角度來(lái)看,在進(jìn)行TCP“打洞”的時(shí)候都發(fā)生了什么呢?假定A首先向B發(fā)出SYN包,該包發(fā)往B的公網(wǎng)endpoint,并且被B的NAT設(shè)備丟棄,但是B發(fā)往A的公網(wǎng)endpoint的SYN包則通過(guò)A的NAT到達(dá)了A,然后,會(huì)發(fā)生以下的兩種結(jié)果中的一種,具體是哪一種取決于操作系統(tǒng)對(duì)TCP協(xié)議的實(shí)現(xiàn):(1)A的TCP實(shí)現(xiàn)會(huì)發(fā)現(xiàn)收到的SYN包就是其發(fā)起連接并希望聯(lián)入的B的SYN包,通俗一點(diǎn)來(lái)說(shuō)就是“說(shuō)曹操,曹操到”的意思,本來(lái)A要去找B,結(jié)果B自己找上門來(lái)了。A的TCP協(xié)議棧因此會(huì)把B做為A向B發(fā)起連接connect的一部分,并認(rèn)為連接已經(jīng)成功。程序A調(diào)用的異步connect()函數(shù)將成功返回,A的listen()等待從外部聯(lián)入的函數(shù)將沒有任何反映。此時(shí),B聯(lián)入A的操作在A程序的內(nèi)部被理解為A聯(lián)入B連接成功,并且A開始使用這個(gè)連接與B開始p2p通信。由于收到的SYN包中不包含A需要的ACK數(shù)據(jù),因此,A的TCP將用SYN-ACK包回應(yīng)B的公網(wǎng)endpoint,并且將使用先前A發(fā)向B的SYN包一樣的序列號(hào)。一旦B的TCP收到由A發(fā)來(lái)的SYN-ACK包,則把自己的ACK包發(fā)給A,然后兩端建立起TCP連接。簡(jiǎn)單的說(shuō),第一種,就是即使A發(fā)往B的SYN包被B的NAT丟棄了,但是由于B發(fā)往A的包到達(dá)了A。結(jié)果是,A認(rèn)為自己連接成功了,B也認(rèn)為自己連接成功了,不管是誰(shuí)成功了,總之連接是已經(jīng)建立起來(lái)了。(2)另外一種結(jié)果是,A的TCP實(shí)現(xiàn)沒有像(1)中所講的那么“智能”,它沒有發(fā)現(xiàn)現(xiàn)在聯(lián)入的B就是自己希望聯(lián)入的。就好比在機(jī)場(chǎng)接人,明明遇到了自己想要接的人卻不認(rèn)識(shí),誤認(rèn)為是其它的人,安排別人給接走了,后來(lái)才知道是自己錯(cuò)過(guò)了機(jī)會(huì),但是無(wú)論如何,人已經(jīng)接到了任務(wù)已經(jīng)完成了。然后,A通過(guò)常規(guī)的listen()函數(shù)和accept()函數(shù)得到與B的連接,而由A發(fā)起的向B的公網(wǎng)endpoint的連接會(huì)以失敗告終。盡管A向B的連接失敗,A仍然得到了B發(fā)起的向A的連接,等效于A與B之間已經(jīng)聯(lián)通,不管中間過(guò)程如何,A與B已經(jīng)連接起來(lái)了,結(jié)果是A和B的基于TCP協(xié)議的p2p連接已經(jīng)建立起來(lái)了。第一種結(jié)果適用于基于BSD的操作系統(tǒng)對(duì)于TCP的實(shí)現(xiàn),而第二種結(jié)果更加普遍一些,多數(shù)linux和windows系統(tǒng)都會(huì)按照第二種結(jié)果來(lái)處理。