双端口监听 – 负载均衡 smart 策略组配置
本文测试ip:203.0.113.10、10.7.0.10、10.8.0.10 需要替换成自己的ip。
如何利用 CN2 和 9929 做双端口线路负载均衡
前言
利群CN2 9929服务器 VPS 同时接入了两条不同质量的网络线路:
-
一条中国电信 CN2 线路;
-
一条中国联通 9929 线路。
虽然拥有两个内网地址和两个网关,但 Linux 通常只会设置一条默认路由。应用程序发起连接时,如果不进行额外配置,所有流量仍然会从默认网关出去,无法真正利用两条线路。
本文介绍一种简单、可维护的方案:
-
使用 Realm 监听两个端口;
-
两个端口转发到同一个目标地址;
-
一个端口固定使用 9929 出口;
-
另一个端口固定使用 CN2 出口;
-
同时支持 IPv4 和 IPv6 客户端接入;
-
使用策略路由保证两个源地址分别走正确网关。
需要说明的是,本文所说的“负载均衡”是双端口线路分流:
端口 A → 9929
端口 B → CN2
由客户端或上层程序选择连接哪个端口。
它不会把单条 TCP 连接同时拆分到两条线路,也不会让一条 SSH、代理或隧道连接获得两条线路带宽之和。
一、网络拓扑
假设服务器存在两张网卡:
| 线路 | 网卡 | 本机地址 | 网关 | 路由表 |
|---|---|---|---|---|
| 9929 | eth0 | 10.7.0.10 | 10.7.0.1 | T_9929 |
| CN2 | eth1 | 10.8.0.10 | 10.8.0.1 | T_CN2 |
服务器默认出口是 CN2:
default via 10.8.0.1 dev eth1
需要转发的目标使用脱敏示例:
203.0.113.10:22
Realm 对外监听两个端口:
52001 → 9929 → 203.0.113.10:22
52002 → CN2 → 203.0.113.10:22
最终结构如下:
客户端
│
├── IPv4/IPv6:52001
│ │
│ └── Realm
│ └── 10.7.0.10
│ └── eth0
│ └── 10.7.0.1
│ └── 9929
│
└── IPv4/IPv6:52002
│
└── Realm
└── 10.8.0.10
└── eth1
└── 10.8.0.1
└── CN2
二、前置条件
本文默认系统为 Debian 12,并且已经存在以下两个路由表:
cat /etc/iproute2/rt_tables
应当能够看到类似内容:
101 T_9929
102 T_CN2
同时确认网卡和地址:
ip -4 addr
ip route
示例输出:
10.7.0.10/23 dev eth0
10.8.0.10/23 dev eth1
default via 10.8.0.1 dev eth1
本文不负责创建 T_9929 和 T_CN2 的名称映射,只会向这两个已经存在的路由表写入对应路由。
三、Realm 双端口配置
假设 Realm 配置文件位于:
/etc/realm/config.toml
创建目录:
mkdir -p /etc/realm
写入配置:
cat >/etc/realm/config.toml <<'EOF'
[log]
level = "info"
output = "stdout"
[network]
no_tcp = false
use_udp = false
# false 表示监听 [::] 时,同时接受 IPv6 和 IPv4 连接
ipv6_only = false
# 建立远程 TCP 连接的超时时间
tcp_timeout = 10
# TCP Keepalive
tcp_keepalive = 15
tcp_keepalive_probe = 3
# ============================================================
# 入口一:9929
# ============================================================
[[endpoints]]
listen = "[::]:52001"
remote = "203.0.113.10:22"
# Realm 建立目标连接时使用的本地源地址
through = "10.7.0.10"
# 指定出站网卡
interface = "eth0"
# ============================================================
# 入口二:CN2
# ============================================================
[[endpoints]]
listen = "[::]:52002"
remote = "203.0.113.10:22"
through = "10.8.0.10"
interface = "eth1"
EOF
其中:
listen = "[::]:52001"
ipv6_only = false
表示使用 IPv6 通配地址监听,并同时接受 IPv4 和 IPv6 连接。
因此客户端可以通过以下方式连接:
服务器 IPv4:52001
服务器 IPv6:52001
服务器 IPv4:52002
服务器 IPv6:52002
through 和 interface 的作用
第一组配置:
through = "10.7.0.10"
interface = "eth0"
表示 Realm 连接目标服务器时:
-
将本地源地址绑定为
10.7.0.10; -
将出站网卡绑定为
eth0。
第二组同理:
through = "10.8.0.10"
interface = "eth1"
但仅仅设置 Realm 还不够。
Linux 最终选择哪个网关,仍然由内核路由系统决定。因此还需要配置策略路由。
四、为什么必须配置策略路由
假设系统默认路由是:
default via 10.8.0.1 dev eth1
即使 Realm 已经将连接源地址绑定为:
10.7.0.10
如果没有源地址策略路由,内核仍可能根据主路由表将连接交给 eth1。
这会造成以下问题:
-
源地址是
10.7.0.10; -
数据包却尝试从
eth1发出; -
网关或上游设备可能无法正确处理;
-
连接可能超时、回程异常或完全不通。
因此需要告诉 Linux:
源地址 10.7.0.10
目标地址 203.0.113.10
必须查询 T_9929
同时:
源地址 10.8.0.10
目标地址 203.0.113.10
必须查询 T_CN2
五、可维护的策略路由脚本
创建脚本:
nano /usr/local/sbin/realm-policy-route.sh
写入以下内容:
#!/bin/bash
set -euo pipefail
# ============================================================
# Realm 目标 IP
#
# 支持配置多个目标。
# 增加或删除转发目标时,只需要修改这个数组。
# 不要在这里填写端口,因为 ip rule 只能匹配 IP,
# 不能匹配 TCP 或 UDP 端口。
# ============================================================
TARGET_IPS=(
"203.0.113.10"
# "198.51.100.20"
# "192.0.2.30"
)
# ============================================================
# 9929 出口
# ============================================================
IP_9929="10.7.0.10"
GW_9929="10.7.0.1"
DEV_9929="eth0"
TABLE_9929="T_9929"
RULE_PRIO_9929="14900"
# ============================================================
# CN2 出口
# ============================================================
IP_CN2="10.8.0.10"
GW_CN2="10.8.0.1"
DEV_CN2="eth1"
TABLE_CN2="T_CN2"
RULE_PRIO_CN2="14901"
# ============================================================
# 基础检查
# ============================================================
if [ "${#TARGET_IPS[@]}" -eq 0 ]; then
echo "错误:TARGET_IPS 数组为空。"
exit 1
fi
if ! ip route show table "$TABLE_9929" >/dev/null 2>&1; then
echo "错误:路由表 $TABLE_9929 不存在。"
exit 1
fi
if ! ip route show table "$TABLE_CN2" >/dev/null 2>&1; then
echo "错误:路由表 $TABLE_CN2 不存在。"
exit 1
fi
for target_ip in "${TARGET_IPS[@]}"; do
if ! [[ "$target_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
echo "错误:目标 IP 格式不正确:$target_ip"
exit 1
fi
done
# ============================================================
# 配置 9929 路由表
# ============================================================
ip route replace 10.7.0.0/23 \
dev "$DEV_9929" \
scope link \
src "$IP_9929" \
table "$TABLE_9929"
ip route replace default \
via "$GW_9929" \
dev "$DEV_9929" \
src "$IP_9929" \
table "$TABLE_9929"
# ============================================================
# 配置 CN2 路由表
# ============================================================
ip route replace 10.8.0.0/23 \
dev "$DEV_CN2" \
scope link \
src "$IP_CN2" \
table "$TABLE_CN2"
ip route replace default \
via "$GW_CN2" \
dev "$DEV_CN2" \
src "$IP_CN2" \
table "$TABLE_CN2"
# ============================================================
# 删除脚本上一次创建的规则
#
# 14900 和 14901 应当专门预留给本脚本使用。
# 请勿在其他策略路由脚本中重复使用这两个优先级。
# ============================================================
while ip rule del priority "$RULE_PRIO_9929" 2>/dev/null; do
true
done
while ip rule del priority "$RULE_PRIO_CN2" 2>/dev/null; do
true
done
# ============================================================
# 为每个目标创建两条策略规则
# ============================================================
for target_ip in "${TARGET_IPS[@]}"; do
# 绑定 10.7.0.10 的 Realm 连接查询 9929 路由表
ip rule add \
priority "$RULE_PRIO_9929" \
from "${IP_9929}/32" \
to "${target_ip}/32" \
table "$TABLE_9929"
# 绑定 10.8.0.10 的 Realm 连接查询 CN2 路由表
ip rule add \
priority "$RULE_PRIO_CN2" \
from "${IP_CN2}/32" \
to "${target_ip}/32" \
table "$TABLE_CN2"
echo "已配置目标:$target_ip"
echo " $IP_9929 -> $TABLE_9929"
echo " $IP_CN2 -> $TABLE_CN2"
done
ip route flush cache 2>/dev/null || true
echo
echo "Realm 策略路由配置完成。"
echo "目标数量:${#TARGET_IPS[@]}"
保存后赋予执行权限:
chmod +x /usr/local/sbin/realm-policy-route.sh
执行:
/usr/local/sbin/realm-policy-route.sh
正常情况下会输出:
已配置目标:203.0.113.10
10.7.0.10 -> T_9929
10.8.0.10 -> T_CN2
Realm 策略路由配置完成。
目标数量:1
六、为什么策略规则要同时限制源地址和目标地址
本文没有直接添加下面这种全局规则:
ip rule add from 10.7.0.10 table T_9929
而是使用:
ip rule add \
from 10.7.0.10/32 \
to 203.0.113.10/32 \
table T_9929
两者的区别很重要。
只限制源地址
from 10.7.0.10 lookup T_9929
这会使所有使用 10.7.0.10 作为源地址的连接都走 9929,影响范围较大。
同时限制源地址和目标地址
from 10.7.0.10 to 203.0.113.10 lookup T_9929
只有同时符合以下条件的连接才会命中:
-
源地址是
10.7.0.10; -
目标地址是
203.0.113.10。
这样可以把规则影响范围限制在 Realm 指定的转发目标,不会轻易干扰服务器上的其他服务。
七、规则优先级设计
Linux 的 ip rule 优先级数字越小,越早匹配。
本文使用:
14900 → Realm 9929
14901 → Realm CN2
如果服务器已经存在以下规则:
15000 → 静态目标分流
15005 → DDNS 目标分流
32766 → main 主路由表
32767 → default 路由表
那么 Realm 的专用规则会先执行:
14900 / 14901
↓
15000 / 15005
↓
32766 main
由于 Realm 规则同时限制了源地址和目标地址,因此虽然优先级更高,但不会接管其他普通流量。
如果服务器现有策略路由使用了不同优先级,需要根据实际情况调整 RULE_PRIO_9929 和 RULE_PRIO_CN2。
查看现有规则:
ip rule show
八、设置策略路由开机启动
创建 systemd 服务:
cat >/etc/systemd/system/realm-policy-routing.service <<'EOF'
[Unit]
Description=Realm dual-line policy routing
Wants=network-online.target
After=network-online.target
Before=realm.service
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/realm-policy-route.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
重新加载 systemd:
systemctl daemon-reload
启用并立即执行:
systemctl enable --now realm-policy-routing.service
查看状态:
systemctl status realm-policy-routing.service --no-pager
正常情况下应显示:
Active: active (exited)
oneshot 服务执行完脚本后显示 active (exited) 是正常现象,并不代表服务异常退出。
九、配置 Realm systemd 服务
假设 Realm 程序安装在:
/opt/realm/realm
创建服务:
cat >/etc/systemd/system/realm.service <<'EOF'
[Unit]
Description=Realm Proxy Service
Wants=network-online.target
Requires=realm-policy-routing.service
After=network-online.target realm-policy-routing.service
[Service]
Type=simple
ExecStart=/opt/realm/realm -c /etc/realm/config.toml
Restart=always
RestartSec=3
LimitNOFILE=524288
[Install]
WantedBy=multi-user.target
EOF
实际路径可以通过以下命令确认:
command -v realm
如果 Realm 不在 /opt/realm/realm,需要修改 ExecStart。
启动并设置开机自启:
systemctl daemon-reload
systemctl enable --now realm
查看状态:
systemctl status realm --no-pager
查看日志:
journalctl -u realm -n 100 --no-pager
实时查看:
journalctl -u realm -f
十、验证策略路由
1. 查看策略规则
ip rule show
预期包含:
14900: from 10.7.0.10 to 203.0.113.10 lookup T_9929
14901: from 10.8.0.10 to 203.0.113.10 lookup T_CN2
2. 查看 9929 路由表
ip route show table T_9929
预期:
default via 10.7.0.1 dev eth0 src 10.7.0.10
10.7.0.0/23 dev eth0 scope link src 10.7.0.10
3. 查看 CN2 路由表
ip route show table T_CN2
预期:
default via 10.8.0.1 dev eth1 src 10.8.0.10
10.8.0.0/23 dev eth1 scope link src 10.8.0.10
4. 模拟 9929 路由选择
ip route get 203.0.113.10 from 10.7.0.10
预期:
203.0.113.10 from 10.7.0.10 via 10.7.0.1 dev eth0 table T_9929
5. 模拟 CN2 路由选择
ip route get 203.0.113.10 from 10.8.0.10
预期:
203.0.113.10 from 10.8.0.10 via 10.8.0.1 dev eth1 table T_CN2
这两个测试是最关键的。
只有分别显示:
10.7.0.1 dev eth0 table T_9929
和:
10.8.0.1 dev eth1 table T_CN2
才能说明策略路由配置正确。
十一、验证 Realm 双栈监听
执行:
ss -lntp | grep realm
或者:
ss -lntp | grep -E ':52001|:52002'
预期看到:
[::]:52001
[::]:52002
Realm 日志中通常也会出现:
inited: [::]:52001 -> [203.0.113.10:22]
inited: [::]:52002 -> [203.0.113.10:22]
ipv6-only=false
这说明两个端口已经监听成功,并且允许 IPv4 映射连接。
十二、通过抓包确认实际出口
策略路由查询正确后,还可以通过 tcpdump 验证实际数据包。
监听 9929 网卡:
tcpdump -ni eth0 host 203.0.113.10
监听 CN2 网卡:
tcpdump -ni eth1 host 203.0.113.10
然后分别连接:
服务器地址:52001
服务器地址:52002
预期结果:
连接 52001
只应在 eth0 上看到:
10.7.0.10 → 203.0.113.10
连接 52002
只应在 eth1 上看到:
10.8.0.10 → 203.0.113.10
如果两个端口都从同一张网卡发出,通常说明策略路由没有命中,需要重新检查:
ip rule show
ip route show table T_9929
ip route show table T_CN2
十三、增加多个目标
策略脚本将目标地址维护为数组:
TARGET_IPS=(
"203.0.113.10"
"198.51.100.20"
"192.0.2.30"
)
修改后重新应用:
systemctl restart realm-policy-routing.service
也可以直接运行:
/usr/local/sbin/realm-policy-route.sh
每个目标会产生两条规则:
源地址 10.7.0.10 → T_9929
源地址 10.8.0.10 → T_CN2
例如配置三个目标后,会产生六条策略规则。
Realm 配置中也需要添加对应的转发端点,例如:
[[endpoints]]
listen = "[::]:52003"
remote = "198.51.100.20:443"
through = "10.7.0.10"
interface = "eth0"
[[endpoints]]
listen = "[::]:52004"
remote = "198.51.100.20:443"
through = "10.8.0.10"
interface = "eth1"
需要注意:
ip rule 只能匹配目标 IP,不能匹配目标端口。
因此,只要源地址和目标 IP 相同,无论连接的是目标服务器上的哪个端口,都会使用对应的策略路由表。
十四、这套方案的优点
1. 配置简单
Realm 只负责:
-
监听端口;
-
转发 TCP;
-
绑定源 IP;
-
绑定出站网卡。
Linux 策略路由只负责:
-
根据源地址和目标地址选择路由表。
两部分职责清晰。
2. 不需要 NAT
Realm 本身作为 TCP 中继重新建立目标连接,因此不需要额外配置 DNAT、SNAT 或 MASQUERADE。
目标服务器看到的来源地址是两条线路对应的公网出口地址。
3. 支持 IPv6 入口
即使转发目标是 IPv4,客户端仍然可以通过服务器 IPv6 地址访问 Realm。
链路可以是:
客户端 IPv6
↓
Realm IPv6 监听
↓
目标 IPv4
4. 不干扰其他业务
策略规则同时限制:
-
Realm 使用的源地址;
-
Realm 对应的目标地址。
因此不会简单粗暴地把整台服务器的所有连接都改到某一线路。
5. 目标维护方便
目标使用 Bash 数组集中管理:
TARGET_IPS=(...)
增加、删除目标时,不需要复制多段 ip rule 命令。
总结
利用 Realm 和 Linux 策略路由,可以在同时拥有 CN2 和 9929 的服务器上实现简单可靠的双端口线路分流:
IPv4/IPv6:52001
→ Realm
→ 10.7.0.10
→ T_9929
→ 9929 出口
IPv4/IPv6:52002
→ Realm
→ 10.8.0.10
→ T_CN2
→ CN2 出口
整个方案的核心只有两点:
第一,Realm 使用不同的 through 源地址区分两个入口:
through = "10.7.0.10"
through = "10.8.0.10"
第二,Linux 根据源地址和目标地址查询不同的路由表:
10.7.0.10 + 指定目标 → T_9929
10.8.0.10 + 指定目标 → T_CN2
这样既可以保留服务器原来的默认路由和目标分流规则,又能让两个 Realm 监听端口稳定地从不同线路建立连接。
对于代理转发、SSH 中继、跨境专线测试、双线路容灾以及手动线路选择,这是一套结构简单、资源占用低、维护成本较小的实现方式。


没有回复内容