需要为从全局网络命名空间中的物理网络适配器(通过一对链接的虚拟网络适配器)到在特殊网络命名空间中运行的服务的两个 UDP 连接设置无状态 NAT。这应该在运行带有内核的 Linux (Debian) 的工业设备中的 CPU (Intel Atom) 上完成5.9.7。
以下是应设置的网络配置方案:
===================== =====================================================
|| 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 端口,用于通过具有 IP 地址的物理网络适配器与通信 CPU 上的服务sv1
和服务进行通信。sv2
enp4s0
0.0.0.5
在应用程序CPU上执行的输出ss --ipv4 --all --numeric --processes --udp
是:
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
是:
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))
sysctl
一般情况下,所有物理网络适配器都会启用 IPv4 转发。
仅广播和组播转发因不需要且不想要而被禁用。
使用以下命令在通信 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
和 的UDP 通道534441
。看起来原因是sva
应用程序 CPU 在服务启动之前就已经发送了多个 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 维基(大部分页面完整)
- ArchWiki 上的 nftables
- 以及许多许多其他有关网络名称空间和网络地址转换的使用的网页。
我尝试了很多不同的配置,使用了 Wireshark,使用过nft monitor trace
,但我找不到适用于带有端口的 UDP 通道50441
以及53441
在目标端口打开sva
之前发送 UDP 数据包的解决方案。0.0.0.17:50441
如果我在应用程序 CPU 上手动终止服务sva
,在通信 CPU 上设置网络配置并启动这两个服务sv1
,sv2
最后sva
在通信 CPU 上已打开的所有 UDP 端口上再次手动启动该服务,则有状态 NAT 配置将起作用。但默认情况下,工业设备中无法执行此启动服务的顺序。应用程序服务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通信绝对没有问题。
答案1
经过很多很多个小时的阅读文档、教程、各种网页上的建议、进行大量的尝试以及进行深入而全面的网络和 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
这网络过滤钩子应首先打开并阅读页面以理解以下说明。
使用命令说明:
- A网络过滤器 桌子
ip
为名称为 的协议 (IPv4)添加prot1
。 - A链被添加到具有优先级的钩子类型
prot1
名称的表中。使用低于的优先级编号非常重要,这样才能绕过连接跟踪。这排除了使用具有更低优先级的目标网络地址转换类型链。prerouting
filter
prerouting
-300
-200
conntrack
nat
- A筛选 规则添加到表链
prot1
,prerouting
仅适用于协议类型为或 的输入接口i
上i
接收的 IPv4 数据包,将数据包的目标从修改为并激活此UDP 数据包的连接。尽管实际上没有必要将从应用程序CPU的服务接收到的用于通信CPU的服务的UDP数据包尽可能快地传递到下一个钩子(在本例中是钩子),但是明确地指定了判决。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
,仅应用于全部i
在输入接口上接收的 IPv4 数据包与协议类型(、、...)无关,具有i
激活该数据包连接的来源。当然也可以仅过滤具有适当源或目标端口号的 UDP 数据包,但此处不需要此附加限制,并且此规则也适用于从尚未打开的目标端口发送回的 ICMP 数据包,因为该服务目前没有运行。再次显式指定判决,而不是使用隐式默认值将数据包尽快传递到钩子。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
名称的表中。重要的是使用类型链而不是类型链,以便能够对绕过连接跟踪的 UDP(和 ICMP)数据包应用源地址转换。postrouting
filter
postrouting
100
filter
nat
- A筛选 规则添加到
prot1
第二条链的表中postrouting
,该链仅适用于在o
输出接口上发送的 IPv4 数据包i
,与协议类型(、、 ...)无关,具有将数据包的来源从修改为 的源ess 。尽管实际上并不需要尽快将从通信CPU的服务接收到的UDP数据包传递到应用CPU的服务,但是再次明确地指定该判决。此规则还将由于服务尚未运行而无法到达的目标端口发送的 ICMP 数据包的源地址更改为。因此,应用程序 CPU 永远不会注意到它使用不同接口进行两个 UDP 通道的通信,这是要满足的第二个要求,尽管这并不重要。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
0.0.0.6
发现这种非常特殊的网络配置和服务之间的通信类型需要无状态网络转换sva
并且sv2
必须完成 NAT是一项艰巨的工作没有使用nat
钩子。
答案2
非常感谢你的文章。对我帮助很大。
还有一个提示:如果您正在进行设置但它不起作用,请使用“conntrack -L”检查 contrack 中的现有连接,并使用“conntrack -D”删除它们。
这是因为我花了几个小时在我的配置中搜索探针 - 没有问题,但过去的连接仍然可用。