在 Cloudflare,我们以极快的速度开发新产品。这些产品的需求经常挑战我们过去做出的架构假设。例如,几年前我们决定避免使用 Linux 的 "conntrack" - 有状态防火墙设施。这带来了很大的好处 - 它简化了我们的 iptables 防火墙设置,稍微加速了系统,并使入站数据包路径更容易理解。

但最终我们的需求发生了变化。我们的一个新产品对它有了合理的需求。但我们不确定 - 我们能否直接启用 conntrack 然后继续?它实际上是如何工作的?我自愿帮助团队理解 "conntrack" 子系统的黑暗角落。

什么是 conntrack?

"Conntrack" 是 Linux 网络栈的一部分,特别是防火墙子系统的一部分。为了理解这一点:早期的防火墙完全是无状态的。它们只能表达基本的逻辑,比如:允许到端口 80 和 443 的 SYN 数据包,阻止其他所有内容。

无状态设计提供了一些基本的网络安全,但很快被认为不够。你看,有些事情无法以无状态的方式表达。典型的例子是评估 ACK 数据包 - 如果不跟踪连接状态,就无法判断 ACK 数据包是合法的还是端口扫描尝试的一部分。

为了填补这些空白,所有操作系统都在其防火墙中实现了连接跟踪。这种跟踪通常实现为一个大表,至少有 6 列:协议(通常是 TCP 或 UDP)、源 IP、源端口、目标 IP、目标端口和连接状态。在 Linux 上,这个子系统被称为 "conntrack",通常默认启用。以下是在我的笔记本电脑上使用 "conntrack -L" 命令检查的表格外观:

Details
marek@mrn:~$ sudo conntrack -L
tcp      6 431995 ESTABLISHED src=192.168.8.144 dst=85.10.202.207 sport=53370 dport=443 src=85.10.202.207 dst=192.168.8.144 sport=443 dport=53370 [ASSURED] mark=0 use=1
tcp      6 431996 ESTABLISHED src=192.168.8.144 dst=216.58.201.142 sport=53838 dport=443 src=216.58.201.142 dst=192.168.8.144 sport=443 dport=53838 [ASSURED] mark=0 use=1
tcp      6 431993 ESTABLISHED src=192.168.8.144 dst=216.58.201.131 sport=58700 dport=443 src=216.58.201.131 dst=192.168.8.144 sport=443 dport=58700 [ASSURED] mark=0 use=1
tcp      6 431977 ESTABLISHED src=192.168.8.144 dst=216.58.201.131 sport=58436 dport=443 src=216.58.201.131 dst=192.168.8.144 sport=443 dport=58436 [ASSURED] mark=0 use=1
tcp      6 431968 ESTABLISHED src=192.168.8.144 dst=216.58.201.138 sport=58936 dport=443 src=216.58.201.138 dst=192.168.8.144 sport=443 dport=58936 [ASSURED] mark=0 use=1
tcp      6 431997 ESTABLISHED src=192.168.8.144 dst=64.233.184.189 sport=39562 dport=443 src=64.233.184.189 dst=192.168.8.144 sport=443 dport=39562 [ASSURED] mark=0 use=1
tcp      6 431979 ESTABLISHED src=192.168.8.144 dst=172.217.168.174 sport=51672 dport=443 src=172.217.168.174 dst=192.168.8.144 sport=443 dport=51672 [ASSURED] mark=0 use=1
tcp      6 263 ESTABLISHED src=192.168.8.144 dst=216.58.211.37 sport=36428 dport=443 src=216.58.211.37 dst=192.168.8.144 sport=443 dport=36428 [ASSURED] mark=0 use=1
tcp      6 431991 ESTABLISHED src=192.168.8.144 dst=172.217.168.174 sport=51570 dport=443 src=172.217.168.174 dst=192.168.8.144 sport=443 dport=51570 [ASSURED] mark=0 use=1
tcp      6 431996 ESTABLISHED src=192.168.8.144 dst=147.135.78.157 sport=59234 dport=443 src=147.135.78.157 dst=192.168.8.144 sport=443 dport=59234 [ASSURED] mark=0 use=1
tcp      6 273 ESTABLISHED src=192.168.8.144 dst=172.217.17.10 sport=46478 dport=443 src=172.217.17.10 dst=192.168.8.144 sport=443 dport=46478 [ASSURED] mark=0 use=1
tcp      6 431996 ESTABLISHED src=192.168.8.144 dst=216.58.201.131 sport=59140 dport=443 src=216.58.201.131 dst=192.168.8.144 sport=443 dport=59140 [ASSURED] mark=0 use=1
tcp      6 431993 ESTABLISHED src=192.168.8.144 dst=52.44.211.134 sport=42430 dport=443 src=52.44.211.134 dst=192.168.8.144 sport=443 dport=42430 [ASSURED] mark=0 use=1
tcp      6 201 ESTABLISHED src=192.168.8.144 dst=172.217.16.238 sport=52550 dport=443 src=172.217.16.238 dst=192.168.8.144 sport=443 dport=52550 [ASSURED] mark=0 use=1
tcp      6 299 ESTABLISHED src=192.168.8.144 dst=74.125.140.188 sport=43698 dport=443 src=74.125.140.188 dst=192.168.8.144 sport=443 dport=43698 [ASSURED] mark=0 use=1
tcp      6 263 ESTABLISHED src=192.168.8.144 dst=74.125.140.188 sport=43592 dport=5228 src=74.125.140.188 dst=192.168.8.144 sport=5228 dport=43592 [ASSURED] mark=0 use=1
conntrack v1.4.4 (conntrack-tools): 17 flow entries have been shown.

显而易见的问题是这个状态跟踪表可以有多大。这个设置在 "/proc/sys/net/nf_conntrack_max" 下:

$ cat /proc/sys/net/nf_conntrack_max
262144

这是一个全局设置,但限制是按容器划分的。在我的系统上,每个容器或 "网络命名空间" 最多可以有 256K 个 conntrack 条目。

当并发连接数超过 conntrack 限制时会发生什么?

测试 conntrack 很困难

过去测试 conntrack 很困难 - 它需要复杂的硬件或虚拟机设置。幸运的是,现在我们可以使用现代的 "user namespace" 设施,它们可以进行权限魔术,允许非特权用户感觉像 root 一样。使用 "unshare" 工具,可以创建一个隔离的环境,我们可以精确控制通过的数据包,并在不威胁主机系统健康的情况下试验 iptables 和 conntrack。通过适当的参数,可以创建和管理网络命名空间,包括访问命名空间的 iptables 和 conntrack,而无需特权用户。

这个脚本是我们测试的核心:

# 启用 tun 接口
ip tuntap add name tun0 mode tun
ip link set tun0 up
ip addr add 192.0.2.1 peer 192.0.2.2 dev tun0
ip route add 0.0.0.0/0 via 192.0.2.2 dev tun0

# 至少引用一次 conntrack 以确保它已启用
iptables -t raw -A PREROUTING -j CT
# 在 mangle 表中创建一个计数器
iptables -t mangle -A PREROUTING
# 确保反向流量不影响 conntrack 状态
iptables -t raw -A OUTPUT -p tcp --sport 80 -j DROP

tcpdump -ni any -B 16384 -ttt &
...
./venv/bin/python3 send_syn.py

conntrack -L
# 显示 iptables 计数器
iptables -nvx -t raw -L PREROUTING
iptables -nvx -t mangle -L PREROUTING

为了可读性,这个 bash 脚本被缩短了。完整版本请参见 完整版本。附带的 "send_syn.py" 只是通过 "tun0" 接口发送 10 个 SYN 数据包。这是 源代码,但让我在这里粘贴它 - 展示 "scapy" 总是很有趣:

tun = TunTapInterface("tun0", mode_tun=True)
tun.open()

for i in range(10000,10000+10):
    ip=IP(src="198.18.0.2", dst="192.0.2.1")
    tcp=TCP(sport=i, dport=80, flags="S")
    send(ip/tcp, verbose=False, inter=0.01, socket=tun)

上面的 bash 脚本包含一些精华。让我们来分析一下。

首先,请注意我们不能直接使用 SOCK_RAW 套接字将数据包注入到环回接口。Linux 网络栈是一个复杂的野兽。通过 SOCK_RAW 发送数据包的语义与通过真实接口传递数据包不同。我们稍后会讨论这个问题,但现在,为了避免触发意外行为,我们将通过 tun/tap 设备传递数据包,它更好地模拟真实接口。

然后我们需要确保 conntrack 在我们希望用于测试的网络命名空间中是活动的。传统上,只需加载内核模块就可以了,但在容器和网络命名空间的勇敢新世界中,必须找到一种方法来允许 conntrack 在某些容器中处于活动状态,而在其他容器中处于非活动状态。因此,这与使用相关 - 引用 conntrack 的规则必须存在于命名空间的 iptables 中,才能在容器内激活 conntrack。

作为旁注, 容器触发主机加载内核模块是一个有趣的 主题

在 "-t raw -A PREROUTING" 规则之后,我们添加了 "-t mangle -A PREROUTING" 规则,但注意 - 它没有任何动作!iptables 允许这种语法,这对于让 iptables 报告规则计数器非常有用。我们很快就会需要这些计数器。细心的读者可能会建议查看 "policy" 计数器,但不幸的是,"policy" 计数器(每个进入链的数据包都会增加)只有在其中至少有一条规则时才有效。

其余步骤是不言自明的。我们在后台设置 "tcpdump",发送 10 个 SYN 数据包到 127.0.0.1:80,然后打印 conntrack 表和 iptables 计数器。

让我们运行这个脚本。记住要在网络命名空间中以假 root 身份运行,使用 "unshare -Ur -n":

Details
marek:~$ unshare -Ur -n bash test-1.bash
[+] tcpdump
00:00:00.000000 IP 198.18.0.2.10000 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.002373 IP 198.18.0.2.10001 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.011834 IP 198.18.0.2.10002 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.021834 IP 198.18.0.2.10003 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.031834 IP 198.18.0.2.10004 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.041834 IP 198.18.0.2.10005 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.051834 IP 198.18.0.2.10006 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.061834 IP 198.18.0.2.10007 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.071834 IP 198.18.0.2.10008 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.081834 IP 198.18.0.2.10009 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
[+] conntrack
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10000 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10000 mark=0 use=1
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10001 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10001 mark=0 use=1
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10002 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10002 mark=0 use=1
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10003 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10003 mark=0 use=1
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10004 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10004 mark=0 use=1
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10005 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10005 mark=0 use=1
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10006 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10006 mark=0 use=1
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10007 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10007 mark=0 use=1
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10008 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10008 mark=0 use=1
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10009 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10009 mark=0 use=1
conntrack v1.4.4 (conntrack-tools): 10 flow entries have been shown.
[+] iptables -t raw
Chain PREROUTING (policy ACCEPT 10 packets, 400 bytes)
    pkts      bytes target     prot opt in     out     source               destination
      10       400 CT         all  --  *      *       0.0.0.0/0            0.0.0.0/0
[+] iptables -t mangle
Chain PREROUTING (policy ACCEPT 10 packets, 400 bytes)
    pkts      bytes target     prot opt in     out     source               destination
      10       400            all  --  *      *       0.0.0.0/0            0.0.0.0/0

这很好。首先我们看到 "tcpdump" 列表显示 10 个 SYN 数据包。然后我们看到 conntrack 表状态,显示创建了 10 个流。最后,我们看到在我们创建的两个规则中,每个都显示处理了 10 个数据包。

conntrack 表会填满吗?

鉴于 conntrack 表的大小有限,当它填满时会发生什么?让我们来检查一下。首先,我们需要减少 conntrack 的大小。如前所述,它由主机端的全局开关控制 - 需要在主机端调整它。让我们将表大小减少到 7 个条目,然后重复我们的测试:

Details
marek:~$ echo 7 | sudo tee /proc/sys/net/netfilter/nf_conntrack_max
7
marek:~$ unshare -Ur -n bash test-1.bash
[+] tcpdump
00:00:00.000000 IP 198.18.0.2.10000 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.001762 IP 198.18.0.2.10001 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.011763 IP 198.18.0.2.10002 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.021763 IP 198.18.0.2.10003 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.031763 IP 198.18.0.2.10004 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.041763 IP 198.18.0.2.10005 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.051763 IP 198.18.0.2.10006 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.061763 IP 198.18.0.2.10007 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.071763 IP 198.18.0.2.10008 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
00:00:00.081763 IP 198.18.0.2.10009 > 192.0.2.1.80: Flags [S], seq 0, win 8192, length 0
[+] conntrack
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10003 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10003 mark=0 use=1
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10004 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10004 mark=0 use=1
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10005 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10005 mark=0 use=1
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10006 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10006 mark=0 use=1
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10007 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10007 mark=0 use=1
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10008 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10008 mark=0 use=1
tcp      6 119 SYN_SENT src=198.18.0.2 dst=192.0.2.1 sport=10009 dport=80 [UNREPLIED] src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10009 mark=0 use=1
conntrack v1.4.4 (conntrack-tools): 7 flow entries have been shown.
[+] iptables -t raw
Chain PREROUTING (policy ACCEPT 10 packets, 400 bytes)
    pkts      bytes target     prot opt in     out     source               destination
      10       400 CT         all  --  *      *       0.0.0.0/0            0.0.0.0/0
[+] iptables -t mangle
Chain PREROUTING (policy ACCEPT 7 packets, 280 bytes)
    pkts      bytes target     prot opt in     out     source               destination
       7       280            all  --  *      *       0.0.0.0/0            0.0.0.0/0
marek:~$ echo 262144 | sudo tee /proc/sys/net/netfilter/nf_conntrack_max
262144

这变得有趣了。我们仍然看到 10 个入站 SYN 数据包。我们仍然看到 "-t raw PREROUTING" 表收到了 10 个数据包,但这就是相似之处的终点。"-t mangle PREROUTING" 表只看到了 7 个数据包。那三个缺失的 SYN 数据包去哪了?

事实证明,它们去了所有死亡数据包的地方。它们被硬丢弃了。当溢出时,Conntrack 就会这样做。它甚至在 "dmesg" 中抱怨:

[32984.896657] nf_conntrack: nf_conntrack: table full, dropping packet
[32984.896676] nf_conntrack: nf_conntrack: table full, dropping packet
[32984.896693] nf_conntrack: nf_conntrack: table full, dropping packet

这由我们的 iptables 计数器确认。让我们回顾一下著名的 iptables 图:

如我们所见,"-t raw PREROUTING" 发生在 conntrack 之前,而 "-t mangle PREROUTING" 就在它之后。这就是为什么我们看到 10 个和 7 个数据包被我们的 iptables 计数器报告。

让我强调我们发现的严重性。我们展示了三个完全有效的 SYN 数据包被 "conntrack" 隐式丢弃。没有明确的 "-j DROP" iptables 规则。没有可以切换的配置。仅仅使用 "conntrack" 就意味着,当它满了时,创建新流的数据包将被丢弃。不问任何问题。

这就是使用 conntrack 的黑暗面。如果你使用它,你绝对必须确保它不会被填满。

我们可以在这里结束调查,但还有一些有趣的注意事项。

严格模式与宽松模式

Conntrack 支持 "严格" 和 "宽松" 模式,由 "nf_conntrack_tcp_loose" 开关配置。

$ cat /proc/sys/net/netfilter/nf_conntrack_tcp_loose
1

默认情况下,它设置为 "宽松",这意味着对于未见过的 TCP 流的零散 ACK 数据包将在表中创建新的流条目。我们可以概括:"conntrack" 将隐式丢弃所有创建新流的数据包,无论是 SYN 还是只是零散的 ACK。

当我们清除“nf_conntrack_tcp_loose=0”设置时会发生什么?这是一个另一个博客文章的主题,但简而言之——一团糟。首先,此设置不能在网络命名空间范围内设置——尽管它应该可以。要测试它,您需要处于根网络命名空间中。然后,由于复杂的逻辑,ACK 将在 conntrack 表满时被丢弃,尽管在这种情况下它不会创建流。如果表不满,ACK 数据包将穿过它,从“mangle”表开始具有“-ctstate INVALID”。

什么时候 conntrack 条目不会创建

在一些条件下 conntrack 条目不会被创建。例如,我们可以在脚本中替换掉这些行:

# Make sure reverse traffic doesn't affect conntrack state
iptables -t raw -A OUTPUT -p tcp --sport 80 -j DROP

替换为:

# Make sure inbound SYN packets don't go to networking stack
iptables -A INPUT -j DROP

我们可能会天真地认为,在 conntrack 层之后丢弃 SYN 数据包不会干扰已创建的流量。这并不正确。尽管这些 SYN 数据包已经被 conntrack 看到,但并不会为它们创建流量状态。遇到“-j DROP”的包不会创建新的 conntrack 流量。这真是太神奇了,不是吗?

全局 Conntrack 导致 EPERM 错误

最近我们遇到了一个案例,其中一个应用程序的 UDP 套接字的“sendto()”系统调用出错,错误码为 EPERM。这相当奇怪,而且在 man 页面上也没有记录。我的同事毫不犹豫地:

image9 1

我不会详细描述这些恐怖的细节,但确实,完整的 conntrack 表会对你的新 UDP 流这样做——你会得到 EPERM 权限。小心。有趣的是,如果出站数据包以其他方式在 OUTPUT 防火墙中被丢弃,也可能得到 EPERM 权限。例如:

marek:~$ sudo iptables -I OUTPUT -p udp --dport 53 --dst 192.0.2.8 -j DROP
marek:~$ strace -e trace=write nc -vu 192.0.2.8 53
write(3, "X", 1)                        = -1 EPERM (Operation not permitted)
+++ exited with 1 +++

如果你从"sendto()"接收到 EPERM,你可能想将其视为暂时性错误,如果你怀疑是 conntrack 已满的问题,或者如果你认为是 iptables 配置错误,则可能是一个永久性错误。

这也是为什么我们无法直接使用 SOCK_RAW 套接字发送我们的 SYN 数据包进行测试的原因。让我们用标准的"hping3"工具看看 conntrack 溢出会发生什么:

$ hping3 -S -i u10000 -c 10 --spoof 192.18.0.2 192.0.2.1 -p 80 -I lo
HPING 192.0.2.1 (lo 192.0.2.1): S set, 40 headers + 0 data bytes
[send_ip] sendto: Operation not permitted

即使在 SOCK_RAW 套接字上,当 conntrack 表满时,"send()"也会失败并返回 EPERM。

Full conntrack 可能在 SYN 泛洪攻击中出现

还有一个需要注意的地方。在 SYN 洪水攻击期间,conntrack 条目将全部为伪造的流量创建。看看我们准备的第二个测试用例,这次正确地监听端口 80,并发送 SYN+ACK:

marek:~$ echo 7| sudo tee /proc/sys/net/netfilter/nf_conntrack_max
marek:~$ unshare -Ur -n bash test-2.bash
[*] tcpdump
IP 198.18.0.2.10000 > 192.0.2.1.80: Flags [S], seq O,win 8192,length 0
IP 192.0.2.1.80 > 198.18.0.2.10000: Flags [S.],seq 731838513,ack 1, win 64240,options [mss 1460],length 0
IP 198.18.0.2.10001 > 192.0.2.1.80: Flags [S],seq O,win 8192,Length 0
IP 192.0.2.1.80 > 198.18.0.2.10001: Flags [S.], seq 1458389715,ack 1,win 64240,options [mss 1460],  Length 0
IP 198.18.0.2.10002 > 192.0.2.1.80: Flags [S], seq O,win 8192,Length 0
IP 192.0.2.1.80 > 198.18.0.2.10002: FLags [S.], seq 505759032,ack 1,win 64240,options [mss 1460], Length O
IP 198.18.0.2.10003 > 192.0.2.1.80: Flags [S], seq O,win 8192,Length 0
IP 192.0.2.1.80 > 198.18.0.2.10003: Flags [S.],seq 1353939747,ack 1,win 64240,options [mss 1460], length 0
IP 198.18.0.2.10004 > 192.0.2.1.80: Flags [S].seq O,win 8192,Length 0
IP 192.0.2.1.80 > 198.18.0.2.10004: Flags [S.],seq 52337575,ack 1,win 64240,options [mss 1460],length o TP 198.18.0.2.10005 > 192.0.2.1.80: FLags [S], seq O,win 8192,Length 0
IP 192.0.2.1.80 > 198.18.0.2.10005: FLags [S.],seq 419812617,ack 1,win 64240,options [mss 1460], length o
IP 198.18.0.2.10006 > 192.0.2.1.80: FLags [S], seq O, win 8192,length 0
IP 192.0.2.1.80 > 198.18.0.2.10006: FLags [S.],seq 2003158651,ack 1,win 64240, options [mss 1460],length 0
IP 198.18.0.2.10007 > 192.0.2.1.80: Flags [S],  seq e, win 8192,  Length 0
IP 198.18.0.2.10008 > 192.0.2.1.80: Flags [S], seq 0,win 8192,length 0
IP 198.18.0.2.10009 > 192.0.2.1.80: Flags [S], seq 0, win 8192,length 0
[*] conntrack
tcp       6 59 SYN RECV src=198.18.0.2 dst=192.0.2.1 sport=10003 dport=80 src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10003 mark=0 use=1
tcp       6 59 SYN RECV src=198.18.0.2 dst=192.0.2.1 sport=10001 dport=80 src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10001 mark=0 use=1
tcp       6 59 SYN RECV src=198.18.0.2 dst=192.0.2.1 sport=10004 dport=80 src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10004 mark=0 use=1
tcp       6 59 SYN RECV src=198.18.0.2 dst=192.0.2.1 sport=10005 dport=80 src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10005 mark=0 use=1
tcp       6 59 SYN RECV src=198.18.0.2 dst=192.0.2.1 sport=10002 dport=80 src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10002 mark=0 uSe=1
tCp       6 59 SYN RECV src=198.18.0.2 dst=192.0.2.1 sport=10000 dport=80 src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10000 mark=0 uSe=1
tcp       6 59 SYN_RECV src=198.18.0.2 dst=192.0.2.1 sport=10006 dport=80 src=192.0.2.1 dst=198.18.0.2 sport=80 dport=10006 mark=0 use=1
conntrack v1.4.4 (conntrack-tools):7 flow entries have been shown.
marek:~$ echo 262144 | sudo tee /proc/sys/net/netfilter/nf_conntrack_max
262144

我们可以看到 7 个 SYN+ACK 从端口 80 的监听套接字中飞出。最后的三个 SYN 没有任何去处,因为它们被 conntrack 丢弃了。

这有重要的意义。如果你在公开可访问的端口上使用 conntrack,在 SYN 洪水缓解技术如 SYN Cookies 的帮助下,你仍然有耗尽 conntrack 空间的风险,从而影响合法连接。

因此,作为一般规则,建议避免在入站连接上使用 conntrack(-j NOTRACK)。或者,在 iptables 层设置一些合理的速率限制,使用"-j DROP"。这将非常有效,并且不会创建新的流,正如我们上面所讨论的。然而,最好的方法是在 conntrack 之前的一层触发 SYN Cookies,比如 XDP。但这将是另一个话题。

摘要

多年来,Linux conntrack 经历了许多变化,并且有了很大的改进。虽然性能曾经是主要关注点,但现在被认为非常快。仍有暗角存在。正确应用 conntrack 是棘手的。

在这篇博客文章中,我们展示了如何使用“unshare”和一系列脚本来测试 conntrack 的部分功能。我们展示了当 conntrack 表被填满时的行为——数据包可能会被隐式丢弃。最后,我们提到了一个有趣的情况,即 SYN 洪水攻击,错误应用的 conntrack 可能会造成损害。

Last moify: 2022-12-04 15:11:33
Build time:2025-07-18 09:41:42
Powered By asphinx