Netfiler
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) 再次查询路由表,以确定最终的出口网卡(例如,此时匹配到指向 eth1 或 br0 的路由)。
如果第二次路由决策失败(例如,DNAT 后的新地址在本机路由表中不可达),该数据包将被丢弃。
策略路由
策略路由允许系统根据预设的策略(而不仅仅是目标 IP)来选择路由。其核心依赖于在路由决策之前为数据包设置标记。
- 对于进入本机的数据包(目标是本机或需要转发):数据包在进行路由决策前,会先经过
iptablesmangle表的PREROUTING链。
在 PREROUTING 链中,可以为符合特定条件(例如源 IP 是 192.168.1.10)的数据包设置一个特定的防火墙标记 (Firewall Mark, fwmark)。
随后,使用 ip rule 添加一条规则,该规则的优先级高于主路由表查询。这条规则指定:携带特定 fwmark 的数据包,须使用 table_10 路由表进行路由查找。
最后,在 table_10 路由表中定义一套完全不同的路由规则(例如,指定默认网关为 eth2 的网关)。
这种方式的本质是,在内核的路由决策阶段,通过 fwmark 和 ip rule 强行干预,将特定数据包“引导”至一个非默认的路由表,从而实现对出口网卡的控制。
PREROUTING DNAT
DNAT (目标地址转换) 规则在 PREROUTING 链中生效,而 PREROUTING 链的处理发生在内核进行路由决策之前。
当一个数据包(通常是用于转发或发往本机的包)进入 PREROUTING 链时,DNAT 规则可以强行修改其目标 IP 地址。
随后,内核将基于这个被修改后的新目标 IP 去查询路由表来进行路由决策。因此,数据包很可能会因为这个新的目标 IP 而匹配到一条(相对于原始目标 IP)完全不同的路由规则,这条规则可能指向一个不同的出口网卡(主要影响转发包)。
应用层指定
SO_BINDTODEVICE 是一个 Linux 特有的套接字选项 (Socket Option)。它允许应用程序在创建套接字 (socket) 时,就将其强制绑定到一个特定的网络接口(如 eth0、wlan0 或 tun0)上。
一旦设置了此选项,所有通过该套接字发送的数据包都将绕过 Linux 的 IP 路由表,被强制从所指定的接口发送出去。同样,该套接字通常也只接收从该特定接口进入的数据包。
这种绑定的优先级非常高。例如,即使 iptables 在 OUTPUT 链中通过 DNAT 规则修改了该数据包的目标 IP 地址,这个修改也无法改变数据包的出口网卡,因为出口已经被 SO_BINDTODEVICE 在应用层锁定了。