Linux服务器如何配置策略路由?多网卡与多出口网络实战

一台服务器上挂两张甚至多张网卡,是很常见的架构:一张走内网、一张走公网,或者一张走普通宽带、一张走专线,再或者一张走国内、一张走国际。默认情况下,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 输出并确认带外通道可用。