彻底解决 conntrack 表满:利用 eBPF Iterator 实现 TCP 半开连接的精准强制回收
在处理高并发网络应用或面临 SYN Flood 攻击时,Linux 内核的 nf_conntrack 表满是一个经典痛点。通常,大家会习惯性地调大 net.netfilter.nf_conntrack_max,或者缩短 nf_conntrack_tcp_timeout_syn_recv 的时间。
但这些方法都是“全局性”的,缺乏灵活性。如果你希望在不影响长连接的前提下,针对性地清理掉那些耗尽资源的 TCP 半开连接(SYN_RECV 状态),eBPF 提供了一种更为优雅且高效的手段:eBPF Iterators。
为什么传统的 GC 不够快?
内核原生的 nf_conntrack GC(垃圾回收)逻辑分为两种:
- 被动回收:在分配新条目时,如果表满,则尝试回收过期条目。
- 周期性回收:内核线程定期扫描部分表项。
当瞬时 SYN 攻击流量巨大时,内核扫描的速度往往跟不上填充的速度。此时,我们需要一个“外部推力”来强制清理特定状态的连接。
eBPF 解决方案:bpf_iter_netfilter
从 Linux 5.12 左右开始,内核引入了针对 Netfilter 的 eBPF 迭代器(Iterators)。它允许用户态程序触发一个 eBPF 程序,遍历内核中所有的 conntrack 条目,并根据自定义逻辑执行操作。
实现思路
我们的目标是:编写一个 eBPF 程序,遍历 nf_conntrack 表,识别出处于 TCP_CONNTRACK_SYN_RECV 状态且持续时间超过阈值的条目,并将其标记为过期或直接删除。
核心代码逻辑(C 语言)
以下是基于 bpf_iter__nf_conntrack 的核心逻辑伪代码:
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
SEC("iter/nf_conntrack")
int gc_half_open_conns(struct bpf_iter__nf_conntrack *ctx)
{
struct nf_conn *ct = ctx->ct;
if (!ct) return 0;
// 1. 获取 TCP 状态
// 注意:nf_conn 内部存储了不同协议的状态信息
u8 proto = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum;
if (proto != IPPROTO_TCP) return 0;
// 2. 识别 SYN_RECV 状态 (通常对应内核常量 TCP_CONNTRACK_SYN_RECV)
// 这里需要根据内核版本通过偏移获取具体的 tcp 状态
u8 tcp_state = ct->proto.tcp.state;
if (tcp_state == 5) { // 5 对应 TCP_CONNTRACK_SYN_RECV
// 3. 计算持续时间或检查特定条件
// 如果该连接已经持续超过 10 秒仍未完成握手
u64 timeout = ct->timeout;
u64 now = bpf_jiffies64();
if (now > timeout) {
// 4. 强制将其超时时间设为 0,触发内核自然回收
// 或者在支持的内核版本上使用 bpf_ct_release (需结合具体 helper)
ct->timeout = 0;
// 也可以通过 bpf_printk 记录日志用于审计
bpf_printk("GC Conntrack: Syn-Recv expired, sport: %d",
ct->tuplehash[0].tuple.src.u3.all[0]);
}
}
return 0;
}
如何触发回收?
编写好上述 eBPF 程序并加载到内核后,它并不会自动运行。你需要通过读取关联的文件描述符来触发迭代:
- 创建 link:使用
bpf_link_create绑定该程序。 - 获取 iter FD:通过
bpf_iter_create创建迭代器实例。 - 触发读取:在用户态调用
read(iter_fd, ...)。此时内核会遍历整个 conntrack 表并执行你的 eBPF 逻辑。
这种方式的优势在于:按需触发。你可以写一个简单的用户态守护进程,监控 conntrack_count,当水位线超过 90% 时,读取一次迭代器执行强制 GC。
进阶优化:结合 XDP 丢弃无效包
强制回收只是“事后处理”。为了更彻底地解决问题,建议配合 XDP (Express Data Path):
- XDP 过滤:在流量进入协议栈之前,利用 eBPF 查看当前
nf_conntrack的负载。 - SYN Cookie 卸载:如果表即将溢出,直接在 XDP 层对非信任 IP 强制开启 SYN Cookie 校验,完全绕过 conntrack 表的创建,直到三路握手真正完成。
注意事项
- 内核版本:bpf_iter 功能需要较新的内核支持(建议 5.15+)。
- BTF 支持:确保你的内核编译了
CONFIG_DEBUG_INFO_BTF=y,否则 eBPF 无法识别struct nf_conn。 - 写权限:直接在 eBPF 中修改
ct->timeout需要程序具有相应的特权,并且在某些严格限制的内核环境中可能需要通过特定的 Helper 函数实现。
总结
当 nf_conntrack 达到瓶颈时,传统的 sysctl 优化往往捉襟见肘。利用 eBPF Iterator,我们可以实现针对特定协议状态(如 SYN_RECV)、特定源 IP 段甚至特定流量特征的 动态连接回收机制。这不仅提升了系统的鲁棒性,也为网络安全防护提供了更底层的控制能力。