TC Hairpin NAT 驱动使用手册(个人版)

发布时间:2026/6/14 2:02:17

TC Hairpin NAT 驱动使用手册(个人版) TC Hairpin NAT 驱动使用手册个人版说明本文档仅供个人回顾使用记录了基于 TC eBPF 实现 Hairpin NAT 的完整操作流程与设计思路。不包含具体代码实现细节仅描述原理、配置、调试与维护方法以保证内核程序的稳定性与安全性。1. 简介本驱动实现了一个基于 eBPF 的 TCTraffic Control层 hairpin NAT 功能。它可以在一个网络接口例如 TUN/TAP 设备、物理网卡上根据预定义的 NAT 规则对 TCP 数据包的源/目的 IP 和端口进行转换并可选地将包重定向到指定的网络接口或原接口。典型应用场景在 VNP 服务端实现端口映射或流量回注。在容器或虚拟化环境中实现 Service 的 Hairpin NAT让访问服务的请求经过转换后回到自身。核心组件内核程序负责数据包匹配、地址端口修改、校验和更新、重定向决策。用户态控制程序用于加载/卸载 TC 程序管理 NAT 规则增删查。编译脚本简化构建过程。2. 技术原理程序工作于 Linux TC 的 egress 方向发包时。当数据包从指定网卡发出时内核会先调用我们的 eBPF 程序。程序解析 IP 头与 TCP 头提取五元组源 IP、源端口、目的 IP、目的端口、协议以该五元组为键查询预先定义的 NAT 映射表。若命中则按照映射表中的新地址、新端口修改数据包并重新计算 IP 头与 TCP 头的校验和保证协议栈能够正确接收。修改完成后数据包被重定向到指定网卡默认原网卡的 ingress 方向继续处理从而实现流量的“折返”效果。整个处理过程在内核态完成避免了用户态与内核态之间的上下文切换具有极高的处理效率。3. 环境准备3.1 系统与内核推荐 Ubuntu 20.04/22.04 或 WSL2内核 5.10。确认内核支持 BPF通常默认开启。3.2 安装基础工具链sudoaptupdate-ysudoaptupgrade-ysudoaptinstall-ybuild-essential clang llvmgitvimnet-tools iproute2 tcpdumpcurlsocat3.3 安装 eBPF 开发库sudoaptinstall-ylibbpf-dev libelf-dev libz-dev3.4 安装 bpftool管理 BPF 程序与 mapsudoaptinstall-ylinux-tools-common linux-tools-generic# 查找 bpftool 实际路径例如 /usr/lib/linux-tools-5.15.0-173-generic/bpftoolfind/usr-namebpftool2/dev/null# 建立软链接sudoln-sf/usr/lib/linux-tools-$(uname-r)/bpftool /usr/sbin/bpftool3.5 挂载 BPF 文件系统用于持久化 mapsudomkdir-p/sys/fs/bpfsudomount-tbpf none /sys/fs/bpf4. 编译4.1 编译内核程序clang-O2-targetbpf-cdriver.c-odriver.ko4.2 编译用户态控制程序gcc loader.c-oloader-lbpf-lelf-lz说明-O2是 BPF 程序推荐的优化级别。driver.ko虽然扩展名是.ko但实际是 eBPF 字节码文件并非内核模块。编译用户态程序时需链接libbpf、libelf、libz。4.3 关于内核常量的手动定义在编写 BPF 程序时可能会遇到某些常量未定义的情况例如TC_ACT_OK。这些常量定义在 Linux 内核头文件中。若编译环境缺少这些定义可在代码开头手动添加已在源码中处理无需用户关心。5. 网络环境准备在加载 NAT 程序之前需要先准备好网络环境。以下以创建一个 TUN 设备tun0IP 10.0.0.2并配置路由为例。5.1 创建 TUN 设备可选如果你使用 VNP 或虚拟网络设备这一步可能需要调整。示例使用socat创建一个 TUN 设备socat TUN:10.0.0.2/24,up,tun-nametun0,iff-up,iff-running,iff-noarp,iff-pointopoint,up -参数解释TUN:10.0.0.2/24TUN 设备 IP 为 10.0.0.2掩码 24。up设备立即启用。tun-nametun0设备名称。iff-up,iff-running等设置标志位。创建后可用ip addr show tun0查看。5.2 添加路由使目标 IP 的流量进入 tun0假设我们要让访问10.0.0.8的流量走tun0iprouteadd10.0.0.8/32 dev tun0注意根据实际场景调整路由确保 NAT 转换前后的 IP 能够正确路由。5.3 启动后端服务NAT 转换后的最终目的地址需要有服务监听。例如我们将在10.0.0.2:8080启动一个 HTTP 服务python3-mhttp.server8080--bind10.0.0.2确保该服务正在运行否则测试时连接会失败。6. 加载 BPF 程序有两种方式推荐使用我们自带的loader工具也可以手动使用tc命令。6.1 方式一使用 loader 工具推荐# 挂载 BPF 文件系统若未挂载只需执行一次sudomount-tbpf none /sys/fs/bpf# 加载程序到指定网卡例如 tun0sudo./loader attach tun0attach命令会完成以下工作打开并加载driver.ko字节码。将规则表持久化到/sys/fs/bpf/tun_nat_map。在指定网卡的 egress 方向附加 TC 程序handle 1, priority 1。成功输出类似规则表已 pin 到 /sys/fs/bpf/tun_nat_map TC egress NAT 已成功附加到 tun0 现在可以使用 add / del / dump 管理规则6.2 方式二手动使用 tc 命令如果你更熟悉tc也可以手动附加前提是已编译出driver.ko# 确保网卡已有 clsact qdisc若没有则创建tc qdiscadddev tun0 clsact# 加载程序指定节名驱动程序中已定义tc filteradddev tun0 egress bpf da obj driver.ko sec节名# 查看挂载状态tc filter show dev tun0 egress此时规则表没有自动 pin如需管理规则可以通过bpftool查找 map id 后操作较繁琐不推荐。7. NAT 规则管理规则通过loader程序进行增、删、查。规则存储在 BPF 规则表中key 是原始五元组源IP、目的IP、源端口、目的端口、协议value 是转换后的地址、端口以及重定向接口。7.1 添加规则sudo./loaderaddsrc_ipsrc_portdst_ipdst_portnew_src_ipnew_src_portnew_dst_ipnew_dst_portredirect_ifindex参数说明src_ip原始源 IP点分十进制。src_port原始源端口整数。dst_ip原始目的 IP。dst_port原始目的端口。new_src_ip转换后的源 IP。new_src_port转换后的源端口。new_dst_ip转换后的目的 IP。new_dst_port转换后的目的端口。redirect_ifindex重定向的目标网卡索引0 表示使用原接口可以是数字 ifindex 或网卡名例如eth0、lo。重要在 hairpin 场景中通常需要添加两条规则双向去程将客户端访问服务的请求转换为服务所在后端地址。回程将后端响应转换为客户端可识别的地址。示例假设客户端 IP10.0.0.2使用源端口1111访问10.0.0.8:7777。我们想将请求转换为从10.0.0.1:2222发往10.0.0.2:8080后端服务。回程时将后端响应10.0.0.2:8080 - 10.0.0.1:2222转换为10.0.0.8:7777 - 10.0.0.2:1111。添加规则# 去程sudo./loaderadd10.0.0.2111110.0.0.8777710.0.0.1222210.0.0.280800# 回程sudo./loaderadd10.0.0.2808010.0.0.1222210.0.0.8777710.0.0.211110注意两条规则严格对称确保转换可逆。redirect_ifindex设为 0表示数据包修改后从原接口发出即还是通过 tun0。7.2 查看所有规则sudo./loader dump输出示例当前 NAT 规则: ---------------------------------------------------------------------------------------- 源IP:端口 → 目的IP:端口 | 新源IP:新端口 新目的IP:新端口 重定向 ifindex ---------------------------------------------------------------------------------------- 10.0.0.2:8080 → 10.0.0.1:2222 | 10.0.0.8:7777 10.0.0.2:1111 0 10.0.0.2:1111 → 10.0.0.8:7777 | 10.0.0.1:2222 10.0.0.2:8080 07.3 删除规则sudo./loader delsrc_ipsrc_portdst_ipdst_port例如sudo./loader del10.0.0.2111110.0.0.87777sudo./loader del10.0.0.2808010.0.0.122228. 测试配置完成后使用 curl 测试确保后端服务已启动curl--local-port1111http://10.0.0.8:7777/--local-port 1111强制使用源端口 1111使其匹配去程规则。如果一切正常应能收到后端 HTTP 服务的响应。9. 调试9.1 查看内核日志bpf_printk驱动中使用调试输出可以通过trace_pipe实时查看# 确保 debugfs 已挂载通常系统自动挂载sudomount-tdebugfs none /sys/kernel/debug# 如果未挂载# 实时输出 BPF 日志cat/sys/kernel/debug/tracing/trace_pipe|grepNAT当有数据包匹配时会看到类似NAT hit, redirect to 09.2 查看 BPF 程序状态使用bpftool# 列出所有 BPF 程序查找 tc_egressbpftool prog list|greptc_egress# 假设程序 id 为 123查看详细信息bpftool prog showid123--pretty9.3 查看规则表内容# 列出所有 map找到规则表对应的 idbpftool map list# 导出规则表内容假设 id 为 42bpftool map dumpid42输出会显示所有规则便于验证。9.4 抓包验证用 tcpdump 抓取双向流量观察 NAT 转换是否生效tcpdump-iany-nhost10.0.0.2 orhost10.0.0.8 orhost10.0.0.1执行 curl 后应能看到原始包10.0.0.2.1111 10.0.0.8.7777转换后包10.0.0.1.2222 10.0.0.2.8080回程包10.0.0.2.8080 10.0.0.1.2222再次转换10.0.0.8.7777 10.0.0.2.111110. 清理10.1 卸载 TC 程序# 使用 loader 卸载sudo./loader detach tun0# 或手动删除 tc 过滤器tc filter del dev tun0 egress10.2 删除规则表持久化文件rm-f/sys/fs/bpf/tun_nat_map10.3 删除 TUN 设备和路由如果不再需要iplinkdel tun0iproute del10.0.0.8/3211. 个人笔记11.1 为何选择 TC 而非 XDPXDP 需要网卡驱动直接支持且工作在驱动层之前。对于 TUN/TAP 这类纯软件虚拟设备数据包在协议栈内部生成不经过驱动层因此 XDP 程序虽然可以附加但实际上不会处理任何包。而 TC 工作在协议栈的出口点egress对包括虚拟设备在内的所有网络设备均有效是实现 TUN 设备 NAT 的正确选择。11.2 规则表容量规则表采用哈希表结构最大条目数设为1310722^17即约 13 万条。这一容量远高于绝大多数实际场景的需求确保即使在大量端口映射或动态连接时也不会出现表满溢出的风险。同时哈希表采用非预分配模式仅在实际插入条目时才分配内存避免因预分配大量内存造成内核资源浪费。11.3 简单至上原则驱动程序的逻辑被刻意保持简单只处理 TCP 协议避免支持 UDP 等复杂协议带来的校验和更新等问题。无状态设计不维护连接跟踪conntrack完全依赖规则表匹配。校验和更新采用增量方式避免全量重新计算的性能开销。错误处理极简遇到无法处理的情况直接放行TC_ACT_OK宁可丢包也不让内核崩溃。这种“简单即稳定”的指导思想确保了内核程序在高压场景下的可靠性与可维护性。任何复杂的特性如 UDP 支持、动态超时等都应放在用户态或单独模块中实现而非塞入这个核心数据路径。12. 常见问题 (FAQ)Q1: 加载时提示libbpf: failed to load program原因可能是 BPF 程序验证失败。解决查看dmesg或/sys/kernel/debug/tracing/trace_pipe获取具体错误信息。常见原因包括校验和更新错误、指针越界等。Q2: curl 无响应但 tcpdump 显示转换后的包检查后端服务是否监听在转换后的目的地址端口。检查回程规则是否正确添加。检查重定向接口是否配置正确例如设为 0 时原接口是否能将包送到正确路径。Q3: 规则添加成功但 dump 看不到可能规则表没有正确 pin或使用了错误的 map id。确保使用./loader attach挂载程序它会自动 pin 规则表。尝试重新 attach 并添加规则。Q4: 修改了驱动代码后重新 attach 不生效必须重新编译内核程序然后先 detach 旧程序再 attach 新程序。如果使用 tc 命令需要先删除旧的过滤器。Q5: 为什么必须添加双向规则驱动基于五元组精确匹配。若不添加回程规则后端响应的数据包无法匹配任何条目将原样发出导致客户端无法识别连接表现为无响应或重置。双向规则确保往返流量均被转换。13. 附录IP 和端口十六进制转换用于 bpftool 手动操作如果你需要绕过 loader 直接用bpftool操作规则表可以使用以下转换# IP 转十六进制大端printf0x%02x%02x%02x%02x\n1921681100# 输出 0xc0a80164# 端口转十六进制大端printf0x%04x\n12345# 输出 0x3039然后使用bpftool map update id map_id key hex ... value hex ...插入规则。

相关新闻