99久久全国免费观看_国产一区二区三区四区五区VM_久久www人成免费看片中文_国产高清在线a视频大全_深夜福利www_日韩一级成人av

NAT穿透的工作原理

一、引言

1.1 背景:IPv4地址短缺,引入NAT

全球IPv4地址早已不夠用,因此人們發明了NAT(網絡地址轉換)來緩解這個問題。

簡單來說,大部分機器都使用私有IP地址,如果它們需要訪問公網服務,那么,

  • 出向流量:需要經過一臺NAT設備,它會對流量進行SNAT,將私有srcIP+Port轉換成NAT設備的公網IP+Port(這樣應答包才能回來),然后再將包發出去;
  • 應答流量(入向):到達NAT設備后進行相反的轉換,然后再轉發給客戶端。

整個過程對雙方透明。

更多關于NAT的內容,可參考(譯)NAT-網絡地址轉換(2016)。譯注。

以上就是本文所討論的問題的基本背景

1.2 需求:兩臺經過NAT的機器建立點對點連接

在以上所描述的NAT背景下,我們從最簡單的問題開始:如何在兩臺經過NAT的機器之間建立點對點連接(直連)。如下圖所示:

直接用機器的IP互連顯然是不行的,因為它們都是私有IP(例如192.168.1.x)。在Tailscale中,我們會建立一個WireGuard®隧道來解決這個問題——但這并不是太重要,因為我們將過去幾代人努力都整合到了一個工具集,這些技術廣泛適用于各種場景。例如,

  1. WebRTC使用這些技術在瀏覽器之間完成peer-to-peer語音、視頻和數據傳輸,
  2. VoIP電話和一些視頻游戲也使用類似機制,雖然不是所有情況下都很成功。

接下來,本文將在一般意義上討論這些技術,并在合適的地方拿Tailscale和其他一些東西作為例子。

1.3 方案:NAT穿透

1.3.1 兩個必備前提:UDP+能直接控制socket

如果想設計自己的協議來實現NAT穿透,那必須滿足以下兩個條件:

  1. 協議應該基于UDP

理論上用TCP也能實現,但它會給本已相當復雜的問題再增加一層復雜性,甚至還需要定制化內核——取決于你想實現到什么程度。本文接下來都將關注在UDP上。

如果考慮TCP是想在NAT穿透時獲得面向流的連接(stream-orientedconnection),可以考慮用QUIC來替代,它構建在UDP之上,因此我們能將關注點放在UDPNAT穿透,而仍然能獲得一個很好的流協議(streamprotocol)。

  1. 對收發包的socket有直接控制權

例如,從經驗上來說,無法基于某個現有的網絡庫實現NAT穿透,因為我們必須在使用的“主要”協議之外,發送和接收額外的數據包

某些協議(例如WebRTC)將NAT穿透與其他部分緊密集成。但如果你在構建自己的協議,建議將NAT穿透作為一個獨立實體,與主協議并行運行,二者僅僅是共享socket的關系,如下圖所示,這將帶來很大幫助:

1.3.2 保底方式:中繼

在某些場景中,直接訪問socket這一條件可能很難滿足。

退而求其次的一個方式是設置一個localproxy(本地代理),主協議與這個proxy通信,后者來完成NAT穿透,將包中繼(relay)給對端。這種方式增加了一個額外的間接成本,但好處是:

  1. 仍然能獲得NAT穿透,
  2. 不需要對已有的應用程序做任何改動

1.4 挑戰:有狀態防火墻和NAT設備

有了以上鋪墊,下面就從最基本的原則開始,一步步看如何實現一個企業級的NAT穿透方案。

我們的目標是:在兩個設備之間通過UDP實現雙向通信,有了這個基礎,上層的其他協議(WireGuard,QUIC,WebRTC等)就能做一些更酷的事情。

但即便這個看似最基本的功能,在實現上也要解決兩個障礙

  1. 有狀態防火墻
  2. NAT設備

二、穿透防火墻

有狀態防火墻是以上兩個問題中相對比較容易解決的。實際上,大部分NAT設備都自帶了一個有狀態防火墻,因此要解決第二個問題,必須先解決有第一個問題。

有狀態防火墻具體有很多種類型,有些你可能見過:

  • WindowsDefenderfirewall
  • Ubuntu’sufw(usingiptables/nftables)
  • BSD/macOSpf
  • AWSSecurityGroups(安全組

2.1 有狀態防火墻

2.1.1 默認行為(策略)

以上防火墻的配置都是很靈活的,但大部分配置默認都是如下行為:

  1. 允許所有出向連接(allowsall“outbound”connections)
  2. 禁止所有入向連接(blocksall“inbound”connections)

可能有少量例外規則,例如allowinginboundSSH。

2.1.2 如何區分入向和出向包

連接(connection)和方向(direction)都是協議設計者頭腦中的概念,到了物理傳輸層,每個連接都是雙向的;允許所有的寶物雙向傳輸。那防火墻是如何區分哪些是入向包、哪些是出向包的呢?這就要回到“有狀態”(stateful)這三個字了:有狀態防火墻會記錄它看到的每個包,當收到下一個包時,會利用這些信息(狀態)來判斷應該做什么。

對UDP來說,規則很簡單:如果防火墻之前看到過一個出向包(outbound),就會允許相應的入向包(inbound)通過,以下圖為例:

筆記本電腦中自帶了一個防火墻,當該防火墻看到從這臺機器出去的2.2.2.2:1234->5.5.5.5:5678包時,就會記錄一下:5.5.5.5:5678->2.2.2.2:1234入向包應該放行。這里的邏輯是:我們信任的世界(即筆記本)想主動與5.5.5.5:5678通信,因此應該放行(allow)其回報路徑。

某些非常寬松的防火墻只要看到有從2.2.2.2:1234出去的包,就會允許所有從外部進入2.2.2.2:1234的流量。這種防火墻對我們的NAT穿透來說非常友好,但已經越來越少見了。

2.2 防火墻朝向(face-off)與穿透方案

2.2.1 防火墻朝向相同

場景特點:服務端IP可直接訪問

在NAT穿透場景中,以上默認規則對UDP流量的影響不大——只要路徑上所有防火墻的“朝向”是一樣的。一般來說,從內網訪問公網上的某個服務器都屬于這種情況。

我們唯一的要求是:連接必須是由防火墻后面的機器發起的。這是因為在它主動和別人通信之前,沒人能主動和它通信,如下圖所示:

穿透方案:客戶端直連服務端,或hub-and-spoke拓撲

但上圖是假設了通信雙方中,其中一端(服務端)是能直接訪問到的。在VPN場景中,這就形成了所謂的hub-and-spoke拓撲:中心的hub沒有任何防火墻策略,誰都能訪問到;防火墻后面的spokes連接到hub。如下圖所示:

2.2.2 防火墻朝向不同的方向

場景特點:服務端IP不可直接訪問

但如果兩個“客戶端”相連,以上方式就不行了,此時兩邊的防火墻相向而立,如下圖所示:

根據前面的討論,這種情況意味著:兩邊要同時發起連接請求,但也意味著兩邊都無法發起有效請求,因為對方先發起請求才能在它的防火墻上打開一條縫讓我們進去!如何破解這個問題呢?一種方式是讓用戶重新配置一邊或兩邊的防火墻,打開一個端口,允許對方的流量進來。

  1. 這顯然對用戶不友好,在像Tailscale這樣的mesh網絡中的擴展性也不好,在mesh網絡中,我們假設對端會以一定的粒度在公網上移動。
  2. 此外,在很多情況下用戶也沒有防火墻的控制權限:例如在咖啡館或機場中,連接的路由器是不受你控制的(否則你可能就有麻煩了)。

因此,我們需要尋找一種不用重新配置防火墻的方式。

穿透方案:兩邊同時主動建連,在本地防火墻為對方打開一個洞

解決的思路還是先重新審視前面提到的有狀態防火墻規則:

  • 對于UDP,其規則(邏輯)是:包必須先出去才能進來(packetsmustflowoutbeforepacketscanflowbackin)。
  • 注意,這里除了要滿足包的IP和端口要匹配這一條件之外,并沒有要求包必須是相關的(related)。換句話說,只要某些包帶著正確的來源和目的地址出去了,任何看起來像是響應的包都會被防火墻放進來——即使對端根本沒收到你發出去的包。

因此,要穿透這些有狀態防火墻,我們只需要共享一些信息:讓兩端提前知道對方使用的ip:port

  • 手動靜態配置是一種方式,但顯然擴展性不好;
  • 我們開發了一個coordinationserver,以靈活、安全的方式來同步ip:port信息。

有了對方的ip:port信息之后,兩端開始給對方發送UDP包。在這個過程中,我們預料到某些寶物將會被丟棄。因此,雙方必須要接受某些包會丟失的事實,因此如果是重要信息,你必須自己準備好重傳。對UDP來說丟包是可接受的,但這里尤其需要接受。

來看一下具體建連(穿透)過程:

  1. 如圖所示,筆記本出去的第一包,2.2.2.2:1234->7.7.7.7:5678,穿過WindowsDefender防火墻進入到公網。

對方的防火墻會將這個包攔截掉,因為它沒有7.7.7.7:5678->2.2.2.2:1234的流量記錄。但另一方面,WindowsDefender此時已經記錄了出向連接,因此會允許7.7.7.7:5678->2.2.2.2:1234的應答包進來。

  1. 接著,第一個7.7.7.7:5678->2.2.2.2:1234穿過它自己的防火墻到達公網。

到達客戶端側時,WindowsDefender認為這是剛才出向包的應答包,因此就放行它進入了!此外,右側的防火墻此時也記錄了:2.2.2.2:1234->7.7.7.7:5678的包應該放行。

  1. 筆記本收到服務器發來的包之后,發送一個包作為應答。這個包穿過WindowsDefender防火墻和服務端防火墻(因為這是對服務端發送的包的應答包),達到服務端。

成功!這樣我們就建立了一個穿透兩個相向防火墻的雙向通信連接。而初看之下,這項任務似乎是不可能完成的。

2.3 關于穿透防火墻的一些思考

穿透防火墻并非永遠這么輕松,有時會受一些第三方系統的間接影響,需要仔細處理。那穿透防火墻需要注意什么呢?重要的一點是:通信雙方必須幾乎同時發起通信,這樣才能在路徑上的防火墻打開一條縫,而且兩端還都是活著的。

2.3.1 雙向主動建連:旁路信道

如何實現“同時”呢?一種方式是兩端不斷重試,但顯然這種方式很浪費資源。假如雙方都知道何時開始建連就好了。

  • 這聽上去是雞生蛋蛋生雞的問題了:雙方想要通信,必須先提前通個信
  • 但實際上,我們可以通過旁路信道(sidechannel)來達到這個目的,并且這個旁路信道并不需要很fancy:它可以有幾秒鐘的延遲、只需要傳送幾KB的信息,因此即使是一個配置非常低的虛擬機,也能為幾千臺機器提供這樣的旁路通信服務。在遙遠的過去,我曾用XMPP聊天消息作為旁路,效果非常不錯。另一個例子是WebRTC,它需要你提供一個自己的“信令信道”(signallingchannel,這個詞也暗示了WebRTC的IPtelephonyancestry),并將其配置到WebRTCAPI。在Tailscale,我們的協調服務器(coordinationserver)和DERP(DetourEncryptedRoutingProtocol)服務器集群是我們的旁路信道。

2.3.2 非活躍連接被防火墻清理

有狀態防火墻內存通常比較有限,因此會定期清理不活躍的連接(UDP常見的是30s),因此要保持連接alive的話需要定期通信,否則就會被防火墻關閉,為避免這個問題,我們,

  1. 要么定期向對方發包來keepalive,
  2. 要么有某種帶外方式來按需重建連接。

2.3.3 問題都解決了?不,挑戰剛剛開始

對于防火墻穿透來說,我們并不需要關心路徑上有幾堵墻——只要它們是有狀態防火墻且允許出向連接,這種同時發包(simultaneoustransmission)機制就能穿透任意多層防火墻。這一點對我們來說非常友好,因為只需要實現一個邏輯,然后能適用于任何地方了。

…對嗎?

其實,不完全對。這個機制有效的前提是:我們能提前知道對方的ip:port。而這就涉及到了我們今天的主題:NAT,它會使前面我們剛獲得的一點滿足感頓時消失。

下面,進入本文正題

三、NAT的本質

3.1 NAT設備與有狀態防火墻

可以認為NAT設備是一個增強版的有狀態防火墻,雖然它的增強功能對于本文場景來說并不受歡迎:除了前面提到的有狀態攔截/放行功能之外,它們還會在數據包經過時修改這些包。

3.2 NAT穿透與SNAT/DNAT

具體來說,NAT設備能完成某種類型的網絡地址轉換,例如,替換源或目的IP地址或端口。

  • 討論連接問題和NAT穿透問題時,我們只會受sourceNAT——SNAT的影響
  • DNAT不會影響NAT穿透。

3.3 SNAT的意義:解決IPv4地址短缺問題

SNAT最常見的使用場景是將很多設備連接到公網,而只使用少數幾個公網IP。例如對于消費級路由器,會將所有設備的(私有)IP地址映射為單個連接到公網的IP地址。

這種方式存在的意義是:我們有遠多于可用公網IP數量的設備需要連接到公網,(至少對IPv4來說如此,IPv6的情況后面會討論)。NAT使多個設備能共享同一IP地址,因此即使面臨IPv4地址短缺的問題,我們仍然能不斷擴張互聯網的規模。

3.4 SNAT過程:以家用路由器為例

假設你的筆記本連接到家里的WiFi,下面看一下它連接到公網某個服務器時的情形:

  1. 筆記本發送UDPpacket192.168.0.20:1234->7.7.7.7:5678。

這一步就好像筆記本有一個公網IP一樣,但源地址192.168.0.20是私有地址,只能出現在私有網絡,公網不認,收到這樣的包時它不知道如何應答。

  1. 家用路由器出場,執行SNAT。

包經過路由器時,路由器發現這是一個它沒有見過的新會話(session)。它知道192.168.0.20是私有IP,公網無法給這樣的地址回包,但它有辦法解決:

  • 在它自己的公網IP上挑一個可用的UDP端口,例如2.2.2.2:4242,然后創建一個NATmapping:192.168.0.20:1234<-->2.2.2.2:4242,然后將包發到公網,此時源地址變成了2.2.2.2:4242而不是原來的192.168.0.20:1234。因此服務端看到的是轉換之后地址,接下來,每個能匹配到這條映射規則的包,都會被路由器改寫IP和端口。

  1. 反向路徑是類似的,路由器會執行相反的地址轉換,將2.2.2.2:4242變回192.168.0.20:1234。對于筆記本來說,它根本感知不知道這正反兩次變換過程。

這里是拿家用路由器作為例子,但辦公網的原理是一樣的。不同之處在于,辦公網的NAT可能有多臺設備組成(高可用、容量等目的),而且它們有不止一個公網IP地址可用,因此在選擇可用的公網ip:port來做映射時,選擇空間更大,能支持更多客戶端。

3.5 SNAT給穿透帶來的挑戰

現在我們遇到了與前面有狀態防火墻類似的情況,但這次是NAT設備:通信雙方不知道對方的ip:port是什么,因此無法主動建連,如下圖所示:

但這次比有狀態防火墻更糟糕,嚴格來說,在雙方發包之前,根本無法確定(自己及對方的)ip:port信息,因為只有出向包經過路由器之后才會產生NATmapping(即,可以被對方連接的ip:port信息)。

因此我們又回到了與防火墻遇到的問題,并且情況更糟糕:雙方都需要主動和對方建連,但又不知道對方的公網地址是多少,只有當對方先說話之后,我們才能拿到它的地址信息。

如何破解以上死鎖呢?這就輪到STUN登場了。

四、穿透“NAT+防火墻”:STUN(SessionTraversalUtilitiesforNAT)協議

STUN既是一些對NAT設備行為的詳細研究,也是一種協助NAT穿透的協議。本文主要關注STUN協議。

4.1 STUN原理

STUN基于一個簡單的觀察:從一個會被NAT的客戶端訪問公網服務器時,服務器看到的是NAT設備的公網ip:port地址,而非該客戶端的局域網ip:port地址

也就是說,服務器能告訴客戶端它看到的客戶端的ip:port是什么。因此,只要將這個信息以某種方式告訴通信對端(peer),后者就知道該和哪個地址建連了!這樣就又簡化為前面的防火墻穿透問題了

本質上這就是STUN協議的工作原理,如下圖所示:

  • 筆記本向STUN服務器發送一個請求:“從你的角度看,我的地址什么?”
  • STUN服務器返回一個響應:“我看到你的UDP包是從這個地址來的:ip:port”。

4.2 為什么NAT穿透邏輯和主協議要共享同一個socket

理解了STUN原理,也就能理解為什么我們在文章開頭說,如果要實現自己的NAT穿透邏輯和主協議,就必須讓二者共享同一個socket

  1. 每個socket在NAT設備上都對應一個映射關系(私網地址->公網地址),
  2. STUN服務器只是輔助穿透的基礎設施,
  3. 與STUN服務器通信之后,在NAT及防火墻設備上打開了一個連接,允許入向包進來(回憶前面內容,只要目的地址對,UDP包就能進來,不管這些包是不是從STUN服務器來的),
  4. 因此,接下來只要將這個地址告訴我們的通信對端(peer),讓它往這個地址發包,就能實現穿透了。

4.3 STUN的問題:不能穿透所有NAT設備(例如企業級NAT網關)

有了STUN,我們的穿透目的似乎已經實現了:每臺機器都通過STUN來獲取自己的私網socket對應的公網ip:port,然后把這個信息告訴對端,然后兩端同時發起穿透防火墻的嘗試,后面的過程就和上一節介紹的防火墻穿透一樣了,對嗎

答案是:看情況。某些情況下確實如此,但有些情況下卻不行。通常來說,

  • 對于大部分家用路由器場景,這種方式是沒問題的;
  • 但對于一些企業級NAT網關來說,這種方式無法奏效。

NAT設備的說明書上越強調它的安全性,STUN方式失敗的可能性就越高。(但注意,從實際意義上來說,NAT設備在任何方面都并不會增強網絡的安全性,但這不是本文重點,因此不展開。)

4.4 重新審視STUN的前提

再次審視前面關于STUN的假設:當STUN服務器告訴客戶端在公網看來它的地址是2.2.2.2:4242時,那所有目的地址是2.2.2.2:4242的包就都能穿透防火墻到達該客戶端。

這也正是問題所在:這一點并不總是成立

  • 某些NAT設備的行為與我們假設的一致,它們的有狀態防火墻組件只要看到有客戶端自己發起的出向包,就會允許相應的入向包進入;因此只要利用STUN功能,再加上兩端同時發起防火墻穿透,就能把連接打通;
  • 另外一些NAT設備就要困難很多了,它會針對每個目的地址來生成一條相應的映射關系。在這樣的設備上,如果我們用相同的socket來分別發送數據包到5.5.5.5:1234and7.7.7.7:2345,我們就會得到2.2.2.2上的兩個不同的端口,每個目的地址對應一個。如果反向包的端口用的不對,包就無法通過防火墻。如下圖所示:

五、中場補課:NAT正式術語

知道NAT設備的行為并不是完全一樣之后,我們來引入一些正式術語。

5.1 早期術語

如果之前接觸過NAT穿透,可能會聽說過下面這些名詞:

  • “FullCone”
  • “RestrictedCone”
  • “Port-RestrictedCone”
  • “Symmetric”NATs

這些都是NAT穿透領域的早期術語。

但其實這些術語相當讓人困惑。我每次都要查一下RestrictedConeNAT是什么意思。從實際經驗來看,我并不是唯一對此感到困惑的人。例如,如今互聯網上將“easy”NAT歸類為FullCone,而實際上它們更應該歸類為Port-RestrictedCone。

5.2 近期研究與新術語

最近的一些研究和RFC已經提出了一些更準確的術語。

  • 首先,它們明確了如下事實:NAT設備的行為差異表現在多個維度,而并非只有早期研究中所說的“cone”這一個維度,因此基于“cone”來劃分類別并不是很有幫助
  • 其次,新研究和新術語能更準確地描述NAT在做什么

前面提到的所謂"easy"和"hard"NAT,只在一個維度有不同:NAT映射是否考慮到目的地址信息。RFC4787中,

  • easyNAT及其變種稱為“Endpoint-IndependentMapping”(EIM,終點無關的映射)

但是,從“命名很難”這一程序員界的偉大傳統來說,EIM這個詞其實也并不是100%準確,因為這種NAT仍然依賴endpoint,只不過依賴的是源endpoint:每個sourceip:port對應一個映射——否則你的包就會和別人的包混在一起,導致混亂。

嚴格來說,EIM應該稱為“DestinationEndpointIndependentMapping”(DEIM?),但這個名字太拗口了,而且按照慣例,Endpoint永遠指的是DestinationEndpoint。

  • hardNAT以及變種稱為“Endpoint-DependentMapping”(EDM,終點相關的映射)。

EDM中還有一個子類型,依據是只根據dst_ip做映射,還是根據dst_ip+dst_port做映射。對于NAT穿透來說,這種區分對來說是一樣的:它們都會導致STUN方式不可用

5.3 老的cone類型劃分

你可能會有疑問:根據是否依賴endpoint這一條件,只能組合出兩種可能,那為什么傳統分類中會有四種cone類型呢?答案是cone包含了兩個正交維度的NAT行為

  • NAT映射行為:前面已經介紹過了,
  • 有狀態防火墻行為:與前者類似,也是分為與endpoint相關還是無關兩種類型。

因此最終組合如下:

NATConeTypes

Endpoint無關NATmapping

Endpoint相關NATmapping(alltypes)

Endpoint無關防火墻

FullConeNAT

N/A*

Endpoint相關防火墻(dst.IPonly)

RestrictedConeNAT

N/A*

Endpoint相關防火墻(dst.IP+port)

Port-RestrictedConeNAT

SymmetricNAT

分解到這種程度之后就可以看出,cone類型對NAT穿透場景來說并沒有什么意義。我們關心的只有一點:是否是Symmetric——換句話說,一個NAT設備是EIM還是EDM類型的。

5.4 針對NAT穿透場景:簡化NAT分類

以上討論可知,雖然理解防火墻的具體行為很重要,但對于編寫NAT穿透代碼來說,這一點并不重要。我們的兩端同時發包方式(simultaneoustransmissiontrick)能有效穿透以上三種類型的防火墻。在真實場景中,我們主要在處理的是IP-and-portendpoint-dependent防火墻。

因此,對于實際NAT穿透實現,我們可以將以上分類簡化成:

Endpoint-IndependentNATmapping

Endpoint-DependentNATmapping(dst.IPonly)

Firewallisyes

EasyNAT

HardNAT

5.5 更多NAT規范(RFC)

想了解更多新的NAT術語,可參考

  • RFC4787(NATBehavioralRequirementsforUDP)
  • RFC5382(forTCP)
  • RFC5508(forICMP)

如果自己實現NAT,那應該(should)遵循這些RFC的規范,這樣才能使你的NAT行為符合業界慣例,與其他廠商的設備或軟件良好兼容。

六、穿透NAT+防火墻:STUN不可用時,fallback到中繼模式

6.1 問題回顧與保底方式(中繼)

補完基礎知識(尤其是定義了什么是hardNAT)之后,回到我們的NAT穿透主題。

  • 第1~4節已經解決了STUN和防火墻穿透的問題,
  • hardNAT對我們來說是個大問題,只要路徑上出現一個這種設備,前面的方案就行不通了。

準備放棄了嗎?這才進入NAT真正有挑戰的部分:如果已經試過了前面介紹的所有方式仍然不能穿透,我們該怎么辦呢?

  • 實際上,確實有很多NAT實現在這種情況下都會選擇放棄,向用戶報一個“無法連接”之類的錯誤。
  • 但對我們來說,這么快就放棄顯然是不可接受的——解決不了連通性問題,Tailscale就沒有存在的意義。

我們的保底解決方式是:創建一個中繼連接(relay)實現雙方的無障礙地通信。但是,中繼方式性能不是很差嗎?這要看具體情況:

  • 如果能直連,那顯然沒必要用中繼方式;
  • 但如果無法直連,而中繼路徑又非常接近雙方直連的真實路徑,并且帶寬足夠大,那中繼方式并不會明顯降低通信質量。延遲肯定會增加一點,帶寬會占用一些,但相比完全連接不上,還是更能讓用戶接受的

不過要注意:我們只有在無法直連時才會選擇中繼方式。實際場景中,

  1. 對于大部分網絡,我們都能通過前面介紹的方式實現直連,
  2. 剩下的長尾用中繼方式來解決,并不算一個很糟的方式。

此外,某些網絡會阻止NAT穿透,其影響比這種hardNAT大多了。例如,我們觀察到UCBerkeleyguestWiFi禁止除DNS流量之外的所有outboundUDP流量。不管用什么NAT黑科技,都無法繞過這個攔截。因此我們終歸還是需要一些可靠的fallback機制。

6.2 中繼協議:TURN、DERP

有多種中繼實現方式。

  1. TURN(TraversalUsingRelaysaroundNAT):經典方式,核心理念是
  • 用戶(人)先去公網上的TURN服務器認證,成功后后者會告訴你:“我已經為你分配了ip:port,接下來將為你中繼流量”,然后將這個ip:port地址告訴對方,讓它去連接這個地址,接下去就是非常簡單的客戶端/服務器通信模型了。

Tailscale并不使用TURN。這種協議用起來并不是很好,而且與STUN不同,它沒有真正的交互性,因為互聯網上并沒有公開的TURN服務器。

  1. DERP(DetouredEncryptedRoutingProtocol)

這是我們創建的一個協議,DERP

  • 它是一個通用目的包中繼協議,運行在HTTP之上,而大部分網絡都是允許HTTP通信的。它根據目的公鑰(destination’spublickey)來中繼加密的流量(encryptedpayloads)。

前面也簡單提到過,DERP既是我們在NAT穿透失敗時的保底通信方式(此時的角色與TURN類似),也是在其他一些場景下幫助我們完成NAT穿透的旁路信道。換句話說,它既是我們的保底方式,也是有更好的穿透鏈路時,幫助我們進行連接升級(upgradetoapeer-to-peerconnection)的基礎設施。

6.3 小結

有了“中繼”這種保底方式之后,我們穿透的成功率大大增加了。如果此時不再閱讀本文接下來的內容,而是把上面介紹的穿透方式都實現了,我預計:

  • 90%的情況下,你都能實現直連穿透;
  • 剩下的10%里,用中繼方式能穿透一些(some);

這已經算是一個“足夠好”的穿透實現了。

七、穿透NAT+防火墻:企業級改進

如果你并不滿足于“足夠好”,那我們可以做的事情還有很多!

本節將介紹一些五花八門的tricks,在某些特殊場景下會幫到我們。單獨使用這項技術都無法解決NAT穿透問題,但將它們巧妙地組合起來,我們能更加接近100%的穿透成功率。

7.1 穿透hardNAT:暴力端口掃描

回憶hardNAT中遇到的問題,如下圖所示,關鍵問題是:easyNAT不知道該往hardNAT方的哪個ip:port發包。

必須要往正確的ip:port發包,才能穿透防火墻,實現雙向互通。怎么辦呢?

  1. 首先,我們能知道hardNAT的一些ip:port,因為我們有STUN服務器。

這里先假設我們獲得的這些IP地址都是正確的(這一點并不總是成立,但這里先這么假設。而實際上,大部分情況下這一點都是成立的,如果對此有興趣,可以參考REQ-2inRFC4787)。

  1. IP地址確定了,剩下的就是端口了。總共有65535中可能,我們能遍歷這個端口范圍嗎?

如果發包速度是100packets/s,那最壞情況下,需要10分鐘來找到正確的端口。還是那句話,這雖然不是最優的,但總比連不上好。

這很像是端口掃描(事實上,確實是),實際中可能會觸發對方的網絡入侵檢測軟件。

7.2 基于生日悖論改進暴力掃描:hardside多開端口+easyside隨機探測

利用birthdayparadox算法,我們能對端口掃描進行改進。

  • 上一節的基本前提是:hardside只打開一個端口,然后easyside暴力掃描65535個端口來尋找這個端口;
  • 這里的改進是:在hardsize開多個端口,例如256個(即同時打開256個socket,目的地址都是easyside的ip:port),然后easyside隨機探測這邊的端口。

這里省去算法的數學模型,如果你對實現干興趣,可以看看我寫的pythoncalculator。計算過程是“經典”生日悖論的一個小變種。下面是隨著easysiderandomprobe次數(假設hardsize256個端口)的變化,兩邊打開的端口有重合(即通信成功)的概率:

隨機探測次數

成功概率

174

50%

256

64%

1024

98%

2048

99.9%

根據以上結果,如果還是假設100ports/s這樣相當溫和的探測速率,那2秒鐘就有約50%的成功概率。即使非常不走運,我們仍然能在20s時幾乎100%穿透成功,而此時只探測了總端口空間的4%

非常好!雖然這種hardNAT給我們帶來了嚴重的穿透延遲,但最終結果仍然是成功的。那么,如果是兩個hardNAT,我們還能處理嗎?

7.3 雙hardNAT場景

這種情況下仍然可以用前面的多端口+隨機探測方式,但成功概率要低很多了:

  • 每次通過一臺hardNAT去探測對方的端口(目的端口)時,我們自己同時也生成了一個隨機源端口
  • 這意味著我們的搜索空間變成了二維{srcport,dstport}對,而不再是之前的一維dstport空間。

這里我們也不就具體計算展開,只告訴結果:仍然假設目的端打開256個端口,從源端發起2048次(20秒),成功的概率是:0.01%

如果你之前學過生日悖論,就并不會對這個結果感到驚訝。理論上來說,

  • 要達到99.9%的成功率,我們需要兩邊各進行170,000次探測——如果還是以100packets/sec的速度,就需要28分鐘
  • 要達到50%的成功率,“只”需要54,000packets,也就是9分鐘
  • 如果不使用生日悖論方式,而且暴力窮舉,需要1.2年時間

對于某些應用來說,28分鐘可能仍然是一個可接受的時間。用半個小時暴力穿透NAT之后,這個連接就可以一直用著——除非NAT設備重啟,那樣就需要再次花半個小時穿透建個新連接。但對于交互式應用來說,這樣顯然是不可接受的。

更糟糕的是,如果去看常見的辦公網路由器,你會震驚于它的activesessionlowlimit有多么低。例如,一臺JuniperSRX300最多支持64,000activesessions。也就是說,

  • 如果我們想創建一個成功的穿透連接,就會把它的整張session表打爆(因為我們要暴力探測65535個端口,每次探測都是一條新連接記錄)!這顯然要求這臺路由器能從容優雅地處理過載的情況
  • 這只是創建一條連接帶來的影響!如果20臺機器同時對這臺路由器發起穿透呢?絕對的災難!

至此,我們通過這種方式穿透了比之前更難一些的網絡拓撲。這是一個很大的成就,因為家用路由器一般都是easyNAT,hardNAT一般都是辦公網路由器或云NAT網關。這意味著這種方式能幫我們解決

  • home-to-office(家->辦公室)
  • home-to-cloud(家->云)

的場景,以及一部分

  • office-to-cloud(辦公室->云)
  • cloud-to-cloud(云->辦公室)

場景。

7.4 控制端口映射(portmapping)過程:UPnP/NAT-PMP/PCP協議

如果我們能讓NAT設備的行為簡單點,不要把事情搞這么復雜,那建立連接(穿透)就會簡單很多。真有這樣的好事嗎?還真有,有專門的一種協議叫端口映射協議(portmappingprotocols)。通過這種協議禁用掉前面遇到的那些亂七八糟的東西之后,我們將得到一個非常簡單的“請求-響應”。

下面是三個具體的端口映射協議:

  1. UPnPIGD(UniversalPlug’n’PlayInternetGatewayDevice)

最老的端口控制協議,誕生于1990s晚期,因此使用了很多上世紀90年代的技術(XML、SOAP、multicastHTTPoverUDP——對,HTTPoverUDP),而且很難準確和安全地實現這個協議。但以前很多路由器都內置了UPnP協議,現在仍然很多。

請求和響應:

  • “你好,請將我的lan-ip:port轉發到公網(WAN)”,“好的,我已經為你分配了一個公網映射wan-ip:port”。
  1. NAT-PMP

UPnPIGD出來幾年之后,Apple推出了一個功能類似的協議,名為NAT-PMP(NATPortMappingProtocol)。

但與UPnP不同,這個協議做端口轉發,不管是在客戶端還是服務端,實現起來都非常簡單。

  1. PCP

稍后一點,又出現了NAT-PMPv2版,并起了個新名字PCP(PortControlProtocol)。

因此要更好地實現穿透,可以

  1. 先判斷本地的默認網關上是否啟用了UPnPIGD,NAT-PMPandPCP
  2. 如果探測發現其中任何一種協議有響應,我們就申請一個公網端口映射

可以將這理解為一個加強版STUN:我們不僅能發現自己的公網ip:port,而且能指示我們的NAT設備對我們的通信對端友好一些——但并不是為這個端口修改或添加防火墻規則。

  1. 接下來,任何到達我們NAT設備的、地址是我們申請的端口的包,都會被設備轉發到我們。

但我們不能假設這個協議一定可用

  1. 本地NAT設備可能不支持這個協議;
  2. 設備支持但默認禁用了,或者沒人知道還有這么個功能,因此從來沒開過;
  3. 安全策略要求關閉這個特性。

這一點非常常見,因為UPnP協議曾曝出一些高危漏洞(后面都修復了,因此如果是較新的設備,可以安全地使用UPnP——如果實現沒問題)。不幸的是,某些設備的配置中,UPnP,NAT-PMP,PCP是放在一個開關里的(可能統稱為“UPnP”功能),一開全開,一關全關。因此如果有人擔心UPnP的安全性,他連另外兩個也用不了。

最后,終歸來說,只要這種協議可用,就能有效地減少一次NAT,大大方便建連過程。但接下來看一些不常見的場景。

7.5 多NAT協商(NegotiatingnumerousNATs)

目前為止,我們看到的客戶端和服務端都各只有一個NAT設備。如果有多個NAT設備會怎么樣?例如下面這種拓撲:

這個例子比較簡單,不會給穿透帶來太大問題。包從客戶端A經過多次NAT到達公網的過程,與前面分析的穿過多層有狀態防火墻是一樣的:

  • 額外的這層(NAT設備)對客戶端和服務端來說都不可見,我們的穿透技術也不關心中間到底經過了多少層設備。
  • 真正有影響的其實只是最后一層設備,因為對端需要在這一層設備上找到入口讓包進來。

具體來說,真正有影響的是端口轉發協議。

  1. 客戶端使用這種協議分配端口時,為我們分配端口的是最靠近客戶端的這層NAT設備;
  2. 而我們期望的是讓最離客戶端最遠的那層NAT來分配,否則我們得到的就是一個網絡中間層分配的ip:port,對端是用不了的;
  3. 不幸的是,這幾種協議都不能遞歸地告訴我們下一層NAT設備是多少——雖然可以用traceroute之類的工具來探測網絡路徑,再加上猜路上的設備是不是NAT設備(嘗試發送NAT請求)——但這個就看運氣了。

這就是為什么互聯網上充斥著大量的文章說double-NAT有多糟糕,以及警告用戶為保持后向兼容不要使用double-NAT。但實際上,double-NAT對于絕大部分互聯網應用來說都是不可見的(透明的),因為大部分應用并不需要主動地做這種NAT穿透。

但我也絕不是在建議你在自己的網絡中設置double-NAT。

  1. 破壞了端口映射協議之后,某些視頻游戲的多人(multiplayer)模式就會無法使用,
  2. 也可能會使你的IPv6網絡無法派上用場,后者是不用NAT就能雙向直連的一個好方案。

但如果double-NAT并不是你能控制的,那除了不能用到這種端口映射協議之外,其他大部分東西都是不受影響的。

double-NAT的故事到這里就結束了嗎?——并沒有,而且更大型的double-NAT場景將展現在我們面前。

7.6 運營商級NAT帶來的問題

即使用NAT來解決IPv4地址不夠的問題,地址仍然是不夠用的,ISP(互聯網服務提供商)顯然無法為每個家庭都分配一個公網IP地址。那怎么解決這個問題呢?ISP的做法是不夠了就再嵌套一層NAT

  1. 家用路由器將你的客戶端SNAT到一個“intermediate”IP然后發送到運營商網絡,
  2. ISP’snetwork中的NAT設備再將這些intermediateIPs映射到少量的公網IP。

后面這種NAT就稱為“運營商級NAT”(carrier-gradeNAT,或稱電信級NAT),縮寫CGNAT。如下圖所示:

CGNAT對NAT穿透來說是一個大麻煩。

  • 在此之前,辦公網用戶要快速實現NAT穿透,只需在他們的路由器上手動設置端口映射就行了。
  • 但有了CGNAT之后就不管用了,因為你無法控制運營商的CGNAT!

好消息是:這其實是double-NAT的一個小變種,因此前面介紹的解決方式大部分還仍然是適用的。某些東西可能會無法按預期工作,但只要肯給ISP交錢,這些也都能解決。除了portmappingprotocols,其他我們已經介紹的所有東西在CGNAT里都是適用的。

新挑戰:同一CGNAT側直連,STUN不可用

但我們確實遇到了一個新挑戰:如何直連兩個在同一CGNAT但不同家用路由器中的對端呢?如下圖所示:

在這種情況下,STUN就無法正常工作了:STUN看到的是客戶端在公網(CGNAT后面)看到的地址,而我們想獲得的是在“middlenetwork”中的ip:port,這才是對端真正需要的地址,

解決方案:如果端口映射協議能用:一端做端口映射

怎么辦呢?

如果你想到了端口映射協議,那恭喜,答對了!如果peer中任何一個NAT支持端口映射協議,對我們就能實現穿透,因為它分配的ip:port正是對端所需要的信息。

這里諷刺的是:double-NAT(指CGNAT)破壞了端口映射協議,但在這里又救了我們!當然,我們假設這些協議一定可用,因為CGNATISP傾向于在它們的家用路由器側關閉這些功能,已避免軟件得到“錯誤的”結果,產生混淆。

解決方案:如果端口映射協議不能用:NAThairpin模式

如果不走運,NAT上沒有端口映射功能怎么辦?

讓我們回到基于STUN的技術,看會發生什么。兩端在CGNAT的同一側,假設STUN告訴我們A的地址是2.2.2.2:1234,B的地址是2.2.2.2:5678。

那么接下來的問題是:如果A向2.2.2.2:5678發包會怎么樣?期望的CGNAT行為是:

  1. 執行A的NAT映射規則,即對2.2.2.2:1234->2.2.2.2:5678進行SNAT。
  2. 注意到目的地址2.2.2.2:5678匹配到的是B的入向NAT映射,因此接著對這個包執行DNAT,將目的IP改成B的私有地址。
  3. 通過CGNAT的internal接口(而不是public接口,對應公網)將包發給B。

這種NAT行為有個專門的術語,叫hairpinning(直譯為發卡,意思是像發卡一樣,沿著一邊上去,然后從另一邊繞回來),

大家應該猜到的一個事實是:不是所以NAT都支持hairpin模式。實際上,大量well-behavedNAT設備都不支持hairpin模式,

  • 因為它們都有“只有src_ip是私有地址且dst_ip是公網地址的包才會經過我”之類的假設。
  • 因此對于這種目的地址不是公網、需要讓路由器把包再轉回內網的包,它們會直接丟棄
  • 這些邏輯甚至是直接實現在路由芯片中的,因此除非升級硬件,否則單靠軟件編程無法改變這種行為。

Hairpin是所有NAT設備的特性(支持或不支持),并不是CGNAT獨有的。

  1. 在大部分情況下,這個特性對我們的NAT穿透目的來說都是無所謂的,因為我們期望中兩個LANNAT設備會直接通信,不會再向上繞到它們的默認網關CGNAT來解決這個問題

Hairpin特性可有可無這件事有點遺憾,這可能也是為什么hairpin功能經常broken的原因。

  1. 一旦必須涉及到CGNAT,那hairpinning對連接性來說就至關重要了。

Hairpinning使內網連接的行為與公網連接的行為完成一致,因此我們無需關心目的地址類型,也不用知曉自己是否在一臺CGNAT后面。

如果hairpinning和portmappingprotocols都不可用,那只能降級到中繼模式了

7.7 全IPv6網絡:理想之地,但并非問題全無

行文至此,一些讀者可能已經對著屏幕咆哮:不要再用IPv4了!花這么多時間精力解決這些沒意義的東西,還不如直接換成IPv6!

  • 的確,之所以有這些亂七八糟的東西,就是因為IPv4地址不夠了,我們一直在用越來越復雜的NAT來給IPv4續命
  • 如果IP地址夠用,無需NAT就能讓世界上的每個設備都有一個自己的公網IP地址,這些問題不就解決了嗎?

簡單來說,是的,這也正是IPv6能做的事情。但是,也只說對了一半:在理想的全IPv6世界中,所有這些東西會變得更加簡單,但我們面臨的問題并不會完全消失——因為有狀態防火墻仍然還是存在的

  • 辦公室中的電腦可能有一個公網IPv6地址,但你們公司肯定會架設一個防火墻,只允許你的電腦主動訪問公網,而不允許反向主動建連。
  • 其他設備上的防火墻也仍然存在,應用類似的規則。

因此,我們仍然會用到

  1. 本文最開始介紹的防火墻穿透技術,以及
  2. 幫助我們獲取自己的公網ip:port信息的旁路信道
  3. 仍然需要在某些場景下fallback到中繼模式,例如fallback到最通用的HTTP中繼協議,以繞過某些網絡禁止outboundUDP的問題。

但我們現在可以拋棄STUN、生日悖論、端口映射協議、hairpin等等東西了。這是一個好消息!

全球IPv4/IPv6部署現狀

另一個更加嚴峻的現實問題是:當前并不是一個全IPv6世界。目前世界上

  • 大部分還是IPv4,
  • 大約33%是IPv6,而且分布極度不均勻,因此某些通信對所在的可能是100%IPv6,也可能是0%,或二者之間。

不幸的是,這意味著,IPv6**還**無法作為我們的解決方案。就目前來說,它只是我們的工具箱中的一個備選。對于某些peer來說,它簡直是完美工具,但對其他peer來說,它是用不了的。如果目標是“任何情況下都能穿透(連接)成功”,那我們就仍然需要IPv4+NAT那些東西。

新場景:NAT64/DNS64

IPv4/IPv6共存也引出了一個新的場景:NAT64設備。

前面介紹的都是NAT44設備:它們將一個IPv4地址轉換成另一IPv4地址。NAT64從名字可以看出,是將一個內側IPv6地址轉換成一個外側IPv4地址。利用DNS64設備,我們能將IPv4DNS應答給IPv6網絡,這樣對終端來說,它看到的就是一個全IPv6網絡,而仍然能訪問IPv4公網。

如果需要處理DNS問題,那這種方式工作良好。例如,如果連接到google.com,將這個域名解析成IP地址的過程會涉及到DNS64設備,它又會進一步involveNAT64設備,但后一步對用戶來說是無感知的。

對于NAT和防火墻穿透來說,我們會關心每個具體的IP地址和端口

解決方案:CLAT(Customer-sidetransLATor)

如果設備支持CLAT(Customer-sidetranslator—fromCustomerXLAT),那我們就很幸運:

  • CLAT假裝操作系統有直接IPv4連接,而背后使用的是NAT64,以對應用程序無感知。在有CLAT的設備上,我們無需做任何特殊的事情。
  • CLAT在移動設備上非常常見,但在桌面電腦、筆記本和服務器上非常少見,因此在后者上,必須自己做CLAT做的事情:檢測NAT64+DNS64的存在,然后正確地使用它們。

解決方案:CLAT不存在時,手動穿透NAT64設備

  1. 首先檢測是否存在NAT64+DNS64。

方法很簡單:向ipv4only.arpa.發送一個DNS請求。這個域名會解析到一個已知的、固定的IPv4地址,而且是純IPv4地址。如果得到的是一個IPv6地址,就可以判斷有DNS64服務器做了轉換,而它必然會用到NAT64。這樣就能判斷出NAT64的前綴是多少。

  1. 此后,要向IPv4地址發包時,發送格式為{NAT64prefix+IPv4address}的IPv6包。類似地,收到來源格式為{NAT64prefix+IPv4address}的包時,就是IPv4流量。
  2. 接下來,通過NAT64網絡與STUN通信來獲取自己在NAT64上的公網ip:port,接下來就回到經典的NAT穿透問題了——除了需要多做一點點事情。

幸運的是,如今的大部分v6-only網絡都是移動運營商網絡,而幾乎所有手機都支持CLAT。運營v6-only網絡的ISPs會在他們給你的路由器上部署CLAT,因此最后你其實不需要做什么事情。但如果想實現100%穿透,就需要解決這種邊邊角角的問題,即必須顯式支持從v6-only網絡連接v4-only對端。

7.8 將所有解決方式集成到ICE協議

針對具體場景,該選擇哪種穿透方式?

至此,我們的NAT穿透之旅終于快結束了。我們已經覆蓋了有狀態防火墻、簡單和高級NAT、IPv4和IPv6。只要將以上解決方式都實現了,NAT穿透的目的就達到了!

但是,

  • 對于給定的peer,如何判斷改用哪種方式呢?
  • 如何判斷這是一個簡單有狀態防火墻的場景,還是該用到生日悖論算法,還是需要手動處理NAT64呢?
  • 還是通信雙方在一個WiFi網絡下,連防火墻都沒有,因此不需要任何操作呢?

早期NAT穿透比較簡單,能讓我們精確判斷出peer之間的路徑特點,然后針對性地采用相應的解決方式。但后面,網絡工程師和NAT設備開發工程師引入了一些新理念,給路徑判斷造成很大困難。因此我們需要簡化客戶端側的思考(判斷邏輯)。

這就要提到InteractiveConnectivityEstablishment(ICE,交換式連接建立)協議了。與STUN/TURN類似,ICE來自電信領域,因此其RFC充滿了SIP、SDP、信令會話、撥號等等電話術語。但如果忽略這些領域術語,我們會看到它描述了一個極其優雅的判斷最佳連接路徑的算法

真的?這個算法是:每種方法都試一遍,然后選擇最佳的那個方法。就是這個算法,驚喜嗎?

來更深入地看一下這個算法。

ICE(InteractiveConnectivityEstablishment)算法

這里的討論不會嚴格遵循ICEspec,因此如果是在自己實現一個可互操作的ICE客戶端,應該通讀RFC8445,根據它的描述來實現。這里忽略所有電信術語,只關注核心的算法邏輯,并提供幾個在ICE規范允許范圍的靈活建議。

  1. 為實現和某個peer的通信,首先需要確定我們自己用的(客戶端側)這個socket的地址,這是一個列表,至少應該包括:
  • 我們自己的IPv6ip:ports我們自己的IPv4LANip:ports(局域網地址)通過STUN服務器獲取到的我們自己的IPv4WANip:ports(公網地址,可能會經過NAT64轉換)通過端口映射協議獲取到的我們自己的IPv4WANip:port(NAT設備的端口映射協議分配的公網地址)運營商提供給我們的endpoints(例如,靜態配置的端口轉發
  1. 通過旁路信道與peer互換這個列表。兩邊都拿到對方的列表后,就開始互相探測對方提供的地址。列表中地址沒有優先級,也就是說,如果對方給的了15個地址,那我們應該把這15個地址都探測一遍。

這些探測包有兩個目的

  • 打開防火墻,穿透NAT,也就是本文一直在介紹的內容;健康檢測。我們在不斷交換(最好是已認證的)“ping/pong”包,來檢測某個特定的路徑是不是端到端通的。
  1. 最后,一小會兒之后,從可用的備選地址中(根據某些條件)選擇“最佳”的那個,任務完成!

這個算法的優美之處在于:只要選擇最佳線路(地址)的算法是正確的,那就總能獲得最佳路徑。

  • ICE會預先對這些備選地址進行排序(通常:LAN>WAN>WAN+NAT),但用戶也可以自己指定這個排序行為。
  • 從v0.100.0開始,Tailscale從原來的hardcode優先級切換成了根據round-triplatency的方式,它大部分情況下排序的結果和LAN>WAN>WAN+NAT是一致的。但相比于靜態排序,我們是動態計算每條路徑應該屬于哪個類別。

ICEspec將協議組織為兩個階段:

  1. 探測階段
  2. 通信階段

但不一定要嚴格遵循這兩個步驟的順序。在Tailscale,

  • 我們發現更優的路徑之后就會自動切換過去,
  • 所有的連接都是先選擇DERP模式(中繼模式)。這意味著連接立即就能建立(優先級最低但100%能成功的模式),用戶不用任何等待,
  • 然后并行進行路徑發現。通常幾秒鐘之后,我們就能發現一條更優路徑,然后將現有連接透明升級(upgrade)過去。

但有一點需要關心:非對稱路徑。ICE花了一些精力來保證通信雙方選擇的是相同的網絡路徑,這樣才能保證這條路徑上有雙向流量,能保持防火墻和NAT設備的連接一直處于open狀態。自己實現的話,其實并不需要花同樣大的精力來實現這個保證,但需要確保你所有使用的所有路徑上,都有雙向流量。這個目標就很簡單了,只需要定期在所有已使用的路徑上發ping/pong就行了。

健壯性與降級

要實現健壯性,還需要檢測當前已選擇的路徑是否已經失敗了(例如,NAT設備維護清掉了所有狀態),如果失敗了就要降級(downgrade)到其他路徑。這里有兩種方式:

  1. 持續探測所有路徑,維護一個降級時會用的備用地址列表;
  2. 直接降級到保底的中繼模式,然后再通過路徑探測升級到更好的路徑。

考慮到發生降級的概率是非常小的,因此這種方式可能是更經濟的。

7.9 安全

最后需要提到安全。

本文的所有內容都假設:我們使用的上層協議已經有了自己的安全機制(例如QUIC協議有TLS證書,WireGuard協議有自己的公鑰)。如果還沒有安全機制,那顯然是要立即補上的。一旦動態切換路徑,基于IP的安全機制就是無用的了(IP協議最開始就沒怎么考慮安全性),至少要有端到端的認證

  • 嚴格來說,如果上層協議有安全機制,那即使收到是欺騙性的ping/pong流量,問題都不大,最壞的情況也就是攻擊者誘導兩端通過他們的系統來中繼流量。而有了端到端安全機制,這并不是一個大問題(取決于你的威脅模型)。
  • 但出于謹慎考慮,最好還是對路徑發現的包也做認證和加密。具體如何做可以咨詢你們的應用安全工程師。

八、結束語

我們終于完成了NAT穿透的目標!

如果實現了以上提到的所有技術,你將得到一個業內領先的NAT穿透軟件,能在絕大多數場景下實現端到端直連。如果直連不了,還可以降級到保底的中繼模式(對于長尾來說只能靠中繼了)。

但這些工作相當復雜!其中一些問題研究起來很有意思,但很難做到完全正確,尤其是那些非常邊邊角角的場景,真正出現的概率極小,但解決它們所需花費的經歷又極大。不過,這種工作只需要做一次,一旦解決了,你就具備了某種超級能力:探索令人激動的、相對還比較嶄新的端到端應用(peer-to-peerapplications)世界。

8.1 跨公網端到端直連

去中心化軟件領域中的許多有趣想法,簡化之后其實都變成了跨過公網(互聯網)實現端到端直連這一問題,開始時可能覺得很簡單,但真正做才發現比想象中難多了。現在知道如何解決這個問題了,動手開做吧!

8.2 結束語之TL;DR

實現健壯的NAT穿透需要下列基礎:

  1. 一種基于UDP的協議;
  2. 能在程序內直接訪問socket;
  3. 有一個與peer通信的旁路信道;
  4. 若干STUN服務器;
  5. 一個保底用的中繼網絡(可選,但強烈推薦)

然后需要:

  1. 遍歷所有的ip:port;
  2. 查詢STUN服務器來獲取自己的公網ip:port信息,以及判斷自己這一側的NAT的“難度”(difficulty);
  3. 使用portmapping協議來獲取更多的公網ip:ports;
  4. 檢查NAT64,通過它獲取自己的公網ip:port;
  5. 將自己的所有公網ip:ports信息通過旁路信道與peer交換,以及某些加密秘鑰來保證通信安全;
  6. 通過保底的中繼方式與對方開始通信(可選,這樣連接能快速建立)
  7. 如果有必要/想這么做,探測對方的提供的所有ip:port,以及執行生日攻擊(birthdayattacks)來穿透harderNAT;
  8. 發現更優路徑之后,透明升級到該路徑;
  9. 如果當前路徑斷了,降級到其他可用的路徑;
  10. 確保所有東西都是加密的,并且有端到端認證。
聲明:本內容為作者獨立觀點,不代表電子星球立場。未經允許不得轉載。授權事宜與稿件投訴,請聯系:editor@netbroad.com
覺得內容不錯的朋友,別忘了一鍵三連哦!
贊 1
收藏 1
關注 181
成為作者 賺取收益
全部留言
0/200
成為第一個和作者交流的人吧
主站蜘蛛池模板: 在线播放网站 | 免费观看黄页网站 | 少妇丰满大乳被男人揉捏视频 | 亚洲色成人WWW永久网站 | 亚洲色偷偷av男人的天堂 | 国产成人AV无码专区亚洲AV | 国产农村女人一级毛片了 | 日韩在线播放网站 | 国产各种高潮合集在线观看 | 天天插天天爱 | 97人妻人人做人碰人人添 | 亚洲成成熟女人专区 | 色欲狠狠躁天天躁无码中文字幕 | 久久久最新 | 久久99热狠狠色一区二区 | 涩涩在线 | 成人欧美在线视频 | 99国产一区 | 毛片久久 | 色偷偷色噜噜狠狠网站久久 | 色大18成网站www在线观看 | 日韩成人精品 | 欧美日韩一区国产 | 久久三级影院 | aaa亚洲精品一二三区 | 夜夜高潮夜夜爽夜夜爱爱一区 | 永久免费A∨片在线观看 | 色婷婷五月综合久久 | 人妻av资源先锋影音av资源 | 法国性迷宫hd在线观看 | 91亚洲国产成人久久精品麻豆 | 91.精品高清在线观看 | 麻豆av资源 | 国产一区二区成人久久免费影院 | av黄色在线 | 91酒色 | 天码AV无码一区二区三区四区 | JAPAN黑人极大黑炮 | 视频自拍一区 | 毛片一级在线 | 任你操av|