一. Nat介紹
1.nat基本介紹
NAT分類


NAPT類型介紹
NAT對待UDP的實現方式有4種,分別如下:
-
Full Cone NAT: 完全錐形NAT,所有從同一個內網IP和端口號發送過來的請求都會被映射成同一個外網IP和端口號,並且任何一個外網主機都可以通過這個映射的外網IP和端口號向這台內網主機發送包。
-
Restricted Cone NAT: 限制錐形NAT,它也是所有從同一個內網IP和端口號發送過來的請求都會被映射成同一個外網IP和端口號。與完全錐形不同的是,外網主機只能夠向先前已經向它發送過數據包的內網主機發送包。
-
Port Restricted Cone NAT: 端口限制錐形NAT,與限制錐形NAT很相似,只不過它包括端口號。也就是說,一台IP地址X和端口P的外網主機想給內網主機發送包,必須是這台內網主機先前已經給這個IP地址X和端口P發送過數據包。
-
Symmetric NAT: 對稱NAT,所有從同一個內網IP和端口號發送到一個特定的目的IP和端口號的請求,都會被映射到同一個IP和端口號。如果同一台主機使用相同的源地址和端口號發送包,但是發往不同的目的地,NAT將會使用不同的映射。此外,只有收到數據的外網主機才可以反過來向內網主機發送包。
二. Stun介紹
1.stun基本介紹
STUN是一種網絡協議,它允許位於NAT(或多重NAT)后的客戶端找出自己的公網地址,查出自己位於哪種類型的NAT之后以及NAT為某一個本地端口所綁定的Internet端端口。這些信息被用來在兩個同時處於NAT路由器之后的主機之間建立UDP通信。該協議由RFC 5389定義。
STUN由三部分組成:
-
STUN客戶端;
-
STUN服務器端;
-
NAT路由器。
STUN服務端部署在一台有着兩個公網IP的服務器上,大概的結構參考下圖。STUN客戶端通過向服務器端發送不同的消息類型,根據服務器端不同的響應來做出相應的判斷,一旦客戶端得知了Internet端的UDP端口,通信就可以開始了。
2. stun的檢測過程
STUN協議定義了三類測試過程來檢測NAT類型:
-
Test1:STUN Client通過端口{IP-C1:Port-C1}向STUN Server{IP-S1:Port-S1}發送一個Binding Request(沒有設置任何屬性)。STUN Server收到該請求后,通過端口{IP-S1:Port-S1}把它所看到的STUN Client的IP和端口{IP-M1,Port-M1}作為Binding Response的內容回送給STUN Client。
-
Test1#2:STUN Client通過端口{IP-C1:Port-C1}向STUN Server{IP-S2:Port-S2}發送一個Binding Request(沒有設置任何屬性)。STUN Server收到該請求后,通過端口{IP-S2:Port-S2}把它所看到的STUN Client的IP和端口{IP-M1#2,Port-M1#2}作為Binding Response的內容回送給STUN Client。
-
Test2:STUN Client通過端口{IP-C1:Port-C1}向STUN Server{IP-S1:Port-S1}發送一個Binding Request(設置了Change IP和Change Port屬性)。STUN Server收到該請求后,通過端口{IP-S2:Port-S2}把它所看到的STUN Client的IP和端口{IP-M2,Port-M2}作為Binding Response的內容回送給STUN Client。
-
Test3:STUN Client通過端口{IP-C1:Port-C1}向STUN Server{IP-S1:Port-S1}發送一個Binding Request(設置了Change Port屬性)。STUN Server收到該請求后,通過端口{IP-S1:Port-S2}把它所看到的STUN Client的IP和端口{IP-M3,Port-M3}作為Binding Response的內容回送給STUN Client。
STUN協議的輸出是:
1)公網IP和Port; 2)防火牆是否設置; 3)客戶端是否在NAT之后,及所處的NAT的類型。
因此我們進而整理出,通過STUN協議,我們可以檢測的類型一共有以下七種:
-
A:公開的互聯網IP:主機擁有公網IP,並且沒有防火牆,可自由與外部通信;
-
B:完全錐形NAT;
-
C:受限制錐形NAT;
-
D:端口受限制形NAT;
-
E:對稱型UDP防火牆:主機出口處沒有NAT設備,但有防火牆,且防火牆規則如下:從主機UDP端口A發出的數據包保持源地址,但只有從之前該主機發出包的目的IP/PORT發出到該主機端口A的包才能通過防火牆;
-
F:對稱型NAT;
-
G:防火牆限制UDP通信。
3. stun協議的判斷過程


STEP1:檢測客戶端是否有能力進行UDP通信以及客戶端是否位於NAT后 -- Test1
客戶端建立UDP socket,然后用這個socket向服務器的(IP-1,Port-1)發送數據包要求服務器返回客戶端的IP和Port,客戶端發送請求后立即開始接受數據包。重復幾次。
-
a)如果每次都超時收不到服務器的響應,則說明客戶端無法進行UDP通信,可能是:G防火牆阻止UDP通信
-
b)如果能收到回應,則把服務器返回的客戶端的(IP:PORT)同(Local IP: Local Port)比較:如果完全相同則客戶端不在NAT后,這樣的客戶端是:A具有公網IP可以直接監聽UDP端口接收數據進行通信或者E。否則客戶端在NAT后要做進一步的NAT類型檢測(繼續)。
STEP2:檢測客戶端防火牆類型 -- Test2
STUN客戶端向STUN服務器發送請求,要求服務器從其他IP和PORT向客戶端回復包:
-
a)收不到服務器從其他IP地址的回復,認為包前被前置防火牆阻斷,網絡類型為E
-
b)收到則認為客戶端處在一個開放的網絡上,網絡類型為A
STEP3:檢測客戶端NAT是否是FULL CONE NAT -- Test2
客戶端建立UDP socket然后用這個socket向服務器的(IP-1,Port-1)發送數據包要求服務器用另一對(IP-2,Port-2)響應客戶端的請求往回發一個數據包,客戶端發送請求后立即開始接受數據包。 重復這個過程若干次。
-
a)如果每次都超時,無法接受到服務器的回應,則說明客戶端的NAT不是一個Full Cone NAT,具體類型有待下一步檢測(繼續)。
-
b)如果能夠接受到服務器從(IP-2,Port-2)返回的應答UDP包,則說明客戶端是一個Full Cone NAT,這樣的客戶端能夠進行UDP-P2P通信。
STEP4:檢測客戶端NAT是否是SYMMETRIC NAT -- Test1#2
客戶端建立UDP socket然后用這個socket向服務器的(IP-1,Port-1)發送數據包要求服務器返回客戶端的IP和Port, 客戶端發送請求后立即開始接受數據包。 重復這個過程直到收到回應(一定能夠收到,因為第一步保證了這個客戶端可以進行UDP通信)。用同樣的方法用一個socket向服務器的(IP-2,Port-2)發送數據包要求服務器返回客戶端的IP和Port。比較上面兩個過程從服務器返回的客戶端(IP,Port),如果兩個過程返回的(IP,Port)有一對不同則說明客戶端為Symmetric NAT,這樣的客戶端無法進行UDP-P2P通信(檢測停止)因為對稱型NAT,每次連接端口都不一樣,所以無法知道對稱NAT的客戶端,下一次會用什么端口。否則是Restricted Cone NAT,是否為Port Restricted Cone NAT有待檢測(繼續)。
STEP5:檢測客戶端NAT是Restricted Cone 還是 Port Restricted Cone -- Test3
客戶端建立UDP socket然后用這個socket向服務器的(IP-1,Port-1)發送數據包要求服務器用IP-1和一個不同於Port-1的端口發送一個UDP 數據包響應客戶端, 客戶端發送請求后立即開始接受數據包。重復這個過程若干次。如果每次都超時,無法接受到服務器的回應,則說明客戶端是一個Port Restricted Cone NAT,如果能夠收到服務器的響應則說明客戶端是一個Restricted Cone NAT。以上兩種NAT都可以進行UDP-P2P通信。
由前述的一對多轉換模型得知,除對稱型NAT以外的模型,NAT網關對內部主機地址端口的映射都是相對固定的,所以比較容易實現NAT穿越。而對稱型NAT為每個連接提供一個映射,使得轉換后的公網地址和端口對不可預測。此時TURN可以與STUN綁定提供穿越NAT的服務,即在公網服務器上提供一個“地址端口對”,所有此“地址端口對”接收到的數據會經由探測建立的連接轉發到內網主機上。TURN分配的這個映射“地址端口對”會通過STUN響應發給內部主機,后者將此信息放入建立連接的信令中通知通信的對端。這種探針技術是一種通用方法,不用在NAT設備上為每種應用協議開發功能,相對於ALG方式有一定普遍性。但是TURN中繼服務會成為通信瓶頸。而且在客戶端中增加探針功能要求每個應用都要增加代碼才能支持。
我理解上述stun+turn的方式可以解決對稱nat的nat穿越問題,因為turn提供公網ip+port,中繼轉發。 就相當於源服務器和turn只建立一個連接,所以映射的ip和port不會變。 所以這個方案其實是從源server到turn至少兩層nat。 其實相當於turn服務器提供了port mapping服務?
三. 對稱型nat打洞分析
實際上大部運營商提供的光貓上網服務都是錐形nat的。 而光纖入戶,3g 4g網絡,公共wifi登因為安全因素都是對稱nat。 對稱型nat是可以打洞的。只要請求鏈接的對方是非端口限制錐型nat就都能實現打洞p2p鏈接的。 只有雙方都是對稱是一定無法實現。 (ICE可以解決?) 或一方對稱一方是端口限制錐型nat的情況也無法實現打洞。 生日攻擊算法(端口預測)確實可以解決對稱型與端口限制型之間的穿透
對於一端是對稱nat,一端是端口限制性Cone nat的情況是可以打洞成功的,特別是我們實驗的對稱nat的端口變化還是有規律的(加1),我們使用端口猜測的方法進行打洞成功率還是非常高的。對於端口變化無規律的對稱nat,這個猜測還是靠算法的設計,你可以看看A New Method for Symmetric NAT Traversal in UDP and TCP (http://www.goto.info.waseda.ac.jp/~wei/file/wei-apan-v10.pdf)另外如果你是做應用的話建議不要始終把自己局限於一定要打洞成功的思路上,對於一些路由器是可以通過配置支持穿透的,比如upnp;對於實在打洞不成功的情況你可以通過設計一個中轉服務器來完成自己的應用;
UDP hole punching
對於Cone NAT要采用UDP打洞.需要一個公網機器C來充當”介紹人”. 內網的A,B先分別和C通信.打開各自的NAT端口. C這個時候知道A,B的公網IP: Port. 現在A和B想直接連接.比如A給B發.除非B是Full Cone.否則不能通信.反之亦然. 但是我們可以這樣: A要連接B.A給B發一個UDP包.同時.A讓那個介紹人給B發一個命令,讓B同時給A發一個UDP包.這樣雙方的NAT都會記錄對方的IP,然后就會允許互相通信.
同一個NAT后面的情況
如果A,B在同一個NAT后面.如果用上面的技術來進行互連.那么如果NAT支持loopback(就是本地到本地的轉換),A,B可以連接,但是比較浪費帶寬和NAT.有一種辦法是,A,B和介紹人通信的時候,同時把自己的local IP也告訴服務器.A,B通信的時候,同時發local ip和公網IP.誰先到就用哪個IP.但是local ip就有可能不知道發到什么地方去了.比如A,B在不同的NAT后面但是他們各自的local ip段一樣.A給B的local IP發的UDP就可能發給自己內部網里面的了.
還有一個辦法是服務器來判斷A,B是否在一個NAT后面.(網絡拓朴不同會不會有問題?)
把4種類型分別標為1234,有兩台主機A:portA和B:portB(port都為外網端口,是與打洞服務器通信的端口),以及打洞服務器S,情景是B拿到了A:portA的信息,要與A通信。 (1)、A為類型1;無論B為哪種類型,都可以直接與A:portA udp通信; (2)、A為類型2;無論B為哪種類型,在A知道B之前都無法直接連接,B給S發一個打洞請求,S轉發該請求到A。若B的類型為1,則A:portA可直接與B:portB udp通信;若B的類型為2或3,則A:portA和B:portB各自向對方發送一個一字節的udp包,分別在自己的路由器上打洞,從此A:portA和B:portB可進行udp通信;若B為類型4,則portB在與不同的ip:port通信時會不一樣,所以A:portA先向B發送一個一字節的udp包,在路由器上打洞,然后等待B:portB先發送數據,A:portA接收到B:portB的數據后,即知道portB,也可互通數據了; (3)、A為類型3;無論B為哪種類型,在A知道B之前都無法直接連接,B給S發一個打洞請求,S轉發該請求到A。若B的類型為1,則A:portA可直接與B:portB udp通信;若B的類型為2或3,則A:portA和B:portB各自向對方發送一個一字節的udp包,分別在自己的路由器上打下洞,從此A:portA和B:portB可進行udp通信;若B為類型4,則portB在與不同的ip:port通信時會不一樣,而A又要求知道portB的才可讓B:portB連進來,所以這種情況A只能猜測與A:portA通信的portB,通信概率小; (4)、A為類型4;無論B為哪種類型,在A知道B之前都無法直接連接,B給S發一個打洞請求,S轉發該請求到A。若B的類型為1,則A:portA可直接與B:portB udp通信;若B的類型為2,則B:portB先向A發送一個一字節的udp包,在路由器上打洞,然后等待A:portA先發送數據,B:portB接收到A:portA的數據后,即知道portA,也可互通數據了;若B的類型為3,見(3)中B為類型4的描述;若B為4,雙方無法知道對方的端口,無法通信。
STUN :RFC 5389 - Session Traversal Utilities for (NAT) (STUN) ICE:RFC 5245 - Interactive Connectivity Establishment (ICE): A Methodology for Network Address Translator (NAT) Traversal for Offer/Answer Protocols
測試結果記錄
目前在公司和在家中測試,stun返回結果都是對稱型nat。
<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n186" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: monospace, monospace; font-size: 0.9rem; white-space: pre; line-height: 1.71429em; display: block; break-inside: avoid; text-align: left; background-image: ; background-position: var(--code-block-bg-color); background-size: ; background-repeat: var(--code-block-bg-color); background-attachment: ; background-origin: ; background-clip: ; position: relative !important; margin-bottom: 3em; padding-left: 1ch; padding-right: 1ch; margin-left: 2em; width: inherit;">
bolen@DESKTOP-G4LODA2 MINGW64 /c/projects/gocode/src/github.com/ccding/go-stun (master)
go build
bolen@DESKTOP-G4LODA2 MINGW64 /c/projects/gocode/src/github.com/ccding/go-stun (master)
./go-stun.exe -v
2019/01/06 08:05:34 Do Test1
2019/01/06 08:05:34 Send To: 217.10.68.145:10000
2019/01/06 08:05:34 Received: {packet nil: false, local: 42.101.65.133:23317, remote: 217.10.68.145:10000, changed: 217.116 .122.141:10001, other: <nil>, identical: false}
2019/01/06 08:05:34 Do Test2
2019/01/06 08:05:34 Send To: 217.10.68.145:10000
2019/01/06 08:05:44 Received: Nil
2019/01/06 08:05:44 Do Test1
2019/01/06 08:05:44 Send To: 217.116.122.141:10001
2019/01/06 08:05:44 Received: {packet nil: false, local: 42.101.65.134:44004, remote: 217.116.122.141:10001, changed: 217.1 0.68.145:10000, other: <nil>, identical: false}
NAT Type: Symmetric NAT
External IP Family: 1
External IP: 42.101.65.133
External Port: 23317
bolen@DESKTOP-G4LODA2 MINGW64 /c/projects/gocode/src/github.com/ccding/go-stun (master)
$ ./go-stun.exe -v
2019/01/06 08:06:02 Do Test1
2019/01/06 08:06:02 Send To: 217.10.68.145:10000
2019/01/06 08:06:03 Received: {packet nil: false, local: 42.101.65.133:13858, remote: 217.10.68.145:10000, changed: 217.116 .122.141:10001, other: <nil>, identical: false}
2019/01/06 08:06:03 Do Test2
2019/01/06 08:06:03 Send To: 217.10.68.145:10000
2019/01/06 08:06:12 Received: Nil
2019/01/06 08:06:12 Do Test1
2019/01/06 08:06:12 Send To: 217.116.122.141:10001
2019/01/06 08:06:13 Received: {packet nil: false, local: 42.101.65.134:25117, remote: 217.116.122.141:10001, changed: 217.1 0.68.145:10000, other: <nil>, identical: false}
NAT Type: Symmetric NAT
External IP Family: 1
External IP: 42.101.65.133
External Port: 13858</pre>
解決方案匯總
-
stun 配合upnp(愛奇藝等內容服務商的方案)
https://github.com/jflyup/nat_traversal (c語言實現的,repo里聲稱解決了including symmetric NATs問題,網上有測試成功案例。)
https://github.com/sjtcumt/wp/tree/master/p2p/p2psrv(解決了對稱nat-對稱nat,對稱nat-端口限制錐形nat的問題,但不穩定)
https://github.com/ccding/go-stun
以上stun解決對稱nat的問題,基本上都是利用了端口猜測和生日攻擊算法。
-
ICE,可解決全部nat穿越問題。