UDP 轉發、NAT type 和 Shadowsocks

遊戲、遊戲、遊戲!

在此之前

在此之前,我只關注 Shadowsocks 的 TCP 轉發,就算連 DNS 查詢我也是使用 TCP。由於 TCP 是有狀態的,穿越 NAT 基本沒有問題。

代理 UDP

問題出現在我在 Steam 購買了 PUBG 這款遊戲之後,不論是遊戲還是使用 Discord 的遊戲語音都需要使用到 Shadowsocks 的 UDP 代理功能。實現也有兩種方法,在 Windows 平台下有 SSTap,或者使用 Linux 作為網關,使用 ss-redir 配合 TPROXY 代理 UDP。

STUN 失敗與分析

就這樣使用了大半年遊戲一直很流暢,直到某一天使用 Facetime Audio 感到不流暢,由於當時關閉了代理,而使用 Facetime Audio 的兩台設備均在中國大陸,便猜測是走了 Apple 的 relay server。但仔細想了一下並不應該,兩台設備均在一層 NAT 之後,只要有一台設備是 Full cone NAT,基於 UDP 是可以實現 P2P 通信的。其中一台設備的環境是路由器有公網 IP,運行 Linux 作為 NAT 網關,NAT 測試是 Port-Restricted cone NAT,加上路由器和應用程式均支援 UPnP,是可以達到 Full cone NAT 的級別的。

一番調研

好像有什麼不對?經過後來一番研究,才發現上面的猜想完全錯誤了。

首先 Linux 默認是不支持 UPnP,需要安裝額外的程式。OpenWrt 一類的路由器系統會自帶 miniupnp 程式,但不會默認開啟。

此外,更大的誤會是,Linux 內核的 NAT 是 Symmetric NAT,但一些基於 Linux 的路由器系統通過 patch 實現了 Full cone NAT。1

造成我判斷錯誤的原因是使用的 STUN 檢測工具對 NAT 類型的標準和 Linux 稍有差異。

brief introduction of NAT type

關於 NAT type,主要有四種類型。2

假設有四台設備:

  • A 機器在私網 192.168.0.4
  • NAT 伺服器 210.21.12.140
  • B 機器在公網 210.15.27.166
  • C 機器在公網 210.15.27.140

現在,A 機器連線過 B 機器,假設是 A(192.168.0.4:5000)到 NAT(轉換後 210.21.12.140:8000)到 B(210.15.27.166:2000)。此外 A 從來沒有和 C 通訊過。則對於不同型別的 NAT,有下列不同的結果:

Full Cone NAT

C 發資料到 NAT(210.21.12.140:8000),NAT 會將資料包送到 A(192.168.0.4:5000)。因為 NAT 上已經有了 192.168.0.4:5000 到 210.21.12.140:8000 的對映。

Restricted Cone

C 無法和 A 通訊,因為 A 從來沒有和 C 通訊過,NAT 將拒絕 C 試圖與 A 連線的動作。但 B 可以通過 210.21.12.140:8000 與 A 的 192.168.0.4:5000 通訊,且這裡 B 可以使用任何埠與 A 通訊。如:210.15.27.166:2001 到 210.21.12.140:8000,NAT 會送到 A 的 5000 埠上。

Port Restricted Cone

C 無法與 A 通訊,因為 A 從來沒有和 C 通訊過。而 B 也只能用它的 210.15.27.166:2000 與 A 的 192.168.0.4:5000 通訊,因為 A 也從來沒有和 B 的其他埠通訊過。該型別 NAT 是埠受限的。

上面 3 種類型,統稱為 Cone NAT,有一個共同點:只要是從同一個內部地址和埠出來的包,NAT 都將它轉換成同一個外部地址和埠。

Symmetric NAT

只要是從同一個內部地址和埠出來,且到同一個外部目標地址和埠,則 NAT 也都將它轉換成同一個外部地址和埠;但如果從同一個內部地址和埠出來,到另一個外部目標地址和埠,則 NAT 將使用不同的對映,轉換成不同的埠。而且和 Port Restricted Cone 一樣,只有曾經收到過內部地址發來包的外部地址,才能通過 NAT 對映後的地址向該內部地址發包。

Cone NAT,有一個共同點:只要是從同一個內部地址和埠出來的包,NAT 都將它轉換成同一個外部地址和埠。Symmetric NAT 如果從同一個內部地址和埠出來,到另一個外部目標地址和埠,則 NAT 將使用不同的對映,轉換成不同的埠。

iptables 與 STUN

iptables 在轉換地址時,遵循如下兩個原則:

  • 儘量不去修改源埠,也就是說,IP 偽裝後的源埠儘可能保持不變。
  • 更為重要的是,IP 偽裝後只需保證偽裝後的(源地址,源埠,目標地址,目標埠)唯一即可。

仍以前例說明如下。

A 機器連線過 B 機器,假使是 A(192.168.0.4:5000)到 NAT(轉換後 210.21.12.140:5000)到 B(210.15.27.166:2000),此處 NAT 遵循原則,換後埠沒有改變。

如果此時 A 機器(192.168.0.4:5000)還想連線 C 機器 (210.15.27.140:2000),則 NAT 上產生一個新的對映,但對應的轉換仍然有可能為 A(192.168.0.4:5000)到 NAT(轉換後 210.21.12.140:5000)到 C(210.15.27.140:2000)。這是因為 NAT 到 B(210.15.27.166:2000)和 NAT 到 C(210.15.27.140:2000)這兩個目標地址不重複。因此,對於 iptables 來說,這是允許的。

Source Server Source NAT Destination Server Destination
A 192.168.0.4:5000 210.21.12.140:5000 B 210.15.27.166:2000
A 192.168.0.4:5000 210.21.12.140:5000 C 210.15.27.140:2000

在該例中,表面上看起來 iptables 似乎不屬於 Symmetric NAT,因為它看起來不符合 Symmetric NAT 的要求:如果從同一個內部地址和埠出來,是到另一個目標地址和埠,則 NAT 將使用不同的對映,轉換成不同的埠。 相反,倒是符合除 Symmetric NAT 外的三種 Cone NAT 的要求:從同一個內部地址和埠出來的包,NAT 都將它轉換成同一個外部地址和埠。加上 iptables 具有埠受限的屬性,所以好多檢測工具就把 iptables 報告為 Port Restricted Cone 了。

現在增加 D 機器在私網(192.168.0.5)。

A 機器連線過 B 機器,假使是 A(192.168.0.4:5000)到 NAT(轉換後 210.21.12.140:5000)到 B(210.15.27.166:2000)。

D 機器連線過 C 機器,假使是 D(192.168.0.5:5000)到 NAT(轉換後 210.21.12.140:5000)到 C(210.15.27.140:2000)。

如果此時 A 機器(192.168.0.4:5000)還想連線 C 機器 (210.15.27.140:2000),則 NAT 上產生一個新的對映,但對應的轉換則變為 A(192.168.0.4:5000)到 NAT(轉換後 210.21.12.140:5001)到 C(210.15.27.140:2000)。

Source Server Source NAT Destination Server Destination
A 192.168.0.4:5000 210.21.12.140:5000 B 210.15.27.166:2000
D 192.168.0.5:5000 210.21.12.140:5000 C 210.15.27.140:2000
A 192.168.0.4:5000 210.21.12.140:5001 C 210.15.27.140:2000

下面,我們再來分析一下 iptables 的埠受限的屬性。

A 機器連線過 B 機器,假使是 A(192.168.0.4:5000)到 NAT(轉換後 210.21.12.140:5000)到 B(210.15.27.166:2000)。

D 機器連線過 C 機器,假使是 D(192.168.0.5:5000)到 NAT(轉換後 210.21.12.140:5000)到 C(210.15.27.140:2000)。

Source Server Source NAT Destination Server Destination
A 192.168.0.4:5000 210.21.12.140:5000 B 210.15.27.166:2000
D 192.168.0.5:5000 210.21.12.140:5000 C 210.15.27.140:2000

現假設 iptables 不具有埠受限的屬性,則另一 E 機器在公網 (210.15.27.153:2000)向 210.21.12.140:5000 發包,應該能夠發到內部機器上。但事實是,當這個包到達 NAT(210.21.12.140:5000)時,NAT 將不知道把這個包發給 A(192.168.0.4:5000)還是 D(192.168.0.5:5000)。顯然該包只能丟棄,至此足以證明 iptables 具有埠受限的屬性。

所以,iptables 是貨真價實的 Symmetric NAT。3

另闢蹊徑得 Full Cone NAT

如何在不修改 Linux 內核的情況下得到 Full Cone NAT?

除了 DMZ ,還可以使用 ss-redir 配合 TPROXY。Shadowsocks 在設計 UDP relay 之初就考慮到這個問題了。4

Long Live Shadowsocks!