Xiaoshae Blog

Netfiler

发布于 2025-11-04 | 最后更新 2025-11-04

Netfiler

Netfilter 是 Linux 内核中用于网络包处理(包过滤、网络地址转换NAT、包修改)的一个框架。它本身不是一个程序,而是一套钩子(Hooks),深深植入到 Linux 内核的网络协议栈中。

当数据包在网络协议栈中传递并到达一个 Netfilter 钩子点时,其常规处理流程会被 Netfilter 框架中断,后者将转而依次调用所有注册于该钩子点上的回调函数。


iptables / nftables 是位于用户空间的前端配置工具。它们提供命令行接口(CLI)供管理员定义规则集(rulesets),这些规则会被注册在 Netfilter 中的相应钩子上。


钩子

Netfilter 在网络协议栈中定义了五个主要的钩子点,数据包在内核中会依次经过它们。


NF_IP_PRE_ROUTING

数据包刚进入网络接口,进行完最基础的完整性检查后,在进行路由决策之前


NF_IP_LOCAL_IN

数据包经过 PRE_ROUTING,并且路由决策判断该数据包是发往本机的(Destination IP 是本机)。


NF_IP_FORWARD

数据包经过 PRE_ROUTING,并且路由决策判断该数据包是需要转发到其他主机的(Destination IP 不是本机)。


NF_IP_LOCAL_OUT

本机进程产生的数据包,在路由决策之后,即将发送出去之前。


NF_IP_POST_ROUTING

数据包(无论是转发的还是本机产生的)即将离开网络接口之前。


触发顺序

触发顺序

  • 目标是本机的数据包: NF_IP_PRE_ROUTING -> NF_IP_LOCAL_IN
  • 经由本机转发的数据包: NF_IP_PRE_ROUTING -> NF_IP_FORWARD -> NF_IP_POST_ROUTING
  • 本机发出的数据包: NF_IP_LOCAL_OUT -> NF_IP_POST_ROUTING


加入路由决策的步骤后,顺序如下:

  • 目标是本机的数据包: NF_IP_PRE_ROUTING -> 路由决策 -> NF_IP_LOCAL_IN
  • 经由本机转发的数据包: NF_IP_PRE_ROUTING -> 路由决策 -> NF_IP_FORWARD -> NF_IP_POST_ROUTING
  • 本机发出的数据包: 路由决策 -> NF_IP_LOCAL_OUT -> NF_IP_POST_ROUTING


路由决策

在 Linux 系统中,内核通过路由决策 (Routing Decision) 来确定一个数据包最终会从哪个物理网卡(如 eth0)发送出去。


路由决策在网络流程中是固定的

对于本机收到的包:(路由决策)发生在 PREROUTING 钩子之后FORWARD 钩子之前

对于本机产生包:发生在进程产生数据包之后OUTPUT 钩子之前


路由决策是:系统内核查看数据包的“目标 IP 地址”,然后去查询一系列的“路由表”,以找出到达该目标的“下一跳(Gateway)”和“出口设备(Interface)”。


OUTPUT DNAT

对于本机进程产生的数据包,其处理流程与转发包(PREROUTING)有所不同。数据包在经过首次路由决策后,才会进入 nat 表的 OUTPUT 链。

这意味着,当数据包到达 nat-OUTPUT 链时,内核已经基于其原始目标 IP(例如 8.8.8.8)进行了一次路由查找,并初步确定了出口网卡(例如 eth0,公网出口)。


此时,如果 OUTPUT 链上的 DNAT 规则(iptables -t nat -A OUTPUT ... -j DNAT ...)被触发,强行修改了数据包的目标 IP 地址(例如,将其从 8.8.8.8 更改为 192.168.1.1),内核会中止当前处理流程。

由于目标地址发生了根本变化,原有的路由决策(指向 eth0)很可能不再有效。因此,数据包将重新进行路由决策,进行第二次路由决策


内核会基于这个新的目标 IP (192.168.1.1) 再次查询路由表,以确定最终的出口网卡(例如,此时匹配到指向 eth1br0 的路由)。

如果第二次路由决策失败(例如,DNAT 后的新地址在本机路由表中不可达),该数据包将被丢弃。


策略路由

策略路由允许系统根据预设的策略(而不仅仅是目标 IP)来选择路由。其核心依赖于在路由决策之前为数据包设置标记。

  • 对于进入本机的数据包(目标是本机或需要转发):数据包在进行路由决策前,会先经过 iptables mangle 表的 PREROUTING 链。


PREROUTING 链中,可以为符合特定条件(例如源 IP 是 192.168.1.10)的数据包设置一个特定的防火墙标记 (Firewall Mark, fwmark)。

随后,使用 ip rule 添加一条规则,该规则的优先级高于主路由表查询。这条规则指定:携带特定 fwmark 的数据包,须使用 table_10 路由表进行路由查找。

最后,在 table_10 路由表中定义一套完全不同的路由规则(例如,指定默认网关为 eth2 的网关)。


这种方式的本质是,在内核的路由决策阶段,通过 fwmarkip rule 强行干预,将特定数据包“引导”至一个非默认的路由表,从而实现对出口网卡的控制。


PREROUTING DNAT

DNAT (目标地址转换) 规则在 PREROUTING 链中生效,而 PREROUTING 链的处理发生在内核进行路由决策之前。

当一个数据包(通常是用于转发或发往本机的包)进入 PREROUTING 链时,DNAT 规则可以强行修改其目标 IP 地址

随后,内核将基于这个被修改后的新目标 IP 去查询路由表来进行路由决策。因此,数据包很可能会因为这个新的目标 IP 而匹配到一条(相对于原始目标 IP)完全不同的路由规则,这条规则可能指向一个不同的出口网卡(主要影响转发包)。


应用层指定

SO_BINDTODEVICE 是一个 Linux 特有的套接字选项 (Socket Option)。它允许应用程序在创建套接字 (socket) 时,就将其强制绑定到一个特定的网络接口(如 eth0wlan0tun0)上。

一旦设置了此选项,所有通过该套接字发送的数据包都将绕过 Linux 的 IP 路由表,被强制从所指定的接口发送出去。同样,该套接字通常也只接收从该特定接口进入的数据包。

这种绑定的优先级非常高。例如,即使 iptablesOUTPUT 链中通过 DNAT 规则修改了该数据包的目标 IP 地址,这个修改也无法改变数据包的出口网卡,因为出口已经被 SO_BINDTODEVICE 在应用层锁定了。