NAT原理

 網路地址轉換(NAT,Network Address Translation)屬接入廣域網(WAN)技術,是一種將私有(保留)地址轉化為合法IP地址的轉換技術。下面介紹兩類不同方式實現的NAT:

  1. NAT(Network Address Translators):稱為基本的NAT

在客戶機時      192.168.0.8:4000——6.7.8.9:8000

在閘道器時         1.2.3.4:4000——6.7.8.9:8000

伺服器C          6.7.8.9:8000

其核心是替換IP地址而不是埠,這會導致192.168.0.8使用4000埠後,192.168.0.9如何處理?具體參考RFC 1631

基本上這種型別的NAT裝置已經很少了。或許根本我們就沒機會見到。

     2.   NAPT(Network Address/Port Translators):其實這種才是我們常說的 NAT

NAPT的特點是在閘道器時,會使用閘道器的 IP,但埠會選擇一個和臨時會話對應的臨時埠。如下圖:

在客戶機時           192.168.0.8:4000——6.7.8.9:8000

在閘道器時              1.2.3.4:62000——6.7.8.9:8000

伺服器C               6.7.8.9:8000

閘道器上建立保持了一個1.2.3.4:62000的會話,用於192.168.0.8:4000與6.7.8.9:8000之間的通訊。

對於NAPT,又分了兩個大的型別,差別在於,當兩個內網使用者同時與8000埠通訊的處理方式不同:

         2.1、Symmetric NAT型 (對稱型)

在客戶機時              192.168.0.8:4000——6.7.8.9:8000 192.168.0.8:4000——6.7.8.10:8000

在閘道器時,兩個不同session但埠號不同      1.2.3.4:62000——6.7.8.9:8000 1.2.3.4:62001——6.7.8.10:8000

伺服器C      6.7.8.9:8000

伺服器 D     6.7.8.10:8000

這種形式會讓很多p2p軟體失靈。

        2.2、Cone NAT型(圓錐型)

在客戶機時              192.168.0.8:4000——6.7.8.9:8000 192.168.0.8:4000——6.7.8.10:8000

在閘道器時,兩個不同session但埠號相同      1.2.3.4:62000——6.7.8.9:8000 1.2.3.4:62000——6.7.8.10:8000

伺服器C           6.7.8.9:8000

伺服器D           6.7.8.10:8000

目前絕大多數屬於這種。Cone NAT又分了3種型別:

  • a)Full Cone NAT(完全圓錐型):從同一私網地址埠192.168.0.8:4000發至公網的所有請求都對映成同一個公網地址埠1.2.3.4:62000 ,192.168.0.8可以收到任意外部主機發到1.2.3.4:62000的資料包。
  • b)Address Restricted Cone NAT (地址限制圓錐型):從同一私網地址埠192.168.0.8:4000發至公網的所有請求都對映成同一個公網地址埠1.2.3.4:62000,只有當內部主機192.168.0.8先給伺服器C 6.7.8.9傳送一個資料包後,192.168.0.8才能收到6.7.8.9傳送到1.2.3.4:62000的資料包。
  • c)Port Restricted Cone NAT(埠限制圓錐型):從同一私網地址埠192.168.0.8:4000發至公網的所有請求都對映成同一個公網地址埠1.2.3.4:62000,只有當內部主機192.168.0.8先向外部主機地址埠6.7.8.9:8000傳送一個資料包後,192.168.0.8才能收到6.7.8.9:8000傳送到1.2.3.4:62000的資料包。   
  • 穿越NAT的實現

A1在客戶機時                192.168.0.8:4000——6.7.8.9:8000

X1在閘道器時                   1.2.3.4:62000——6.7.8.9:8000

伺服器C                       6.7.8.9:8000

B1在客戶機時                192.168.1.8:4000——6.7.8.9:8000

Y1在閘道器時                   1.2.3.5:31000——6.7.8.9:8000

兩內網使用者要實現通過各自閘道器的直接呼叫,需要以下過程:

1、 客戶機A1、B1順利通過格子閘道器訪問伺服器C ,均沒有問題(類似於登入)

2、 伺服器C儲存了 A1、B1各自在其閘道器的資訊(1.2.3.4:62000、1.2.3.5:31000)沒有問題。並可將該資訊告知A1、B2。

3、 此時A1傳送給B1閘道器的1.2.3.5:31000是否會被B1收到?答案是基本上不行(除非Y1設定為完全圓錐型,但這種設定非常少),因為Y1上檢測到其存活的會話中沒有一個的目的IP或埠於1.2.3.4:62000有關而將資料包全部丟棄!

4、 此時要實現A1、B1通過X1、Y1來互訪,需要伺服器C告訴它們各自在自己的閘道器上建立“UDP隧道”,即命令A1傳送一個 192.168.0.8:4000——1.2.3.5:31000的資料包,B1傳送一個192.168.1.8:4000——1.2.3.4:62000的資料包,UDP形式,這樣X1、Y1上均存在了IP埠相同的兩個不同會話(很顯然,這要求閘道器為Cone NAT型,否則,對稱型Symmetric NAT設定閘道器將導致對不同會話開啟了不同埠,而該埠無法為伺服器和對方所知,也就沒有意義)。

5、 此時A1發給Y1,或者B1發給X1的資料包將不會被丟棄且正確的被對方收到.

綜合P2P可實現的條件需要:

1、 中間伺服器儲存資訊、並能發出建立UDP隧道的命令

2、 閘道器均要求為Cone NAT型別。Symmetric NAT不適合。

3、 完全圓錐型閘道器可以無需建立udp隧道,但這種情況非常少,要求雙方均為這種型別閘道器的更少。

4、 假如X1閘道器為Symmetric NAT, Y1為Address Restricted Cone NAT 或Full Cone NAT型閘道器,各自建立隧道後,A1可通過X1傳送資料包給Y1到B1(因為Y1最多隻進行IP級別的甄別),但B2傳送給X1的將會被丟棄(因為傳送來的資料包中埠與X1上存在會話的埠不一致,雖然IP地址一致),所以同樣沒有什麼意義。

5、 假如雙方均為Symmetric NAT的情形,新開了埠,對方可以在不知道的情況下嘗試猜解,也可以達到目的,但這種情形成功率很低,且帶來額外的系統開支,不是個好的解決辦法。

6、 不同閘道器型設定的差異在於,對內會採用替換IP的方式、使用不同埠不同會話的方式,使用相同埠不同會話的方式;對外會採用什麼都不限制、限制IP地址、限制IP地址及埠。

7、 這裡還沒有考慮同一內網不同使用者同時訪問同一伺服器的情形,如果此時閘道器採用AddressRestricted Cone NAT 或Full Cone NAT型,有可能導致不同使用者客戶端可收到別人的資料包,這顯然是不合適的。

 UDP和TCP打洞

為什麼網上講到的P2P打洞基本上都是基於UDP協議的打洞?難道TCP不可能打洞?還是TCP打洞難於實現?
    假設現在有內網客戶端A和內網客戶端B,有公網服務端S。
    如果A和B想要進行UDP通訊,則必須穿透雙方的NAT路由。假設為NAT-A和NAT-B。
    
    A傳送資料包到公網S,B傳送資料包到公網S,則S分別得到了A和B的公網IP,
S也和A B 分別建立了會話,由S發到NAT-A的資料包會被NAT-A直接轉發給A,
由S發到NAT-B的資料包會被NAT-B直接轉發給B,除了S發出的資料包之外的則會被丟棄。
所以:現在A B 都能分別和S進行全雙工通訊了,但是A B之間還不能直接通訊。

    解決辦法是:A向B的公網IP傳送一個資料包,則NAT-A能接收來自NAT-B的資料包
並轉發給A了(即B現在能訪問A了);再由S命令B向A的公網IP傳送一個資料包,則
NAT-B能接收來自NAT-A的資料包並轉發給B了(即A現在能訪問B了)。

以上就是“打洞”的原理。

為了保證A的路由器有與B的session,A要定時與B做心跳包,同樣,B也要定時與A做心跳,這樣,雙方的通訊通道都是通的,就可以進行任意的通訊了。

    但是TCP和UDP在打洞上卻有點不同。這是因為伯克利socket(標準socket規範)的
API造成的。
    UDP的socket允許多個socket繫結到同一個本地埠,而TCP的socket則不允許。
    這是這樣一個意思:A B要連線到S,肯定首先A B雙方都會在本地建立一個socket,
去連線S上的socket。建立一個socket必然會繫結一個本地埠(就算應用程式裡面沒寫
埠,實際上也是繫結了的,至少java確實如此),假設為8888,這樣A和B才分別建立了到
S的通訊通道。接下來就需要打洞了,打洞則需要A和B分別傳送資料包到對方的公網IP。但是
問題就在這裡:因為NAT裝置是根據埠號來確定session,如果是UDP的socket,A B可以
分別再建立socket,然後將socket繫結到8888,這樣打洞就成功了。但是如果是TCP的
socket,則不能再建立socket並繫結到8888了,這樣打洞就無法成功。

UDP打洞的過程大致如此: 

1、雙方都通過UDP與伺服器通訊後,閘道器預設就是做了一個外網IP和埠號 與你內網IP與埠號的對映,這個無需設定的,伺服器也不需要知道客戶的真正內網IP 

2、使用者A先通過伺服器知道使用者B的外網地址與埠  

3、使用者A向使用者B的外網地址與埠傳送訊息,  

4、在這一次傳送中,使用者B的閘道器會拒收這條訊息,因為它的對映中並沒有這條規則。  

5、但是使用者A的閘道器就會增加了一條允許規則,允許接收從B傳送過來的訊息  

6、伺服器要求使用者B傳送一個訊息到使用者A的外網IP與埠號  

7、使用者B傳送一條訊息,這時使用者A就可以接收到B的訊息,而且閘道器B也增加了允許規則  

8、之後,由於閘道器A與閘道器B都增加了允許規則,所以A與B都可以向對方的外網IP和埠號傳送訊息。

TCP打洞技術:
tcp打洞也需要NAT裝置支援才行。
tcp的打洞流程和udp的基本一樣,但tcp的api決定了tcp打洞的實現過程和udp不一樣。
tcp按cs方式工作,一個埠只能用來connect或listen,所以需要使用埠重用,才能利用本地nat的埠對映關係。(設定SO_REUSEADDR,在支援SO_REUSEPORT的系統上,要設定這兩個引數。)

連線過程:(以udp打洞的第2種情況為例(典型情況))
nat後的兩個peer,A和B,A和B都bind自己listen的埠,向對方發起連線(connect),即使用相同的埠同時連線和等待連線。因為A和B發出連線的順序有時間差,假設A的syn包到達B的nat時,B的syn包還沒有發出,那麼B的nat對映還沒有建立,會導致A的連線請求失敗(連線失敗或無法連線,如果nat返回RST或者icmp差錯,api上可能表現為被RST;有些nat不返回資訊直接丟棄syn包(反而更好)),(應用程式發現失敗時,不能關閉socket,closesocket()可能會導致NAT刪除埠對映;隔一段時間(1-2s)後未連線還要繼續嘗試);但後發B的syn包在到達A的nat時,由於A的nat已經建立的對映關係,B的syn包會通過A的nat,被nat轉給A的listen埠,從而進去三次握手,完成tcp連線。

從應用程式角度看,連線成功的過程可能有兩種不同表現:(以上述假設過程為例)
1、連線建立成功表現為A的connect返回成功。即A端以TCP的同時開啟流程完成連線。
2、A端通過listen的埠完成和B的握手,而connect嘗試持續失敗,應用程式通過accept獲取到連線,最終放棄connect(這時可closesocket(conn_fd))。
多數Linux和Windows的協議棧表現為第2種。

但有一個問題是,建立連線的client端,其connect繫結的埠號就是主機listen的埠號,或許這個peer後續還會有更多的這種socket。雖然理論上說,socket是一個五元組,埠號是一個邏輯數字,傳輸層能夠因為五元組的不同而區分開這些socket,但是是否存在實際上的異常,還有待更多觀察。

另外的問題:
1、Windows XP SP2作業系統之前的主機,這些主機不能正確處理TCP同時開啟,或者TCP套接字不支援SO_REUSEADDR的引數。需要讓AB有序的發起連線才可能完成。

上述tcp連線過程,僅對NAT1、2、3有效,對NAT4(對稱型)無效。
由於對稱型nat通常採用規律的外部埠分配方法,對於nat4的打洞,可以採用埠預測的方式進行嘗試。

一些現在常用的技術:

ALG(應用層閘道器):它可以是一個裝置或外掛,用於支援SIP協議,主要類似與在閘道器上專門開闢一個通道,用於建立內網與外網的連線,也就是說,這是一種定製的閘道器。更多隻適用於使用他們的應用群體內部之間。

UpnP:它是讓閘道器裝置在進行工作時尋找一個全球共享的可路由IP來作為通道,這樣避免埠造成的影響。要求裝置支援且開啟upnp功能,但大部分時候,這些功能處於安全考慮,是被關閉的。即時開啟,實際應用效果還沒經過測試。

STUN(Simple Traversalof UDP Through Network):這種方式即是類似於我們上面舉例中伺服器C的處理方式。也是目前普遍採用的方式。但具體實現要比我們描述的複雜許多,光是做閘道器Nat型別判斷就由許多工作,RFC3489中詳細描述了。

TURN(Traveral Using Relay NAT):該方式是將所有的資料交換都經由伺服器來完成,這樣NAT將沒有障礙,但伺服器的負載、丟包、延遲性就是很大的問題。目前很多遊戲均採用該方式避開NAT的問題。這種方式不叫p2p。

ICE(Interactive Connectivity Establishment):是對上述各種技術的綜合,但明顯帶來了複雜性。