Linux 内核参数 rp_filter 完全解析
一、什么是 rp_filter?
rp_filter 是 Linux 内核中用于反向路径过滤(Reverse Path Filtering) 的安全机制,核心功能是防止 IP 地址欺骗(IP Spoofing)攻击。
其工作原理如下:
当系统收到一个 IP 数据包时,内核会提取数据包的源 IP 地址,然后拿这个源 IP 地址去查询路由表(FIB,Forwarding Information Base),看系统如果要发送数据包到这个源 IP,应该从哪个本地网卡出去。接着,内核将路由表返回的“出口网卡”与“实际接收该数据包的网卡”进行比对:
- 如果两者一致:校验通过,数据包正常进入协议栈处理。
- 如果两者不一致:校验失败,数据包被直接丢弃。
核心逻辑可以概括为:系统回复这个数据包时准备走的路,必须和这个数据包进来的路是同一条。如果路径不一致,内核就认为源 IP 可能是伪造的。
二、三种工作模式
rp_filter 提供三个可选值:
| 值 | 模式名称 | 行为描述 |
|---|---|---|
| 0 | 关闭验证 | 不进行任何反向路径检查,所有数据包照单全收 |
| 1 | 严格模式 | 要求路由表查询到的出口网卡与接收网卡完全一致。这是大多数 Linux 发行版的默认值 |
| 2 | 宽松模式 | 只检查源 IP 是否“可达”(即路由表中存在通往该 IP 的路由),不要求进出同一网卡 |
宽松模式(=2)不要求进出同口,因此能兼容非对称路由环境,同时保留了对完全不可达的伪造源 IP 的基本防御能力。
三、用三个具体例子说明 rp_filter 的判断逻辑
为了说清楚 rp_filter 到底在检查什么,我们用一台双网卡服务器来演示。假设服务器配置如下:
- eth0:IP 地址
192.168.1.10/24 - eth1:IP 地址
10.0.0.10/24 - 路由表:
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.10
10.0.0.0/24 dev eth1 proto kernel scope link src 10.0.0.10
default via 10.0.0.1 dev eth1
关键理解:rp_filter 查询路由表时用的是数据包的源 IP 地址,跟发送方有几块网卡、MAC 地址是什么完全没有关系。它只问一个问题——“如果我要回复这个源 IP,应该走哪个本地网卡?”
例一:同网段放行
数据包从 eth0 进来,源 IP 是 192.168.1.100。
| 检查步骤 | 结果 |
|---|---|
| 接收网卡 | eth0 |
用源 IP 192.168.1.100 查路由表 | 匹配 192.168.1.0/24 dev eth0 |
| 路由表返回出口网卡 | eth0 |
| 比对:接收网卡 vs 出口网卡 | eth0 == eth0,放行 |
这个例子里,发送方的 IP(192.168.1.100)恰好落在 eth0 的直连网段内,所以路由表告诉内核“去这个 IP 走 eth0”,而数据包也确实是从 eth0 进来的,路径对称,校验通过。
例二:不同网段丢弃
数据包从 eth0 进来,源 IP 是 10.0.0.100。
| 检查步骤 | 结果 |
|---|---|
| 接收网卡 | eth0 |
用源 IP 10.0.0.100 查路由表 | 匹配 10.0.0.0/24 dev eth1 |
| 路由表返回出口网卡 | eth1 |
| 比对:接收网卡 vs 出口网卡 | eth0 ≠ eth1,丢弃 |
这里源 IP 10.0.0.100 落在 eth1 的直连网段内,路由表告诉内核“去这个 IP 走 eth1”,但数据包却是从 eth0 进来的。内核认为路径不一致,包可能是伪造的,直接丢弃。
例三:陌生网段走默认路由丢弃
数据包从 eth0 进来,源 IP 是 8.8.8.8。
| 检查步骤 | 结果 |
|---|---|
| 接收网卡 | eth0 |
用源 IP 8.8.8.8 查路由表 | 不匹配任何直连网段,匹配默认路由 default via 10.0.0.1 dev eth1 |
| 路由表返回出口网卡 | eth1 |
| 比对:接收网卡 vs 出口网卡 | eth0 ≠ eth1,丢弃 |
源 IP 8.8.8.8 是一个公网地址,服务器没有任何网卡属于这个网段,路由表通过默认路由将去往 8.8.8.8 的流量指向 eth1。由于接收网卡是 eth0,路径不一致,校验失败丢弃。如果默认路由指向的是 eth0,则 eth0 == eth0,校验通过,放行。
四、一张表总结所有情况
| 接收网卡 | 数据包源 IP | 路由表匹配结果 | 出口网卡 | rp_filter=1 裁决 |
|---|---|---|---|---|
| eth0 | 192.168.1.100 | 匹配 192.168.1.0/24 | eth0 | ✅ 放行 |
| eth0 | 10.0.0.100 | 匹配 10.0.0.0/24 | eth1 | ❌ 丢弃 |
| eth0 | 8.8.8.8 | 匹配默认路由(dev eth1) | eth1 | ❌ 丢弃 |
| eth0 | 172.16.0.100 | 无默认路由,查不到 | 无 | ❌ 丢弃(查无结果即失败) |
| eth1 | 10.0.0.100 | 匹配 10.0.0.0/24 | eth1 | ✅ 放行 |
| eth1 | 192.168.1.100 | 匹配 192.168.1.0/24 | eth0 | ❌ 丢弃 |
从表中可以清晰看到:放行的唯一条件是路由表查到的出口网卡 == 实际接收网卡。其他所有情况——查到不同网卡、查不到路由、只能匹配默认路由——都会导致数据包被丢弃。
五、什么情况下需要修改 rp_filter?
1. 非对称路由(最常见)
当数据包的往返路径不一致时,严格模式必然丢包。典型场景包括:
- 多网卡服务器:请求从 eth0 进入,但默认网关在 eth1 上,响应包从 eth1 发出。
- LVS-DR 负载均衡模式:请求经过 LVS 到达后端服务器,响应绕过 LVS 直接返回客户端,对后端服务器来说就是非对称路由。
- BGP 多线机房:不同运营商的流量从不同网卡进出,路径天然不对称。
上述场景下,如果用 tcpdump 能在网卡上抓到请求包,但应用程序完全收不到,很可能是 rp_filter 在静默丢包。
2. 策略路由(Policy Routing)冲突
当系统根据 fwmark、源 IP 等条件选择不同路由表时,rp_filter 的严格模式容易产生冲突。因为反向路径验证时,策略路由所需的匹配条件(如 iif、mark)通常无法满足,导致校验失败。
3. VPN 和隧道技术(IPsec、GRE)
隧道流量的封装和解封装会改变数据包的路径特征,反向路径过滤可能将正常隧道流量误判为欺骗包。由于 IPsec 等协议自身已经提供了更强的反欺骗能力,这种情况下关闭 rp_filter 是合理选择。
4. 多播数据包接收
通过 AF_PACKET 或 UDP 多播接收数据时,多播包的源 IP 可能是任意地址。如果该源 IP 不在接收网卡的直连网段内,或只能匹配默认路由,严格模式就会丢弃数据包。实践中大量案例表明,多播接收不通时,将 rp_filter 设为 0 或 2 是最常见且有效的解决方案。
5. 容器和网络命名空间环境
Docker、Kubernetes 等容器环境中,网络插件(如 Calico、Flannel)的流量往往跨越多个网络命名空间,网络路径复杂。很多容器网络解决方案在初始化时会主动关闭 rp_filter,以确保流量不被误拦截。
六、如何查看和修改 rp_filter
查看当前配置
# 查看所有接口的 rp_filter 配置
sysctl -a | grep rp_filter
# 查看全局配置
sysctl net.ipv4.conf.all.rp_filter
# 查看特定网卡的配置
sysctl net.ipv4.conf.eth0.rp_filter
临时修改(立即生效,重启失效)
# 设置为宽松模式
sysctl -w net.ipv4.conf.eth0.rp_filter=2
# 完全关闭
sysctl -w net.ipv4.conf.eth0.rp_filter=0
永久修改
编辑 /etc/sysctl.conf 文件:
# 只修改特定网卡(推荐)
net.ipv4.conf.eth0.rp_filter = 2
# 或全局修改(影响所有网卡,不推荐)
net.ipv4.conf.all.rp_filter = 2
执行 sysctl -p 使配置生效。
关于 all、default 与具体接口的关系
conf/all/rp_filter:全局开关,影响所有已存在的网卡。conf/default/rp_filter:新创建网卡的默认值,不影响已有网卡。conf/eth0/rp_filter:仅影响 eth0 这一块网卡。
从 Linux 2.6.31 内核开始,实际生效值是 all 和具体接口配置的 最大值(max)。也就是说,如果 net.ipv4.conf.all.rp_filter=1,即使某个接口设为 0,该接口依然会执行严格校验。
因此,要关闭或放宽某个特定网卡的 rp_filter,必须同时确保 all.rp_filter 不为严格模式(设为 0 或 2)。
七、不同场景的推荐配置
| 场景 | 推荐配置 | 说明 |
|---|---|---|
| 普通单网卡服务器 | rp_filter = 1 | 保持默认,安全性最高 |
| 双网卡/多网卡服务器 | rp_filter = 2 | 宽松模式,兼容非对称路由 |
| 接收多播数据 | rp_filter = 0 或 2 | 避免源 IP 不在直连网段时被丢弃 |
| LVS-DR 后端服务器 | rp_filter = 0 | 响应直通客户端,必须关闭 |
| VPN/隧道网关 | rp_filter = 0 | 隧道协议自身保证安全 |
| 容器/Pod 网络 | 保持网络插件默认值 | 插件通常会按需配置 |
八、修改 rp_filter 的安全考量
关闭 rp_filter 或使用宽松模式会降低系统对 IP 欺骗攻击的防御能力。RFC 3704 标准推荐在网络边缘(如边界路由器)启用严格模式的 uRPF,以防范 DDoS 和 IP Spoofing。
在实际操作中,应遵循以下原则:
- 优先使用宽松模式(=2)而非完全关闭(=0):宽松模式仍然会检查源 IP 是否可达,只是不苛求进出同口,能在安全性和兼容性之间取得平衡。
- 精准修改而非全局关闭:如果只有 eth0 存在问题,只修改
conf/eth0/rp_filter,不要轻易修改conf/all/rp_filter。 - 注意 all 和具体接口的叠加关系:关闭某个具体接口时,务必确认
all.rp_filter不会覆盖掉这个设置。 - 修改后必须验证:用
tcpdump确认数据包能到达网卡,用应用程序确认能正常接收,必要时用dropwatch等工具确认丢包是否消失。
九、历史演进
rp_filter 在内核中经历了数次重要变化:
- 2.6 早期内核:
all与具体接口采用 AND 逻辑,必须两者都开启才生效。 - 2.6.31 版本:改为 max() 取最大值,
all和具体接口中取较大值作为实际生效值。这一逻辑沿用至今。 - 3.3 版本:rp_filter 实现从协议栈迁移到 Netfilter 框架的 PREROUTING 钩子点,使得严格模式和宽松模式都能在框架内实现。
了解这些变化有助于理解为什么 all 和具体接口的配置关系如此特殊。
十、总结
rp_filter 是 Linux 网络栈中一项“守卫森严”的安全机制,它的设计初衷是防止 IP 地址欺骗,在单网卡、对称路由的环境中工作良好。但在多网卡、负载均衡、多播接收、容器网络等复杂场景下,它会因为“路径不一致”而丢弃大量本应正常处理的数据包。
理解 rp_filter 的核心在于记住一件事:它检查的不是多播组地址,不是发送方的 MAC 地址,而是数据包的源 IP 地址。 它拿着这个源 IP 去查路由表,看系统回复时走哪个网卡,再和接收网卡做比对。只有两者一致才放行,其他所有情况——查到不同网卡、查不到路由、匹配默认路由——都会导致数据包被丢弃。
排查网络不通问题时,如果 tcpdump 能在网卡上抓到包但应用程序收不到,rp_filter 是应该首先排查的嫌疑对象之一。