正文:
一. 首先來簡單講講什么是NAT?
原來這是因為IPV4引起的,我們上網很可能會處在一個NAT設備(無線路由器之類)之后。
NAT設備會在IP封包通過設備時修改源/目的IP地址. 對於家用路由器來說, 使用的是網絡地址端口轉換(NAPT), 它不僅改IP, 還修改TCP和UDP協議的端口號, 這樣就能讓內網中的設備共用同一個外網IP. 舉個例子, NAPT維護一個類似下表的NAT表:

NAT設備會根據NAT表對出去和進來的數據做修改, 比如將192.168.0.3:8888
發出去的封包改成120.132.92.21:9202
, 外部就認為他們是在和120.132.92.21:9202
通信. 同時NAT設備會將120.132.92.21:9202
收到的封包的IP和端口改成192.168.0.3:8888
, 再發給內網的主機, 這樣內部和外部就能雙向通信了, 但如果其中192.168.0.3:8888
== 120.132.92.21:9202
這一映射因為某些原因被NAT設備淘汰了, 那么外部設備就無法直接與192.168.0.3:8888
通信了。
我們的設備經常是處在NAT設備的后面, 比如在大學里的校園網, 查一下自己分配到的IP, 其實是內網IP, 表明我們在NAT設備后面, 如果我們在寢室再接個路由器, 那么我們發出的數據包會多經過一次NAT.
二. NAT的副作用以及解決方案
國內移動無線網絡運營商在鏈路上一段時間內沒有數據通訊后, 會淘汰NAT表中的對應項, 造成鏈路中斷。
這是NAT帶來的第一個副作用:NAT超時:
而國內的運營商一般NAT超時的時間為5分鍾,所以通常我們TCP長連接的心跳設置的時間間隔為3-5分鍾。**
而第二個副作用就是:我們這邊文章要提到的NAT牆。
NAT會有一個機制,所有外界對內網的請求,到達NAT的時候,都會被NAT所丟棄,這樣如果我們處於一個NAT設備后面,我們將無法得到任何外界的數據。
但是這種機制有一個解決方案:就是如果我們A主動往B發送一條信息,這樣A就在自己的NAT上打了一個B的洞。這樣A的這條消息到達B的NAT的時候,雖然被丟掉了,但是如果B這個時候在給A發信息,到達A的NAT的時候,就可以從A之前打的那個洞中,發送給到A手上了。
簡單來講,就是如果A和B要進行通信,那么得事先A發一條信息給B,B發一條信息給A。這樣提前在各自的NAT上打了對方的洞,這樣下一次A和B之間就可以進行通信了。
三. 四種NAT類型:
RFC3489 中將 NAT 的實現分為四大類:
-
Full Cone NAT (完全錐形 NAT)
-
Restricted Cone NAT (限制錐形 NAT ,可以理解為 IP 限制,Port不限制)
-
Port Restricted Cone NAT (端口限制錐形 NAT,IP+Port 限制)
-
Symmetric NAT (對稱 NAT)
其中完全最上層的完全錐形NAT的穿透性最好,而最下層的對稱形NAT的安全性最高。
簡單來講講這4種類型的NAT代表什么:
- 如果一個NAT是Full Cone NAT,那么無論什么IP地址訪問,都不會被NAT牆掉(這種基本很少)。
- Restricted Cone NAT,僅僅是經過打洞的IP能穿越NAT,但是不限於Port。
- Port Restricted Cone NAT,僅僅是經過打洞的IP+端口號能穿越NAT。
- Symmetric NAT 這種也是僅僅是經過打洞的IP+端口號能穿越NAT,但是它有一個最大的和Cone類型的NAT的區別,它對外的公網Port是不停的變化的:
比如A是一個對稱NAT,那么A給B發信息,經過NAT映射到一個Port:10000,A給C發信息,經過NAT映射到一個Port:10001,這樣會導致一個問題,我們服務器根本無法協調進行NAT打洞。
至於為什么無法協調打洞,下面我們會從STUN和TURN的工作原理來講。
四. STUN和TURN的實現:
1.STUN Server主要做了兩件事:
- 接受客戶端的請求,並且把客戶端的公網IP、Port封裝到ICE Candidate中。
- 通過一個復雜的機制,得到客戶端的NAT類型。
完成了這些STUN Server就會這些基本信息發送回客戶端,然后根據NAT類型,來判斷是否需要TURN服務器協調進行下一步工作。
我們來講講這兩步具體做了什么吧:
第一件事就不用說了,其實就是得到客戶端的請求,把源IP和Port拿到,添加到ICE Candidate中。
來講講第二件事,STUN是如何判斷NAT的類型的:
假設B是客戶端,C是STUN服務器,C有兩個IP分別為IP1和IP2(至於為什么要兩個IP,接着往下看):
STEP1.判斷客戶端是否在NAT后:
B向C的IP1的pot1端口發送一個UDP 包。C收到這個包后,會把它收到包的源IP和port寫到UDP包中,然后把此包通過IP1和port1發還給B。這個IP和port也就是NAT的外網 IP和port(如果你不理解,那么請你去看我的BLOG里面的NAT的原理和分類),也就是說你在STEP1中就得到了NAT的外網IP。
熟悉NAT工作原理的朋友可以知道,C返回給B的這個UDP包B一定收到。如果在你的應用中,向一個STUN服務器發送數據包后,你沒有收到STUN的任何回應包,那只有兩種可能:1、STUN服務器不存在,或者你弄錯了port。2、你的NAT拒絕一切UDP包從外部向內部通過。
當B收到此UDP后,把此UDP中的IP和自己的IP做比較,如果是一樣的,就說明自己是在公網,下步NAT將去探測防火牆類型,我不想多說。如果不一樣,說明有NAT的存在,系統進行STEP2的操作。
STEP2.判斷是否處於Full Cone Nat下:
B向C的IP1發送一個UDP包,請求C通過另外一個IP2和PORT(不同與SETP1的IP1)向B返回一個UDP數據包(現在知道為什么C要有兩個IP了吧,雖然還不理解為什么,呵呵)。
我們來分析一下,如果B收到了這個數據包,那說明什么?說明NAT來着不拒,不對數據包進行任何過濾,這也就是STUN標准中的full cone NAT。遺憾的是,Full Cone Nat太少了,這也意味着你能收到這個數據包的可能性不大。如果沒收到,那么系統進行STEP3的操作。
STEP3.判斷是否處於對稱NAT下:
B向C的IP2的port2發送一個數據包,C收到數據包后,把它收到包的源IP和port寫到UDP包中,然后通過自己的IP2和port2把此包發還給B。
和step1一樣,B肯定能收到這個回應UDP包。此包中的port是我們最關心的數據,下面我們來分析:
如果這個port和step1中的port一樣,那么可以肯定這個NAT是個CONE NAT,否則是對稱NAT。道理很簡單:根據對稱NAT的規則,當目的地址的IP和port有任何一個改變,那么NAT都會重新分配一個port使用,而在step3中,和step1對應,我們改變了IP和port。因此,如果是對稱NAT,那這兩個port肯定是不同的。
如果在你的應用中,到此步的時候PORT是不同的,那么這個它就是處在一個對稱NAT下了。如果相同,那么只剩下了restrict cone 和port restrict cone。系統用step4探測是是那一種。
STEP4.判斷是處於Restrict Cone NAT還是Port Restrict NAT之下:
B向C的IP2的一個端口PD發送一個數據請求包,要求C用IP2和不同於PD的port返回一個數據包給B。
我們來分析結果:如果B收到了,那也就意味着只要IP相同,即使port不同,NAT也允許UDP包通過。顯然這是Restrict Cone NAT。如果沒收到,沒別的好說,Port Restrict NAT.
到這里STUN Server一共通過這4步,判斷出客戶端處於什么類型的NAT下,然后去做后續的處理:
這4步都會返回給客戶端它的公網IP、Port和NAT類型,除此之外:
-
如果A處於公網或者Full Cone Nat下,STUN不做其他的了,因為其他客戶端可以直接和A進行通信。
-
如果A處於Restrict Cone或者Port Restrict NAT下,STUN還會協調TURN進行NAT打洞。
-
如果A處於對稱NAT下,那么點對點連接下,NAT是無法進行打洞的。所以為了通信,只能采取最后的手段了,就是轉成C/S架構了,STUN會協調TURN進行消息轉發。
2.TURN Server也主要做了兩件事:
-
為NAT打洞:
如果A和B要互相通信,那么TURN Server,會命令A和B互相發一條信息,這樣各自的NAT就留下了對方的洞,下次他們就可以之間進行通信了。
-
為對稱NAT提供消息轉發:
當A或者B其中一方是對稱NAT時,那么給這一方發信息,就只能通過TURN Server來轉發了。
最后補充一下,為什么對稱NAT無法打洞:
假如A、B進行通信,而B處於對稱NAT之下,那么A與B通信,STUN拿到A,B的公網地址和端口號都為10000,然后去協調TURN打洞,那么TURN去命令A發信息給B,則A就在NAT打了個B的洞,但是這個B的洞是端口號為10000的洞,但是下次B如果給A發信息,因為B是對稱NAT,它給每個新的IP發送信息時,都重新對應一個公網端口,所以給A發送請求可能是公網10001端口,但是A只有B的10000端口被打洞過,所以B的請求就被丟棄了。
顯然Server是無法協調客戶端打洞的,因為協調客戶端打得洞僅僅是上次對端為Server發送端口的洞,並不適用於另一個請求。