路由器UDP端口转发无效, 竟然是conntrack的问题

问题现象

需求是将服务的UDP流量从机器A切换到机器B. 路由器操作前机器A和机器B相关服务已准备就绪. 切换期间上游会一直有流量过来.

路由器设置端口转发, UDP协议, 外部端口保持不变的情况下改变内部IP, 保存后不生效, UDP包仍然会发送到原来的内网IP.

在机器A上运行tcpdump:

1
2
06:13:05.130930 IP (tos 0x28, ttl 61, id 60828, offset 0, flags [none], proto UDP (17), length 176)
##.##.##.##.51820 > ##.##.##.##.51820: UDP, length 148

注意此时机器A上已没有服务监听在目标端口, 已通过iptables DROP来源包, 否则会有ICMP不可达报文

1
2
3
4
05:48:51.052956 IP (tos 0xc8, ttl 64, id 24675, offset 0, flags [none], proto ICMP (1), length 204)
##.##.##.## > ##.##.##.##: ICMP ##.##.##.## udp port 51820 unreachable, length 184
IP (tos 0x28, ttl 61, id 15781, offset 0, flags [none], proto UDP (17), length 176)
##.##.##.##.51820 > ##.##.##.##.51820: UDP, length 148

在机器B(新的目标机)运行tcpdump接收不到包.

问题排查

机器A和B本地排查无果, 登录路由器进行排查.

运行conntrack -L:

1
2
udp      17 47 src=[远端机器的IP] dst=[路由器公网侧IP] sport=51820 dport=53820 src=[机器A的IP] dst=[远端机器的IP] sport=51820 dport=51820 [ASSURED] mark=0 use=1
conntrack v1.4.5 (conntrack-tools): 686 flow entries have been shown.

运行iptables -t nat -vnL

1
0    0 DNAT       udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:53820 to:192.168.50.3:53820

可以看到端口转发配置是生效的, 但是因为有conntrack规则的存在所以后续来的包并没有被当成”新的链接”走iptables, 而是继续按照conntrack中的规则进行转发.

这里比较怀疑这个特定版本的conntrack可能经过魔改, 查到的标准是180秒内如果没有回包, conntrack规则就应该被移除. 但实际上在路由器上可以看到:

1
2
3
4
5
6
udp      17 1 src=##.##.##.## dst=##.##.##.## sport=51820 dport=53820 src=##.##.##.## dst=##.##.##.## sport=51820 dport=51820 [ASSURED] mark=0 use=1
conntrack v1.4.5 (conntrack-tools): 710 flow entries have been shown.
udp 17 0 src=##.##.##.## dst=##.##.##.## sport=51820 dport=53820 src=##.##.##.## dst=##.##.##.## sport=51820 dport=51820 [ASSURED] mark=0 use=1
conntrack v1.4.5 (conntrack-tools): 710 flow entries have been shown.
udp 17 179 src=##.##.##.## dst=##.##.##.## sport=51820 dport=53820 src=##.##.##.## dst=##.##.##.## sport=51820 dport=51820 [ASSURED] mark=0 use=1
conntrack v1.4.5 (conntrack-tools): 707 flow entries have been shown.

很明显, 规则定时归0时没有被删除而是重置了倒计时.

解决方案

在路由器上, 使用命令删除这条规则: conntrack -D -p udp --dport 53820

删除后马上在机器B的tcpdump上就可以看到来自远端机器的UDP流量了.

参考

portmap: delete UDP conntrack entries on teardown · Issue #123 · containernetworking/plugins

在CNI github issue里找到了一个类似的问题, 带udp端口转发的pod移除的时候需要手动调conntrack删除掉NAT规则, 否则流量将无法分配到新的pod上. 但这种场景里路由器(上层NAT设备)一般是不在控制范围内的, 可能除了在远端发起换端口之外没有任何办法了…

netfilter: Kill unreplied conntracks by ICMP errors

这里有一个patch提议说可以用ICMP错误回包来剔除掉netfilter conntrack里无效的规则, 但应该没有被merge到linux kernel里.

Linux Packet Filtering and iptables - 7.5. UDP connections

Iptables Tutorial 1.2.1 - 7.5. UDP connections

The conntrack-tools user manual

Conntrack tales - one thousand and one flows

Connection Tracking (conntrack): Design and Implementation Inside Linux Kernel