利群 CN2 · 9929 双端口线路负载均衡配置思路分享

双端口监听 – 负载均衡 smart 策略组配置

声明:此文由 GPT-5.6-Sol 生成。撰写该文的目的是为了双出口负载均衡,可以在代理软件中配合 smart 或 url-test 策略组来达成多出口设置。

 

本文测试ip:203.0.113.10、10.7.0.10、10.8.0.10 需要替换成自己的ip。

 

如何利用 CN2 和 9929 做双端口线路负载均衡

前言

利群CN2 9929服务器 VPS 同时接入了两条不同质量的网络线路:

  • 一条中国电信 CN2 线路;

  • 一条中国联通 9929 线路。

虽然拥有两个内网地址和两个网关,但 Linux 通常只会设置一条默认路由。应用程序发起连接时,如果不进行额外配置,所有流量仍然会从默认网关出去,无法真正利用两条线路。

本文介绍一种简单、可维护的方案:

  1. 使用 Realm 监听两个端口;

  2. 两个端口转发到同一个目标地址;

  3. 一个端口固定使用 9929 出口;

  4. 另一个端口固定使用 CN2 出口;

  5. 同时支持 IPv4 和 IPv6 客户端接入;

  6. 使用策略路由保证两个源地址分别走正确网关。

需要说明的是,本文所说的“负载均衡”是双端口线路分流

端口 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_9929T_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

throughinterface 的作用

第一组配置:

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_9929RULE_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 中继、跨境专线测试、双线路容灾以及手动线路选择,这是一套结构简单、资源占用低、维护成本较小的实现方式。

 

请登录后发表评论

    没有回复内容