我的最终目标是阻止具有指定目标 IPv6 地址的所有 TCP IPv6 片段(假设它是 google.com 地址之一2a00:1450:4010:c0e::79
)。
主机将该流量从 Docker 容器中的应用程序转发到上游接口上可用的下一个路由器(假设它是eth0
)。
问题是,
ip6tables -t filter -A FORWARD -d 2a00:1450:4010:c0e::79/128 -o eth0 -m ipv6header --header ipv6-frag --soft -j DROP
或
ip6tables -t mangle -A POSTROUTING -d 2a00:1450:4010:c0e::79/128 -o eth0 -m ipv6header --header ipv6-frag --soft -j DROP
或使用模块等规则frag
不会过滤碎片数据包:
ubuntu@ip-172-31-11-241:/# sudo tcpdump -ni eth0 -Q out ip6 host 2a00:1450:4010:c0e::79
...
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
...
调查显示,ip6tables 和 tcpdump 对数据包发生的情况有稍许不同:
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
# dmesg after enabling logging with sudo ip6tables -t mangle -A POSTROUTING -d 2a00:1450:4010:c0e::79/128 -o eth0 -j LOG:
ubuntu@ip-172-31-11-241:~$ sudo dmesg -T
...
# packet #1, looks good
[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
# packet #2, this one is unexpected
[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
# packet #3, as well as this one
[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
# packet #4, looks good
[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 FIN URGP=0
...
当 netfilter 记录长度为 1572 的数据包 #2 和 #3 时,会发生一些黑魔法。
首先是数据包 #2。实际上,客户端发送了两个 ipv6 数据包:
- 第一个长度为 1500(1428 + 32 + 40)
- 最后一个长度为 104 (32 + 32 + 40)
第一个被主机丢弃,因为它大于 1450 的 mtu,另一个使用原子 ipv6 片段转发。netfilter 认为它是一包 1572 的镜头!
然后是数据包 #3。客户端从路由器获取 ICMPv6 TOO_BIG 后,形成 2 个新的数据包:
- 第一个长度为 1450(1378 + 32 + 40)
- 另一个长度为 194(122 + 32 + 40)
现在两个数据包都通过了 MTU,因此它们被转发,并出现在 tcpdump 输出中。然而 netfilter 认为它一个数据包长度为1572!
似乎 netfilter 适用于聚合数据包(TSO?GSO?),并且根本看不到 IPv6 片段标头。这种行为有什么解释吗?我如何过滤主机发送的实际数据包?
UPD1:禁用 TSO (tcp-segmentation-offload)容器内部使根 netns 中的 netfilter 在两种情况下都看到两个大小正确的数据包,而不是一个长度为 1572 的数据包。此外,在第一种情况下,内核不会创建原子 ipv6 片段。但是,原始问题仍然相关。