P2P技術(2)——NAT穿透

尹瑞星發表於2021-07-06

P2P可以是一種通訊模式、一種邏輯網路模型、一種技術、甚至一種理念。在P2P網路中,所有通訊節點的地位都是對等的,每個節點都扮演著客戶機和伺服器雙重角色,節點之間通過直接通訊實現檔案資訊、處理器運算能力、儲存空間等資源的共享。P2P網路具有分散性、可擴充套件性、健壯性等特點,這使得P2P技術在資訊共享、即時通訊、協同工作、分散式計算、網路儲存等領域都有廣闊的應用。

由於主機可能位於防火牆或NAT之後,在進行P2P通訊之前,我們需要進行檢測以確認它們之間能否進行P2P通訊以及如何通訊。這種技術通常稱為NAT穿透(NAT Traversal)。最常見的NAT穿透是基於UDP的技術。

1、應用層閘道器技術

NAT帶來的問題之一就是限制了使用高層協議的兩端P2P通訊,因為NAT不允許外部的Peer節點主動連線或傳送資料包給NAT後面的主機。ALG是解決NAT對應用層協議無感知的一個最常用方法。

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

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

2、中介軟體技術

這是一種通過開發通用方法解決NAT穿越問題的努力。與前者不同之處是,AGL技術中NAT閘道器是這一解決方案的唯一參與者,而中介軟體技術中客戶端會參與閘道器公網對映資訊的維護。UPnP就是這樣一種方法,UPnP中文全稱為通用即插即用,是一個通用的網路終端與閘道器的通訊協議,具備資訊釋出和管理控制的能力。

3、打洞技術

Hole Punching技術是工作在運輸層的技術,可以遮蔽上層應用層的差異,並且不需要NAT閘道器特定的支援,因此其通用性比較強,應用性也比較廣。

3.1、反向連結技術

適用場景:通訊雙方只有一方位於NAT裝置之後

客戶端A位於NAT之後,NAT裝置重新分配了TCP埠62000,由於B擁有外網地址,A可以直接通過TCP連結到B。但B想要與A通訊,就需要通過伺服器給A轉發一個連線請求,反過來請求A連線到B(即反向連結),A在收到從伺服器轉發過來的請求以後,會主動向B發起一個連線請求,這樣在NAT裝置上就會建立起關於這個連線的相關表項,使AB之間能夠正常通訊,建立TCP連線。

\begin{figure}\centerline{\epsfig{file=reversal.eps, scale=0.40}}\end{figure}

3.2、基於UDP協議的P2P打洞技術

3.2.1、原理

UDP打洞技術是通過中間伺服器的協助在各自的NAT閘道器上建立相關的表項,使P2P連線的雙方傳送的報文能夠直接穿透對方的NAT閘道器,從而實現P2P客戶端互連。如果兩臺位於NAT裝置後面的P2P客戶端希望在自己的NAT閘道器上打個洞,那麼他們需要一個協助者——集中伺服器,並且還需要一種用於打洞的Session建立機制。

集中伺服器本質上是一臺被設定在公網上的伺服器,建立P2P的雙方都可以直接訪問到這臺伺服器。 位於NAT閘道器後面的客戶端A和B都可以與一臺已知的集中伺服器建立連線,並通過這臺集中伺服器瞭解對方的資訊並中轉各自的資訊。 同時集中伺服器的另一個重要作用在於判斷某個客戶端是否在NAT閘道器之後,集中伺服器可以從客戶端的登陸訊息中得到該客戶端的內網相關資訊,還可以通過登陸訊息的IP頭和UDP頭得到該客戶端的外網相關資訊。如果該客戶端不是位於NAT裝置後面,那麼採用上述方法得到的兩對地址二元組資訊是完全相同的。

P2P的Session建立的原理就是通過集中伺服器將包含各自的內外網二元組傳送給對方,這樣彼此都能知道對方內外網的地址了。然後A開始向B的內外網地址傳送UDP資料包,並且A會自動鎖定第一個給出相應的B的地址的二元組。

3.2.2、場景一:兩客戶端位於同一NAT裝置後(相同內網)

\begin{figure}\centerline{\epsfig{file=samenat.eps, scale=0.34}}\end{figure}

當A向集中伺服器發出訊息請求與B進行連線,集中伺服器將B的外網地址二元組以及內網地址二元組發給A,同時把A的外網以及內網的地址二元組資訊發給B。A和B發往對方公網地址二元組資訊的UDP資料包不一定會被對方收到,這取決於當前的NAT裝置是否支援不同埠之間的UDP資料包能否到達(即Hairpin轉換特性),無論如何A與B發往對方內網的地址二元組資訊的UDP資料包是一定可以到達的,內網資料包不需要路由,且速度更快。A與B推薦採用內網的地址二元組資訊進行常規的P2P通訊。

就目前的網路情況而言,應用程式在“打洞”的時候,最好還是把外網和內網的地址二元組都嘗試一下。如果都能成功,優先以內網地址進行連線。

Hairpin技術需要NAT閘道器支援,它能夠讓兩臺位於同一臺NAT閘道器後面的主機,通過對方的公網地址和埠相互訪問。目前有很多NAT裝置不支援該技術,這種情況下,NAT閘道器在一些特定場合下將會阻斷P2P穿越NAT的行為,打洞的嘗試是無法成功的。

3.2.3、場景二:兩客戶端位於不同NAT裝置後面(不同內網)

\begin{figure}\centerline{\epsfig{file=diffnat.eps, scale=0.34}}\end{figure}

在客戶端向伺服器傳送的登陸訊息中,包含有客戶端的內網地址二元組資訊;伺服器會記錄下客戶端的內網地址二元組資訊,同時會把自己觀察到的客戶端的外網地址二元組資訊記錄下來。

無論A、B二者中任何一方向伺服器傳送P2P連線請求,伺服器都會將起記錄下來內外網地址傳送給A和B。

A拿到B的外網地址後,開始傳送訊息,這個過程就是“打洞”。如果A發給B的外網地址二元組的訊息包在B向A傳送訊息包之前到達B的NAT裝置,B的NAT裝置會認為A發過來的訊息是未經授權的外網訊息,並丟棄該資料包,B發往A的訊息包也會在B的NAT裝置上建立一個【10.10.1.3:4321 155.99.25.11:62000】會話。

一旦A與B都向對方的NAT裝置在外網上的地址二元組傳送了資料包,就開啟了A與B之間的“洞”,A與B向對方的外網地址傳送資料,等效為向對方的客戶端直接傳送UDP資料包了。一旦應用程式確認已經可以通過往對方的外網地址傳送資料包的方式讓資料包到達NAT後面的目的應用程式,程式會自動停止繼續傳送用於“打洞”的資料包,轉而開始真正的P2P資料傳輸。

3.2.4、場景三:兩客戶端位於兩層(或多層)NAT裝置之後(不同內網)

此種情景最典型的部署情況就像這樣:最上層的NAT裝置通常是由網路提供商(ISP)提供,下層NAT裝置是家用路由器。

\begin{figure}\centerline{\epsfig{file=multinat.eps, scale=0.34}}\end{figure}

從這種拓撲結構上來看,只有伺服器與NAT C是真正擁有公網可路由IP地址的裝置,而NAT A和NAT B所使用的公網IP地址,實際上是由ISP服務提供商設定的(相對於NAT C而言)內網地址(我們將這種由ISP提供的內網地址稱之為“偽”公網地址)。

最優化的路由策略當然是通過“偽公網”IP通訊,但不幸的是A、B是無法通過伺服器知道這些“偽公網”地址,即使知道,也不建議,因為這些地址是由ISP提供的,存在重複的可能性(例如:NAT A的內網的IP地址域恰好與NAT A在NAT C的“偽”公網IP地址域重複,這樣就會導致打洞資料包無法發出的問題)。

因此客戶端只能使用公網伺服器觀察到的A、B的公網地址進行“打洞”操作,用於“打洞”的資料包將由NAT C進行轉發。

3.3、基於TCP協議的P2P打洞技術

這種技術從協議層面和UDP類似,只要NAT裝置支援的話,基於TCP的P2P技術的健壯性將比基於UDP技術的更強一些,因為TCP協議的狀態機給出了一種標準的方法來精確的獲取某個TCP session的生命期,而UDP協議則無法做到這一點。

3.3.1、套接字和TCP埠重用

API不提供類似UDP那樣的,同一個埠既可以向外連線,又能夠接受來自外部的連線。

TCP的套接字通常僅允許建立1對1的響應,即應用程式在將一個套接字繫結到本地的一個埠以後,任何試圖將第二個套接字繫結到該埠的操作都會失敗。

為了能夠打洞順利,需要使用一個TCP埠監聽來自外部分TCP連線,同時建立多個向外的TCP連線。幸運的是,所有主流作業系統支援特殊的TCP套接字引數SO_REUSEADDR

3.3.2、開啟P2P的TCP流

假定客戶端A希望建立與B的TCP連線。我們像通常一樣假定A和B已經與公網上的已知伺服器建立了TCP連線。伺服器記錄下來每個接入的客戶端的公網和內網的地址二元組,如同為UDP服務的時候一樣。

從協議層面來看,兩者過程幾乎完全相同。

P2P技術詳解(二):P2P中的NAT穿越(打洞)方案詳解_7.png

與UDP不同的是,因為使用UDP協議的每個客戶端只需要一個套接字即可完成與伺服器的通訊,而TCP客戶端必須處理多個套接字繫結到同一個本地TCP埠的問題。

客戶端向彼此公網地址二元組發起連線的操作,會使得各自的NAT裝置開啟新的“洞”允許A與B的TCP資料通過。如果NAT裝置支援TCP“打洞”操作的話,一個在客戶端之間的基於TCP協議的流通道就會自動建立起來。如果A向B傳送的第一個SYN包發到了B的NAT裝置,而B在此前沒有向A傳送SYN包,B的NAT裝置會丟棄這個包,這會引起A的“連線失敗”或“無法連線”問題。而此時,由於A已經向B傳送過SYN包,B發往A的SYN包將被看作是由A發往B的包的回應的一部分,所以B發往A的SYN包會順利地通過A的NAT裝置,到達A,從而建立起A與B的P2P連線。

3.3.3、從應用程式角度來看TCP打洞

假定A首先向B發出SYN包,該包發往B的公網地址二元組,並且被B的NAT裝置丟棄,但是B發往A的公網地址二元組的SYN包則通過A的NAT到達了A,然後,會發生以下的兩種結果中的一種,具體是哪一種取決於作業系統對TCP協議的實現:

  • A會認為自己連線成功了,本來A要找B,結果B自己送上門了,此時connect()會成功返回,A的listen()等待從外部聯入的函式將沒有任何反應。此時,B聯入A的操作在A程式的內部被理解為A聯入B連線成功,A的TCP將用SYN-ACK包回應B,B也認為自己連線成功了,連線建立起來。
  • A沒有前面那麼聰明,沒有發現聯入的B就是自己希望的。A通過常規的listen()函式和accept()函式得到與B的連線,而由A發起的向B的公網地址二元組連線將以失敗告終。

第一種結果適用於基於BSD的作業系統對於TCP的實現,而第二種結果更加普遍一些,多數Linux和Windows系統都會按照第二種結果來處理。

參考連結:

Peer-to-Peer Communication Across Network Address Translators

http://www.52im.net/thread-1055-1-1.html

http://www.52im.net/thread-2872-1-1.html

相關文章