ip6tables无法过滤特定目标IPv6地址的TCP分片数据包问题咨询
ip6tables无法过滤特定目标IPv6地址的TCP分片数据包问题咨询
各位大佬好,最近碰到一个ip6tables过滤IPv6分片数据包的棘手问题,试了各种规则都不管用,想请教下大家怎么解决。
我的核心需求
我最终想要实现的是:阻断所有发往特定IPv6地址(比如google.com的2a00:1450:4010:c0e::79)的TCP IPv6分片数据包。这些流量是主机转发来自Docker容器内应用的数据包,最终通过上游接口eth0发往下一跳路由器。
尝试过的无效规则
我先后试了以下几种ip6tables规则,都没法过滤掉目标分片数据包:
# 规则1:在filter表的FORWARD链添加过滤 ip6tables -t filter -A FORWARD -d 2a00:1450:4010:c0e::79/128 -o eth0 -m ipv6header --header ipv6-frag --soft -j DROP # 规则2:在mangle表的POSTROUTING链添加过滤 ip6tables -t mangle -A POSTROUTING -d 2a00:1450:4010:c0e::79/128 -o eth0 -m ipv6header --header ipv6-frag --soft -j DROP # 尝试使用frag模块的规则,同样无效
奇怪的现象:tcpdump与ip6tables日志不一致
为了排查问题,我同时用tcpdump抓包和开启ip6tables日志,结果发现两者看到的数据包状态完全不一样:
tcpdump抓包结果(eth0出口方向)
ubuntu@ip-172-31-11-241:/# sudo tcpdump -ni eth0 -Q out ip6 host 2a00:1450:4010:c0e::79 ... 15:53:31.088278 IP6 2600:1f16:c97:7a02:f52b:8bb4:1751:e72a.37292 > 2a00:1450:4010:c0e::79.443: Flags [.], ack 2909720068, win 507, options [nop,nop,TS val 751022009 ecr 3180988502], length 0 15:53:31.088597 IP6 2600:1f16:c97:7a02:f52b:8bb4:1751:e72a > 2a00:1450:4010:c0e::79: frag (0|104) 37292 > 443: Flags [P.], seq 1428:1500, ack 1, win 507, options [nop,nop,TS val 751022009 ecr 3180988502], length 72 15:53:31.088781 IP6 2600:1f16:c97:7a02:f52b:8bb4:1751:e72a.37292 > 2a00:1450:4010:c0e::79.443: Flags [.], seq 0:1378, ack 1, win 507, options [nop,nop,TS val 751022009 ecr 3180988502], length 1378 15:53:31.088798 IP6 2600:1f16:c97:7a02:f52b:8bb4:1751:e72a.37292 > 2a00:1450:4010:c0e::79.443: Flags [P.], seq 1378:1500, ack 1, win 507, options [nop,nop,TS val 751022009 ecr 3180988502], length 122 15:53:31.089023 IP6 2600:1f16:c97:7a02:f52b:8bb4:1751:e72a.37292 > 2a00:1450:4010:c0e::79.443: Flags [F.], seq 1500, ack 1, win 507, options [nop,nop,TS val 751022010 ecr 3180988502], length 0
ip6tables日志(开启POSTROUTING链LOG后的dmesg输出)
ubuntu@ip-172-31-11-241:~$ sudo dmesg -T ... # 包1:显示正常 [Mon Nov 27 15:53:33 2023] IN=docker0 OUT=eth0 PHYSIN=veth9c34169 MAC=02:42:49:39:59:c0:02:42:ac:11:00:02:86:dd SRC=fc00:0000:0000:0000:0000:0242:ac11:0002 DST=2a00:1450:4010:0c0e:0000:0000:0000:0079 LEN=72 TC=0 HOPLIMIT=63 FLOWLBL=191995 PROTO=TCP SPT=37292 DPT=443 WINDOW=507 RES=0x00 ACK URGP=0 # 包2:异常,日志显示长度为1572 [Mon Nov 27 15:53:33 2023] IN=docker0 OUT=eth0 PHYSIN=veth9c34169 MAC=02:42:49:39:59:c0:02:42:ac:11:00:02:86:dd SRC=fc00:0000:0000:0000:0000:0242:ac11:0002 DST=2a00:1450:4010:0c0e:0000:0000:0000:0079 LEN=1572 TC=0 HOPLIMIT=63 FLOWLBL=191995 PROTO=TCP SPT=37292 DPT=443 WINDOW=507 RES=0x00 ACK PSH URGP=0 # 包3:同样异常,长度显示1572 [Mon Nov 27 15:53:33 2023] IN=docker0 OUT=eth0 PHYSIN=veth9c34169 MAC=02:42:49:39:59:c0:02:42:ac:11:00:02:86:dd SRC=fc00:0000:0000:0000:0000:0242:ac11:0002 DST=2a00:1450:4010:0c0e:0000:0000:0000:0079 LEN=1572 TC=0 HOPLIMIT=63 FLOWLBL=191995 PROTO=TCP SPT=37292 DPT=443 WINDOW=507 RES=0x00 ACK PSH URGP=0 # 包4:显示正常 [Mon Nov 27 15:53:33 2023] IN=docker0 OUT=eth0 PHYSIN=veth9c34169 MAC=02:42:49:39:59:c0:02:42:ac11:00:02:86:dd SRC=fc00:0000:0000:0000:0000:0242:ac11:0002 DST=2a00:1450:4010:0c0e:0000:0000:0000:0079 LEN=72 TC=0 HOPLIMIT=63 FLOWLBL=191995 PROTO=TCP SPT=37292 DPT=443 WINDOW=507 RES=0x00 ACK FIN URGP=0
问题细节分析
我仔细对比了两种输出,发现两个关键场景的异常:
- 第一次场景:客户端发送两个IPv6包(一个1500字节,一个104字节),因为第一个包超过MTU1450被主机丢弃,第二个包被作为原子IPv6分片转发,但netfilter日志里却把这两个包合并成了一个1572字节的包;
- 第二次场景:客户端收到路由器的ICMPv6 TOO_BIG后,拆成两个符合MTU的包(1450字节和194字节),tcpdump里能清晰看到这两个独立的包,但netfilter日志里还是显示成一个1572字节的包;
我猜测这可能是因为netfilter在处理聚合后的数据包(比如TSO/GSO),导致它根本看不到实际的IPv6分片头?
临时 workaround 但未解决根本问题(UPD1)
我试着在容器内禁用TSO(tcp-segmentation-offload)后,根命名空间的netfilter就能看到正确的两个包,而且内核也不会生成原子IPv6分片了。但这只是临时的解决方案,我还是想搞清楚:
- 为什么会出现netfilter和tcpdump视角不一致的情况?
- 有没有办法不用禁用TSO,就能让ip6tables正确过滤主机实际发送的分片数据包?
备注:内容来源于stack exchange,提问作者Kamil Zaripov




