一台服务器上挂两张甚至多张网卡,是很常见的架构:一张走内网、一张走公网,或者一张走普通宽带、一张走专线,再或者一张走国内、一张走国际。默认情况下,Linux 只会用一张路由表,所有跨网段的流量都挤同一条默认路由出去,结果是"内网流量也走了公网出口""专线买了却没流量""不同来源的请求回包从错误的网卡出去被对端丢弃"。这些问题的答案都是同一个:策略路由。
策略路由(Policy Routing)的核心思想是"路由不只看目的地址,还看源地址、入接口、标记等信息"。传统路由是"目标地址决定走哪条路",策略路由则可以先判断"这个包是谁发的、从哪进来的、有什么标记",再决定查哪张路由表、走哪个出口。这篇文章从原理讲到实操,给出一套多网卡多出口的完整配置示例,并说明各发行版的持久化方法。
需要提醒的是,策略路由属于偏底层的网络配置,配错会造成路由黑洞甚至整机失联。文中涉及改路由表的操作,务必先备份 ip rule show 和 ip route show table all 的输出,远程操作前确认带外(VNC/串口)通道可用。
一、为什么默认单表路由不够用
Linux 内核传统的路由行为是:收到一个要发出的包,拿目的地址去查唯一的主路由表(main 表),选出一条路由。这个模型只以"目的地址"为唯一依据,天然无法表达"同一目的地址,不同来源走不同出口"的需求。
举几个典型场景:
- 双出口分流:服务器有电信和联通两条线,希望访问联通网段的流量走联通出口、访问电信网段的走电信出口,以获得最优延迟。默认路由只能二选一。
- 内外网分离:eth0 接公网、eth1 接内网,希望内网流量只走 eth1、公网流量只走 eth0,避免敏感内网流量绕公网。
- 源地址决定出口:服务器有两个公网 IP,希望"从某个应用发出的流量固定用某个 IP",保证对端看到的源地址稳定(很多白名单机制依赖这个)。
- VPN 分流:只有特定网段的流量走 VPN 隧道,其他走直连,避免全局代理。
这些需求用一张路由表都做不到,必须引入多张路由表加"选表规则"。
二、策略路由的三个核心概念
理解策略路由,抓住三样东西就够了:路由表(table)、规则(rule)、优先级(priority)。
多张路由表:Linux 内核支持最多 255 张路由表(编号 0-255),每张表里可以各自有一套完整的路由记录。默认情况下只有 main 表(254)在用。除了 main,内核还预留了 local(255,本地地址)、default(253)等。用户自定义的表用编号 1-252,或者用 /etc/iproute2/rt_tables 给编号起有意义的名字。
规则(rule):规则决定"什么样的包去查哪张表"。规则可以基于源地址、目的地址、入接口、fwmark 标记、TOS 等条件。规则有优先级,数字小的先匹配,一旦匹配就按对应表查路由,不再往下匹配(除非规则里写了 continue)。
默认行为:内核自带三条优先级极高的规则:
# 查看当前所有规则
ip rule show
# 典型输出:
# 0: from all lookup local
# 32766: from all lookup main
# 32767: from all lookup default
0: from all lookup local:查 local 表(本机地址),优先级最高,先处理发往本机的包。32766: from all lookup main:查 main 表(我们日常看到的默认路由就在这)。32767: from all lookup default:fallback,default 表通常为空。
我们要做的,就是在优先级 32766 之前插入自己的规则,让特定流量先查自定义表。
三、一个完整的多出口配置示例
假设场景:服务器有两张网卡,eth0 接联通出口(网段 10.0.0.0/24,网关 10.0.0.1),eth1 接电信出口(网段 192.168.1.0/24,网关 192.168.1.1)。需求是:默认流量走电信(eth1),从 eth0 进来的连接仍然从 eth0 回包,保证源地址一致。
第一步:给两张网卡配好地址,暂时不要设两个默认路由(会打架)。
# 查看网卡地址
ip -br addr show
# 先清掉可能存在的重复默认路由(远程操作请谨慎,先备份)
ip route show > /root/route.bak # 备份
第二步:在 rt_tables 里给自定义表起名字(可选但推荐,方便阅读)。
# 编辑 /etc/iproute2/rt_tables,追加两行
# 100 telecom
# 200 unicom
echo "100 telecom" >> /etc/iproute2/rt_tables
echo "200 unicom" >> /etc/iproute2/rt_tables
第三步:分别往两张自定义表里写路由。
# 联通表:默认走 10.0.0.1
ip route add default via 10.0.0.1 dev eth0 table unicom
# 电信表:默认走 192.168.1.1
ip route add default via 192.168.1.1 dev eth1 table telecom
# 两张表里还要加上本地直连网段,否则策略表找不到直连路由
ip route add 10.0.0.0/24 dev eth0 scope link table unicom
ip route add 192.168.1.0/24 dev eth1 scope link table telecom
第四步:添加规则,决定哪些流量查哪张表。
# 从 eth0(联通)进来的包,回包走联通表,保证源地址一致
ip rule add from 10.0.0.0/24 table unicom priority 100
# 从 eth1(电信)进来的包,走电信表
ip rule add from 192.168.1.0/24 table telecom priority 200
# 验证规则生效
ip rule show
第五步:主表(main)里放一条默认路由作为最终兜底。
# 主表默认走电信
ip route add default via 192.168.1.1 dev eth1
# 或者直接 replace
ip route replace default via 192.168.1.1 dev eth1
这样配置后:从联通地址发起的连接,回包走联通;从电信地址发起的连接,回包走电信;其他没有匹配规则的流量,走主表默认的电信出口。这就是最典型的"按源地址分流"策略,解决了"回包源地址不一致"的老大难问题。
四、用 fwmark 做更灵活的分流
基于源地址的规则只解决了"哪张网卡发起就用哪张网卡回"的问题。如果需要更精细的分流,比如"某个进程的流量走专线,其他走默认",就要用防火墙标记(fwmark)。原理是:先在 mangle 表的 OUTPUT 链给符合条件的包打上标记,再让路由规则根据标记选表。
# 1. 给特定目的端口(比如某业务)的包打 mark 0x1
iptables -t mangle -A OUTPUT -p tcp --dport 8443 -j MARK --set-mark 0x1
# 2. 建一张专线路由表并填路由
ip route add default via 172.16.0.1 dev eth2 table 300
ip route add 172.16.0.0/24 dev eth2 scope link table 300
# 3. 规则:有 mark 0x1 的包查 300 号表
ip rule add fwmark 0x1 table 300 priority 100
# 验证
ip rule show
ip route get 8.8.8.8 mark 0x1
fwmark 的灵活性在于,iptables 的匹配条件极其丰富(源/目的 IP、端口、协议、uid、连接状态等),几乎可以实现任意粒度的分流策略。代价是配置复杂、排查链路长,改一处要同时看 iptables 和 ip rule 两处。
下表对比几种常见分流方式,方便选型:
| 分流方式 | 依据 | 适用场景 | 复杂度 |
|---|---|---|---|
| 单默认路由 + metric | 目的地址 | 简单的双线主备 | 低 |
| 按源地址策略路由 | 源 IP/网段 | 双出口回包一致性 | 中 |
| 按入接口策略路由 | iif 入接口 |
多网卡流进流出对应 | 中 |
| fwmark 标记分流 | 端口/协议/uid | 进程级、业务级精细化分流 | 高 |
| 按目的地址分流 | 目的网段 | 联通/电信/国际线路优选 | 中 |
五、持久化:各发行版配置方式
临时用 ip rule add 和 ip route add 配的规则路由,重启就没了。持久化按发行版有以下几种方式:
方式一:RHEL/CentOS/Rocky 的 rule-<接口> 文件
# 在 /etc/sysconfig/network-scripts/ 下创建 rule-eth0
# 内容示例:
# from 10.0.0.0/24 table unicom
# from 192.168.1.0/24 table telecom
# 对应的路由表文件 route-eth0 / route-eth1 里写:
# default via 10.0.0.1 dev eth0 table unicom
# 10.0.0.0/24 dev eth0 scope link table unicom
需要确保 iproute2 已安装,且 network 服务或 NetworkManager 支持读取这些文件。老版本 CentOS 7 用 network 服务时最稳。
方式二:NetworkManager 的 dispatcher 脚本
# 在 /etc/NetworkManager/dispatcher.d/ 下放一个可执行脚本,
# 当接口 up 时执行 ip rule/ip route 命令
# 例如 /etc/NetworkManager/dispatcher.d/90-policy-route
#!/bin/bash
if [ "$1" = "eth0" ] && [ "$2" = "up" ]; then
ip rule add from 10.0.0.0/24 table unicom priority 100
ip route add default via 10.0.0.1 dev eth0 table unicom
fi
# 记得 chmod +x,且脚本属主必须为 root
方式三:systemd 服务或 rc.local
# 写一个 systemd unit,在网络就绪后执行脚本,兼容性最好
# /etc/systemd/system/policy-route.service
# [Unit]
# After=network-online.target
# [Service]
# Type=oneshot
# ExecStart=/usr/local/sbin/policy-route.sh
# [Install]
# WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now policy-route.service
方式四:Debian/Ubuntu 的 if-up.d
# /etc/network/interfaces 里可以写 up / post-up 脚本
# 例如:
# iface eth0 inet static
# address 10.0.0.10
# netmask 255.255.255.0
# up ip rule add from 10.0.0.0/24 table unicom priority 100
# up ip route add default via 10.0.0.1 dev eth0 table unicom
# 也可以在 /etc/network/if-up.d/ 下放脚本
方式五:netplan(Ubuntu 18.04+ / Debian 12)
# /etc/netplan/01-netcfg.yaml 中原生支持 routing-policy
network:
version: 2
ethernets:
eth0:
addresses: [10.0.0.10/24]
routes:
- to: default
via: 10.0.0.1
table: 100
routing-policy:
- from: 10.0.0.0/24
table: 100
netplan 原生支持路由策略,是最干净的写法,改完 netplan apply 或 netplan try 即可。
六、验证与排错
配置完策略路由后,必须验证每条规则都按预期工作。
# 查看所有规则(按优先级排序)
ip rule show
# 查看某张自定义表的全部路由
ip route show table unicom
ip route show table 300
ip route show table all | head -50 # 所有表
# 最关键:验证"从某个源地址发出的包会走哪条路"
ip route get 8.8.8.8 from 10.0.0.10
ip route get 8.8.8.8 from 192.168.1.10
# 带 mark 验证
ip route get 8.8.8.8 mark 0x1
# 实际发流验证(从指定源地址 ping)
ping -I 10.0.0.10 -c 3 8.8.8.8
ping -I 192.168.1.10 -c 3 8.8.8.8
# 查看内核路由缓存和统计
ip -s route show cache
排错要点:
- 规则没生效:先
ip rule show确认规则存在且优先级正确(数字小的先匹配)。注意内核自带的32766和32767规则,你新加的规则优先级必须小于 32766 才会被先匹配。 - 自定义表里有默认路由但直连网段没写:会导致"表里找不到直连路由",包里带不上正确的源地址。自定义表里一定要把相关直连网段
scope link补上。 - 回包源地址不对:检查是否用了
ip rule按源地址分流,以及网卡上是否配了正确的地址。必要时用ip route add ... src <地址>显式指定源地址。 - 连接不稳定:可能是
arp_filter或rp_filter反向路径过滤在捣乱。多网卡同网段场景下rp_filter会丢弃"来路不明"的包,需要检查sysctl net.ipv4.conf.all.rp_filter。
七、风险提示与回滚
策略路由的错误配置很容易导致服务器彻底失联,务必按以下原则操作:
# 1. 操作前完整备份
ip rule show > /root/rule.bak
ip route show table all > /root/route-all.bak
iptables-save > /root/iptables.bak # 如果涉及 fwmark
# 2. 保留带外通道:云控制台 VNC/串口、物理机的 IPMI/iDRAC
# 3. 用 "ip rule add ... priority N" 时从高到低验证,别一次加一堆
# 4. 不要轻易 flush(清空)
# ip rule flush # 危险:清掉所有规则,可能导致失联
# ip route flush table N # 只清指定表,相对安全
# 回滚:删除某条规则(按条件精确删)
ip rule del from 10.0.0.0/24 table unicom priority 100
# 回滚:清空某张自定义表
ip route flush table unicom
# 回滚:删除某条路由
ip route del default via 10.0.0.1 dev eth0 table unicom
如果已经把网络搞断、ip rule flush 之后连不上,只能通过带外控制台重新加载网络服务(systemctl restart NetworkManager 或 systemctl restart networking),让配置文件里的规则重新生效。
常见问题
Q:ip rule 和 ip route 有什么区别?
ip rule 决定"用哪张表",ip route 决定"表里怎么走"。策略路由是两者配合:先由规则按源地址/mark 选中一张表,再在表里按目的地址选一条路由。
Q:加了策略路由后,反而访问某网段不通了,为什么?
最常见是自定义表里只写了默认路由、没写直连网段,导致该表内找不到直连路由,包发不出去。另一个原因是规则的优先级比预期低,被内核自带的 main 表规则抢先匹配。用 ip route get <目标> from <源> 逐条验证决策结果。
Q:两台服务器双网卡对接,为什么 ping 得通但业务连不上?
很可能是回包从另一张网卡出去,源地址对不上,被对端因"状态不匹配"丢弃。这正是策略路由要解决的"回包源地址一致性"问题,按源地址做分流即可。
Q:改完 netplan 的 routing-policy,netplan apply 后没生效?
先确认 YAML 缩进和字段名正确(routing-policy),再用 ip rule show 验证内核是否真的加载了规则。如果没加载,检查 netplan 版本是否支持该字段,必要时退回用 dispatcher 脚本方式。
Q:策略路由规则重启后就丢了,怎么持久化最省事?
优先用发行版原生方式:RHEL 系用 rule-<接口> 文件或 NetworkManager dispatcher,Ubuntu 用 netplan 的 routing-policy。写一个开机执行的 systemd oneshot 服务是兼容性最好的兜底方案,但要注意它必须在网络就绪之后运行(After=network-online.target)。
总结
策略路由解决的是"路由不能只看目的地址"的问题,核心是三个概念:多张路由表、匹配规则、规则优先级,内核用 ip rule 选表、用 ip route 选路。
最典型的多出口场景是"按源地址分流",让从某网卡进来的连接回包也走同一网卡,保证源地址一致,避免对端因状态不匹配丢包。
配置顺序是:给自定义表写路由(含直连网段)→ 加规则并设优先级(小于 32766)→ 主表放兜底默认路由 → 用 ip route get ... from ... 验证。
需要更细粒度分流时用 fwmark:先在 mangle 表打标记,再用 ip rule add fwmark 按标记选表,可实现进程级、业务级分流。
持久化按发行版选择:RHEL 用 rule- 文件或 NetworkManager dispatcher,Ubuntu 用 netplan 的 routing-policy,通用兜底用 systemd oneshot 服务。任何路由规则变更前都要备份 ip rule show 输出并确认带外通道可用。
企业QQ咨询




