Linux内核模块重定向TCP包需在netfilter hook中修改skb字段并处理conntrack:先校验协议、调用skb_make_writable(),更新IP头TTL和TCP校验和,再清理或绕过conntrack状态,否则连接会卡在SYN_SENT或无ACK。

Linux 内核模块中注册 netfilter hook 处理 TCP 包
不能在内核模块里直接调用用户态的 libnetfilter_queue 或 iptables 命令——那些是用户空间机制。真正修改/重定向数据包,必须在内核态注册 nf_hook_ops,挂载到 NF_INET_PRE_ROUTING 或 NF_INET_LOCAL_OUT 等关键点。
常见错误是 hook 函数返回值写错:NF_ACCEPT 表示放行、NF_DROP 丢弃、NF_STOLEN 表示已接管(不再走后续流程)、NF_QUEUE 是交给用户态队列(需配合 nfnetlink_queue 模块)。TCP 包重定向通常要改 sk_buff->ip_hdr()->daddr 和 tcp_hdr()->dest,但必须先调用 skb_make_writable(),否则可能因分页或共享缓冲区导致崩溃。
- 务必检查
skb->protocol == htons(ETH_P_IP)且ip_hdr(skb)->protocol == IPPROTO_TCP - 修改 IP 头前用
ip_decrease_ttl(ip_hdr(skb))更新 TTL,否则可能被中间设备丢弃 - 若修改目的端口,需同步更新 TCP 校验和:调用
tcp_v4_csum_init(skb)或手动重算tcp_hdr(skb)->check = 0; tcp_hdr(skb)->check = tcp_v4_checksum(skb);
重定向 TCP 连接时绕过 conntrack 的坑
内核默认启用 nf_conntrack,一旦你修改了目的 IP/端口,conntrack 会认为这是新连接,原连接状态丢失,ACK 包可能被拒绝或无法回包。最直接的规避方式是在 hook 函数中调用 nf_ct_destroy(ct) 主动销毁旧连接跟踪项,或在模块初始化时通过 nf_ct_netns_get() 获取对应 netns 的 conntrack 表并清理匹配项。
更稳妥的做法是:在 NF_INET_PRE_ROUTING 中重定向后,立即在 NF_INET_POST_ROUTING 中对返回包做反向 DNAT,保持连接对称。但这要求你能在两个 hook 间传递标识信息——可用 skb->mark 或自定义 skb_ext 存储原始目的地址,避免依赖 conntrack 状态。
立即学习“C++免费学习笔记(深入)”;
Linux 安全审计工具,检查 SSH 配置、开放/监听端口、防火墙规则(ufw/iptables/nftables)、失败登录尝试、sudoers 权限...
- 不要在
NF_INET_LOCAL_OUT中只改 dst 而不 touch conntrack:本地发起的 TCP 连接会卡在 SYN_SENT,因为 reply 包找不到匹配的 conntrack entry -
nf_conntrack_tcp_be_liberal = 1可缓解部分乱序/校验问题,但不解决重定向后的状态不一致 - 使用
nf_ct_delete()前确认ct != NULL,否则 oops
编译和加载 netfilter 内核模块的最小依赖
内核配置必须开启:CONFIG_NETFILTER=y、CONFIG_NETFILTER_ADVANCED=y、CONFIG_NF_CONNTRACK=m(如果要用 conntrack)、CONFIG_IP_NF_IPTABLES=m(非必须,但调试时方便用 iptables -j NFLOG 配合 dmesg 查看流量路径)。
Makefile 必须显式链接 net 和 nf_conntrack(如果用了):
obj-m += my_nf_redirect.o my_nf_redirect-objs := nf_redirect_main.o nf_redirect_hook.o KDIR := /lib/modules/$(shell uname -r)/build all: make -C $(KDIR) M=$(PWD) modules
加载前检查依赖模块是否已载入:lsmod | grep -E 'nf_conntrack|nf_defrag_ipv4|nf_nat'。缺 nf_defrag_ipv4 会导致 IPv4 分片包无法重组,hook 收到的可能是不完整的 TCP 包。
- 模块中不能使用
printk输出大量日志——TCP 高频场景下极易引发 soft lockup - 所有内存分配优先用
kmalloc(..., GFP_ATOMIC),不能在 hook 中 sleep - 修改 sk_buff 后建议调用
skb_clear_hash(skb),防止路由缓存误判
调试 TCP 重定向失败的关键信号
最常遇到的是“包发出去了,但没收到 ACK”或“连接超时”。先确认是否真进了你的 hook:dmesg -w 看是否有你写的 printk;再用 tcpdump -i any port XXX 对比进出接口的包内容,重点看 IP 头目的地址、TCP 端口、SYN/FIN 标志位是否符合预期。
若发现重定向后的包 TTL=1 或 checksum 错误,说明没调 ip_decrease_ttl() 或没重算 TCP 校验和;若只有第一次 SYN 能发出去,后续 ACK 不见,则大概率是 conntrack 干扰,此时执行 echo 0 > /proc/sys/net/netfilter/nf_conntrack_tcp_be_liberal 并清空表:conntrack -F(仅测试环境)。
-
cat /proc/net/nf_conntrack | grep :XXXX查看对应端口的连接状态,观察 src/dst 是否被你改写后仍被正确跟踪 - 用
iptables -t raw -A OUTPUT -p tcp --dport YYYY -j TRACE触发内核 trace,配合sudo cat /sys/kernel/debug/tracing/trace_pipe定位 hook 执行位置 - 不要依赖
skb->dev判断方向——在LOCAL_OUT中它可能是 loopback,在PRE_ROUTING中可能是 eth0,应以skb->skb_iif和网络命名空间上下文为准

















