![Conntrack 无法从另一个 VRF 对其自己的 TCP 数据包进行 NAT](https://linux22.com/image/768930/Conntrack%20%E6%97%A0%E6%B3%95%E4%BB%8E%E5%8F%A6%E4%B8%80%E4%B8%AA%20VRF%20%E5%AF%B9%E5%85%B6%E8%87%AA%E5%B7%B1%E7%9A%84%20TCP%20%E6%95%B0%E6%8D%AE%E5%8C%85%E8%BF%9B%E8%A1%8C%20NAT.png)
在基于 Debian 的路由器上使用多个 VRF 时,我遇到了源 NAT 的一个棘手问题。解释起来有点复杂,所以我会尽量说清楚,但不会简短,对此我深表歉意。不过这个问题应该很容易重现。
为了将路由器的“管理”部分(ssh 和其他服务)与其路由器作业(路由和 NATing 数据包)隔离,我尝试在默认 VRF(更容易处理服务套接字)中设置“mgmt”VRF,并在称为“防火墙”的 VRF 中设置路由 VRF。
该图可以总结如下:
“管理”网络为 192.168.100.0/24,由 L3 交换机路由,该交换机通过网络 10.254.5.0/24 与路由器的“防火墙”VRF 具有 L3。第三个路由器接口是“互联网”接口,通过它的数据包经过源 NAT。此设置对于管理子网中的所有内容都运行良好,但路由器自己的数据包除外,这是由于 conntrack 的原因。
关于 iptables 规则:
# Table filter
# chain INPUT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
(some INPUT rules, for ssh, snmp, etc)
-A INPUT -j DROP
# chain FORWARD
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -m conntrack --ctstate INVALID -j DROP
-A FORWARD -o eth2 -j ACCEPT
-A FORWARD -j DROP
# Table nat
# chain POSTROUTING
-A POSTROUTING -o eth2 -j SNAT --to-source 192.168.122.100
关于路由表:
# default VRF
default via 192.168.100.1 dev eth0 proto static metric 20
192.168.100.0/24 dev eth0 proto kernel scope link src 192.168.100.90
# firewall VRF
default via 192.168.122.1 dev eth2 proto static metric 20
10.254.5.0/24 dev eth1 proto kernel scope link src 10.254.5.2
192.168.100.0/24 proto bgp metric 20 nexthop via 10.254.5.10 dev eth1 weight 1
192.168.122.0/24 dev eth2 proto kernel scope link src 192.168.122.100
因此,当来自默认 VRF 的数据包尝试访问互联网时,它会离开 eth0,由 L3 交换机路由,通过 eth1 进入防火墙 VRF,并通过 eth2 路由和 NAT。由于我跟踪 INPUT 和 FORWARD 连接,因此当数据包返回时,conntrack 有点困惑,无法知道该如何处理该数据包。
我可以通过在原始表中使用 conntrack zone 来修复 ICMP 和 UDP 的问题
# Table raw
# chain PREROUTING
-A PREROUTING -i eth0 -j CT --zone 5
# chain OUTPUT
-A OUTPUT -o eth0 -j CT --zone 5
有了这些规则,从路由器发出并经过的数据包eth0
都会被标记zone 5
,并且当数据包进入时eth0
也会被标记zone 5
。
对 8.8.8.8 进行 ping 操作后,结果如下所示(使用命令conntrack -E
):
[NEW] icmp 1 30 src=192.168.100.90 dst=8.8.8.8 type=8 code=0 id=1999 [UNREPLIED] src=8.8.8.8 dst=192.168.100.90 type=0 code=0 id=1999 zone=5
[NEW] icmp 1 30 src=192.168.100.90 dst=8.8.8.8 type=8 code=0 id=1999 [UNREPLIED] src=8.8.8.8 dst=192.168.122.100 type=0 code=0 id=1999
[UPDATE] icmp 1 30 src=192.168.100.90 dst=8.8.8.8 type=8 code=0 id=1999 src=8.8.8.8 dst=192.168.122.100 type=0 code=0 id=1999
[UPDATE] icmp 1 30 src=192.168.100.90 dst=8.8.8.8 type=8 code=0 id=1999 src=8.8.8.8 dst=192.168.100.90 type=0 code=0 id=1999 zone=5
我们可以看到,NEW
当数据包带标签通过时,会创建第一个连接eth0
,zone=5
当数据包不带标签进入防火墙 VRF 时,会创建一个新eth1
连接。当收到答复时,会先更新第二个连接(因为它是面向互联网的连接),然后再更新第一个连接。
这也适用于 UDP,例如对 8.8.8.8 进行 DNS 查询
[NEW] udp 17 30 src=192.168.100.90 dst=8.8.8.8 sport=53369 dport=53 [UNREPLIED] src=8.8.8.8 dst=192.168.100.90 sport=53 dport=53369 zone=5
[NEW] udp 17 30 src=192.168.100.90 dst=8.8.8.8 sport=53369 dport=53 [UNREPLIED] src=8.8.8.8 dst=192.168.122.100 sport=53 dport=53369
[UPDATE] udp 17 30 src=192.168.100.90 dst=8.8.8.8 sport=53369 dport=53 src=8.8.8.8 dst=192.168.122.100 sport=53 dport=53369
[UPDATE] udp 17 30 src=192.168.100.90 dst=8.8.8.8 sport=53369 dport=53 src=8.8.8.8 dst=192.168.100.90 sport=53 dport=53369 zone=5
但使用 TCP 则不行。对 172.16.10.10 端口 80 的 telnet 查询如下所示:
[NEW] tcp 6 120 SYN_SENT src=192.168.100.90 dst=172.16.10.10 sport=60234 dport=80 [UNREPLIED] src=172.16.10.10 dst=192.168.100.90 sport=80 dport=60234 zone=5
[NEW] tcp 6 120 SYN_SENT src=192.168.100.90 dst=172.16.10.10 sport=60234 dport=80 [UNREPLIED] src=172.16.10.10 dst=192.168.122.100 sport=80 dport=60234
[UPDATE] tcp 6 58 SYN_RECV src=192.168.100.90 dst=172.16.10.10 sport=60234 dport=80 src=172.16.10.10 dst=192.168.122.100 sport=80 dport=60234
[UPDATE] tcp 6 57 SYN_RECV src=192.168.100.90 dst=172.16.10.10 sport=60234 dport=80 src=172.16.10.10 dst=192.168.122.100 sport=80 dport=60234
(The last line repeat multiple times)
如果我使用 tcpdumpeth2
得到答案,则会出现:
IP 192.168.122.100.60236 > 172.16.10.10.80: Flags [S], seq 4203590660, win 62720, options [mss 1460,sackOK,TS val 1511828881 ecr 0,nop,wscale 7], length 0
IP 172.16.10.10.80 > 192.168.122.100.60236: Flags [S.], seq 3672808466, ack 4203590661, win 65535, options [mss 1430,sackOK,TS val 2474659117 ecr 1511828881,nop,wscale 8], length 0
IP 192.168.122.100.60236 > 172.16.10.10.80: Flags [S], seq 4203590660, win 62720, options [mss 1460,sackOK,TS val 1511829887 ecr 0,nop,wscale 7], length 0
IP 172.16.10.10.80 > 192.168.122.100.60236: Flags [S.], seq 3672808466, ack 4203590661, win 65535, options [mss 1430,sackOK,TS val 2474660123 ecr 1511828881,nop,wscale 8], length 0
但由于 SIN ACK 从未被确认,路由器继续发送新的 SIN。
现在,如果我使用 tcpdump eth1
:
IP 192.168.100.90.60238 > 172.16.10.10.80: Flags [S], seq 3124513394, win 62720, options [mss 1460,sackOK,TS val 1511928806 ecr 0,nop,wscale 7], length 0
IP 192.168.100.90.60238 > 172.16.10.10.80: Flags [S], seq 3124513394, win 62720, options [mss 1460,sackOK,TS val 1511929823 ecr 0,nop,wscale 7], length 0
IP 192.168.100.90.60238 > 172.16.10.10.80: Flags [S], seq 3124513394, win 62720, options [mss 1460,sackOK,TS val 1511931839 ecr 0,nop,wscale 7], length 0
我们可以看到答案从未被路由回192.168.100.90。
如果我禁用连接跟踪并允许 iptables 中的所有内容,它就会正常工作。所以我认为 conntrack 在管理从自身到另一个区域的 TCP 连接时遇到了麻烦,当它们是 NAT 时?如果有什么不清楚的地方,我很乐意回答有关这方面的任何问题。
答案1
该问题出现在内核为 4.19.0-12-amd64 的 debian 10 上,但升级到内核为 5.10.0-11-amd64 的 debian 11 后,它可以正常运行,甚至对于 TCP 流也是如此。