修网日志:一个 Linux 路由器的 Conntrack 表因为 BT 满了,这是它发生的奇怪变化

· 384 words · 2 minute read

本文转载自个人 Telegram 频道 2022 年 8 月 18 日的 Post。

背景 #

有路由器通过 iptables mangle PREROUTING 给进来的包 MARK fwmark 并 connmark SAVE,并在返回包的时候 connmark RESTORE 并切换不同的 fwmark 来进行策略路由。(需求是,有多个 IPv4 出口,都是 VPN,且在远端有端口转发。要求回包走同一个出口。全文和 IPv6 无关。)本地 LAN 是 10.16.1.0/24 。路由器有多个 NIC(VPN 有 yvr 和 sea;Ethernet 有 guest 和 mgmt)。

iptables mangle 供参考:

-A PREROUTING -i guest -j CONNMARK --restore-mark # 回包时,读取 conntrack 中的 fwmark
-A PREROUTING -d 10.16.1.0/24 -i yvr -m mark --mark 0x0 -j MARK --set-xmark 0x1e # 从出口 YVR 进来的新包(mark = 0),标记 fwmark 0x1e,并保存进 conntrack
-A PREROUTING -d 10.16.1.0/24 -i sea -m mark --mark 0x0 -j MARK --set-xmark 0x1f # 同上,但适用于从 SEA 进来的包,标记 0x1f
-A PREROUTING -d 10.16.1.0/24 -j CONNMARK --save-mark # 将 fwmark 保存进 connmark

# 下面的只有回包或者出去会用
-A PREROUTING -i guest -m mark --mark 0x1e -j MARK --set-xmark 0x14 # 若标记为 0x1e(从 YVR 进来),则改标 0x14(接策略路由)
-A PREROUTING -i guest -m mark --mark 0x1f -j MARK --set-xmark 0x16 # 同上,若从 SEA 过来,则改标 0x16

-A PREROUTING -i guest -m mark --mark 0x0 -j MARK --set-xmark 0x14 # 若没有标记,也就是从 LAN 出去的包,标 0x14

连接该路由器 LAN 的是一个交换机,上面跑了一些 VM。我的 VM 跑了 bt 下载,其他 VM 也有多少使用 IPv4 上网或者接请求,皆使用上面的路由器的 iptables 规则来出入。

现象 #

前几天的早上和今天早上分别出现了同一个问题,现象如下:

  1. 从 VPN 远端连接本地 LAN 内机器,不通
  2. 从 mgmt LAN 连接路由器,不通

遂物理连接路由器,发现满屏都是 conntrack full,日志可见 dhcpd 和 dhcpcd 发包返回 EPERM,ping 127.0.0.1 绝大多数情况下不通且抓不到任何包,IP 地址和 NIC 正常,WireGuard VPN 握手正常,IPv6 BGP 正常,IPv6 通信正常。

问题 #

经过一个小时的分析,发现是由于 conntrack 表满导致的这些现象。因为 conntrack 满后 sendto(2) 会返回 EPERM 且 recvmsg(2) 会返回 EAGAIN(通过观察 strace ping 127.0.0.1 得知)。因此:

  • LAN 机器出去或者远端进来都不通:因为 conntrack 满,新包会被 drop
  • 路由器 mgmt LAN 不通、ping 127.0.0.1 不通、dhcpd 和 dhcpcd EPERM:因为 conntrack 满,发包返回 EPERM
  • WireGuard 握手正常:因为 UDP 连接较早,因此该连接的 conntrack entry 一直位于表内,即可正常握手。一旦 WireGuard link 被重建、conntrack entry 丢失,再想握手便会由于 conntrack 表满而失败
  • BGP 正常:BGP 工作在 IPv6 上,和 IPv4 无关
  • 有时可以 ping:conntrack 表满了之后由于已有 entry 超时或连接关闭等因素,会腾出空位,此时新 entry 有几率进入

conntrack 的导致问题基本查明,但什么导致了 conntrack 表满呢?观察 conntrack 表可发现,绝大多数都是由我的 VM 建立的 TCP 连接(有 7663 个,限制为 7680)。然而我的机器很明显不会同时打开这么多连接,因此怀疑是由于 iptables mangle 配置不当,导致该移除的 entry 没有被移除,或是不该进入 conntrack 的连接被记录。然而一个偶然的机会,我在我的 VM 上也跑了下 conntrack,发现其中类似的 entries 比路由器上都多(因为我的 VM 内存比路由器大,因此 limit 也大)。里面的 entries 几乎都是 ESTABLISHED TCP,且 Timeout 很高(意味着如果继续 established 则短时间内不会被 conntrack 自动删除),以及它们的 dst 很随机、dport 一般比较高。

是 bt #

随机的 dst 以及较高的 dport 完美符合 BT 连接的特征。这也排除了路由器上 iptables 配置不当导致 conntrack 表包含无用数据的嫌疑。因此可能性也只有一个:TCP 连接关闭后,conntrack 没有将 entry 标记为 CLOSE(默认有很低的 timeout,而 established 则很高),导致一直残留。

接下来的实验也证明了这个猜想:ss -apn4t 中看到的连接数远不及 conntrack 表中的数量,且我亲眼看到 TCP 连接 FIN 了甚至消失了之后(ss(1) 输出),在 conntrack 表中还是 ESTABLISHED。

然后我就不懂了,请教喵教授(

喵喵给出的怀疑是:「 我怀疑 established 之后直接 timeout (peer 消失了)

这个时候系统的网络栈对于链接的处理和 conntrack 不一样 」

我也证明不了对吧,抓这个包太麻烦了

于是就作罢了。

解决方案 #

喵喵的建议是将 conntrack established timeout 降低到 30s。我一开始还怀疑是否会掐断连接,但经过测试后发现一旦有包,conntrack 就会自动重置 timeout,因此可行。

后续 #

BT 一时爽,防火墙火葬场。

参考文献 #

  1. https://blog.cloudflare.com/conntrack-tales-one-thousand-and-one-flows/
  2. https://book.huihoo.com/iptables-tutorial/x4273.htm