一:TURN協議了解
TURN的全稱為Traversal Using Relays around NAT,是STUN/RFC5389的一個拓展,主要添加了Relay中繼功能。
那么在特定的情景下,有可能使得終端無法和其對等端(peer)進行直接的通信,這時就需要公網的服務器作為一個中繼,對來往的數據進行轉發。
這個轉發的協議就被定義為TURN。TURN和其他中繼協議的不同之處在於,它允許客戶端使用同一個中繼地址(relay address)與多個不同的peer進行通信。
使用TURN協議的客戶端必須能夠通過中繼地址和對等端進行通訊,並且能夠得知每個peer的的IP地址和端口(確切地說,應該是peer的服務器反射地址)。
而這些行為如何完成,是不在TURN協議范圍之內的。其中一個可用的方式是客戶端通過email來告知對等端信息,另一種方式是客戶端使用一些指定的協議,如“introduction” 或 “rendezvous”,詳見RFC5128。
如果TURN使用於ICE協議中,relay地址會作為一個候選,由ICE在多個候選中進行評估,選取最合適的通訊地址。一般來說中繼的優先級都是最低的。
TURN協議被設計為ICE協議(Interactive Connectivity Establishment)的一部分,而且也強烈建議用戶在他們的程序里使用ICE,但是也可以獨立於ICE的運行。
值得一提的是,TURN協議本身是STUN的一個拓展,因此絕大部分TURN報文都是STUN類型的,作為STUN的一個拓展,TURN增加了新的方法(method)和屬性(attribute)。
因此閱讀本章時最好先了解一下STUN協議。
(一)TURN協議出現的目的(解決對稱NAT無法穿越的問題)
在之前我們介紹過,NAT的四種類型(完全錐型NAT (Full Cone NAT)、地址限制錐型NAT(Address Restricted Cone NAT)、端口限制錐型NAT (Port Restricted Cone NAT)、對稱型NAT (Symmetric NAT))。
當我們檢測到一端是端口受限錐型一端是對稱型或者兩端都是對稱型,那肯定是無法穿越的;
在NAT無法穿越的時候,我們如何才能保證業務的運行呢?
那這個時候就要引入TURN協議,TURN協議實際上就是在服務端架設一個TURN服務,客戶端在發送數據的時候無法NAT穿越的時候將這個媒體流數據首先傳給TURN服務,通過
TURN的中介然后轉給其他的接收者,或者其他接收者也可以發送數據給這個TURN服務,TURN在轉給client端。
這就是TURN 出現的目的。
(二)TURN協議與STUN/RFC5389協議的關系
其建立在STUN/RFC5389之上,消息格式使用STUN格式消息
上次我們說了STUN協議的頭和body是什么樣子的,那TURN協議就是建立在STUN協議之上的,它的協議頭和body幾乎是一樣的,只是里面的一些屬性和內容不一樣,外殼形式什么
的都是一樣的,所以很多服務器都是將STUN協議和TURN協議放在一起形成了一個服務器,就是既提供STUN的功能又提供TURN的功能 。
TURN Client要求服務端分配一個公共IP和Port用於接收或發送數據
實際上在進行TURN協議的時候我們應該將它分成兩類,一類是TURN Client,一類是服務端,這與我們之前介紹的STUN是一樣的,就是客戶端服務器模式。
那如何在這個TURN服務器上提供這種中繼服務呢?
那首先要TURN Client向TURN 服務端發送一個請求,發送了請求之后就就在服務端根據這個請求建立一個公共的IP地址和端口用戶接收和發送數據 。
那么對端(TURN client想要通訊的對端)其實是不需要是一個TURN Client端的,它只需要正常的發送UDP包就可以了。
(三)TURN使用案例
下面我們講個具體的例子,下圖是講述整個TURN Client端去請求服務,服務端創建相應的公網IP地址和端口提供服務,以及與各個Peer終端進行交互的一個案例:
由上圖我們可以看的有個TURN client端和一個TURN Server端以及兩個Peer對端 ,首先我們來看看他們是怎么通訊的:
整體流程:
在上圖中,左邊的TURN Client是位於NAT后面的一個客戶端(內網地址是10.1.1.2:49721),連接公網的TURN服務器(默認端口3478)后,
服務器會得到一個Client的反射地址
(Reflexive Transport Address, 即NAT分配的公網IP和端口)192.0.2.1:7000,
此時Client會通過TURN命令創建或管理ALLOCATION
,allocation是服務器上的一個數據結構,包含了中繼地址的信息。
服務器隨后會給Client分配一個中繼地址,即圖中的192.0.2.15:50000,
另外兩個對等端若要通過TURN協議和Client進行通信,可以直接往中繼地址收發數據即可,TURN服務器會把發往指定中繼地址的數據轉發到對應的Client,這里是客戶端反射地址。
Server上的每一個allocation都唯一對應一個client,並且只有一個中繼地址,因此當數據包到達某個中繼地址時,服務器總是知道應該將其轉發到什么地方。
但值得一提的是,一個Client可能在同一時間在一個Server上會有多個allocation,這和上述規則是並不矛盾的。
Client<------>PeerB
首先一個TURN Client端是在一個NAT之后的,這個時候TURN Client端它要發送一個請求給這個TURN Server,那么TURN Server是在另外一個網絡地址,端口是3487, TURN Client在向TURN Server發送請求的時候會形成一組映射地址(出口)地址. 此時TURN Client發送一個Arrow的請求到TURN 服務端,TURN Server收到請求之后就會另外開通一個 服務地址 192.0.2.15的地址端口是50000來提供這種UDP中轉的這種服務, 所以TURN Server對同一個TURN Client端來說有兩個端口,一個是與TURN Client端連接的端口,另外一個是提供中轉的端口50000, 如果它現在與Peer B進行通訊,Peer B與TURN Server是在同一個網段,地址是192.0.2.210端口是49191,這個時候它就可以向中轉的TURN Server中轉的去發數據了。 同樣的他們建立連接之后 ,TURN Server也可以給這個Peer B發送數據,那這個時候Peer B如果發送數據到 50000這個端口,在TURN server的內部就會轉到3478然后最終中繼給這個TURN Client端; 同樣的如果TURN Client端想給Peer B發送消息的時候,它是先發到3478端口,然后經過內部的中轉轉成UDP然后再給Peer B,這就是它的一個邏輯。
Client<------>PeerA
那同樣的在一個 NAT 之后的一個 Peer A也是可以通訊的,那么在通訊之前它首先要穿越NAT,在NAT上形成一個映射的IP地址也就是192.0.2.150端口是32102,
所以對TURN Server來說它可以識別這個IP地址和端口就是這個地址,那如果與Peer B 進行通訊的時候,它就通過50000這個端口向這個32102端口發送消息,那么Peer A就收到了。 相反的如果Peer A要給這個TURN Client端發送數據的時候,它就是往192.0.2.15:50000這個端口發數據,從這個端口又轉到3478這個端口,最終傳給TURN Client端。
這就是整個TURN中轉的基本的例子。通過這個例子大家就很清楚它是怎么工作的。
二:TURN協議通信流程
(一)TURN使用的傳輸協議
TURN Client 到TURN server使用的UDP、TCP包括加密后的TCP都是可以的,而對於TURN Server到Peer端,使用的都是UDP;
在協議中,TURN服務器與peer之間的連接都是基於UDP的,
但是服務器和客戶端之間可以通過其他各種連接來傳輸STUN報文,比如TCP/UDP/TLS-over-TCP.
服務器與客戶端之間通過中繼傳輸數據時候,如果用了TCP,也會在服務端轉換為UDP,因此建議客戶端使用UDP來進行傳輸.
至於為什么要支持TCP,那是因為一部分防火牆會完全阻擋UDP數據,而對於三次握手的TCP數據則不做隔離.
(二)TURN Allocate請求
那TURN客戶端如何讓TURN Server提供通訊服務呢?
要在服務器端獲得一個中繼分配,客戶端須使用分配事務,發送一個Allocate請求,在客戶端首先發送一個Allocate請求到Server端,在Server首先是要做一些鑒權的處理,
如果發現請求沒有相應的權限,就返回一個401,就是無權限未授權的 ,整個底層用的都是STUN的消息格式,
TURN Client收到這個未授權的消息之后它會重新再發一個請求這次會把鑒權信息也帶過來給Server端,Server端這時候鑒權就通過了,然后返回一個成功應答 ,就是我給你提供的IP地址和端口是多少。
這個時候Client就能和Peer A和Peer B進行通訊了,那在這之前通過Peer A和Peer B它要通過一個信令服務器那要拿到他們相應的地址,否則的話還是沒法通信的;
在這個前提之下如果中繼服務已經開通了,TURN Client就可以源源不斷的向TURN Server發送數據,然后TURN Server通過50000這個 端口給Peer A和Peer B進行轉發;
相反的如果Peer A 和Peer B給50000這個端口發數據,它就會被傳給這個TURN 客戶端;
除此之外這個TURN Client端還要發送一個Refresh請求,實際上就是保活用的相當於心跳。
在TURN Client端要求這個TURN Server分配 一個服務之后它是有一定的超時時間的,有可能是10分鍾有可能是20分鍾或者你設置為1個小時,那這個在這個設置時間段之內它是活着的,超過十分鍾如果沒有這個Refresh request刷新請求的話,那這個服務就算是失效了。客戶端也可以在刷新請求里指定一個更長的生命期,而服務器會返回一個實際上分配的時間. 當客戶端想中指通信時,可以發送一個生命期為0的刷新請求.
那如果在失效之前它收到這個心跳,它就返回了一個成功,繼續演示上面設置的一段時間,這就是它整體的一個分配和保活的一個邏輯。
(三)TURN 發送機制
兩種方法都通過某種方式告知服務器哪個peer應該接收數據,以及服務器告知client數據來自哪個peer.
1.Send和Data
整個服務開通之后就涉及到對數據的發送了,那TURN有兩種發送數據的機制,第一種就是Send和Data。
首先我們看Send和Data,要求服務端開啟這個服務之后呢,緊接着就可以發送數據了,那在發送數據之前首先要鑒權,檢查是否有發送的權限,這個完成之后它就來發數據。
那么首先客戶端向服務端發送數據,信令是Send,發送到服務端,服務端收到數據之后需要將TURN協議頭給去掉,拿到里面的數據 ,
里面的數據就是原始的UDP數據,拿到這個數據之后直接通過剛才的 5000端口轉給你想轉發的對端Peer A,那么數據就到Peer A,
Peer A如果想給這個TURN Client發送數據,那它發的是UDP數據,那到了中繼服務之后,中繼服務STUN首先給它加一個消息頭,加了消息頭之后再通過這個TURN Client轉給這個TURN Client端,
所以這個Send和Data一個是表示上行一個是表示下型行。
補充1:send方法
server發送到client.當使用Send指令時,客戶端發送一個Send Indication到服務端,其中包含:
- XOR-PEER-ADDRESS屬性,指定對等端的(服務器反射)地址---中繼出口.
- DATA屬性,包含要傳給對等端的信息.
當服務器收到Send Indication之后,會將DATA部分的數據解析出來,並將其以UDP的格式轉發到對應的端點去,並且在封裝數據包的時候把client的中繼地址作為源地址.從而從對等端發送到中繼地址的數據也會被服務器轉發到client上.
值得一提的是,Send/Data Indication是不支持驗證的,因為長效驗證機制不支持對indication的驗證,因此為了防止攻擊,TURN要求client在給對等端發送indication之前先安裝一個到對等端的許可(permission),如下圖所示,client到Peer B沒有安裝許可,導致其indication數據包將被服務器丟棄,對於peer B也是同樣:
TURN支持兩種方式來創建許可,一種是發送CreatePermission request
2.Channel
send方法相比Channel的一個缺點就是在每次發送消息的時候都要帶一個30多個字節的頭,這個對於一般的情況下其實是不受影響的,
但是因為我們傳輸的是流媒體數據,它的數據量非常大 ,如果每個數據包都帶一個30多個的字節頭那對整個帶寬是有很大影響的。那如何去解決這個問題呢?
為改善這種情況,TURN提供了第二種方法來讓client和peer交互數據.該方法使用另一種數據包格式,即ChannelData message
,信道數據報文.
ChannelData message不使用STUN頭部,而使用一個4字節的頭部,包含了一個稱之為信道號的值(channel number).
每一個使用中的信道號都與一個特定的peer綁定,即作為對等端地址的一個記號.
要將一個信道與對等端綁定,客戶端首先發送一個信道綁定請求(ChannelBind Request)到服務器,並且指定一個未綁定的信道號以及對等端的地址信息.
綁定后client和server都能通過ChannelData message來發送和轉發數據.信道綁定默認持續10分鍾,並且可以通過重新發送
ChannelBind Request來刷新持續時間.和Allocation不同的是,並沒有直接刪除綁定的方法,只能等待其超時自動失效.
有了另外一種數據傳輸方式就是Channel,Channel方式就是大家規定一個Channel Id,我們都相當於加入了這個管道中,有了這個管道我們就不用每次都帶着這個頭消息告訴你這是什么?
一些基本信息在這里我們一開始就創建好了,我們都在管道里,我們現在只要發送數據就好了
在Channel模式下,首先客戶端要發送一個ChannelBind綁定請求,服務端收到這個請求之后給它一個響應,
綁定這個ChannelId就是這個16進制的0x4001(隨機值),就是隨便一個數它都能隨機產生的,當然不能重復,那當整個Channel創建成功之后,
Client端就可以發送數據給Server,那這個Server就可以轉發數據給這個Peer A,
同樣的如果Peer A如果想轉發數據給這個TURN Server,那TURN Server再中繼通過channel就轉給這個Client端了。
另外Send和Data這種方式其實是可以繼續發送的,也就是說Send、Data和Channel這兩種方式是可以並存的,可以混着發的,這就是Channel。
上圖中0x4001為信道號,即ChannelData message的頭部中頭2字節,值得一提的是信道號的選取有如下要求:
- 0x0000-0x3FFF : 這一段的值不能用來作為信道號
- 0x4000-0x7FFF : 這一段是可以作為信道號的值,一共有16383種不同值在目前來看是足夠用的
- 0x8000-0xFFFF : 這一段是保留值,留給以后使用
還是那句老話,關於協議具體的細節可以去翻閱RFC5766的草稿,其中每個屬性以及其格式都介紹得很詳細.
三:TURN的使用
TURN的使用,尤其是在WEBRTC中我們怎么使用:
首先是發送一個綁定,要進行這個打通,就是客戶端到服務端要打通要拿到它的映射IP地址,
之后發起方Caller要調用allocation,就是讓TURN Server開辟一個服務接收我發送數據的IP地址和端口,就是一個中繼的IP地址和端口,那整個服務開通之后,
這個Caller獲取了基本的信息,然后通過信令將這個SDP也就是說一些媒體信息還有網絡信息通過這個 SDP的offer發送該這個被調用者;
對方收到這個信息之后也要給這個TURN服務發送一個allocation,也要創建一個這樣的服務,用來接收對方的數據,
那這個創建成功之后,因為這個Caller給它發了一個Offer然后它要回一個answer,這樣他們整個數據交換就交換完了,
在交換的過程中實際就是將這個candidate,我們說SE的時候回說到這個candidate,它是一個候選者,相當於我每一個IP地址和端口都是一個候選者,就是有可能進行網絡傳輸的一個地址,所以把它叫做candidate,就是將這個 candidate的這個地址進行交換,那它這個信息就交換了,
那交換完成之后通過這個ICE這個框架,首先檢查P2P能否成功,因為最高的傳輸效率還是端對端之間,它不經過第三方的服務也不受第三方的服務帶寬的影響,只要雙方的帶寬夠就好了,這個是最基本的,所以它是最高效的,所以首先檢查 P2P是否能夠打通NAT,如果打不通的話,這個時候就要通過中繼,向對方所開通的端口去發送數據,這樣另一方就能收到,同樣的,另一方想要發送數據也給對方的中繼服務發送數據就好了,
那么以上就是整個TURN相關的一些基本的知識。
四: 協議交互過程詳細舉例
(一)相關術語
- TURN client:遵循RFC5766的STUN客戶端。
- TURN server:遵循RFC5766的STUN服務器。
- Peer:TURN客戶端希望連接的主機。TURN服務器為TURN客戶端和它的對端中繼流量,但Peer並不與TURN服務器使用TURN協議進行交互,它接收從TURN服務器發送過來的數據,並向TURN服務器發送數據。
- Transport Address:IP地址與端口號的組合。
- Host Transport Address:客戶端或對端的傳輸地址。
- Server-Reflexive Transport Address:NAT公網側的傳輸地址,該地址由NAT分配,相當於一個特定的主機傳輸地址。
- Relayed Transport Address:TURN服務器上的傳輸地址,用於客戶端和對端中繼數據。
- TURN Server Transport Address:TURN服務器上的傳輸地址,用於客戶端發送STUN消息給服務器。
- Peer Transport Address:服務器看到的對端的傳輸地址,當對端是在NAT后面,則是對端的服務器反射傳輸地址。
- Allocation:通過Allocate請求將中繼傳輸地址提供給客戶端,除了中繼狀態外,還有許可和超時定時器等。
- 5-tuple:五元組,包括客戶端IP地址和端口,服務器IP地址和端口和傳輸協議(包括UDP、TCP、TLS)的組合。
- Channel:通道號與對端傳輸地址的關聯,一旦一個通道號與一個對端的傳輸地址綁定,客戶端和服務器就能夠利用帶寬效應更大的通道數據消息來交換數據。
- Permission:一個對端允許使用它的IP地址和傳輸協議來發送數據到TURN服務器,服務器只為從對端發來的並且匹配一個已經存在的許可的流量中繼到相應的客戶端。
- Realm:服務器內用於描述服務器或內容的一個字符串,這個realm告訴客戶端哪些用戶名和密碼的組合可用於認證請求。
- Nonce:服務器隨機選擇的一個字符串,包含在報文摘要中。為了防止中繼攻擊,服務器應該有規律的改變這個nonce。
(二)交互過程
以上圖為例進行講解,每個消息中,多個屬性包含在消息中並顯示它們的值。
交互過程1:分配Allocate空間
1.客戶端使用10.1.1.2:49271作為傳輸地址向服務器的傳輸地址發送Allocate請求。
客戶端隨機選擇一個96位的事務ID。----因為TURN是基於STUN/RFC5389
該Allocate請求消息:
包括SOFTWARE屬性來提供客戶端的軟件版本信息; 包括LIFETIME屬性,指明客戶端希望該allocation具有1小時的生命期而非缺省的10分鍾; 包括REQUESTED-TRANSPORT屬性來告訴服務器與對端之間采用UDP協議來傳輸; 包括DONT-FRAGMENT屬性因為客戶端希望在隨后的Send indications中使用DON’T-FRAGMENT屬性。
2.服務器需要任何請求必須是經過認證的,因此服務器拒絕了該最初的Allocation請求,並且回應了攜帶有錯誤響應號為401(未授權)的Allocate錯誤響應;該響應包括一個REALM屬性,指明認證的域;還包括一個NONCE屬性和一個SOFTWARE屬性。
3.客戶端收到了錯誤響應號為401的Allocate錯誤響應,將重新嘗試發送Allocate請求,此時將包括認證屬性。
客戶端在新的請求中重新選擇一個新的事務ID。
客戶端包括一個USERNAME屬性,使用從服務器那收到的realm值來幫助它決定使用哪個值;
請求還包括REALM和NONCE屬性,這兩個屬性是從收到的錯誤響應中拷貝出來的。
最后,客戶端包括一個MESSAGE-INTEGRITY屬性。
4.服務器收到認證的Allocate請求后,檢查每個屬性是否正確;然后,產生一個allocation,並給客戶端回應Allocate成功響應。
服務器在該成功響應中攜帶一個LIFETIME屬性,本例中服務器將客戶端請求的1小時生命期減小為20分鍾,這是因為這個特定的服務器可能不允許超過20分鍾的生命期;
該響應包括XOR-RELAYED-ADDRESS屬性,值為該allocation的中繼傳輸地址;
該響應還包括XOR-MAPPED-ADDRESS屬性,值為客戶端的server-reflexive地址(公網地址);
該響應也包含一個SOFTWARE屬性;
最后,包括一個MESSAGE-INTEGRITY屬性來證明該響應,確保它的完整性。

XOR-MAPPED-ADDRESS和MAPPED-ADDRESS基本相同,不同點是反射地址部分經過一次異或(XOR)處理。
X-port字段:是將NAT的映射端口以小端形式與magic cookie的高16位進行異或,再將結果轉換成大段形式而得到的,X-Address也是類似。之所以要經過這么一次轉換,是因為在實踐中發現很多NAT會修改payload中自身公網IP的32位數據,從而導致NAT打洞失敗。
交互過程2:創建許可
5.接着,客戶端為了准備向對端A發送一些應用數據而創建一個permission。
這里通過一個CreatePermission請求來做到。
該請求攜帶XOR-PEER-ADDRESS屬性包含有確定的請求的IP地址(對端A的公網地址),這里為對端A的地址;
需要注意的是,屬性中地址的端口號被設置為0在CreatePermission請求中,並且客戶端使用的是對端A的server-reflexive地址而不是它的主機地址(私網地址);
客戶端在該請求中攜帶與之前的Allocate請求中一樣的username、realm和nonce值,因此該請求被服務器認可。
此時在該請求中,客戶端沒有攜帶SOFTWARE屬性。
6.服務器收到該CreatePermission請求,產生一個相應的許可,並以CreatePermission成功響應來回應。
該響應中只包含了Transaction-ID和MESSAGE-INTEGRITY屬性。
交互過程3:客戶端發送數據給對端PeerA---使用Send和Data
7.現在客戶端使用Send indication來發送應用數據到對端A。
對端的server-reflexive傳輸地址包含在XOR-PEER-ADDRESS屬性中,應用數據包含在DATA屬性中。
客戶端已經在應用層上執行了路徑MTU發現功能,因此通過DON’T-FRAGMENT屬性來告知服務器當通過UDP方式來向對端發送數據時應設置DF位。
Indications不能使用長期證書機制來認證,所以該消息中沒有MESSAGE-INTEGRITY屬性。
8.服務器收到Send indication后,提取出應用數據封裝成UDP格式發給對端A;UDP報文的源傳輸地址為中繼傳輸地址,並設置DF位。
9.對端A回應它自己的包含有應用數據的UDP包給服務器。目的地址為服務器的中繼傳輸地址。當服務器收到后,將生成Data indication消息給客戶端,攜帶有XOR-PEER-ADDRESS屬性(對端A的公網)。應用數據包含在DATA屬性中。
交互過程4:客戶端綁定一個通道到對端B----開始使用Channel
10.客戶端現在若要綁定一個通道到對端B,將指定一個空閑的通道號(本例中為0x4000)包含在CHANNEL-NUMBER屬性中,對端B的傳輸地址包含在XOR-PEER-ADDRESS屬性中。與以前一樣,客戶端再次利用上次請求中的username、realm和nonce。
11.當服務器收到該請求后,服務器綁定這個對端的通道號,為對端B的IP地址安裝一個permission,然后給客戶端回應一個ChannelBind成功響應消息。
交互過程5:客戶端發送數據給對端PeerB---使用Channel
12.客戶端現在發送一個ChannelData消息給服務器,攜帶有發送給對端B的數據。
這個消息不是一個STUN消息,因此沒有事務ID。它只有3個字段:通道號、數據、數據長度;服務器收到后,檢查通道號后發現當前已經綁定了,就以UDP方式發送數據給對端B。
13.接着,對端B發送UDP數據包回應給服務器的中繼傳輸地址。服務器收到后,回應給客戶端ChannelData消息,包含UDP數據包中的數據。
服務器知道是給哪個客戶端發送ChannelData消息,這是因為收到的UDP數據包中的目的地址(即服務器的中繼傳輸地址),並且知道使用的是哪個通道號,這是因為通道已經與相應的傳輸地址綁定了。
交互過程6:客戶端刷新allocation
14.有時候,20分鍾的生命期已經到了,客戶端需要刷新allocation。此時通過發送Refresh請求來進行。該請求包含最后一次使用的username、realm和nonce,還包含SOFTWARE屬性。
15.當服務器收到這個Refresh請求時,它注意到這個nonce值已經超期了,則給客戶端回應一個錯誤響應號為438(過期Nonce)的Refresh錯誤響應,並提供一個新的nonce值。
16.客戶端將重試該請求,此時攜帶新的nonce值。
17若第二次嘗試被接受,服務器將回應一個成功響應。
需要注意的是,此時客戶端在請求中沒有攜帶LIFETIME屬性,所以服務器刷新客戶端的allocation時采用缺省的10分鍾生命期。
五:搭建TURN/STUN服務見:
(一)搭建步驟(下面都可以)---使用騰訊雲(15天免費系列)
https://blog.csdn.net/qq_28880087/article/details/106960293
https://www.jianshu.com/p/cf194367c779
https://blog.csdn.net/xqj198404/article/details/48222273
https://blog.csdn.net/weixin_33196646/article/details/112224589(重點:包含防火牆設置!!!)
(二)注意事項
按照上面搭建,發現在校園網中居然不顯示stun服務的結果!!!
但實際上在服務端是顯示了結果的:
並且是對稱型NAT!!!
在手機熱點下,可以正常顯示結果: