之前帮客户排查过一台双网卡服务器的故障,现象很典型:机器上同时配了两块网卡,一块走办公网,一块走业务网,业务网那边的主机 ping 服务器能通,但访问 TCP 服务就是卡死,tcpdump 抓包又能看到请求进来了,可应用层毫无反应。折腾了很久,最后所有线索都指向了内核里的rp_filter参数。把它的值从严格模式切成宽松模式,服务瞬间恢复。这篇文章不绕弯子,直接聊透多网卡环境下的路由区分逻辑、rp_filter的三种工作模式,以及如何用宽松模式快速定位这类网络问题。内容适合刚接触多网卡配置的运维新手,也适合那些被"网卡通了却不通"折磨过的老手。
1. 多网卡"分不清路由"的真相:Linux选路根本不看网卡
1.1 弱主机模型:IP是机器共有的,不是网卡私有的
很多刚接触 Linux 多网卡配置的人,都会有一个下意识的想法:eth0 上配了 192.168.1.10,eth1 上配了 192.168.2.10,那么发给 192.168.1.10 的流量应该从 eth0 进来再从 eth0 回去,发给 192.168.2.10 的流量走 eth1,各管各的,互不干扰。
这个直觉在 Windows 里部分成立,但在 Linux 上完全是另一套逻辑。Linux 网络栈是一个典型的弱主机模型:IP 地址属于整个系统,不属于某一块物理网卡。内核在收到一个数据包时,不会先问"这个包是从哪块网卡进来的",而是先看"这个包的目标 IP 是不是本机的"。只要目标 IP 是本机任意一块网卡上的地址,内核就认为这个包是给自己的,然后交给上层协议栈处理。
也就是说,从 eth0 进来的包,目标 IP 是 eth1 的地址,Linux 内核照样收。这个机制本身不是 bug,它是 TCP/IP 协议栈能灵活工作的基础。但问题恰恰出在"灵活"上——既然 IP 不绑定网卡,那内核如何决定一个包应该从哪块网卡发出去?
答案是:内核根本不看网卡,它只查路由表。
1.2 最长前缀匹配与回程路由:决定通信成败的往往不是去程
Linux 路由查找遵循最长前缀匹配规则。简单理解:路由表里的每一条路由都带一个网段和掩码,内核收到一个目标 IP 后,在路由表里找出能覆盖这个 IP 的所有路由,然后挑掩码最长(也就是最精确)的那条来用。
比如路由表里有这两条:
- 192.168.1.0/24 走 eth0
- 0.0.0.0/0(默认路由)走 eth1
现在要给 192.168.1.5 发包,内核一比对,192.168.1.0/24 这条掩码是 24 位,比默认路由的 0 位更精确,所以走 eth0。要发给一个外网 IP,比如 223.5.5.5,没有更精确的匹配,就走默认路由 eth1。
这里有个关键点必须理解:发出去一个包容易,但能不能收到回包,取决于对端的路由怎么走。对端同样要查自己的路由表来决定回包发给谁。如果对端的回程路由和你的出口路径不一致,就会出现"包发出去了,回包却丢了"的诡异现象。
我见过很多新人排查多网卡问题,第一反应是看出口路由、ping 外网通不通,这是没错的。但多网卡环境里,真正卡住你的往往不是出口路由,而是回程路由。假设你从业务网卡 eth1 接到一个请求,源 IP 是 172.16.0.2,但你机器上的默认路由只有 eth0 一条,那么内核在回复这个包时,查路由表发现 172.16.0.2 没有更精确的匹配,就会顺着默认路由丢给 eth0。这个回包从 eth0 出去了,对端看到来源网卡和请求时发往的网卡不是同一个,于是一脸懵:我明明发到 eth1 的,怎么回包从 eth0 来了?
这时候如果没有额外机制兜底,对端要么直接丢弃,要么陷入无限重传。而你自己在服务器上 tcpdump,又能看到回包确实发出去了,于是陷入"我明明在回包啊"的困惑。
1.3 多网卡环境的两种典型非对称场景
实际运维中,多网卡导致路由非对称的场景就两种最常见:
场景一:默认路由只有一个出口
机器有两块网卡,eth0 配了外网 IP 和默认网关,eth1 配了内网 IP 但没有配网关。内网主机往 eth1 发送请求时,服务器收到的包没问题,但回复时查路由表,发现目标 IP(内网主机)不属于任何内网直连网段,也没有针对内网网段的明细路由,最终走了默认路由 eth0 出外网。这就是典型的"进得来、出不去(该走的出口)"。
场景二:多个网卡在同一网段或路由表里有等价路径
这种更隐蔽。机器上有 eth0 和 eth1,两个网卡都配了同一网段的 IP(比如都是 192.168.10.x),内核路由表里就有两条等价直连路由。包从 eth0 进来,回复时内核做哈希或轮询选择一条出接口,可能正好选了 eth1。对端的交换机如果没有开启对称路由检测,通常问题不大;但如果对端主机也开了反向路径校验,就会把这个回包当成"伪造源地址"丢弃。
这两种场景的共同特点是:去程路径和回程路径不一致,也就是非对称路由。在非对称路由环境下,如果内核或者对端设备启用了严格的反向路径过滤,通信就会失败。这时候,rp_filter就要登场了。
2. rp_filter 三种模式:严格、宽松、关闭各拦什么
2.1 严格模式(rp_filter=1):回程出接口必须与入接口一致
rp_filter的全称是 Reverse Path Filtering,中文常翻译为反向路径过滤。它的作用是防止 IP 欺骗:一个数据包从某块网卡进来,内核会以这个包的源 IP 为目标 IP,反向查一次路由表,看看如果我要给这个源 IP 发包,是不是应该从当前这块网卡出去。
如果设置为严格模式(值等于 1),要求就非常苛刻:反向查询得到的最佳路由,出接口必须和这个包进来的网卡完全一致,否则直接丢包。
回到前面那个例子:eth1 收到一个源 IP 为 172.16.0.2 的包,内核以 172.16.0.2 为目标反向查路由表,发现只有默认路由,出接口是 eth0,与当前入接口 eth1 不一致。严格模式下,这个包在内核协议栈的 IP 接收阶段就直接被丢弃了,根本不会向上递交给 TCP/UDP 层。
所以你会看到什么现象?tcpdump 抓包能看到客户端发的包确实到达了网卡,但应用层没有任何响应,因为内核压根没把包交上去。客户端那边自然是超时重试,最终连接失败。
严格模式是很多服务器发行版的默认行为,所以默认情况下,上面说的非对称路由场景就会导致网络不通。这也是为什么很多人加了一块网卡之后,莫名其妙发现新网卡 Ping 不通,但网卡物理链路明明是通的。
2.2 宽松模式(rp_filter=2):只校验源IP可达性
宽松模式(值等于 2)就好说话得多。它同样以数据包的源 IP 反向查路由表,但不要求出接口与入接口一致,只要求:只要路由表里有任何一条路由能到达这个源 IP,就认为这个包是合法的,放行。
还是那个例子:eth1 收到源 IP 172.16.0.2 的包,反向查路由表,即使最优路由是走 eth0 的默认路由,但至少存在一条路径能到 172.16.0.2,宽松模式判断通过,包被交给上层协议栈处理。
这就是标题里"宽松模式"的核心价值所在:它允许非对称路由存在,同时仍然能拦截那些源 IP 完全不可达的伪造包。
对比一下:
| 模式 | 检查逻辑 | 对非对称路由的态度 | 安全性 |
|---|---|---|---|
| 严格(1) | 回程最佳路由的出接口必须等于入接口 | 直接丢弃 | 最高 |
| 宽松(2) | 源 IP 在路由表里可达即可 | 放行 | 中等 |
| 关闭(0) | 不做任何反向路径检查 | 放行 | 最低 |
宽松模式不是不设防,它还是会挡掉一部分明显非法的包。比如一个源 IP 是 203.0.113.5 的包从内网网卡进来,但你的路由表里根本没有到 203.0.113.5 的路由(不管走哪个接口),宽松模式下照样丢弃。只有那些"源 IP 合法,但回程路径不对称"的包,宽松模式才会放行。
2.3 关闭模式(rp_filter=0)与配置生效规则:all 和接口参数取最严格值
还有一种配置是 0,也就是完全关闭反向路径过滤。这个模式下内核不关心包是从哪来的,只要目标地址是自己的,就照单全收。关闭模式能解决所有因非对称路由引起的丢包问题,但代价是服务器更容易被伪造源 IP 的流量攻击,比如放大型的反射攻击。
实际操作里踩坑最多的不是选哪个模式,而是不知道rp_filter内核参数的聚合生效规则。
Linux 的rp_filter参数有三种配置前缀:
net.ipv4.conf.all.rp_filternet.ipv4.conf.default.rp_filternet.ipv4.conf.<具体网卡名>.rp_filter
内核实际取的是all和具体网卡两者之间的最大值,也就是更严格的那个值生效。举个例子:你把 all 设成了 1(严格),把 eth1 设成了 2(宽松),你以为 eth1 上是宽松模式,实际上 eth1 仍然按严格模式运行,因为max(1, 2)是 2,等等——这里我要纠正一下,网上很多说法不严谨。
内核代码里的逻辑是:在计算某个接口的有效rp_filter值时,取conf->all.rp_filter和conf->ethX.rp_filter的最大值。如果 all=1、eth1=2,最大值是 2,eth1 实际按宽松模式运行,这是对的。但你如果想着"把 all 保持默认 0,只把 eth1 设成 1",那 eth1 的有效值也是 1,没问题。最怕的情况是:all 已经是 1(很多发行版默认如此),你把 eth1 改成 2 想开宽松,结果 eth1 上依然按严格模式运行,因为max(1, 2)=2——这里我又绕回来了,算了直接说结论吧:
正确做法是:要开宽松,就把
all也设成 2,或者设成 0。只改单个网卡往往不生效,因为发行版默认的all.rp_filter已经是 1。排查时一定要把all和每个网卡的参数都看一遍。
这个坑我至少见过三次:有人改完net.ipv4.conf.eth1.rp_filter=2,sysctl -p也执行了,但问题依旧,折腾半天才发现all.rp_filter=1把 eth1 的设置给顶掉了。
3. 宽松模式实测:一次多网卡故障的完整排查链路
3.1 故障现场:eth0 通外网,eth1 接内网,内网却连不上
直接还原一次真实排障过程,你就能理解宽松模式到底是怎么"测试网络"的。
客户服务器是双网卡:
- eth0:公网 IP,网关指向公网出口,外网访问正常
- eth1:内网 IP 192.168.50.10/24,网关为空(内网不设网关)
内网有一台办公主机 192.168.50.20,需要访问服务器上的一个应用端口。问题很简单:办公主机 Ping 192.168.50.10 能通,但 TCP 连接永远卡在 TCP 握手,应用完全连不上。
先说明 Ping 能通这一点特别误导人。ICMP 是内核协议栈直接处理的,如果入包被rp_filter丢弃,那 Ping 应该也不通。但实际中可能出现 Ping 却通的情况,因为某些系统配置里 ICMP 走的是另一套处理路径,或者办公主机 Ping 用的是网卡直连地址,恰好触发了对称路由。不管怎样,Ping 通不代表 TCP 就通,这个案例里 Ping 能通反而让排查绕了远路。
3.2 排查步骤:从 ping/tcpdump 到抓出 rp_filter
我给的排查路径是这样的:
第一步,确认回程路由。
在服务器上执行:
ip route get 192.168.50.20这条命令非常关键,它直接告诉你:如果我要给 192.168.50.20 发包,内核会选择哪个出接口。结果出来是:
192.168.50.20 dev eth0 ...也就是说,服务器回复给 192.168.50.20 的包,居然要绕道 eth0 出去,而不是从 eth1 直连出去。这就确认了非对称路由存在。原因是服务器上可能有一条覆盖 192.168.50.0/24 的静态路由指向了 eth0,或者 eth1 上没起来直连路由。
第二步,验证入包是否被丢。
在服务器上抓 eth1 的包:
tcpdump -i eth1 host 192.168.50.20可以看到办公主机的 SYN 包确实到达了网卡。但此时再看服务器上的 TCP 连接状态,没有任何 192.168.50.20 的半连接记录。说明包在协议栈就被丢掉了,根本没到 TCP 层。
第三步,查 rp_filter 的值。
sysctl net.ipv4.conf.all.rp_filter net.ipv4.conf.default.rp_filter net.ipv4.conf.eth0.rp_filter net.ipv4.conf.eth1.rp_filter输出大概是这样:
net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 net.ipv4.conf.eth0.rp_filter = 0 net.ipv4.conf.eth1.rp_filter = 0注意,关键卡点出现了:eth0 和 eth1 都明确写成 0(关闭),但 all 的值是 1,按照内核的聚合规则,所有网卡的有效rp_filter值都是max(1, 0)=1,也就是严格模式。这就是包被丢弃的根本原因。
3.3 切到宽松模式验证:为什么网络立刻通了
把 all 改成 2,临时验证:
sysctl -w net.ipv4.conf.all.rp_filter=2改完这一条,办公主机再连服务,瞬间通了。这个"瞬间"本身就是有力的诊断证据:物理链路没问题,路由表没问题,防火墙规则也没问题,唯一的变量就是反向路径过滤策略。
为什么切到宽松模式就通了?因为宽松模式不要求回程出接口与入接口一致。办公主机 192.168.50.20 的源 IP 在路由表里合法可达(虽然要走 eth0 绕行),内核判定这个包不是伪造的,放行交给上层。TCP 握手完成,回包依然走ip route get查出来的 eth0 出口,办公主机上如果没有反向路径过滤,自然能正常收到。
这里也可以反向推断一次:如果改成宽松模式后问题依旧,那说明丢包原因不在rp_filter,而是防火墙、应用监听地址、或者对端的反向路径校验,就需要往别的方向排查。
整个排查链路里,宽松模式充当的角色不是一个"治本方案",而是一个隔离变量用的探针:它把"反向路径过滤"从嫌疑名单里摘出去,让你能确认问题根因。这也是"使用宽松模式测试网络"这句话的真正含义。
4. 多网卡路由区分的根治方案:策略路由与多路由表
4.1 为什么宽松模式只是"测试手段"
宽松模式能解决问题,但代价是放行了一些本可以被严格模式拦截的包。对一台公网服务来说,长期开着宽松模式等于降低了对伪造源 IP 攻击的防御能力。所以在生产环境里,宽松模式只适合作为临时验证手段,验证完根因之后,应该考虑更体面的治本方案。
治本的方向只有一条:让回程路由变得对称。换句话说,从 eth1 进来的流量,回复时也必须从 eth1 出去。这就要用到策略路由。
默认的 Linux 路由机制是"目的地路由":只看目标 IP,不看来源。多网卡区分路由的痛点恰恰在于"来源"——我希望根据来源网卡或者来源 IP 决定走哪条路。这就是策略路由做的事。
策略路由的核心组件是两个:ip rule和自定义路由表。
4.2 ip rule + 多路由表:让回程按来源走指定出口
先看两个概念:
- 路由表:Linux 本身支持多张路由表,常见的有
main、default和local。你可以创建自己的表,比如给 eth1 单独建一张表,里面写"凡是来源是内网网段,默认网关走 eth1"。 - 策略规则(ip rule):决定"哪些包查哪张路由表"。每条规则可以匹配来源地址、入接口、fmark 等条件,并指定跳转到某张路由表里查找。
整体流程是这样的:数据包到来 → 内核按优先级逐条匹配ip rule→ 命中某条规则后去对应的路由表里做最长前缀匹配 → 找到出接口。
实际操作以之前的场景为例,给 eth1 建独立的回程路由表:
先给新表起个名字。编辑/etc/iproute2/rt_tables,加一行:
100 eth1_table然后添加策略规则和路由:
# 来源为 192.168.50.0/24 的包,跳到 eth1_table 查路由 ip rule add from 192.168.50.0/24 lookup eth1_table pref 100 # 在 eth1_table 里写入直连路由和默认路由 ip route add 192.168.50.0/24 dev eth1 src 192.168.50.10 table eth1_table ip route add default via 192.168.50.1 dev eth1 table eth1_table这里的核心逻辑是:凡是从 192.168.50.0/24 网段来的包,内核在回复时只查 eth1_table,不再去看 main 表。eth1_table 里明确写了这个网段直连 dev eth1,所以回包就会从 eth1 出去,路由对称了。
验证一下:
ip route get 192.168.50.20 from 192.168.50.10 iif eth1这个命令模拟"一个来自 192.168.50.10、到达本机的包,回程应该如何走"。预期结果是出接口为 eth1,与入接口一致。这样即使rp_filter保持严格模式,也不会丢包了。
4.3 策略路由配置示例与持久化
策略路由的持久化有两种做法:写脚本开机自启,或者在/etc/iproute2/rt_tables配合 NetworkManager 或 systemd-networkd 的 routing rule 配置来实现。不同发行版差异很大,我习惯用一个独立脚本,简单可控:
#!/bin/bash # /etc/network/if-up.d/eth1_policy_routing # 或者 systemd service 方式 ip rule add from 192.168.50.0/24 lookup eth1_table pref 100 ip route add 192.168.50.0/24 dev eth1 src 192.168.50.10 table eth1_table 2>/dev/null ip route add default via 192.168.50.1 dev eth1 table eth1_table 2>/dev/null需要注意,策略路由的ip rule规则是幂等的,重复添加会报错,所以脚本里最好先判断规则是否存在,或者用ip rule del from 192.168.50.0/24 lookup eth1_table 2>/dev/null先删除再加。实际生产环境里,网卡重启、NetworkManager 重载时可能会清掉自定义路由表和规则,最好用系统的 network dispatcher 钩子来保证鲁棒性。
5. 多网卡网络测试的经验补充与避坑清单
5.1 我常用的网络测试工具组合
多网卡问题排查里,最怕的就是"凭感觉改配置"。我自己的习惯是固定一套工具组合,按顺序跑:
- ip route get—— 第一利器,快速判断去程和回程的出口,比人脑想路由表靠谱得多。
- ip rule show—— 看有没有策略路由在生效,尤其是那些计划外的规则,经常是它们抢了默认路由。
- tcpdump + 特定端口/ICMP—— 判断包是否到达网卡。注意 tcpdump 看到包不代表内核接受了包,要看上层协议栈的行为。
- sysctl 查看 rp_filter 全家桶—— 一定要把
all和所有网卡的值都列出来,再计算聚合结果。 - nc 或 curl 做真实连接测试—— 拿端到端的连接结果说话,比 Ping 可靠得多。
我见过有人在多网卡机器上 ping 了一大通都通,就下了"网络没问题"的结论,结果实际上业务端口全是卡的。建议测试时始终用真实业务流量验证,ICMP 只作为链路是否 up 的参考。
5.2 宽松模式的安全边界:能开多久、哪些场景别开
需要明确一点:宽松模式不是"长期配置选项",而是"临时诊断选项"。除了安全性的考虑,宽松模式开着还会掩盖很多网络问题。比如你新接了一条专线或者换了交换机,本应该出现非对称路由告警,但宽松模式下包照样通,问题就被淹没了。
有几个场景特别不建议开宽松模式:
- 直接暴露在公网的 Web/数据库服务器。严格模式能挡住一部分源 IP 伪造的扫描和反射放大攻击,把它关了等于削弱一层防护。
- 有等保或安全合规要求的业务环境。很多安全基线检查会强查
rp_filter的值,不符直接扣分。 - 对端也启用了严格反向路径校验的网络。这种情况光在自己这边开宽松没用,回包到对端还是会被丢,必须靠策略路由根治。
5.3 多网卡环境踩坑汇总清单
最后把多网卡路由相关的坑集中总结一下,都是我实际踩过或者帮别人擦过屁股的:
| 坑 | 现象 | 解法 |
|---|---|---|
| 只改具体网卡 rp_filter,不改 all | 明明配了宽松模式,实际仍是严格 | 两个都要改,确认聚合值 |
| 多个网卡同网段 | 等价路由导致回程随机选接口 | 用 ip rule 按入接口绑路由表 |
| 默认路由唯一 | 内网业务网卡收到的包回程走外网 | 给业务网卡建独立路由表 |
| 网卡重启后自定义路由丢失 | 策略路由临时生效,重启消失 | 写持久化脚本,挂到 network 钩子 |
| 防火墙规则掩盖真实问题 | 改完 rp_filter 还是不通 | 先查 iptables/nftables 日志,确认是否被独立规则拦截 |
还有一个容易被忽略的点:云主机和虚拟化平台的多网卡行为。大部分云平台虚拟网卡的接收队列行为、底层交换机的对称路由策略,与物理机差异不小。你在物理机上改rp_filter能生效,在云主机上可能因为虚拟化层的 VPC 网关策略而失效。遇到云上的多网卡非对称路由问题,建议先看云平台文档有没有"启用多网卡对称路由"的开关,再谈内核参数。
多网卡路由问题看着玄乎,本质就是"去程和回程路径不一致 + 反向路径过滤策略过严"的组合。理解了这条底层逻辑,再看到"网卡通了却连不上"的故障,你至少不会再盲猜了。先用ip route get看路径,再用宽松模式验证rp_filter,最后用策略路由治本——这套组合拳打下来,基本能解决九成以上的多网卡疑难杂症。