如何為從全域網路到特殊網路命名空間的兩個 UDP 連接設置無狀態 NAT?
需要為從全域網路命名空間中的物理網路適配器通過一對連結的虛擬網路適配器到在特殊網路命名空間中執行的服務的兩個 UDP 連接設置無狀態 NAT。這應該在執行具有核心5.9.7的 Linux (Debian) 的工業設備中的 CPU (Intel Atom) 上完成。
這是應設置的網路配置方案:
===================== ===================================================== || application CPU || || communication CPU || || || || || || || || global namespace | nsprot1 namespace || || || || | || || enp4s0 || || enp1s0 | enp3s0 || || 0.0.0.5/30 ========== 0.0.0.6/30 | 192.168.2.15/24 ======= || || || | || || UDP port 50001 || || UDP port 50001 for sv1 | TCP port 2404 for sv2 || || UDP port 50002 || || UDP port 50002 for sv1 | || || UDP port 53401 || || UDP port 50401 for sv1 | || || UDP port 53402 || || UDP port 50402 for sv1 | || || || || | || || || || vprot0 | vprot1 || || || || 0.0.0.16/31 --- 0.0.0.17/31 || || || || | || || UDP port 53404 || || UDP port 50404 for sv2 - UDP port 50404 for sv2 || || UDP port 53441 || || UDP port 50441 for sv2 - UDP port 50441 for sv2 || ===================== =====================================================
應用程序 CPU 總是首先啟動並打開幾個 UDP 埠用於與通信 CPU 上的服務
sv1
和服務sv2
通信,通過其物理網路適配器enp4s0
使用 IP 地址0.0.0.5
。
ss --ipv4 --all --numeric --processes --udp
在應用程序 CPU 上執行的輸出是:Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process udp UNCONN 0 0 0.0.0.0:50001 0.0.0.0:* users:(("sva",pid=471,fd=5)) udp UNCONN 0 0 0.0.0.0:50002 0.0.0.0:* users:(("sva",pid=471,fd=6)) udp ESTAB 0 0 0.0.0.5:53401 0.0.0.6:50401 users:(("sva",pid=471,fd=12)) udp ESTAB 0 0 0.0.0.5:53402 0.0.0.6:50402 users:(("sva",pid=471,fd=13)) udp ESTAB 0 0 0.0.0.5:53404 0.0.0.6:50404 users:(("sva",pid=471,fd=19)) udp ESTAB 0 0 0.0.0.5:53441 0.0.0.6:50441 users:(("sva",pid=471,fd=21))
通信 CPU 第二個啟動,最後執行了兩個服務:
sv1
在全域命名空間和sv2
在特殊的網路命名空間nsprot1
中。
ss --ipv4 --all --numeric --processes --udp
在通信 CPU 的全域命名空間中執行的輸出為:Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process udp UNCONN 0 0 0.0.0.0:50001 0.0.0.0:* users:(("sv1",pid=812,fd=18)) udp UNCONN 0 0 0.0.0.6:50002 0.0.0.0:* users:(("sv1",pid=812,fd=17)) udp UNCONN 0 0 0.0.0.6:50401 0.0.0.0:* users:(("sv1",pid=812,fd=13)) udp UNCONN 0 0 0.0.0.6:50402 0.0.0.0:* users:(("sv1",pid=812,fd=15))
ip netns exec nsprot1 ss --ipv4 --all --numeric --processes --udp
(nsprot1
namespace)的輸出是:Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process udp ESTAB 0 0 0.0.0.17:50404 0.0.0.5:53404 users:(("sv2",pid=2421,fd=11)) udp ESTAB 0 0 0.0.0.17:50441 0.0.0.5:53441 users:(("sv2",pid=2421,fd=12))
IPv4 轉發通常啟用,
sysctl
並且適用於所有物理網路適配器。僅廣播和多播轉發被禁用,因為不需要並且不需要。
使用以下命令在通信 CPU 上設置網路配置:
ip netns add nsprot1 ip link add vprot0 type veth peer name vprot1 netns nsprot1 ip link set dev enp3s0 netns nsprot1 ip address add 0.0.0.16/31 dev vprot0 ip netns exec nsprot1 ip address add 0.0.0.17/31 dev vprot1 ip netns exec nsprot1 ip address add 192.168.2.15/24 dev enp3s0 ip link set dev vprot0 up ip netns exec nsprot1 ip link set vprot1 up ip netns exec nsprot1 ip link set enp3s0 up ip netns exec nsprot1 ip route add 0.0.0.4/30 via 0.0.0.16 dev vprot1
使用以下命令設置網路地址轉換:
nft add table ip prot1 nft add chain ip prot1 prerouting '{ type nat hook prerouting priority -100; policy accept; }' nft add rule prot1 prerouting iif enp1s0 udp dport '{ 50404, 50441 }' dnat 0.0.0.17 nft add chain ip prot1 postrouting '{ type nat hook postrouting priority 100; policy accept; }' nft add rule prot1 postrouting ip saddr 0.0.0.16/31 oif enp1s0 snat 0.0.0.6
的輸出
nft list table ip prot1
是:table ip prot1 { chain prerouting { type nat hook prerouting priority -100; policy accept; iif "enp1s0" udp dport { 50404, 50441 } dnat to 0.0.0.17 } chain postrouting { type nat hook postrouting priority 100; policy accept; ip saddr 0.0.0.16/31 oif "enp1s0" snat to 0.0.0.6 } }
在全域命名空間中僅定義了具有以下內容的表
inet filter
:table inet filter { chain input { type filter hook input priority 0; policy accept; } chain forward { type filter hook forward priority 0; policy accept; } chain output { type filter hook output priority 0; policy accept; } }
該 NAT 配置用於有狀態 NAT。它適用於具有埠號的 UDP 通道,
50404
並且53404
由於sv2
最後開始打開0.0.0.17:50404
並發送一個 UDP 數據包, 在全域命名空間中的鉤子中0.0.0.5:53404
應用源網路地址轉換。應用 CPU的服務發回一個 UDP 數據包,從那裡到達。UDP 數據包沒有通過to的規則。正如我後來發現的那樣,它是通過連接跟踪直接發送到的。postrouting``enp1s0``sva``0.0.0.5:53404``0.0.0.6:50404``0.0.0.17:50404``prerouting``dnat``0.0.0.17``0.0.0.17
但是這種有狀態的 NAT 配置不適用於具有埠號
50441
和534441
. 看起來原因是應用程序 CPU在服務啟動之前sva
已經發送了幾個 UDP 數據包0.0.0.5:53441
,並且目標埠在網路命名空間中打開。ICMP 返回目的埠不可達。考慮到目標埠還沒有打開,這並不奇怪。不幸的是,在服務啟動並打開兩個 UDP 埠之前,無法阻止服務中發送的 UDP 數據包。服務會定期發送自發的 UDP 數據包,有時還會額外觸發0.0.0.6:50441``sv2``nsprot1``sva``sv2``sva``0.0.0.5:53441``0.0.0.6:50441
獨立於連接狀態。所以這個配置的問題似乎是有狀態的 NAT,因為鉤子中的
dnat
規則prerouting
仍然沒有用於最終在網路命名空間中打開的目標埠nsprot1
。仍然繼續路由 UDP 數據包0.0.0.6:50441
,導致丟棄 UDP 數據包並返回目標埠不可達的 ICMP。因此,解決方案可能是使用無狀態 NAT。因此,還執行了以下命令:
nft add table ip raw nft add chain ip raw prerouting '{ type filter hook prerouting priority -300; policy accept; }' nft add rule ip raw prerouting udp dport '{ 50404, 50441, 53404, 53441 }' notrack
但結果並不如預期。將來自輸入介面的 UDP 數據包
prerouting
的目標地址從到更改為0.0.0.6
目標埠的規則, 仍然沒有被考慮在內。0.0.0.17``enp1s0``50404``50441
接下來被我執行了:
nft add table ip filter nft add chain filter trace_in '{ type filter hook prerouting priority -301; }' nft add rule filter trace_in meta nftrace set 1 nft add chain filter trace_out '{ type filter hook postrouting priority 99; }' nft add rule filter trace_out meta nftrace set 1 nft monitor trace
我查看了跟踪,可以看到該
notrack
規則被考慮在內,但是帶有目標埠的 UDP 數據包50441
直接傳遞給了input
鉤子。我不知道為什麼。我非常仔細地研究了很多很多小時以下頁面:
- nft 手冊(從上到下完整讀了幾遍)
- nftables wiki(大部分頁面完全)
- ArchWiki 上的 nftables
- 以及許多關於網路名稱空間和網路地址轉換的其他網頁。
我嘗試了很多不同的配置,使用過 Wireshark,使用過
nft monitor trace
,但我找不到適用於帶有埠的 UDP 通道50441
以及53441
在完全打開sva
目標埠之前就已經發送 UDP 數據包的解決方案。0.0.0.17:50441
如果我在應用程序 CPU 上手動終止服務
sva
,在通信 CPU 上設置網路配置並啟動兩個服務sv1
,然後在通信 CPU 上已打開的所有 UDP 埠上再次sv2
手動啟動服務,則有狀態 NAT 配置有效。sva
但是這種啟動服務的順序在工業設備中預設是無法做到的。應用程序服務sva
必須獨立於通信服務是否準備好通信而執行。哪些命令(鏈/規則)對於兩個 UDP 通道具有無狀態 NAT 是必需的,
0.0.0.5:53404 - 0.0.0.17:50404
並且0.0.0.5:53441 - 0.0.0.17:50441
獨立於目標埠的打開狀態以及哪個服務首先向另一個服務發送 UDP 數據包?PS:服務
sv2
可以根據設備的配置啟動,也可以在全域命名空間中使用不同的物理網路適配器啟動,不需要 NAT 和網路命名空間。在此網路配置中,三個服務之間的 UDP 通信絕對沒有問題。
經過無數小時的閱讀文件、教程、各種網頁上的建議,進行大量試驗,並對網路和 netfilter 進行深入而全面的監控和分析,我終於自己找到了解決方案。
nft add table ip prot1 nft add chain ip prot1 prerouting '{ type filter hook prerouting priority -300; policy accept; }' nft add rule ip prot1 prerouting iif enp1s0 udp dport '{ 50404, 50441 }' ip daddr set 0.0.0.17 notrack accept nft add rule ip prot1 prerouting iif vprot0 ip saddr 0.0.0.17 notrack accept nft add chain ip prot1 postrouting '{ type filter hook postrouting priority 100; policy accept; }' nft add rule ip prot1 postrouting oif enp1s0 ip saddr 0.0.0.17 ip saddr set 0.0.0.6 accept
應先打開netfilter hooks頁面並閱讀以了解以下說明。
使用命令說明:
- 為帶有 name的協議(IPv4)添加了一個netfilter 表。
ip``prot1
- 一個鏈被添加到表中
prot1
,其名稱為具有優先級的鉤子prerouting
的類型。使用低於能夠繞過連接跟踪的優先級編號非常重要。這不包括使用具有更低優先級的目標網路地址轉換的類型鏈。filter``prerouting``-300``-200``conntrack``nat
- 過濾 規則被添加到
prot1
鍊錶,該規則僅適用於在協議類型的輸入介面prerouting
上接收到的 IPv4 數據包,該協議類型具有作為estination或者修改數據包的estination from to並啟動此 UDP 數據包的連接。判決明確指定,儘管實際上沒有必要將從應用程序 CPU 的服務接收到的 UDP 數據包盡快傳遞給通信 CPU 服務的下一個鉤子,在這種情況下是鉤子。i``i``f``enp1s0``udp``d``port``50404``50441``ip
d``addr``0.0.0.6``0.0.0.17``no track``accept``sva``sv2``forward
- 將第二個過濾 規則添加到鍊錶
prot1
,該規則prerouting
僅適用於在輸入介面上接收的所有IPv4 數據包,獨立於協議類型(… 當然也可以只過濾具有適當源或目標埠號的 UDP 數據包,但這裡不需要這個額外的限制,而且這個規則也適用於 ICMP 數據包從尚未打開的目標埠發送回,因為服務i``i``f``vprot0``udp``icmp``ip
s``addr``0.0.0.17``no track``0.0.0.17``0.0.0.5``sv2
目前沒有執行。再次明確指定判決,accept
而不是使用隱式預設值continue
以盡可能快地將數據包傳遞給forward
鉤子。- 將第二個鏈添加到表中
prot1
,其名稱為具有優先級的鉤子postrouting
的類型。重要的是使用類型鏈而不是類型鏈,以便能夠對繞過連接跟踪的 UDP(和 ICMP)數據包應用源地址轉換。filter``postrouting``100``filter``nat
- 將過濾 規則添加到
prot1
第二個鏈的表中,該規則僅適用於在輸出介面postrouting
上發送的 IPv4 數據包,而與協議類型 ( , , …)無關,該協議類型將數據包的來源從修改為。再次明確指定該判定,儘管並非真的有必要盡快將從通信 CPU 的服務接收到的 UDP 數據包傳遞給應用 CPU 的服務。此規則還將ICMP 數據包的源地址更改為o``i``f``enp1s0``udp``icmp``s``addr``0.0.0.17``ip
s``addr``0.0.0.17``0.0.0.6``accept``sv2``sva``0.0.0.6``0.0.0.17``sv2
由於服務尚未執行,無法訪問目標埠。因此,應用程序 CPU 永遠不會注意到它為兩個具有不同介面的 UDP 通道進行通信,0.0.0.6
這是第二個要滿足的要求,儘管這並不重要。很難發現這種非常特殊的網路配置和服務之間的通信需要無狀態網路轉換,
sva
並且sv2
必須在不使用nat
鉤子的情況下完成 NAT。