來(lái)自: ARTHURCHIAO'S BLOG 鏈接:https://arthurchiao.art/blog/how-nat-traversal-works-zh/ 原文:https://tailscale.com/blog/how-nat-traversal-works/
譯者序 本文翻譯自 2020 年的一篇英文博客: How NAT traversal works [1] 。
設(shè)想這樣一個(gè)問(wèn)題:在北京和上海各有一臺(tái) 局域網(wǎng)的機(jī)器 (例如一臺(tái)是家里的臺(tái)式機(jī),一 臺(tái)是連接到星巴克 WiFi 的筆記本),二者都是私網(wǎng) IP 地址,但可以訪問(wèn)公網(wǎng),? 如何讓這兩臺(tái)機(jī)器通信呢?
既然二者都能訪問(wèn)公網(wǎng),那最簡(jiǎn)單的方式當(dāng)然是在公網(wǎng)上架設(shè)一個(gè)中繼服務(wù)器:兩臺(tái)機(jī)器分別連接到中繼服務(wù),后者完成雙向轉(zhuǎn)發(fā)。這種方式顯然有很大的性能開(kāi)銷(xiāo),而 且中繼服務(wù)器很容易成為瓶頸。
有沒(méi)有辦法不用中繼,讓 兩臺(tái)機(jī)器直接通信 呢?
如果有一定的網(wǎng)絡(luò)和協(xié)議基礎(chǔ),就會(huì)明白這事兒是可能的。Tailscale 的這篇 史詩(shī)級(jí)長(zhǎng)文 由淺入深地展示了這種“可能”,如果完全實(shí)現(xiàn)本文所 介紹的技術(shù),你將得到一個(gè)企業(yè)級(jí)的 NAT/防火墻穿透工具。此外,如作者所說(shuō), 去中心化軟件 領(lǐng)域中的許多有趣想法,簡(jiǎn)化之后其實(shí)都變成了? 跨過(guò)公網(wǎng)(互聯(lián)網(wǎng))實(shí)現(xiàn)端到端直連 ?這一問(wèn)題,因此本文的意義并不僅限于 NAT 穿透本身。
由于譯者水平有限,本文不免存在遺漏或錯(cuò)誤之處。如有疑問(wèn),請(qǐng)查閱原文。
以下是譯文。
在前一篇文章? How Tailscale Works [2] ?中, 我們已經(jīng)用較長(zhǎng)篇幅介紹了 Tailscale 是如何工作的。但其中并沒(méi)有詳細(xì)描述我們是? 如何穿透 NAT 設(shè)備,從而實(shí)現(xiàn)終端設(shè)備直連的 ?—— 不管這些終端之間 有什么設(shè)備(防火墻、NAT 等),以及有多少設(shè)備。本文試圖補(bǔ)足這一內(nèi)容。
1 引言 1.1 背景:IPv4 地址短缺,引入 NAT 全球 IPv4 地址早已不夠用,因此人們發(fā)明了 NAT(網(wǎng)絡(luò)地址轉(zhuǎn)換)來(lái)緩解這個(gè)問(wèn)題。
簡(jiǎn)單來(lái)說(shuō),大部分機(jī)器都使用 私有 IP 地址 ,如果它們需要訪問(wèn)公網(wǎng)服務(wù),那么,
出向流量:需要經(jīng)過(guò)一臺(tái) NAT 設(shè)備,它會(huì)對(duì)流量進(jìn)行 SNAT,將私有 srcIP+Port 轉(zhuǎn) 換成 NAT 設(shè)備的公網(wǎng) IP+Port(這樣應(yīng)答包才能回來(lái)),然后再將包發(fā)出去; 應(yīng)答流量(入向):到達(dá) NAT 設(shè)備后進(jìn)行相反的轉(zhuǎn)換,然后再轉(zhuǎn)發(fā)給客戶(hù)端。 整個(gè)過(guò)程對(duì)雙方透明。
更多關(guān)于 NAT 的內(nèi)容,可參考? (譯) NAT - 網(wǎng)絡(luò)地址轉(zhuǎn)換(2016) [3] 。譯注。
以上是本文所討論問(wèn)題的 基本背景 。
1.2 需求:兩臺(tái)經(jīng)過(guò) NAT 的機(jī)器建立點(diǎn)對(duì)點(diǎn)連接 在以上所描述的 NAT 背景下,我們從最簡(jiǎn)單的問(wèn)題開(kāi)始:如何在兩臺(tái)經(jīng)過(guò) NAT 的機(jī)器之間建立? 點(diǎn)對(duì)點(diǎn)連接 (直連)。如下圖所示:
直接用機(jī)器的 IP 互連顯然是不行的,因?yàn)樗鼈兌际撬接?IP(例如? 192.168.1.x
)。在 Tailscale 中,我們會(huì)建立一個(gè)? WireGuard? 隧道 ?來(lái)解決這個(gè)問(wèn)題 —— 但這并不是太重要,因?yàn)槲覀儗?/span>過(guò)去幾代人努力 都整合到了一個(gè)工具集,? 這些技術(shù)廣泛適用于各種場(chǎng)景 。例如,
WebRTC [4] ?使用這些技術(shù)在瀏覽器之間完成 peer-to-peer 語(yǔ)音、視頻和數(shù)據(jù)傳輸, VoIP 電話和一些視頻游戲 也使用類(lèi)似機(jī)制,雖然不是所有情況下都很成功。 接下來(lái),本文將 在一般意義上討論這些技術(shù) ,并在合適的地方拿 Tailscale 和其他一些東西作為例子。
1.3 方案:NAT 穿透 1.3.1 兩個(gè)必備前提:UDP + 能直接控制 socket 如果想 設(shè)計(jì)自己的協(xié)議來(lái)實(shí)現(xiàn) NAT 穿透 ,那必須滿(mǎn)足以下兩個(gè)條件:
協(xié)議應(yīng)該基于 UDP 。
理論上用 TCP 也能實(shí)現(xiàn),但它會(huì)給本已相當(dāng)復(fù)雜的問(wèn)題再增加一層復(fù)雜性, 甚至還需要定制化內(nèi)核 —— 取決于你想實(shí)現(xiàn)到什么程度。本文接下來(lái)都將關(guān)注在 UDP 上。
如果考慮 TCP 是想在 NAT 穿透時(shí)獲得 面向流的連接 ( stream-oriented connection),可以考慮用? QUIC ?來(lái)替代,它構(gòu) 建在 UDP 之上,因此我們能將關(guān)注點(diǎn)放在 UDP NAT 穿透,而仍然能獲得一個(gè) 很好的流協(xié)議(stream protocol)。
對(duì)收發(fā)包的? socket 有直接控制權(quán) 。
例如,從經(jīng)驗(yàn)上來(lái)說(shuō),無(wú)法基于某個(gè)現(xiàn)有的網(wǎng)絡(luò)庫(kù)實(shí)現(xiàn) NAT 穿透,因?yàn)槲覀? 必須在使用的“主要”協(xié)議之外,發(fā)送和接收額外的數(shù)據(jù)包 。
某些協(xié)議(例如 ?WebRTC)將 NAT 穿透與其他部分緊密集成。但如果你在構(gòu)建自己的協(xié)議,? 建議將 NAT 穿透作為一個(gè)獨(dú)立實(shí)體,與主協(xié)議并行運(yùn)行 ,二者僅 僅是共享 socket 的關(guān)系,如下圖所示,這將帶來(lái)很大幫助:
1.3.2 保底方式:中繼 在某些場(chǎng)景中,直接訪問(wèn) socket 這一條件可能很難滿(mǎn)足。
退而求其次的一個(gè)方式是設(shè)置一個(gè) local proxy(本地代理),主協(xié)議與這個(gè) proxy 通信 ,后者來(lái)完成 NAT 穿透,將包中繼(relay)給對(duì)端。這種方式增加了一個(gè)額外的間接層 ,但好處是:
不需要對(duì)已有的應(yīng)用程序做任何改動(dòng) 。 1.4 挑戰(zhàn):有狀態(tài)防火墻和 NAT 設(shè)備 有了以上鋪墊,下面就從最基本的原則開(kāi)始,一步步看如何實(shí)現(xiàn)一個(gè)企業(yè)級(jí)的 NAT 穿透方案。
我們的 目標(biāo) 是: 在兩個(gè)設(shè)備之間通過(guò) UDP 實(shí)現(xiàn)雙向通信 , 有了這個(gè)基礎(chǔ),上層的其他協(xié)議(WireGuard, QUIC, WebRTC 等)就能做一些更酷的事情。
但即便這個(gè)看似最基本的功能,在實(shí)現(xiàn)上也要解決 兩個(gè)障礙 :
2 穿透防火墻 有狀態(tài)防火墻是以上兩個(gè)問(wèn)題中相對(duì)比較容易解決的。實(shí)際上, 大部分 NAT 設(shè)備都自帶了一個(gè)有狀態(tài)防火墻 , 因此要解決第二個(gè)問(wèn)題,必須先解決有第一個(gè)問(wèn)題。
有狀態(tài)防火墻具體有很多種類(lèi)型,有些你可能見(jiàn)過(guò):
Windows Defender firewall Ubuntu’s ufw (using iptables/nftables) 2.1 有狀態(tài)防火墻 2.1.1 默認(rèn)行為(策略) 以上防火墻的配置都是很靈活的,但大部分配置默認(rèn)都是如下行為:
允許所有出向連接 (allows all “outbound” connections) 禁止所有入向連接 (blocks all “inbound” connections) 可能有少量例外規(guī)則,例如 allowing inbound SSH。
2.1.2 如何區(qū)分入向和出向包 連接(connection)和方向(direction)都是協(xié)議設(shè)計(jì)者頭腦中的概念,到了? 物理傳輸層,每個(gè)連接都是雙向的 ;允許所有的包雙向傳輸。那 防火墻是如何區(qū)分哪些是入向包、哪些是出向包的呢 ?這就要回到**“有狀態(tài)”(stateful)**這三個(gè)字了:有狀態(tài)防火墻會(huì)記錄它 看到的每個(gè)包,當(dāng)收到下一個(gè)包時(shí),會(huì)利用這些信息(狀態(tài))來(lái)判斷應(yīng)該做什么。
對(duì) UDP 來(lái)說(shuō),規(guī)則很簡(jiǎn)單:如果防火墻之前看到過(guò)一個(gè)出向包(outbound),就會(huì)允許 相應(yīng)的入向包(inbound)通過(guò),以下圖為例:
筆記本電腦中自帶了一個(gè)防火墻,當(dāng)該防火墻看到從這臺(tái)機(jī)器出去的? 2.2.2.2:1234 -> 5.5.5.5:5678
?包時(shí),就會(huì)記錄一下: 5.5.5.5:5678 -> 2.2.2.2:1234
?入向包應(yīng)該放行。 這里的邏輯 是:我們信任的世界(即筆記本)想主動(dòng)與? 5.5.5.5:5678
?通信,因此應(yīng)該放行(allow)其回包路徑。
某些 非常 寬松的防火墻只要看到有從? 2.2.2.2:1234
?出去的包,就 會(huì)允許所有從外部進(jìn)入? 2.2.2.2:1234
?的流量。這種防火墻對(duì)我們的 NAT 穿透來(lái)說(shuō)非 常友好,但已經(jīng)越來(lái)越少見(jiàn)了。
2.2 防火墻朝向(face-off)與穿透方案 2.2.1 防火墻朝向相同 場(chǎng)景特點(diǎn):服務(wù)端 IP 可直接訪問(wèn)
在 NAT 穿透場(chǎng)景中,以上默認(rèn)規(guī)則對(duì) UDP 流量的影響不大 —— 只要 路徑上所有防火墻的“朝向”是一樣的 。一般來(lái)說(shuō),從內(nèi)網(wǎng)訪問(wèn)公網(wǎng)上的某個(gè)服務(wù)器都屬于這種情況。
我們唯一的要求是: 連接必須是由防火墻后面的機(jī)器發(fā)起的 。這是因?yàn)?在它主動(dòng)和別人通信之前,沒(méi)人能主動(dòng)和它通信,如下圖所示:
穿透方案:客戶(hù)端直連服務(wù)端,或 hub-and-spoke 拓?fù)?/span>
但上圖是 假設(shè)了 通信雙方中,其中一端**(服務(wù)端)是能直接訪問(wèn)到的**。在 VPN 場(chǎng)景中,這就形成了所謂的? hub-and-spoke 拓?fù)?/strong>:中心的 hub 沒(méi)有任何防火墻策略,誰(shuí)都能訪問(wèn)到;防火墻后面的 spokes 連接到 hub。如下圖所示:
2.2.2 防火墻朝向不同 場(chǎng)景特點(diǎn):服務(wù)端 IP 不可直接訪問(wèn)
但如果兩個(gè)“客戶(hù)端”想直連,以上方式就不行了,此時(shí)兩邊的防火墻相向而立,如下圖所示:
根據(jù)前面的討論,這種情況意味著: 兩邊要同時(shí)發(fā)起連接請(qǐng)求 ,但也意味著 兩邊都無(wú)法發(fā)起有效請(qǐng)求,因?yàn)閷?duì)方先發(fā)起請(qǐng)求才能在它的防火墻上打開(kāi)一條縫讓我們進(jìn)去!如何破解這個(gè)問(wèn)題呢?一種方式是 讓用戶(hù)重新配置一邊或兩邊的防火墻,打開(kāi)一個(gè)端口 , 允許對(duì)方的流量進(jìn)來(lái)。
這顯然對(duì)用戶(hù)不友好,在像 Tailscale 這樣的 mesh 網(wǎng)絡(luò)中的擴(kuò)展性也不好,在 mesh 網(wǎng)絡(luò)中,我們假設(shè)對(duì)端會(huì)以一定的粒度在公網(wǎng)上移動(dòng)。 此外,在很多情況下用戶(hù)也沒(méi)有防火墻的控制權(quán)限:例如在咖啡館或機(jī)場(chǎng)中,連接的路 由器是不受你控制的(否則你可能就有麻煩了)。 因此,我們需要尋找一種不用重新配置防火墻的方式。
穿透方案:兩邊同時(shí)主動(dòng)建連,在本地防火墻為對(duì)方打開(kāi)一個(gè)洞
解決的思路還是先重新審視前面提到的有狀態(tài)防火墻規(guī)則:
對(duì)于 UDP,其規(guī)則(邏輯)是: 包必須先出去才能進(jìn)來(lái) (packets must flow out before packets can flow back in)。 注意,這里除了要滿(mǎn)足包的 IP 和端口要匹配這一條件之外, 并沒(méi)有要求包必須是相關(guān)的 (related)。換句話說(shuō),只要某些包帶著正確的源和目的地址出去了, 任何看起來(lái)像是響應(yīng)的包都會(huì)被防火墻放進(jìn)來(lái) ?—— 即使對(duì)端根本沒(méi)收到你發(fā)出去的包。 因此,要穿透這些有狀態(tài)防火墻,我們只需要 共享一些信息:讓兩端提前知道對(duì)方使用的 ip:port :
手動(dòng)靜態(tài)配置是一種方式,但顯然擴(kuò)展性不好; 我們開(kāi)發(fā)了一個(gè)? coordination server [5] , 以靈活、安全的方式來(lái)同步? ip:port
?信息。 有了對(duì)方的? ip:port
?信息之后,兩端開(kāi)始給對(duì)方發(fā)送 UDP 包。在這個(gè)過(guò)程中,我們預(yù) 料到某些包將會(huì)被丟棄。因此,雙方 必須要接受某些包會(huì)丟失的事實(shí) , 因此如果是重要信息,你必須自己準(zhǔn)備好重傳。對(duì) UDP 來(lái)說(shuō)丟包是可接受的,但這里尤其需要接受。
來(lái)看一下具體建連(穿透)過(guò)程:
如圖所示,筆記本出去的第一包, 2.2.2.2:1234 -> 7.7.7.7:5678
,穿過(guò) Windows Defender 防火墻進(jìn)入到公網(wǎng)。
對(duì)方的防火墻會(huì)將這個(gè)包攔截掉,因?yàn)樗鼪](méi)有? 7.7.7.7:5678 -> 2.2.2.2:1234
?的流量記錄。但另一方面,Windows Defender 此時(shí)已經(jīng)記錄了出向連接,因此會(huì)允許? 7.7.7.7:5678 -> 2.2.2.2:1234
?的應(yīng)答包進(jìn)來(lái)。
接著,第一個(gè)? 7.7.7.7:5678 -> 2.2.2.2:1234
?穿過(guò)它自己的防火墻到達(dá)公網(wǎng)。
到達(dá)客戶(hù)端側(cè)時(shí),Windows Defender? 認(rèn)為這是剛才出向包的應(yīng)答包,因此就放行它進(jìn)入了! ?此外,右側(cè)的防火墻此時(shí)也記錄了: 2.2.2.2:1234 -> 7.7.7.7:5678
?的包應(yīng)該放行。
筆記本收到服務(wù)器發(fā)來(lái)的包之后,發(fā)送一個(gè)包作為應(yīng)答。這個(gè)包穿過(guò) Windows Defender 防火墻 ?和服務(wù)端防火墻(因?yàn)檫@是對(duì)服務(wù)端發(fā)送的包的應(yīng)答包),達(dá)到服務(wù)端。
成功!這樣我們就建立了一個(gè) 穿透兩個(gè)相向防火墻 的雙向通信連接。而初看之下,這項(xiàng)任務(wù)似乎是不可能完成的。
2.3 關(guān)于穿透防火墻的一些思考 穿透防火墻并非永遠(yuǎn)這么輕松,有時(shí)會(huì)受一些第三方系統(tǒng)的間接影響,需要仔細(xì)處理。那穿透防火墻需要注意什么呢?重要的一點(diǎn)是: 通信雙方必須幾乎同時(shí)發(fā)起通信 , 這樣才能在路徑上的防火墻打開(kāi)一條縫,而且兩端還都是活著的。
2.3.1 雙向主動(dòng)建連:旁路信道 如何實(shí)現(xiàn)“同時(shí)”呢?一種方式是兩端不斷重試,但顯然這種方式很浪費(fèi)資源。假如雙方都 知道何時(shí)開(kāi)始建連就好了。
這聽(tīng)上去是 雞生蛋蛋生雞的問(wèn)題 了: 雙方想要通信,必須先提前通個(gè)信 。 但實(shí)際上,我們可以通過(guò) 旁路信道 (side channel)來(lái)達(dá)到這個(gè)目的 ,并且這個(gè)旁路信道并不需要很 fancy:它可以有幾秒鐘的延遲、只需要傳送幾 KB 的 信息,因此即使是一個(gè)配置非常低的虛擬機(jī),也能為幾千臺(tái)機(jī)器提供這樣的旁路通信服務(wù)。 在遙遠(yuǎn)的過(guò)去,我曾用 ?XMPP 聊天消息作為旁路,效果非常不錯(cuò)。 另一個(gè)例子是 WebRTC,它需要你提供一個(gè)自己的“信令信道”(signalling channel, 這個(gè)詞也暗示了 WebRTC 的 IP telephony ancestry),并將其配置到 WebRTC API。 在 Tailscale,我們的協(xié)調(diào)服務(wù)器(coordination server)和 DERP (Detour Encrypted Routing Protocol) 服務(wù)器集群是我們的旁路信道。 2.3.2 非活躍連接被防火墻清理 有狀態(tài)防火墻內(nèi)存通常比較有限,因此會(huì)定期清理不活躍的連接(UDP 常見(jiàn)的是 30s), 因此要保持連接 alive 的話需要定期通信,否則就會(huì)被防火墻關(guān)閉,為避免這個(gè)問(wèn)題, 我們,
要么定期向?qū)Ψ桨l(fā)包來(lái) keepalive, 2.3.3 問(wèn)題都解決了?不,挑戰(zhàn)剛剛開(kāi)始 對(duì)于防火墻穿透來(lái)說(shuō), 我們 并不需要關(guān)心路徑上有幾堵墻 ?—— 只要它們是有狀態(tài)防火墻且允許出 向連接,這種同時(shí)發(fā)包(simultaneous transmission)機(jī)制就能穿透任意多層防火墻。這一點(diǎn)對(duì)我們來(lái)說(shuō)非常友好,因?yàn)橹恍枰獙?shí)現(xiàn)一個(gè)邏輯,然后能適用于任何地方了。
…對(duì)嗎?
其實(shí), 不完全對(duì) 。這個(gè)機(jī)制有效的前提是:我們能 提前知道對(duì)方的 ip:port 。而這就涉及到了我們今天的主題:NAT,它會(huì)使前面我們剛獲得的一點(diǎn)滿(mǎn)足感頓時(shí)消失。
下面, 進(jìn)入本文正題 。
3 NAT 的本質(zhì) 3.1 NAT 設(shè)備與有狀態(tài)防火墻 可以認(rèn)為 NAT 設(shè)備是一個(gè) 增強(qiáng)版的有狀態(tài)防火墻 ,雖然它的增強(qiáng)功能 對(duì)于本文場(chǎng)景來(lái)說(shuō)并不受歡迎:除了前面提到的有狀態(tài)攔截/放行功能之外,它們還會(huì)在數(shù)據(jù)包經(jīng)過(guò)時(shí)修改這些包。
3.2 NAT 穿透與 SNAT/DNAT 具體來(lái)說(shuō),NAT 設(shè)備能完成某種類(lèi)型的網(wǎng)絡(luò)地址轉(zhuǎn)換,例如,替換源或目的 IP 地址或端口。
討論連接問(wèn)題和 NAT 穿透問(wèn)題時(shí) ,我們 只會(huì)受 source NAT —— SNAT 的影響 。 3.3 SNAT 的意義:解決 IPv4 地址短缺問(wèn)題 SNAT 最常見(jiàn)的使用場(chǎng)景是 將很多設(shè)備連接到公網(wǎng),而只使用少數(shù)幾個(gè)公網(wǎng) IP 。例如對(duì)于消費(fèi)級(jí)路由器,會(huì)將所有設(shè)備的(私有) IP 地址映射為 單個(gè) 連接到公網(wǎng)的 IP 地址。
這種方式存在的意義是:我們有遠(yuǎn)多于可用公網(wǎng) IP 數(shù)量的設(shè)備需要連接到公網(wǎng),(至少 對(duì) IPv4 來(lái)說(shuō)如此,IPv6 的情況后面會(huì)討論)。NAT 使多個(gè)設(shè)備能共享同一 IP 地址,因 此即使面臨 IPv4 地址短缺的問(wèn)題,我們?nèi)匀荒懿粩鄶U(kuò)張互聯(lián)網(wǎng)的規(guī)模。
3.4 SNAT 過(guò)程:以家用路由器為例 假設(shè)你的筆記本連接到家里的 WiFi,下面看一下它連接到公網(wǎng)某個(gè)服務(wù)器時(shí)的情形:
筆記本發(fā)送 UDP packet? 192.168.0.20:1234 -> 7.7.7.7:5678
。
這一步就好像筆記本有一個(gè)公網(wǎng) IP 一樣,但源地址? 192.168.0.20
?是私有地址, 只能出現(xiàn)在私有網(wǎng)絡(luò),公網(wǎng)不認(rèn),收到這樣的包時(shí)它不知道如何應(yīng)答。
家用路由器出場(chǎng),執(zhí)行 SNAT。
包經(jīng)過(guò)路由器時(shí),路由器發(fā)現(xiàn)這是一個(gè)它沒(méi)有見(jiàn)過(guò)的新會(huì)話(session)。它知道? 192.168.0.20
?是私有 IP,公網(wǎng)無(wú)法給這樣的地址回包,但它有辦法解決:
在它 自己的公網(wǎng) IP 上挑一個(gè)可用的 UDP 端口 ,例如? 2.2.2.2:4242
, 然后創(chuàng)建一個(gè)? NAT mapping : 192.168.0.20:1234
? <-->
? 2.2.2.2:4242
, 然后將包發(fā)到公網(wǎng),此時(shí)源地址變成了? 2.2.2.2:4242
?而不是原來(lái)的? 192.168.0.20:1234
。因此服務(wù)端看到的是轉(zhuǎn)換之后地址, 接下來(lái),每個(gè)能匹配到這條映射規(guī)則的包,都會(huì)被路由器改寫(xiě) IP 和 端口。 反向路徑是類(lèi)似的,路由器會(huì)執(zhí)行相反的地址轉(zhuǎn)換,將? 2.2.2.2:4242
?變回 ? 192.168.0.20:1234
。對(duì)于筆記本來(lái)說(shuō),它根本感知不知道這正反兩次變換過(guò)程。
這里是拿家用路由器作為例子,但 辦公網(wǎng)的原理是一樣的 。不同之處在 于,辦公網(wǎng)的 NAT 可能有多臺(tái)設(shè)備組成(高可用、容量等目的),而且它們有不止一個(gè)公 網(wǎng) IP 地址可用,因此在選擇可用的公網(wǎng)? ip:port
?來(lái)做映射時(shí),選擇空間更大,能支持 更多客戶(hù)端。
3.5 SNAT 給穿透帶來(lái)的挑戰(zhàn) 現(xiàn)在我們遇到了與前面有狀態(tài)防火墻類(lèi)似的情況,但這次是 NAT 設(shè)備: 通信雙方 不知道對(duì)方的 ip:port 是什么 ,因此 無(wú)法主動(dòng)建連 ,如下圖所示:
但這次比有狀態(tài)防火墻更糟糕,嚴(yán)格來(lái)說(shuō), 在雙方發(fā)包之前,根本無(wú)法確定(自己及對(duì)方的)ip:port 信息 ,因?yàn)? 只有出向包經(jīng)過(guò)路由器之后才會(huì)產(chǎn)生 NAT mapping (即,可以被對(duì)方連接的? ip:port
?信息)。
因此我們又回到了與防火墻遇到的問(wèn)題,并且情況更糟糕: 雙方都需要主動(dòng)和對(duì) 方建連,但又不知道對(duì)方的公網(wǎng)地址是多少 ,只有當(dāng)對(duì)方先說(shuō)話之后,我們才能拿到它的地址信息。
如何破解以上死鎖呢?這就輪到? STUN [6] ?登場(chǎng)了。
4 穿透 “NAT+防火墻”:STUN (Session Traversal Utilities for NAT) 協(xié)議 STUN [7] ? 既是一些對(duì) NAT 設(shè)備行為的詳細(xì)研究,也是一種協(xié)助 NAT 穿透的協(xié)議。本文主要關(guān)注 STUN 協(xié)議。
4.1 STUN 原理 STUN 基于一個(gè)簡(jiǎn)單的觀察 :從一個(gè)會(huì)被 NAT 的客戶(hù)端訪問(wèn)公網(wǎng)服務(wù)器時(shí), 服務(wù)器看到的是? NAT 設(shè)備的公網(wǎng) ip:port 地址 ,而非該? 客戶(hù)端的局域網(wǎng) ip:port 地址 。
也就是說(shuō),服務(wù)器能告訴客戶(hù)端 它看到的客戶(hù)端的 ip:port 是什么 。因此,只要將這個(gè)信息以某種方式告訴通信對(duì)端(peer),后者就知道該和哪個(gè)地址建連了!這樣就又 簡(jiǎn)化為前面的防火墻穿透問(wèn)題了 。
本質(zhì)上這就是? STUN 協(xié)議的工作原理 ,如下圖所示:
筆記本向 STUN 服務(wù)器發(fā)送一個(gè)請(qǐng)求:“從你的角度看,我的地址什么?” STUN 服務(wù)器返回一個(gè)響應(yīng):“我看到你的 UDP 包是從這個(gè)地址來(lái)的: ip:port
”。
4.2 為什么 NAT 穿透邏輯和主協(xié)議要共享同一個(gè) socket 理解了 STUN 原理,也就能理解為什么我們?cè)谖恼麻_(kāi)頭說(shuō),如果? 要實(shí)現(xiàn)自己的 NAT 穿透邏輯和主協(xié)議,就必須讓二者共享同一個(gè) socket :
每個(gè) socket 在 NAT 設(shè)備上都對(duì)應(yīng)一個(gè)映射關(guān)系(私網(wǎng)地址 -> 公網(wǎng)地址), STUN 服務(wù)器只是 輔助 穿透的基礎(chǔ)設(shè)施, 與 STUN 服務(wù)器通信之后,在 NAT 及防火墻設(shè)備上打開(kāi)了一個(gè)連接,允許入向包進(jìn)來(lái)(回憶前面內(nèi)容, ? 只要目的地址對(duì),UDP 包就能進(jìn)來(lái) ,不管這些包是不是從 STUN 服務(wù)器來(lái)的), 因此,接下來(lái)只要將這個(gè)地址告訴我們的通信對(duì)端(peer),讓它往這個(gè)地址發(fā)包,就能實(shí)現(xiàn)穿透了。 4.3 STUN 的問(wèn)題:不能穿透所有 NAT 設(shè)備(例如企業(yè)級(jí) NAT 網(wǎng)關(guān)) 有了 STUN,我們的 穿透目的似乎已經(jīng)實(shí)現(xiàn)了 :每臺(tái)機(jī)器都通過(guò) STUN 來(lái)獲取自己的私網(wǎng) socket 對(duì)應(yīng)的公網(wǎng)? ip:port
,然后把這個(gè)信息告訴對(duì)端,然后兩端 同時(shí)發(fā)起穿透防火墻的嘗試,后面的過(guò)程就和上一節(jié)介紹的防火墻穿透一樣了, 對(duì)嗎 ?
答案是: 看情況 。某些情況下確實(shí)如此,但有些情況下卻不行。通常來(lái)說(shuō),
對(duì)于大部分 家用路由器場(chǎng)景 ,這種方式是沒(méi)問(wèn)題的; 但對(duì)于一些 企業(yè)級(jí) NAT 網(wǎng)關(guān) 來(lái)說(shuō),這種方式無(wú)法奏效。 NAT 設(shè)備的說(shuō)明書(shū)上越強(qiáng)調(diào)它的安全性,STUN 方式失敗的可能性就越高。(但注意,從實(shí)際意義上來(lái)說(shuō),? NAT 設(shè)備在任何方面都并不會(huì)增強(qiáng)網(wǎng)絡(luò)的安全性 ,但這不是本文重點(diǎn),因此不展開(kāi)。)
4.4 重新審視 STUN 的前提 再次審視前面 關(guān)于 STUN 的假設(shè) :當(dāng) STUN 服務(wù)器告訴客戶(hù)端在公網(wǎng)看來(lái)它的地址是? 2.2.2.2:4242
?時(shí),那所有目的地址是? 2.2.2.2:4242
?的包就都能穿透防火墻到達(dá)該客戶(hù)端。
這也正是問(wèn)題所在: 這一點(diǎn)并不總是成立 。
某些 NAT 設(shè)備的行為與我們假設(shè)的一致,它們的有狀態(tài)防火墻組件只要看到有客戶(hù)端自己 發(fā)起的出向包,就會(huì)允許相應(yīng)的入向包進(jìn)入;因此只要利用 STUN 功能,再加上兩端同時(shí) 發(fā)起防火墻穿透,就能把連接打通;
in theory, there are also NAT devices that are super relaxed, and don’t ship with stateful firewall stuff at all. In those, you don’t even need simultaneous transmission, the STUN request gives you an internet? ip:port
?that anyone can connect to with no further ceremony. If such devices do still exist, they’re increasingly rare.
另外一些 NAT 設(shè)備就要困難很多了,它會(huì) 針對(duì)每個(gè)目的地址來(lái)生成一條相應(yīng)的映射關(guān)系 。在這樣的設(shè)備上,如果我們用相同的 socket 來(lái)分別發(fā)送數(shù)據(jù)包到? 5.5.5.5:1234
?and? 7.7.7.7:2345
,我們就會(huì)得到? 2.2.2.2
?上的兩個(gè)不同的端口,每個(gè)目的地址對(duì)應(yīng)一個(gè)。如果反向包的端口用的不對(duì),包就無(wú)法通過(guò)防火墻。如下圖所示:
5 中場(chǎng)補(bǔ)課:NAT 正式術(shù)語(yǔ) 知道 NAT 設(shè)備的行為并不是完全一樣之后,我們來(lái)引入一些正式術(shù)語(yǔ)。
5.1 早期術(shù)語(yǔ) 如果之前接觸過(guò) NAT 穿透,可能會(huì)聽(tīng)說(shuō)過(guò)下面這些名詞:
這些都是 NAT 穿透領(lǐng)域的早期術(shù)語(yǔ)。
但其實(shí)這些術(shù)語(yǔ) 相當(dāng)讓人困惑 。我每次都要 查一下 Restricted Cone NAT 是什么意思。從實(shí)際經(jīng)驗(yàn)來(lái)看,我并不是唯一對(duì)此感到困惑的人。例如,如今互聯(lián)網(wǎng)上將 “easy” NAT 歸類(lèi)為 Full Cone,而實(shí)際上它們更應(yīng)該歸類(lèi)為 Port-Restricted Cone。
5.2 近期研究與新術(shù)語(yǔ) 最近的一些研究和 RFC 已經(jīng)提出了一些更準(zhǔn)確的術(shù)語(yǔ)。
首先,它們明確了如下事實(shí): NAT 設(shè)備的行為差異表現(xiàn)在多個(gè)維度 , 而并非只有早期研究中所說(shuō)的 “cone” 這一個(gè)維度,因此 基于 “cone” 來(lái)劃分類(lèi)別并不是很有幫助 。 其次,新研究和新術(shù)語(yǔ)能 更準(zhǔn)確地描述 NAT 在做什么 。 前面提到的所謂? "easy" 和 "hard" NAT,只在一個(gè)維度有不同 :NAT 映射是否考慮到目的地址信息。 RFC 4787 [8] ?中,
將? easy NAT 及其變種 稱(chēng)為 “Endpoint-Independent Mapping” ( EIM,終點(diǎn)無(wú)關(guān)的映射 )
但是,從**“命名很難”**這一程序員界的偉大傳統(tǒng)來(lái)說(shuō),EIM 這個(gè)詞其實(shí) ?也并不是 100% 準(zhǔn)確,因?yàn)檫@種 NAT 仍然依賴(lài) endpoint,只不過(guò)依賴(lài)的是源 endpoint:每個(gè) source ? ip:port
?對(duì)應(yīng)一個(gè)映射 —— 否則你的包就會(huì)和別人的包混在一起,導(dǎo)致混亂。
嚴(yán)格來(lái)說(shuō),EIM 應(yīng)該稱(chēng)為 “Destination Endpoint Independent Mapping” (DEIM?), ?但這個(gè)名字太拗口了,而且按照慣例,Endpoint 永遠(yuǎn)指的是 Destination Endpoint。
將? hard NAT 以及變種 稱(chēng)為 “Endpoint-Dependent Mapping”( EDM,終點(diǎn)相關(guān)的映射 ) 。
EDM 中還有一個(gè)子類(lèi)型,依據(jù)是只根據(jù) dst_ip 做映射,還是根據(jù) dst_ip + dst_port 做映射。對(duì)于 NAT 穿透來(lái)說(shuō),這種區(qū)分對(duì)來(lái)說(shuō)是一樣的:它們 都會(huì)導(dǎo)致 STUN 方式不可用 。
5.3 老的 cone 類(lèi)型劃分 你可能會(huì)有疑問(wèn):根據(jù)是否依賴(lài) endpoint 這一條件,只能組合出兩種可能,那為什么傳 統(tǒng)分類(lèi)中會(huì)有四種 cone 類(lèi)型呢?答案是? cone 包含了兩個(gè)正交維度的 NAT 行為 :
NAT 映射行為 :前面已經(jīng)介紹過(guò)了, 有狀態(tài)防火墻行為 :與前者類(lèi)似,也是分為與 endpoint 相關(guān)還是無(wú)關(guān)兩種類(lèi)型。 因此最終組合如下:
NAT Cone Types
Endpoint 無(wú)關(guān) NAT mapping Endpoint 相關(guān) NAT mapping (all types) Endpoint 無(wú)關(guān)防火墻 Endpoint 相關(guān)防火墻 (dst. IP only) Endpoint 相關(guān)防火墻 (dst. IP+port)
分解到這種程度之后就可以看出, cone 類(lèi)型對(duì) NAT 穿透場(chǎng)景來(lái)說(shuō)并沒(méi)有什么意義 。我們關(guān)心的只有一點(diǎn):是否是 Symmetric —— 換句話說(shuō),一個(gè) NAT 設(shè)備是 EIM 還是 EDM 類(lèi)型的。
5.4 針對(duì) NAT 穿透場(chǎng)景:簡(jiǎn)化 NAT 分類(lèi) 以上討論可知,雖然理解防火墻的具體行為很重要,但對(duì)于編寫(xiě) NAT 穿透代碼來(lái)說(shuō),這一點(diǎn)并不重要。我們的 兩端同時(shí)發(fā)包 方式(simultaneous transmission trick)能? 有效穿透以上三種類(lèi)型的防火墻 。在真實(shí)場(chǎng)景中, 我們主要在處理的是 IP-and-port endpoint-dependent 防火墻。
因此,對(duì)于實(shí)際 NAT 穿透實(shí)現(xiàn),我們可以將以上分類(lèi)簡(jiǎn)化成:
Endpoint-Independent NAT mapping Endpoint-Dependent NAT mapping (dst. IP only) Firewall is yes
5.5 更多 NAT 規(guī)范(RFC) 想了解更多新的 NAT 術(shù)語(yǔ),可參考
RFC? 4787 [9] ?(NAT Behavioral Requirements for UDP) 如果自己實(shí)現(xiàn) NAT,那應(yīng)該(should)遵循這些 RFC 的規(guī)范,這樣才能使你的 NAT 行為符合業(yè)界慣例,與其他廠商的設(shè)備或軟件良好兼容。
6 穿透 NAT+防火墻:STUN 不可用時(shí),fallback 到中繼模式 6.1 問(wèn)題回顧與保底方式(中繼) 補(bǔ)完基礎(chǔ)知識(shí)(尤其是定義了什么是 hard NAT)之后,回到我們的 NAT 穿透主題。
第 1~4 節(jié)已經(jīng)解決了 STUN 和防火墻穿透的問(wèn)題, 但? hard NAT 對(duì)我們來(lái)說(shuō)是個(gè)大問(wèn)題 ,只要路徑上出現(xiàn)一個(gè)這種設(shè)備,前面的方案就行不通了。 準(zhǔn)備放棄了嗎?這才 進(jìn)入 NAT 真正有挑戰(zhàn)的部分 :如果已經(jīng)試過(guò)了前面介紹的所有方式 仍然不能穿透,我們?cè)撛趺崔k呢?
實(shí)際上,確實(shí)有很多 NAT 實(shí)現(xiàn)在這種情況下都會(huì)選擇放棄,向用戶(hù)報(bào)一個(gè)**“無(wú)法連接”**之類(lèi)的錯(cuò)誤。 但對(duì)我們來(lái)說(shuō),這么快就放棄顯然是不可接受的 —— 解決不了連通性問(wèn)題,Tailscale 就沒(méi)有存在的意義。 我們的保底解決方式是:創(chuàng)建一個(gè) 中繼連接 (relay)實(shí)現(xiàn)雙方的無(wú)障礙地通信。但是,中繼方式性能不是很差嗎?這要看具體情況:
但如果無(wú)法直連,而中繼路徑又非常接近雙方直連的真實(shí)路徑,并且?guī)捵銐虼螅侵?繼方式并不會(huì)明顯降低通信質(zhì)量。延遲肯定會(huì)增加一點(diǎn),帶寬會(huì)占用一些,但? 相比完全連接不上,還是更能讓用戶(hù)接受的 。 不過(guò)要注意:我們只有在無(wú)法直連時(shí)才會(huì)選擇中繼方式。實(shí)際場(chǎng)景中:
對(duì)于大部分網(wǎng)絡(luò),我們都能通過(guò)前面介紹的方式實(shí)現(xiàn)直連,
剩下的長(zhǎng)尾用中繼方式來(lái)解決,并不算一個(gè)很糟的方式。
此外,某些網(wǎng)絡(luò)會(huì)阻止 NAT 穿透,其影響比這種 hard NAT 大多了。例如,我們觀察到 UC Berkeley guest WiFi 禁止除 DNS 流量之外的所有 outbound UDP 流量。不管用什么 NAT 黑科技,都無(wú)法繞過(guò)這個(gè)攔截。因此我們終歸還是需要一些可靠的 fallback 機(jī)制。
6.2 中繼協(xié)議:TURN、DERP 有多種中繼實(shí)現(xiàn)方式。
TURN ?(Traversal Using Relays around NAT):經(jīng)典方式,核心理念是
Tailscale 并不使用 TURN。這種協(xié)議 用起來(lái)并不是很好 ,而且與 STUN 不同, 它沒(méi)有真正的交互性,因?yàn)榛ヂ?lián)網(wǎng)上并沒(méi)有公開(kāi)的 TURN 服務(wù)器。
用戶(hù) (人)先去公網(wǎng)上的 TURN 服務(wù)器認(rèn)證,成功后后者會(huì)告訴你:“我已經(jīng)為你分配了 ip:port,接下來(lái)將為你中繼流量”,
然后將這個(gè) ip:port 地址告訴對(duì)方,讓它去連接這個(gè)地址,接下去就是非常簡(jiǎn)單的客戶(hù)端/服務(wù)器通信模型了。
DERP (Detoured Encrypted Routing Protocol)
這是我們創(chuàng)建的一個(gè)協(xié)議, DERP [12] ,
前面也簡(jiǎn)單提到過(guò),DERP 既是我們?cè)?NAT 穿透失敗時(shí)的保底通信方式(此時(shí)的角色 與 TURN 類(lèi)似),也是在其他一些場(chǎng)景下幫助我們完成 NAT 穿透的旁路信道。換句話說(shuō),它既是我們的保底方式,也是有更好的穿透鏈路時(shí),幫助我們進(jìn)行連接升 級(jí)(upgrade to a peer-to-peer connection)的基礎(chǔ)設(shè)施。
它是一個(gè) 通用目的包中繼協(xié)議,運(yùn)行在 HTTP 之上 ,而大部分網(wǎng)絡(luò)都是允許 HTTP 通信的。
它根據(jù)目的公鑰(destination’s public key)來(lái)中繼加密的流量(encrypted payloads)。
6.3 小結(jié) 有了“中繼”這種保底方式之后,我們穿透的成功率大大增加了。如果此時(shí)不再閱讀本文接下來(lái)的內(nèi)容,而是把上面介紹的穿透方式都實(shí)現(xiàn)了,我預(yù)計(jì):
這已經(jīng)算是一個(gè)“足夠好”的穿透實(shí)現(xiàn)了。
7 穿透 NAT+防火墻:企業(yè)級(jí)改進(jìn) 如果你并不滿(mǎn)足于“足夠好”,那我們可以做的事情還有很多!
本節(jié)將介紹一些五花八門(mén)的 tricks,在某些特殊場(chǎng)景下會(huì)幫到我們。單獨(dú)使用這項(xiàng)技術(shù)都 無(wú)法解決 NAT 穿透問(wèn)題,但將它們巧妙地組合起來(lái),我們能更加接近 100% 的穿透成功率。
7.1 穿透 hard NAT:暴力端口掃描 回憶 hard NAT 中遇到的問(wèn)題,如下圖所示,關(guān)鍵問(wèn)題是:easy NAT 不知道該往 hard NAT 方的哪個(gè)? ip:port
?發(fā)包。
但 必須 要往正確的? ip:port
?發(fā)包,才能穿透防火墻,實(shí)現(xiàn)雙向互通。怎么辦呢?
7.2 基于生日悖論改進(jìn)暴力掃描:hard side 多開(kāi)端口 + easy side 隨機(jī)探測(cè) 利用? birthday paradox [14] ?算法, 我們能對(duì)端口掃描進(jìn)行改進(jìn)。
這里省去算法的數(shù)學(xué)模型,如果你對(duì)實(shí)現(xiàn)干興趣,可以看看我寫(xiě)的 ? python calculator [15] 。計(jì)算過(guò)程是“經(jīng)典”生日悖論的一個(gè)小變種。下面是隨著 easy side random probe 次數(shù)(假設(shè) hard size 256 個(gè)端口)的變化,兩邊打開(kāi)的端口有重合(即通信成功)的概率:
根據(jù)以上結(jié)果,如果還是假設(shè) 100 ports/s 這樣相當(dāng)溫和的探測(cè)速率,那? 2 秒鐘就有約 50% 的成功概率 。即使非常不走運(yùn),我們?nèi)匀荒茉? 20s 時(shí)幾乎 100% 穿透成功 ,而此時(shí) 只探測(cè)了總端口空間的 4% 。
非常好!雖然這種 hard NAT 給我們帶來(lái)了嚴(yán)重的穿透延遲,但最終結(jié)果仍然是成功的。那么,如果是兩個(gè) hard NAT,我們還能處理嗎?
7.3 雙 hard NAT 場(chǎng)景
這種情況下仍然可以用前面的? 多端口+隨機(jī)探測(cè) ?方式,但成功概率要低很多了:
這里我們也不就具體計(jì)算展開(kāi),只告訴結(jié)果:仍然 假設(shè)目的端打開(kāi) 256 個(gè)端口,從源端發(fā)起 2048 次(20 秒) , 成功的概率是: 0.01% 。
如果你之前學(xué)過(guò)生日悖論,就并不會(huì)對(duì)這個(gè)結(jié)果感到驚訝。理論上來(lái)說(shuō),
對(duì)于某些應(yīng)用來(lái)說(shuō),28 分鐘可能仍然是一個(gè)可接受的時(shí)間 。用半個(gè)小時(shí)暴力穿透 NAT 之后, 這個(gè)連接就可以一直用著 —— 除非 NAT 設(shè)備重啟,那樣就需要再次花半個(gè)小時(shí)穿透建個(gè)新連接。但對(duì)于 交互式應(yīng)用來(lái)說(shuō),這樣顯然是不可接受的。
更糟糕的是,如果去看常見(jiàn)的辦公網(wǎng)路由器,你會(huì)震驚于它的 active session low limit 有多么低。例如,一臺(tái) Juniper SRX 300? 最多支持 64,000 active sessions 。也就是說(shuō),
至此,我們通過(guò)這種方式穿透了比之前更難一些的網(wǎng)絡(luò)拓?fù)洹_@是一個(gè)很大的成就,因?yàn)? 家用路由器一般都是 easy NAT,hard NAT 一般都是辦公網(wǎng)路由器或云 NAT 網(wǎng)關(guān) 。這意味著這種方式能幫我們解決
的場(chǎng)景,以及一部分
場(chǎng)景。
7.4 控制端口映射(port mapping)過(guò)程:UPnP/NAT-PMP/PCP 協(xié)議 如果我們能 讓 NAT 設(shè)備的行為簡(jiǎn)單點(diǎn) ,不要把事情搞這么復(fù)雜,那建 立連接(穿透)就會(huì)簡(jiǎn)單很多。真有這樣的好事嗎?還真有,有專(zhuān)門(mén)的一種協(xié)議叫? 端口映射協(xié)議 (port mapping protocols)。通過(guò)這種協(xié)議禁用掉前面 遇到的那些亂七八糟的東西之后,我們將得到一個(gè)非常簡(jiǎn)單的“請(qǐng)求-響應(yīng)”。
下面是三個(gè)具體的端口映射協(xié)議:
因此要更好地實(shí)現(xiàn)穿透,可以
但我們 不能假設(shè)這個(gè)協(xié)議一定可用 :
最后,終歸來(lái)說(shuō), 只要這種協(xié)議可用,就能有效地減少一次 NAT ,大大方便建連過(guò)程。但接下來(lái)看一些不常見(jiàn)的場(chǎng)景。
7.5 多 NAT 協(xié)商(Negotiating numerous NATs) 目前為止,我們看到的客戶(hù)端和服務(wù)端都各只有一個(gè) NAT 設(shè)備。如果有多個(gè) NAT 設(shè)備會(huì) 怎么樣?例如下面這種拓?fù)洌?/span>
“你好,請(qǐng)將我的? lan-ip:port
?轉(zhuǎn)發(fā)到公網(wǎng)(WAN)”,
“好的,我已經(jīng)為你分配了一個(gè)公網(wǎng)映射? wan-ip:port
?”。
如果我們想創(chuàng)建 一個(gè) 成功的穿透連接, 就會(huì)把它的整張 session 表打爆 ?(因?yàn)槲覀円┝μ綔y(cè) 65535 個(gè)端口,每次探測(cè)都是一條新連接記錄)!這顯然要求這臺(tái)路由器能 從容優(yōu)雅地處理過(guò)載的情況 。
這只是創(chuàng)建一條連接帶來(lái)的影響!如果 20 臺(tái)機(jī)器同時(shí)對(duì)這臺(tái)路由器發(fā)起穿透呢? 絕對(duì)的災(zāi)難!
要達(dá)到? 99.9% 的成功率 ,我們需要兩邊各進(jìn)行 170,000 次 探測(cè) —— 如果還是以 100 packets/sec 的速度,就需要? 28 分鐘 。
要達(dá)到? 50% 的成功率 ,“只”需要 54,000 packets,也就是? 9 分鐘 。
如果不使用生日悖論方式,而且 暴力窮舉,需要 1.2 年時(shí)間 !
每次通過(guò)一臺(tái) hard NAT 去探測(cè)對(duì)方的端口(目的端口)時(shí),我們 自己同時(shí)也生成了一個(gè)隨機(jī)源端口 ,
這意味著我們的搜索空間變成了二維? {src port, dst port}
?對(duì),而不再是之前的一維 dst port 空間。
上一節(jié)的基本前提是:hard side 只打開(kāi)一個(gè)端口,然后 easy side 暴力掃描 65535 個(gè)端口來(lái)尋找這個(gè)端口;
這里的改進(jìn)是:在 hard size 開(kāi)多個(gè)端口,例如 256 個(gè)(即同時(shí)打開(kāi) 256 個(gè) socket,目的地址都是 easy side 的? ip:port
), 然后 easy side 隨機(jī)探測(cè)這邊的端口。
90% 的情況下,你都能實(shí)現(xiàn)直連穿透;
剩下的 10% 里,用中繼方式能穿透 一些 (some);
本地 NAT 設(shè)備可能不支持這個(gè)協(xié)議;
設(shè)備支持但默認(rèn)禁用了,或者沒(méi)人知道還有這么個(gè)功能,因此從來(lái)沒(méi)開(kāi)過(guò);
安全策略要求關(guān)閉這個(gè)特性。
這一點(diǎn)非常常見(jiàn),因?yàn)?UPnP 協(xié)議曾曝出一些高危漏洞(后面都修復(fù)了,因此如果是較新的設(shè)備,可以安全地使用 UPnP —— 如果實(shí)現(xiàn)沒(méi)問(wèn)題)。不幸的是,某些設(shè)備的配置中,UPnP, NAT-PMP,PCP 是放在一個(gè)開(kāi)關(guān)里的(可能 統(tǒng)稱(chēng)為 “UPnP” 功能),一開(kāi)全開(kāi),一關(guān)全關(guān)。因此如果有人擔(dān)心 UPnP 的安全性,他連另 外兩個(gè)也用不了。
先判斷本地的默認(rèn)網(wǎng)關(guān)上是否啟用了 UPnP IGD, NAT-PMP and PCP ,
如果探測(cè)發(fā)現(xiàn)其中任何一種協(xié)議有響應(yīng),我們就 申請(qǐng)一個(gè)公網(wǎng)端口映射 ,
可以將這理解為一個(gè) 加強(qiáng)版 STUN :我們不僅能發(fā)現(xiàn)自己的公網(wǎng)? ip:port
,而且能指示我們的 NAT 設(shè)備對(duì)我們的通信對(duì)端友好一些 —— 但并不是為這個(gè)端口修改或添加防火墻規(guī)則。
接下來(lái),任何到達(dá)我們 NAT 設(shè)備的、地址是我們申請(qǐng)的端口的包,都會(huì)被設(shè)備轉(zhuǎn)發(fā)到我們。
UPnP IGD [16] ?(Universal Plug’n’Play Internet Gateway Device)
最老的端口控制協(xié)議, 誕生于 1990s 晚期,因此使用了很多上世紀(jì) 90 年代的技術(shù) (XML、SOAP、 multicast HTTP over UDP —— 對(duì),HTTP over UDP ?),而且很難準(zhǔn)確和安全地實(shí)現(xiàn)這個(gè)協(xié)議。但以前很多路由器都內(nèi)置了 UPnP 協(xié)議, 現(xiàn)在仍然很多。
請(qǐng)求和響應(yīng):
NAT-PMP
UPnP IGD 出來(lái)幾年之后,Apple 推出了一個(gè)功能類(lèi)似的協(xié)議,名為? NAT-PMP [17] ?(NAT Port Mapping Protocol)。
但與 UPnP 不同,這個(gè)協(xié)議 只 做端口轉(zhuǎn)發(fā),不管是在客戶(hù)端還是服務(wù)端,實(shí)現(xiàn)起來(lái)都非常簡(jiǎn)單。
PCP
稍后一點(diǎn),又出現(xiàn)了 NAT-PMP v2 版,并起了個(gè)新名字 PCP [18] ?(Port Control Protocol)。
首先,我們能知道 hard NAT 的 一些 ? ip:port
,因?yàn)槲覀冇?STUN 服務(wù)器。
這里先假設(shè)我們獲得的這些 IP 地址都是正確的(這一點(diǎn)并不總是成立,但這里先這么假 設(shè)。而實(shí)際上,大部分情況下這一點(diǎn)都是成立的,如果對(duì)此有興趣,可以參考 REQ-2 in? RFC 4787 [13] )。
IP 地址確定了,剩下的就是端口了。總共有 65535 中可能,我們能 遍歷這個(gè)端口范圍 嗎?
如果發(fā)包速度是 100 packets/s,那最壞情況下,需要? 10 分鐘 來(lái)找到正確的端口。還是那句話,這雖然不是最優(yōu)的,但總比連不上好。
這很像是端口掃描(事實(shí)上,確實(shí)是),實(shí)際中可能會(huì)觸發(fā)對(duì)方的網(wǎng)絡(luò)入侵檢測(cè)軟件。
這個(gè)例子比較簡(jiǎn)單,不會(huì)給穿透帶來(lái)太大問(wèn)題。包從客戶(hù)端 A? 經(jīng)過(guò)多次 NAT ?到達(dá)公網(wǎng)的過(guò)程,與前面分析的 穿過(guò)多層有狀態(tài)防火墻 是一樣的:
額外的這層(NAT 設(shè)備) 對(duì)客戶(hù)端和服務(wù)端來(lái)說(shuō)都不可見(jiàn) ,我們的穿 透技術(shù)也不關(guān)心中間到底經(jīng)過(guò)了多少層設(shè)備。 真正有影響的其實(shí)只是最后一層設(shè)備 ,因?yàn)閷?duì)端需要在這一層設(shè)備上 找到入口讓包進(jìn)來(lái)。 具體來(lái)說(shuō),真正有影響的是端口轉(zhuǎn)發(fā)協(xié)議。
客戶(hù)端使用這種協(xié)議分配端口時(shí),為我們分配端口的是最靠近客戶(hù)端的這層 NAT 設(shè)備;
而我們期望的是讓最離客戶(hù)端最遠(yuǎn)的那層 NAT 來(lái)分配,否則我們得到的就是一個(gè)網(wǎng)絡(luò)中間層分配的? ip:port
,對(duì)端是用不了的;
不幸的是, 這幾種協(xié)議都不能遞歸地 告訴我們下一層 NAT 設(shè)備是多少 —— ?雖然可以用 traceroute 之類(lèi)的工具來(lái)探測(cè)網(wǎng)絡(luò)路徑,再加上 ?猜路上的設(shè)備是不是 NAT 設(shè)備(嘗試發(fā)送 NAT 請(qǐng)求) —— 但這個(gè)就看運(yùn)氣了。
這就是為什么互聯(lián)網(wǎng)上充斥著大量的文章說(shuō)? double-NAT 有多糟糕 ,以 及警告用戶(hù)為保持后向兼容不要使用 double-NAT。但實(shí)際上,double-NAT? 對(duì)于絕大部分 互聯(lián)網(wǎng)應(yīng)用來(lái)說(shuō)都是不可見(jiàn)的(透明的) ,因?yàn)榇蟛糠謶?yīng)用并不需要主動(dòng)地做這種 NAT 穿 透。
但我也絕不是在建議你在自己的網(wǎng)絡(luò)中設(shè)置 double-NAT。
破壞了端口映射協(xié)議之后,某些視頻游戲的多人(multiplayer)模式就會(huì)無(wú)法使用,
也可能會(huì)使你的 IPv6 網(wǎng)絡(luò)無(wú)法派上用場(chǎng),后者是不用 NAT 就能雙向直連的一個(gè)好方案。
但如果 double-NAT 并不是你能控制的,那除了不能用到這種端口映射協(xié)議之外,其他大部分東西都是不受影響的。
double-NAT 的故事到這里就結(jié)束了嗎?—— 并沒(méi)有,而且更大型的 double-NAT 場(chǎng)景將展現(xiàn)在我們面前。
7.6 運(yùn)營(yíng)商級(jí) NAT 帶來(lái)的問(wèn)題 即使用 NAT 來(lái)解決 IPv4 地址不夠的問(wèn)題,地址仍然是不夠用的,ISP(互聯(lián)網(wǎng)服務(wù)提供商) 顯然 無(wú)法為每個(gè)家庭都分配一個(gè)公網(wǎng) IP 地址。那怎么解決這個(gè)問(wèn)題呢?ISP 的做法是 不夠了就再嵌套一層 NAT :
家用路由器將你的客戶(hù)端 SNAT 到一個(gè) “intermediate” IP 然后發(fā)送到運(yùn)營(yíng)商網(wǎng)絡(luò),
ISP’s network 中的 NAT 設(shè)備再將這些 intermediate IPs 映射到少量的公網(wǎng) IP。
后面這種 NAT 就稱(chēng)為“運(yùn)營(yíng)商級(jí) NAT”( carrier-grade NAT ,或稱(chēng)電信級(jí) NAT),縮寫(xiě) CGNAT。如下圖所示:
CGNAT 對(duì) NAT 穿透來(lái)說(shuō)是一個(gè)大麻煩。
在此之前,辦公網(wǎng)用戶(hù)要快速實(shí)現(xiàn) NAT 穿透,只需在他們的路由器上手動(dòng)設(shè)置端口映射就行了。
但有了 CGNAT 之后就不管用了,因?yàn)槟銦o(wú)法控制運(yùn)營(yíng)商的 CGNAT!
好消息是:這其實(shí)是 double-NAT 的一個(gè)小變種,因此前面介紹的解決方式大部分還仍然是適用的。某些東西可能會(huì)無(wú)法按預(yù)期工作,但只要肯給 ISP 交錢(qián),這些也都能解決。除了 port mapping protocols,其他我們已經(jīng)介紹的所有東西在 CGNAT 里都是適用的。
新挑戰(zhàn):同一 CGNAT 側(cè)直連,STUN 不可用 但我們確實(shí)遇到了一個(gè)新挑戰(zhàn):如何直連兩個(gè)在同一 CGNAT 但不同家用路由器中的對(duì)端呢?如下圖所示:
在這種情況下,STUN 就無(wú)法正常工作了 :STUN 看到的是客戶(hù)端在公網(wǎng)(CGNAT 后面)看到的地址, 而我們想獲得的是在 “middle network” 中的? ip:port
,這才是對(duì)端真正需要的地址,
解決方案:如果端口映射協(xié)議能用:一端做端口映射 怎么辦呢?
如果你想到了端口映射協(xié)議,那恭喜,答對(duì)了! 如果 peer 中任何一個(gè) NAT 支持端口映射協(xié)議 , 對(duì)我們就能實(shí)現(xiàn)穿透,因?yàn)樗峙涞? ip:port
?正是對(duì)端所需要的信息。
這里諷刺的是:double-NAT(指 CGNAT)破壞了端口映射協(xié)議,但在這里又救了我們!當(dāng)然,我們假設(shè)這些協(xié)議一定可用,因?yàn)?CGNAT ISP 傾向于在它們的家用路由器側(cè)關(guān)閉 這些功能,已避免軟件得到“錯(cuò)誤的”結(jié)果,產(chǎn)生混淆。
解決方案:如果端口映射協(xié)議不能用:NAT hairpin 模式 如果不走運(yùn),NAT 上沒(méi)有端口映射功能怎么辦?
讓我們回到基于 STUN 的技術(shù),看會(huì)發(fā)生什么。兩端在 CGNAT 的同一側(cè),假設(shè) STUN 告訴我們 A 的地址是? 2.2.2.2:1234
,B 的地址是? 2.2.2.2:5678
。
那么接下來(lái)的問(wèn)題是:如果 A 向? 2.2.2.2:5678
?發(fā)包會(huì)怎么樣?期望的 CGNAT 行為是:
執(zhí)行 A 的 NAT 映射規(guī)則,即對(duì)? 2.2.2.2:1234 -> 2.2.2.2:5678
?進(jìn)行 SNAT。
注意到目的地址? 2.2.2.2:5678
?匹配到的是 B 的入向 NAT 映射,因此接著對(duì)這個(gè)包執(zhí)行 DNAT,將目的 IP 改成 B 的私有地址。
通過(guò) ?CGNAT 的 internal 接口(而不是 public 接口,對(duì)應(yīng)公網(wǎng))將包發(fā)給 B。
這種 NAT 行為有個(gè)專(zhuān)門(mén)的術(shù)語(yǔ),叫? hairpinning (直譯為發(fā)卡,意思 是像發(fā)卡一樣,沿著一邊上去,然后從另一邊繞回來(lái)),
大家應(yīng)該猜到的一個(gè)事實(shí)是: 不是所以 NAT 都支持 hairpin 模式 。實(shí)際上,大量 well-behaved NAT 設(shè)備都不支持 hairpin 模式,
因?yàn)樗鼈兌加? “只有 src_ip 是私有地址且 dst_ip 是公網(wǎng)地址的包才會(huì)經(jīng)過(guò)我” ?之類(lèi)的假設(shè)。
因此對(duì)于這種目的地址不是公網(wǎng)、需要讓路由器把包再轉(zhuǎn)回內(nèi)網(wǎng)的包,它們會(huì) 直接丟棄 。
這些邏輯甚至是直接實(shí)現(xiàn)在路由芯片中的,因此除非升級(jí)硬件,否則單靠軟件編程無(wú)法改變這種行為。
Hairpin 是所有 NAT 設(shè)備的特性(支持或不支持),并不是 CGNAT 獨(dú)有的。
在大部分情況下,這個(gè)特性對(duì)我們的 NAT 穿透目的來(lái)說(shuō)都是無(wú)所謂的,因?yàn)槲覀兤谕?? 兩個(gè) LAN NAT 設(shè)備會(huì)直接通信,不會(huì)再向上繞到它們的默認(rèn)網(wǎng)關(guān) CGNAT 來(lái)解決這個(gè)問(wèn)題 。
Hairpin 特性可有可無(wú)這件事有點(diǎn)遺憾,這可能也是為什么 hairpin 功能經(jīng)常 broken 的原因。
一旦必須涉及到 CGNAT,那 hairpinning 對(duì)連接性來(lái)說(shuō)就至關(guān)重要了。
Hairpinning 使內(nèi)網(wǎng)連接的行為與公網(wǎng)連接的行為完成一致,因此我們無(wú)需關(guān)心目的 地址類(lèi)型,也不用知曉自己是否在一臺(tái) CGNAT 后面。
如果 hairpinning 和 port mapping protocols 都不可用,那只能降級(jí)到中繼模式了 。
7.7 全 IPv6 網(wǎng)絡(luò):理想之地,但并非問(wèn)題全無(wú) 行文至此,一些讀者可能已經(jīng)對(duì)著屏幕咆哮: 不要再用 IPv4 了! ?花這么多時(shí)間精力解決這些沒(méi)意義的東西,還不如直接換成 IPv6!
的確,之所以有這些亂七八糟的東西,就是因?yàn)?IPv4 地址不夠了,我們 一直在用越來(lái)越復(fù)雜的 NAT 來(lái)給 IPv4 續(xù)命 。
如果 IP 地址夠用,無(wú)需 NAT 就能讓世界上的每個(gè)設(shè)備都有一個(gè)自己的公網(wǎng) IP 地址,這些問(wèn)題不就解決了嗎?
簡(jiǎn)單來(lái)說(shuō),是的,這也正是 IPv6 能做的事情。但是,也只說(shuō)對(duì)了一半:在理想的全 IPv6 世界中,所有這些東西會(huì)變得更加簡(jiǎn)單,但我們面臨的 問(wèn)題并不會(huì)完全消失 ?—— 因?yàn)?/span>有狀態(tài)防火墻仍然還是存在的 。
辦公室中的電腦可能有一個(gè)公網(wǎng) IPv6 地址,但你們公司肯定會(huì)架設(shè)一個(gè)防火墻,只允許 你的電腦主動(dòng)訪問(wèn)公網(wǎng),而不允許反向主動(dòng)建連。
其他設(shè)備上的防火墻也仍然存在,應(yīng)用類(lèi)似的規(guī)則。
因此,我們?nèi)匀粫?huì)用到:
本文最開(kāi)始介紹的防火墻穿透技術(shù),以及
幫助我們獲取自己的公網(wǎng)? ip:port
?信息的旁路信道
仍然需要在某些場(chǎng)景下 fallback 到中繼模式,例如 fallback 到最通用的 HTTP 中繼 協(xié)議,以繞過(guò)某些網(wǎng)絡(luò)禁止 outbound UDP 的問(wèn)題。
但我們現(xiàn)在可以拋棄? STUN、生日悖論、端口映射協(xié)議、hairpin ?等等東西了。這是一個(gè)好消息!
全球 IPv4/IPv6 部署現(xiàn)狀 另一個(gè)更加嚴(yán)峻的現(xiàn)實(shí)問(wèn)題是:當(dāng)前并不是一個(gè)全 IPv6 世界。目前世界上:
大約 33% 是 IPv6 [19] ,而且分布極度不均勻,因此某些 通信對(duì)所在的可能是 100% IPv6,也可能是 0%,或二者之間。
不幸的是,這意味著,IPv6? 還 無(wú)法作為我們的解決方案。就目前來(lái)說(shuō),它只是我們的工具箱中的一個(gè)備選。對(duì)于某些 peer 來(lái)說(shuō),它簡(jiǎn)直是完美工 具,但對(duì)其他 peer 來(lái)說(shuō),它是用不了的。如果目標(biāo)是“任何情況下都能穿透(連接) 成功”,那我們就仍然需要 IPv4+NAT 那些東西。
新場(chǎng)景:NAT64/DNS64 IPv4/IPv6 共存也引出了一個(gè)新的場(chǎng)景:NAT64 設(shè)備。
前面介紹的都是 NAT44 設(shè)備:它們將一個(gè) IPv4 地址轉(zhuǎn)換成另一 IPv4 地址。NAT64 從名字可以看出,是將一個(gè)內(nèi)側(cè) IPv6 地址轉(zhuǎn)換成一個(gè)外側(cè) IPv4 地址。利用 DNS64 設(shè)備,我們能將 IPv4 DNS 應(yīng)答給 IPv6 網(wǎng)絡(luò),這樣對(duì)終端來(lái)說(shuō),它看到的就是一個(gè) 全 IPv6 網(wǎng)絡(luò),而仍然能訪問(wèn) IPv4 公網(wǎng)。
Incidentally, you can extend this naming scheme indefinitely. There have been some experiments with NAT46; you could deploy NAT66 if you enjoy chaos; and some RFCs use NAT444 for carrier-grade NAT.
如果需要處理 DNS 問(wèn)題,那這種方式工作良好。例如,如果連接到 google.com,將這個(gè)域名解析成 IP 地址的過(guò)程會(huì)涉及到 DNS64 設(shè)備,它又會(huì)進(jìn)一步 involve NAT64 設(shè)備,但后一步對(duì)用戶(hù)來(lái)說(shuō)是無(wú)感知的。
但 對(duì)于 NAT 和防火墻穿透來(lái)說(shuō),我們會(huì)關(guān)心每個(gè)具體的 IP 地址和端口 。
解決方案:CLAT (Customer-side transLATor) 如果設(shè)備支持 CLAT (Customer-side translator — from Customer XLAT),那我們就很幸運(yùn):
CLAT 假裝操作系統(tǒng)有直接 IPv4 連接,而背后使用的是 NAT64 ,以對(duì)應(yīng)用程序無(wú)感知。在有 CLAT 的設(shè)備上,我們無(wú)需做任何特殊的事情。 CLAT? 在移動(dòng)設(shè)備上非常常見(jiàn) ,但在桌面電腦、筆記本和服務(wù)器上非常少見(jiàn), 因此在后者上,必須自己做 CLAT 做的事情:檢測(cè) NAT64+DNS64 的存在,然后正確地使用它們。 解決方案:CLAT 不存在時(shí),手動(dòng)穿透 NAT64 設(shè)備 首先檢測(cè)是否存在 NAT64+DNS64。
方法很簡(jiǎn)單:向? ipv4only.arpa.
?發(fā)送一個(gè) DNS 請(qǐng)求。這個(gè)域名會(huì)解析 到一個(gè)已知的、固定的 IPv4 地址,而且是 純 IPv4 地址 。如果得到的 是一個(gè) IPv6 地址,就可以判斷有 DNS64 服務(wù)器做了轉(zhuǎn)換,而它必然會(huì)用到 NAT64。這樣 就能判斷出 NAT64 的前綴是多少。
此后,要向 IPv4 地址發(fā)包時(shí),發(fā)送格式為 {NAT64 prefix + IPv4 address}
?的 IPv6 包。類(lèi)似地,收到來(lái)源格式為? {NAT64 prefix + IPv4 address}
?的包時(shí),就是 IPv4 流量。
接下來(lái),通過(guò) NAT64 網(wǎng)絡(luò)與 STUN 通信來(lái)獲取自己在 NAT64 上的公網(wǎng)? ip:port
,接 下來(lái)就回到經(jīng)典的 NAT 穿透問(wèn)題了 —— 除了需要多做一點(diǎn)點(diǎn)事情。
幸運(yùn)的是,如今的大部分 v6-only 網(wǎng)絡(luò)都是移動(dòng)運(yùn)營(yíng)商網(wǎng)絡(luò),而幾乎所有手機(jī)都支持 CLAT。運(yùn)營(yíng) v6-only 網(wǎng)絡(luò)的 ISPs 會(huì)在他們給你的路由器上部署 CLAT,因此最后你其實(shí)不需要做什么事情。但如果想實(shí)現(xiàn) 100% 穿透,就需要解決這種邊邊角角的問(wèn)題,即必須顯式支持從 v6-only 網(wǎng)絡(luò)連接 v4-only 對(duì)端。
7.8 將所有解決方式集成到 ICE 協(xié)議 針對(duì)具體場(chǎng)景,該選擇哪種穿透方式? 至此,我們的 NAT 穿透之旅終于快結(jié)束了。我們已經(jīng)覆蓋了有狀態(tài)防火墻、簡(jiǎn)單和高級(jí) NAT、IPv4 和 IPv6。只要將以上解決方式都實(shí)現(xiàn)了,NAT 穿透的目的就達(dá)到了!
但是,
對(duì)于給定的 peer,如何判斷改用哪種方式呢? 如何判斷這是一個(gè)簡(jiǎn)單有狀態(tài)防火墻的場(chǎng)景,還是該用到生日悖論算法,還是需要手動(dòng)處理 NAT64 呢? 還是通信雙方在一個(gè) WiFi 網(wǎng)絡(luò)下,連防火墻都沒(méi)有,因此不需要任何操作呢? 早期 NAT 穿透 比較簡(jiǎn)單,能讓我們 精確判斷出 peer 之間的路徑特點(diǎn) ,然后針對(duì)性地采用相應(yīng)的解決方式。但后面,網(wǎng)絡(luò)工程師和 NAT 設(shè)備開(kāi)發(fā)工程師引入了一些新理念,給路徑判斷造成很大困難。因此 我們需要簡(jiǎn)化客戶(hù)端側(cè)的思考(判斷邏輯)。
這就要提到 Interactive Connectivity Establishment (ICE,交換式連接建立) 協(xié)議了。與 STUN/TURN 類(lèi)似,ICE 來(lái)自 電信領(lǐng)域 ,因此其 RFC 充滿(mǎn)了 SIP、SDP、信令會(huì)話、撥號(hào)等等電話術(shù)語(yǔ)。但如果忽略這些領(lǐng)域術(shù)語(yǔ),我們會(huì)看到它 描述了一個(gè)極其優(yōu)雅的判斷最佳連接路徑的算法 。
真的?這個(gè)算法是: 每種方法都試一遍,然后選擇最佳的那個(gè)方法 。就是這個(gè)算法,驚喜嗎?
來(lái)更深入地看一下這個(gè)算法。
ICE (Interactive Connectivity Establishment) 算法 這里的討論不會(huì)嚴(yán)格遵循 ICE spec,因此如果是在自己實(shí)現(xiàn)一個(gè)可互操作的 ICE 客戶(hù)端,應(yīng)該通讀 RFC 8445 [20] , 根據(jù)它的描述來(lái)實(shí)現(xiàn)。這里忽略所有電信術(shù)語(yǔ),只關(guān)注核心的算法邏輯, 并提供幾個(gè)在 ICE 規(guī)范允許范圍的靈活建議。
為實(shí)現(xiàn)和某個(gè) peer 的通信,首先需要確定我們自己用的(客戶(hù)端側(cè))這個(gè) socket 的地址, ?這是一個(gè)列表,至少應(yīng)該包括:
我們自己的 IPv4 LAN? ip:ports
(局域網(wǎng)地址) 通過(guò) STUN 服務(wù)器獲取到的我們自己的 IPv4 WAN? ip:ports
( 公網(wǎng)地址 ,可能會(huì)經(jīng)過(guò) NAT64 轉(zhuǎn)換) 通過(guò)端口映射協(xié)議獲取到的我們自己的 IPv4 WAN? ip:port
(NAT 設(shè)備的 端口映射協(xié)議分配的公網(wǎng)地址 ) 運(yùn)營(yíng)商提供給我們的 endpoints(例如, 靜態(tài)配置的端口轉(zhuǎn)發(fā) ) 通過(guò)旁路信道與 peer 互換這個(gè)列表。兩邊都拿到對(duì)方的列表后,就開(kāi)始互相探測(cè)對(duì)方提供的地址。? 列表中地址沒(méi)有優(yōu)先級(jí) ,也就是說(shuō),如果對(duì)方給的了 15 個(gè)地址,那我們應(yīng)該把這 15 個(gè)地址都探測(cè)一遍。
這些 探測(cè)包有兩個(gè)目的 :
打開(kāi)防火墻,穿透 NAT ,也就是本文一直在介紹的內(nèi)容; 健康檢測(cè) 。我們?cè)诓粩嘟粨Q(最好是已認(rèn)證的)“ping/pong” 包,來(lái)檢測(cè)某個(gè)特定的路徑是不是端到端通的。 最后,一小會(huì)兒之后,從可用的備選地址中(根據(jù)某些條件)選擇“最佳”的那個(gè),任務(wù)完成!
這個(gè)算法的優(yōu)美之處在于:只要選擇最佳線路(地址)的算法是正確的,那就總能獲得最佳路徑。
ICE 會(huì)預(yù)先對(duì)這些備選地址進(jìn)行排序(通常:LAN > WAN > WAN+NAT),但用戶(hù)也可以自己指定這個(gè)排序行為。 從 v0.100.0 開(kāi)始,Tailscale 從原來(lái)的 hardcode 優(yōu)先級(jí)切換成了根據(jù) round-trip latency 的方式,它大部分情況下排序的結(jié)果和? LAN > WAN > WAN+NAT
?是一致的。但相比于靜態(tài)排序,我們是動(dòng)態(tài)計(jì)算每條路徑應(yīng)該屬于哪個(gè)類(lèi)別。 ICE spec 將協(xié)議組織為兩個(gè)階段:
但不一定要嚴(yán)格遵循這兩個(gè)步驟的順序。在 Tailscale,
我們發(fā)現(xiàn)更優(yōu)的路徑之后就會(huì)自動(dòng)切換過(guò)去, 所有的連接都是先選擇 DERP 模式(中繼模式)。這意味著連接立即就能建立( 優(yōu)先級(jí)最低但 100% 能成功的模式 ),用戶(hù)不用任何等待, 然后并行進(jìn)行路徑發(fā)現(xiàn)。通常幾秒鐘之后,我們就能發(fā)現(xiàn)一條更優(yōu)路徑,然后將現(xiàn)有連接透明升級(jí)(upgrade)過(guò)去。 但有一點(diǎn)需要關(guān)心:非對(duì)稱(chēng)路徑。ICE 花了一些精力來(lái)保證通信雙方選擇的是相同的網(wǎng)絡(luò) 路徑,這樣才能保證這條路徑上有雙向流量,能保持防火墻和 NAT 設(shè)備的連接一直處于 open 狀態(tài)。自己實(shí)現(xiàn)的話,其實(shí)并不需要花同樣大的精力來(lái)實(shí)現(xiàn)這個(gè)保證,但需要確保你所有使用的所有路徑上,都有雙向流量。這個(gè)目標(biāo)就很簡(jiǎn)單了,只需要定期在所有已使用的路徑上發(fā) ping/pong 就行了。
健壯性與降級(jí) 要實(shí)現(xiàn)健壯性,還需要檢測(cè)當(dāng)前已選擇的路徑是否已經(jīng)失敗了(例如,NAT 設(shè)備維護(hù)清掉了所有狀態(tài)), 如果失敗了就要 降級(jí)(downgrade)到其他路徑 。這里有兩種方式:
持續(xù)探測(cè)所有路徑,維護(hù)一個(gè)降級(jí)時(shí)會(huì)用的備用地址列表;
直接降級(jí)到保底的中繼模式 ,然后再通過(guò)路徑探測(cè)升級(jí)到更好的路徑。
考慮到發(fā)生降級(jí)的概率是非常小的,因此這種方式可能是 更經(jīng)濟(jì) 的。
7.9 安全 最后需要提到安全。
本文的所有內(nèi)容都假設(shè):我們使用的 上層協(xié)議已經(jīng)有了自己的安全機(jī)制 ( 例如 QUIC 協(xié)議有 TLS 證書(shū),WireGuard 協(xié)議有自己的公鑰)。如果還沒(méi)有安全機(jī)制,那顯然是要立即補(bǔ)上的。一旦動(dòng)態(tài)切換路徑, 基于 IP 的安全機(jī)制就是無(wú)用的了 ?(IP 協(xié)議最開(kāi)始就沒(méi)怎么考慮安全性),至少要有 端到端的認(rèn)證 。
嚴(yán)格來(lái)說(shuō),如果上層協(xié)議有安全機(jī)制,那即使收到是欺騙性的 ping/pong 流量,問(wèn)題都不大, 最壞的情況也就是 攻擊者誘導(dǎo)兩端通過(guò)他們的系統(tǒng)來(lái)中繼流量 。而有了端到端安全機(jī)制,這并不是一個(gè)大問(wèn)題(取決于你的威脅模型)。 但出于謹(jǐn)慎考慮,最好還是對(duì)路徑發(fā)現(xiàn)的包也做認(rèn)證和加密。具體如何做可以咨詢(xún)你們的應(yīng)用安全工程師。 8 結(jié)束語(yǔ) 我們終于完成了 NAT 穿透的目標(biāo)!
如果實(shí)現(xiàn)了以上提到的所有技術(shù),你將得到一個(gè)業(yè)內(nèi)領(lǐng)先的 NAT 穿透軟件,能在絕大多數(shù)場(chǎng)景下實(shí)現(xiàn)端到端直連。如果直連不了,還可以降級(jí)到保底的中繼模式(對(duì)于長(zhǎng)尾來(lái)說(shuō)只能靠中繼了)。
但這些工作相當(dāng)復(fù)雜!其中一些問(wèn)題研究起來(lái)很有意思,但很難做到完全正確,尤其是那些 非常邊邊角角的場(chǎng)景,真正出現(xiàn)的概率極小,但解決它們所需花費(fèi)的經(jīng)歷又極大。不過(guò),這種工作只需要做一次,一旦解決了,你就具備了某種超級(jí)能力:探索令人激動(dòng)的、相對(duì)還比較嶄新的 端到端應(yīng)用 (peer-to-peer applications)世界。
8.1 跨公網(wǎng) 端到端直連 去中心化軟件 領(lǐng)域中的許多有趣想法,簡(jiǎn)化之后其實(shí)都變成了? 跨過(guò)公網(wǎng)(互聯(lián)網(wǎng))實(shí)現(xiàn)端到端直連 ?這一問(wèn)題,開(kāi)始時(shí)可能覺(jué)得很簡(jiǎn)單,但真正做才 發(fā)現(xiàn)比想象中難多了。現(xiàn)在知道如何解決這個(gè)問(wèn)題了,動(dòng)手開(kāi)做吧!
8.2 結(jié)束語(yǔ)之 TL; DR 實(shí)現(xiàn)健壯的 NAT 穿透需要下列基礎(chǔ):
能在程序內(nèi)直接訪問(wèn) socket; 一個(gè)保底用的中繼網(wǎng)絡(luò)(可選,但強(qiáng)烈推薦) 然后需要:
查詢(xún) STUN 服務(wù)器來(lái)獲取自己的公網(wǎng)? ip:port
?信息,以及判斷自己這一側(cè)的 NAT 的“難度”(difficulty); 使用 port mapping 協(xié)議來(lái)獲取更多的公網(wǎng)? ip:ports
; 檢查 NAT64,通過(guò)它獲取自己的公網(wǎng)? ip:port
; 將自己的所有公網(wǎng)? ip:ports
?信息通過(guò)旁路信道與 peer 交換,以及某些加密秘鑰來(lái)保證通信安全; 通過(guò)保底的中繼方式與對(duì)方開(kāi)始通信(可選,這樣連接能快速建立) 如果有必要/想這么做,探測(cè)對(duì)方的提供的所有? ip:port
,以及執(zhí)行生日攻擊(birthday attacks)來(lái)穿透 harder NAT; 發(fā)現(xiàn)更優(yōu)路徑之后,透明升級(jí)到該路徑; 如果當(dāng)前路徑斷了,降級(jí)到其他可用的路徑; 確保所有東西都是加密的,并且有端到端認(rèn)證。 引用鏈接 [1] How NAT traversal works:? https://tailscale.com/blog/how-nat-traversal-works/
[2] How Tailscale Works:? https://tailscale.com/blog/how-tailscale-works/
[3] (譯) NAT - 網(wǎng)絡(luò)地址轉(zhuǎn)換(2016):? https://arthurchiao.art/blog/nat-zh/
[4] WebRTC:? https://webrtc.org/
[5] coordination server:? https://tailscale.com/blog/how-tailscale-works/#the-control-plane-key-exchange-and-coordination
[6] STUN:? https://en.wikipedia.org/wiki/STUN
[7] STUN:? https://en.wikipedia.org/wiki/STUN
[8] RFC 4787:? https://tools.ietf.org/html/rfc4787
[9] 4787:? https://tools.ietf.org/html/rfc4787
[10] 5382:? https://tools.ietf.org/html/rfc5382
[11] 5508:? https://tools.ietf.org/html/rfc5508
[12] DERP:? https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp
[13] RFC 4787:? https://tools.ietf.org/html/rfc4787
[14] birthday paradox:? https://en.wikipedia.org/wiki/Birthday_problem
[15] python calculator:? https://github.com/danderson/nat-birthday-paradox
[16] UPnP IGD:? https://openconnectivity.org/developer/specifications/upnp-resources/upnp/internet-gateway-device-igd-v-2-0/
[17] NAT-PMP:? https://tools.ietf.org/html/rfc6886
[18] PCP:? https://tools.ietf.org/html/rfc6887
[19] 大約 33% 是 IPv6:? https://www.google.com/intl/en/ipv6/statistics.html
[20] RFC 8445:? https://tools.ietf.org/html/rfc8445
該文章在 2025/1/26 9:24:27 編輯過(guò)