NAT技術及穿透原理解析


隨着因特網規模的飛速發展,聯網設備數量不斷增加,地址空間大小只有2^32的IPv4地址正面臨着枯竭,而作為下一代網絡層協議的IPv6雖然擁有巨大的地址數量,但面對龐大的歷史遺留問題也顯得力不從心。在這種情況下,NAT(Network Address Translation,網絡地址轉換)技術應運而生。NAT的作用,是讓多個擁有獨立內網IP的設備,能夠共用一個外網IP和外部進行通信。由於不同內網IP互不干涉,不同內網下的設備可以使用同一個內網IP,這樣一來,IP地址的短缺現象就大大緩解了。

NAT基本原理和大致流程

NAT技術搭載在路由器上,通過修改經由該路由器轉發的數據報來達到目的。眾所周知,無論是TCP還是UDP傳輸協議,都涉及到四個屬性:源地址、源端口、目標地址和目標端口。假設設備HostA要向設備HostB發送數據,它會把自己的地址,以及HostB的IP地址放入IP數據報的首部。在沒有NAT存在的情況下,兩個設備之間的路由器會根據這個首部信息和自身的路由表,將數據報轉發向另一個更接近HostB的路由器,直至數據報最終到達HostB。在這個過程中,雖然數據報經過了多個路由器的轉發,但它的IP數據報首部是不變的。
然而,使用了NAT技術的路由器(以下稱它為Nat)會打破這個規則。Nat會把自己的管理的網段分為內外兩側,位於兩側的主機進行的一切通信都要經過N的轉發。在每個外側網段,Nat都需要一個全球唯一的外網IP地址,除非這個網段位於另一個NAT設備的內部網段,這種情況下,外網IP只需在該網段內唯一即可。而在內側網段,所有主機,包括Nat自身的IP都可以任意決定,但為了避免和已有的外網IP沖突導致分不清內外側的主機,一般都會是一些特殊格式,例如我們常見的“192.168.x.x”。
假設HostA位於Nat的內側,那么在HostA第一次用自己的端口PortA向位於外部網段的HostB發送數據的時候,Nat首先會遵循傳統路由器的行為,根據路由表找出HostB位於哪個外側網段上。然后,它會在自身的內存中新建一對映射關系 (HostA, PortA) <-> (HostX, PortX),其中 HostX 是Nat在對應外側網段的IP地址,而 PortX 是為此關系動態分配的一個空閑端口。建立映射后,Nat會偷偷修改IP數據報的首部,把源地址從 HostA 改成 HostX,還會修改數據報的內容,把源端口從 PortA 改成 PortX(注意“端口”是傳輸層的概念,位於TCP或UDP數據包的首部,不屬於IP數據報的首部)。最后,它又會像一個傳統路由器那樣,把數據報轉發向下一跳路由器。這樣一來,由於數據報的源地址和端口都被改成了Nat的,位於外側網段的HostB不會意識到HostA的存在,而所有來自HostA的通信都像是由Nat發起的。於是,它會被“蒙在鼓里”,將屬於Nat的 (HostX, PortX) 作為目標地址和目標端口來發送響應。Nat在收到響應后,會利用數據報中的 (HostX, PortX) 作為 key,從映射表中查找出 (HostA, PortA),然后根據條目內容再一次修改數據報,把目的地址改成HostA,目的端口改成PortA,最后把數據報轉發給內網的HostA。HostA也不會意識到Nat對這個數據報動的手腳,因為Nat只改動了目的地址,而沒有改動源地址。在HostA看來,這個數據報的來源就是 (HostB, PortB),沒有任何異常之處。於是,它就高興地接受了這個數據報。在后續的通信中,這個映射關系也會一直保留下去,直到一段時間沒有使用后被Nat刪除(這也是TCP長連接需要發送心跳包保活的主要原因)。
以上就是NAT的完整工作流程了。從上面的描述中可以看出,NAT是一種跨越網絡層和傳輸層的,對通信雙方和中間路由透明的外網地址復用手段。由於內網中的所有IP經NAT轉換后,都只對應着一個由NAT設備持有,和它們毫不相干的外網IP,自然就不用考慮為每個內網設備分配一個在外網中唯一的IP了。

NAT的固有缺陷

俗話說“沒有銀彈”,看似美好的NAT技術也有着一個致命的缺陷:NAT內側的設備無法作為連接的接受方,換句話說,不能作為服務器。理由很顯然:在HostA沒有主動向外側發起連接的情況下,NAT設備中根本沒有對應的映射關系,無論外網設備訪問Nat的哪個端口,都沒辦法讓Nat將數據轉發給A,除非在Nat中手動配置一個靜態的關系(家用路由器中的“端口映射”設置就是做這個的)。這個問題在傳統的C/S模式下還沒那么嚴重,因為服務器一般都有自己的公網IP;但在P2P網絡中,每個主機都需要扮演服務器的角色,被動接受其他主機的連接。因此,NAT后的主機根本無法成為有效的P2P節點。例如,P2P下載速度在國內很慢的主要原因,就是國內的ISP大量使用了NAT技術,導致用戶只能從他人處下載數據,而無法接受他人的下載請求,長久一來,其他下載者也不願意向用戶提供數據了。因此,人們對P2P應用的需求,催生了各種NAT穿透技術。

NAT類型

在講解NAT穿透的各種方式前,首先有必要了解NAT的不同類型,以便 “逐個擊破”。總的來說,NAT主要分為兩大類:

  • 錐型(Cone)
  • 對稱型(Symmetric)(D類)

其中錐形NAT還分為以下三個子類:

  • 完全(Full)(A類)
  • 地址受限型(Address Restricted)(B類)
  • 端口受限型(Port Restricted)(C類)

習慣上,還可以用后面括號中的字母代指NAT類型。

錐型NAT vs 對稱型NAT

錐型NAT和對稱型NAT的主要區別,在於內部主機向不同的外部地址發送數據時,NAT設備的行為。下面我們假設Nat中已經存在了映射關系 (HostA, PortA) <-> (HostX, PortX),而這個關系是HostA通過訪問 (HostB, PortB) 建立的。

  • 在錐型NAT中,只要HostA繼續用PortA發送數據,不管發送給誰,這個數據的外網側源端口都會被轉換成PortX。
  • 在對稱型NAT中,只要 (HostB, PortB) 中的任意一項發生了變化,哪怕源端口依然是PortA,路由器也會建立一個新的映射關系,分配一個新的外網端口向這個目標進行轉發。

用形式化的說法來總結的話,錐型NAT的映射函數是 (內網源IP, 源端口) <-> (外網IP, 外網端口),而對稱型NAT的映射函數是 (內網源IP, 源端口, 目標IP, 目標端口) <-> (外網IP, 外網端口)。顯然,對稱型NAT對內網設備的限制更大,穿透難度也更大。
此外,一個NAT設備在同一外網網段中還可能會擁有多個IP,在建立映射關系的過程中,NAT設備會根據實際的網絡使用情況,從IP地址池中為每條映射關系分配一個合適的外網IP。為簡略起見,在之后的討論中,我們忽略外網IP的選擇和后續路由過程,直接假設NAT設備只有唯一一個外網IP,也就是將映射右側的 (HostX, PortX) 簡化為 (PortX)。

錐型NAT的三個子類

錐型NAT三個子類的主要區別,在於外部主機向Nat上已經建立過映射關系的端口發送數據時,NAT設備的行為。下面我們依然假設存在映射關系 (HostA, PortA) <-> (PortX),而這個關系是HostA通過訪問 (HostB, PortB) 建立的。

  • 在完全錐型NAT中,NAT設備會無條件將PortX收到的數據報轉發給 (HostA, PortA),不論數據報來自何處。
  • 在地址受限型NAT中,只有來自HostB的數據報才會被轉發,但不限制來自HostB的哪個端口。
  • 在端口受限型NAT中,只有來自 (HostB, PortB) 的數據報才會被轉發。
  • 對稱型NAT在這方面的行為和端口受限型NAT一致。
    可見,三個子類的限制依次加強,穿透難度也依次增加。

NAT穿透

首先給出(筆者個人)對NAT穿透的定義:一種在外網中繼節點的幫助下,讓兩個位於各自NAT后的主機建立點對點連接的手段。從以上定義中可以看出:

  • 只有通信雙方都位於NAT后面時,才需要進行穿透。如果任意一方擁有外網地址,只需讓該主機作為連接接受方,另一方作為連接發起方即可。
  • NAT穿透必須有一個擁有外網IP的中繼節點的幫助,但連接建立后發送的數據並不經過中繼節點,否則就和流量轉發沒有區別了。

接下來,我們將通信雙方的NAT類型分為三種情況,分別討論對應的穿透手段。下面我們假設HostA和HostB之間要建立連接,它們所屬的NAT設備分別為NatA(外網地址為 HostNA)和NatB(外網地址為 HostNB),中繼節點為Relay。

雙方都是ABC類NAT

這是最簡單的情況,流程如下:

  1. HostA通過端口PortA,向Relay發送一個數據報,用一個唯一的主機ID進行注冊。Relay收到數據報后,可以提取出NatA的外網地址,以及NatA為此次連接新分配的端口號,表示為 (HostNA, PortNA)。
  2. 當HostB希望向HostA發起連接時,首先要通過某種手段獲取到HostA的主機ID(一般也是通過中繼服務器),然后通過端口PortB,向Relay發出請求查詢對應的 (HostNA, PortNA)。此時,NatB也會為HostB分配一個新的端口PortNB。Relay收到請求后,不僅向B返回數據,還會向HostA通知HostB的連接意圖,並附帶上NatB的對應信息 (HostNB, PortNB)。由於HostA剛剛在NatA上建立了和Relay的映射關系,這個消息可以成功到達HostA。
  3. HostA通過和之前相同的端口PortA,向 (HostNB, PortNB) 發送一個數據報。由於NatB有可能是B類或C類,而HostA(在NatB看來是NatA)之前沒有向NatB發送過任何數據,這個數據報不一定會被轉發給HostB,但會在NatA上建立一條目的地址是 (HostNB, PortNB) 的映射關系,讓所有來自 (HostNB, PortNB) 並且目的端口為 PortNA 的數據都能轉發給 HostA 的 PortA。
  4. HostB通過和之前相同的端口PortB,向 (HostNA, PortNA) 發出真正的連接請求。由於第2步中已經建立了PortB到PortNB的映射關系,這個請求在外網的源端口依舊會是PortNB。又由於第3步中HostA已經在NatA上建立了和 (HostNB, PortNB) 相關的映射關系,這個請求以及后續數據會成功地被NatA轉發給HostA。
  5. HostA向 (HostNB, PortNB) 發送回復。由於第4步中NatB上已建立好映射關系,回復以及后續數據會成功地被NatB轉發給HostB。於是,連接成功建立。

注意:1、3、5步HostA發送信息使用的端口PortA必須是同一個,2、4步HostB的端口PortB也必須是同一個。原因留給大家自行思考,結合錐形NAT的定義就能很快得出答案。
第3步中HostA向NatB發送數據報的過程被形象地稱為“打洞”,因為這個數據報雖然不一定被NatB所接受,卻可以在NatA上開一個“洞”,來自NatB的后續數據都可以從“洞”里進來到達HostA。退一步來說,假設NatA是一個完全錐型NAT,甚至連打洞都不需要,HostB在第2步從Relay獲取到 (HostNA, PortNA) 后,直接向該地址發送連接請求即可。

一方是D類NAT,另一方是AB類NAT

這種情況和上一種差不多,但“打洞”的一方必須是位於AB類NAT后的那方。具體流程和上面相同,只是要把AB類NAT后的一方看成HostA,另一方看成HostB即可。
為什么D類NAT不能打洞?這是因為在第3步中,就算HostA使用了和第1步相同的端口PortA,由於目的地址從Relay變成了HostNB,NatA也會建立一條新的映射關系,不會使用之前PortA對應的端口PortNA。如此一來,這個“洞”的信息除了NatA本人以外,沒有任何人知道,HostB也就無從得知應該向NatA上的哪個端口發送數據,才能成功“進洞”到達HostA了。

一方是D類NAT,另一方是CD類NAT

不幸的是,這種情況下NAT穿透是不可行的,只能通過位於外網上的中繼服務器來轉發HostA和HostB之間的一切流量。
照理說,C類NAT也屬於錐形,為何面對D類NAT時卻不能成功打洞了?原因很簡單,如果對面是D類NAT,就算在NatA上打了洞,第4步HostB發起連接時,就算使用和第2步相同的端口PortB,由於目標地址從Relay變為了HostNA,NatB也會建立一條新的映射關系,不會使用之前PortB對應的端口PortNB。這樣一來,由於傳入數據報的源端口和之前打洞時約定好的PortNB不同,“端口受限型”的NatA就會拒絕將數據轉發給HostA,也就是“進不去洞”。假如NatA是AB類的話,只要傳入數據報的源IP保持HostNB不變,數據就可以成功“進洞”。

NAT穿透相關協議

  • 部署STUN協議的服務器可以檢測用戶的NAT類型,並且告知用戶在公網上的IP地址。
  • 部署TURN協議的服務器可以在無法穿透時轉發用戶的TCP和UDP流量。
  • UPnP協議允許應用主動在(任何類型的)NAT上打洞,並且讓這個洞的表現看起來像是完全錐型NAT。

這些協議的具體細節就不再詳細敘述了,感興趣的讀者可以自行了解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM