多线 VPS 的源地址选择与策略路由

发布于 8 天前 34 次阅读


多线 VPS(一台机器挂多个 IP、多个运营商出口)最常踩的坑不是「不通」,而是「从哪个 IP 出去不受控」。表现往往很迷惑:

  • 服务明明绑定了 A 网卡,对端看到的却是 B 的 IP
  • 从 A IP 进来,回包却从 B 出去,导致连接被丢
  • 想让代理从「干净的那个 IP」出口,结果一直用的是主 IP

这篇讲清三件事:内核怎么选源地址、怎么查当前选的是谁、怎么强制指定(从简单到精确三种手段)。

本文属于 VPS 网络与代理链路系列,该系列共六篇,可从此页查看完整阅读顺序与全部索引。

一、问题的根源:源地址是谁决定的

很多人以为「绑定网卡就等于指定出口 IP」,其实不是。Linux 选源地址有固定顺序:

  1. 路由表条目里的 src 字段(优先级最高)
  2. 如果没有 src,用出口网卡的主 IP
  3. 都不满足则报错 Network is unreachable

所以如果路由表里没写 src,内核就按网卡主 IP 走——这往往是错的,因为多线机器每块网卡都有独立的 IP 和网关。

典型多线配置 网卡 IP 段 网关
电信线路 eth0 1.1.1.0/24 1.1.1.1
联通线路 eth1 2.2.2.0/24 2.2.2.1
移动线路 eth2 3.3.3.0/24 3.3.3.1

如果只配了一条默认路由,那所有流量都从一个出口走,另外两条线的 IP 就是「摆设」——收包能用,发包不用。

二、先查清现状:三条命令

# 1) 看所有 IP 和网卡
ip -4 addr show

# 2) 看路由表(注意每条 default 有没有 src 字段)
ip route show

# 3) 最关键:去某个目标,实际会用哪个源 IP
ip route get 8.8.8.8

第 3 条命令的输出长这样:

8.8.8.8 via 1.1.1.1 dev eth0 src 1.1.1.10 uid 0
    cache

读法:via 1.1.1.1 是网关,dev eth0 是出口网卡,src 1.1.1.10 就是实际用的源 IP。

想验证某个特定源 IP 会走哪条路,加 from 参数:

ip route get 8.8.8.8 from 2.2.2.10

三、手段一:路由表加 src 字段(最简单)

适合「只想要一个固定出口」的场景。

# 指定默认路由的源地址
ip route replace default via 1.1.1.1 dev eth0 src 1.1.1.10

# 验证
ip route get 8.8.8.8

局限:只能设一个默认出口。想让「访问 A 网段走电信、访问 B 网段走联通」就做不到,得上策略路由。

四、手段二:策略路由(按规则分流)

策略路由的核心是 ip rule + 多张路由表。规则按优先级(数字小的先匹配)决定用哪张表。

4.1 建两张自定义路由表

# 表名只是便于阅读,数字是实际标识
echo "100 telecom" >> /etc/iproute2/rt_tables
echo "200 unicom"  >> /etc/iproute2/rt_tables

4.2 给每张表配默认路由

# 电信表
ip route add default via 1.1.1.1 dev eth0 src 1.1.1.10 table telecom
# 联通表
ip route add default via 2.2.2.1 dev eth1 src 2.2.2.10 table unicom

# 表内还要能到达自己的网段(否则回包出不去)
ip route add 1.1.1.0/24 dev eth0 src 1.1.1.10 table telecom
ip route add 2.2.2.0/24 dev eth1 src 2.2.2.10 table unicom

4.3 加规则

# 从电信 IP 发出的流量,查电信表
ip rule add from 1.1.1.10 table telecom priority 100

# 从联通 IP 发出的流量,查联通表
ip rule add from 2.2.2.10 table unicom priority 200

# 查看规则顺序(priority 小的在前)
ip rule list

这样配置后,源 IP 决定了走哪条线:服务绑定 1.1.1.10 就从电信出去,绑定 2.2.2.10 就从联通出去。

4.4 也可以按目标地址分流

# 去 8.8.8.0/24 的流量强制走联通
ip rule add to 8.8.8.0/24 table unicom priority 150

五、手段三:应用层绑定(最精确)

如果只想让某个程序从特定 IP 出去,不动系统路由,用应用层绑定最省事。

5.1 命令行工具

# curl 指定源 IP
curl --interface 1.1.1.10 -4 https://ifconfig.me

# ping 指定源 IP
ping -I 1.1.1.10 8.8.8.8

# ssh 指定源 IP
ssh -b 1.1.1.10 用户@目标

5.2 Xray 出站绑定

{
  "outbounds": [{
    "protocol": "freedom",
    "sendThrough": "1.1.1.10",
    "settings": { "domainStrategy": "UseIP" },
    "tag": "direct"
  }]
}

sendThrough 就是指定出站源 IP。搭配 domainStrategy: UseIP 能确保先解析再绑定,避免域名解析阶段走错出口。

5.3 sing-box 出站绑定

{
  "outbounds": [{
    "type": "direct",
    "tag": "direct",
    "inet4_bind_address": "1.1.1.10"
  }]
}

六、验证:三处都要对

检查点 命令 期望
内核选路 ip route get 8.8.8.8 from 1.1.1.10 src 是你指定的 IP
实际出口 curl --interface 1.1.1.10 https://ifconfig.me 返回该 IP
代理出口 客户端连上后 curl ifconfig.me 返回期望的 IP

三处都对才算配好。只改路由不看实际出口,很容易出现「路由看着对、实际发出去还是主 IP」的情况。

七、四个必踩的坑

7.1 只配路由,没配 src → 回程从别的 IP 出

最常见。路由指向了 eth1 的网关,但没写 src,内核用 eth1 主 IP 发包;如果对端按源 IP 做白名单,直接拒绝。

解法:路由条目里显式写 src。

7.2 rp_filter 严格模式丢弃非对称回包

多线机器天然存在非对称路由(从 A 进、从 B 出)。内核默认的严格 rp_filter 会认为「这个包不该从这张网卡回来」,直接丢弃——表现为「能连上但收不到响应」。

# 查看当前值(1 = 严格,2 = 松散,0 = 关闭)
sysctl net.ipv4.conf.all.rp_filter
sysctl net.ipv4.conf.eth0.rp_filter

# 改成松散模式(推荐,比直接关更安全)
sysctl -w net.ipv4.conf.all.rp_filter=2
sysctl -w net.ipv4.conf.default.rp_filter=2

# 按网卡设置
for i in eth0 eth1 eth2; do
  sysctl -w net.ipv4.conf.$i.rp_filter=2
done

# 持久化
cat >> /etc/sysctl.conf <<EOF
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
EOF

7.3 策略路由没考虑已建立的连接

用 ip rule + from 时,规则对新建连接有效,但已建立的连接仍走旧路径。改完规则后旧连接会僵住,看起来像「改了没生效」。

解法:改完策略路由后重启相关服务,或等旧连接超时。用 fwmark 做规则时更要注意这点。

7.4 重启后全部失效

ip route / ip rule 命令都是运行时配置,重启即丢。

解法:写成一个 systemd service,开机自动执行:

cat > /usr/local/bin/setup-multiline.sh <<'EOF'
#!/bin/bash
# 清掉旧规则(幂等)
ip rule del table telecom 2>/dev/null
ip rule del table unicom  2>/dev/null

ip route replace default via 1.1.1.1 dev eth0 src 1.1.1.10 table telecom
ip route replace default via 2.2.2.1 dev eth1 src 2.2.2.10 table unicom
ip route replace 1.1.1.0/24 dev eth0 src 1.1.1.10 table telecom
ip route replace 2.2.2.0/24 dev eth1 src 2.2.2.10 table unicom

ip rule add from 1.1.1.10 table telecom priority 100
ip rule add from 2.2.2.10 table unicom  priority 200
EOF
chmod +x /usr/local/bin/setup-multiline.sh

cat > /etc/systemd/system/multiline-routing.service <<'EOF'
[Unit]
Description=Multi-WAN source routing
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/setup-multiline.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

systemctl enable --now multiline-routing.service
systemctl status multiline-routing.service

八、三种手段怎么选

手段 粒度 适合 复杂度
路由加 src 全局默认出口 单出口机器,只想固定源 IP 低
策略路由 按源 IP / 目标网段 多线分流,不同业务走不同线 中
应用层绑定 单个进程 只让某个服务走特定 IP 低

推荐顺序:能用应用层绑定解决就不动系统路由(影响面最小);需要整机默认出口就加 src;只有真正需要按规则分流时才上策略路由。

九、小结

多线机器的核心认知就一句话:源地址是内核按路由表选的,不是按你绑定的网卡选的。

排查顺序固定三步:ip -4 addr 看有哪些 IP → ip route get 看实际用哪个 → 用 src/ip rule/sendThrough 强制指定。

配完一定用 curl --interface 验证真实出口,别只看路由表——这两个不一致是这类问题最常见的翻车点。

相关阅读