上一篇 下一篇 分享链接 返回 返回顶部

海外物理机如何按ASN配置CN2 GIA、CMI与9929策略路由?

发布人:Minchunlin 发布时间:2026-10-06 22:02 阅读量:1

目标状态是:一台海外物理机同时具备 CN2 GIA、CMI、9929 三个可独立使用的出口,新建的 IPv4 外连连接按目标地址所属 ASN 选择线路;已经从某个公网 IP 建立的业务连接,回复流量仍从该 IP 对应的出口返回。实施前必须拿到每条线路的接口、地址、网关及源地址限制,并具备带外控制台,不能仅凭“服务器包含三网优化线路”就开始配置。

下面采用 Ubuntu Server 24.04 LTS、iproute2 和 Python 3 实施:将 ASN 转换为目标 IPv4 前缀,把前缀安装到三张独立路由表,再用 ip rule 依次查询。Linux 路由并不直接识别 ASN,因此真正落地的是“ASN 前缀清单+策略路由”。本方案不要求物理机运行 BGP,也不改写业务流量的源地址。

一、准备条件:先确认三条线路确实可以分别选择

确认服务器交付形态

本方案适用于三个独立三层出口:可以是三张网卡,也可以是已经配置完成的三个 VLAN 接口。每个出口都有可用的地址和下一跳。

如果服务商只交付一个公网 IP、一个默认网关,由上游设备自动选择 CN2 GIA、CMI 或 9929,主机侧无法通过新增路由表强制指定上游线路。需要服务商另外提供独立网关、VLAN 或相应的选路机制。

上线前核对以下条件:

  • 三个出口已经能分别到达互联网,地址和网关不是待配置状态。
  • 服务商允许各线路使用对应的源 IP,不存在未说明的反欺骗限制。
  • 默认路由已能正常使用,SSH 管理地址及管理出口已经记录。
  • 本机不是承担容器网关或网络转发的宿主机;本文仅处理物理机自身的连接。
  • 本次仅部署 IPv4。IPv6 仍按现有 IPv6 路由运行,不能据此宣称双栈均已完成分流。

以下使用文档示例地址,执行时必须替换为实际交付参数:

用途接口本机地址直连网段网关ASN 目的路由表源地址固定表
CN2 GIAeno1192.0.2.2192.0.2.0/29192.0.2.1101201
CMIeno2198.51.100.2198.51.100.0/29198.51.100.1102202
9929eno3203.0.113.2203.0.113.0/29203.0.113.1103203

表中的网关位于直连网段内。如果实际交付是 /32 地址、链路外网关或其他特殊方式,应先按服务商给出的网络配置完成连通性验证,不能直接照搬这组路由命令。

明确 ASN 与线路的对应关系

ASN 表示地址前缀的路由起源,不表示数据包经过的每一个骨干网络。以下是可作为起点的运营商分组,并非完整的中国大陆地址清单:

目标前缀起源 ASN默认选择出口需要理解的边界
AS4134、AS4809CN2 GIA分别涉及电信相关网络;目标属于这些 ASN,不代表实际路径一定全程经过 AS4809
AS9808、AS58453CMI包含移动相关国内及国际网络,覆盖范围不只限于大陆终端
AS4837、AS99299929联通普通网络与相关精品网络的目标都可按业务策略指定走此出口

这张表是业务选路策略,不是线路质量证明。其他 ASN、未识别前缀以及未纳入清单的运营商地址,继续走系统原有默认路由。

另一个必须保留的边界是:

按目标 ASN 分流只负责选择服务器发出的新连接路径,不能控制远端如何到达服务器。业务回复必须优先服从源 IP 对应出口,否则容易触发上游源地址过滤,或形成不符合交付要求的非对称路径。

因此,本文把“按源 IP 固定出口”的规则放在“按目标 ASN 选择出口”的规则之前。

二、分步操作:备份、生成前缀、安装策略

1. 安装依赖并记录现有网络

sudo apt-get update
sudo apt-get install -y iproute2 python3 curl traceroute tcpdump

记录接口、地址、规则和路由:

ip -br link
ip -4 -br address
ip -4 rule show
ip -4 route show table all

核对表号 101、102、103、201、202、203 未被其他业务使用,规则优先级 100、101、102、200、201、202 也未被占用。已有非默认策略规则可能影响本方案,应先分析其匹配顺序,不能直接插入。

本方案不修改 Netplan,也不替换 main 表的默认路由。网卡、地址、默认路由应由服务器原有网络配置持续管理。

创建配置目录和备份目录:

sudo install -d -m 0750 /etc/asn-policy
sudo install -d -m 0700 /root/asn-policy-backup

ip -4 rule show | sudo tee /root/asn-policy-backup/rules.txt >/dev/null
ip -4 route show table all | sudo tee /root/asn-policy-backup/routes.txt >/dev/null
ip -j -4 address show | sudo tee /root/asn-policy-backup/addresses.json >/dev/null

文本快照用于核对原状,不应不加区分地作为批量恢复命令执行。

保存稍后要修改的反向路径检查参数:

for key in \
  net.ipv4.conf.all.rp_filter \
  net.ipv4.conf.default.rp_filter \
  net.ipv4.conf.eno1.rp_filter \
  net.ipv4.conf.eno2.rp_filter \
  net.ipv4.conf.eno3.rp_filter
do
  printf '%s = %s\n' "$key" "$(sysctl -n "$key")"
done | sudo tee /root/asn-policy-backup/rp-filter.conf >/dev/null

实际接口名不同,需同时修改这里及后续脚本。

2. 生成经过校验的 ASN 前缀文件

可以使用已审核的前缀库,也可以从公开路由数据服务获取。以下示例使用 RIPEstat 的 announced-prefixes 接口:

https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS4134

它提供的是路由观测数据,不是地理位置数据库,也不能证明某条线路的实际转发质量。数据缺失、路由起源变化和多起源前缀都应作为更新风险处理。

保存为 /etc/asn-policy/fetch-prefixes.py:

#!/usr/bin/env python3
import ipaddress
import json
import pathlib
import urllib.request

groups = {
    "cn2": [4134, 4809],
    "cmi": [9808, 58453],
    "cu9929": [4837, 9929],
}
result = {}

for name, asns in groups.items():
    networks = []
    for asn in asns:
        url = (
            "https://stat.ripe.net/data/announced-prefixes/data.json"
            f"?resource=AS{asn}"
        )
        with urllib.request.urlopen(url, timeout=30) as response:
            payload = json.load(response)

        if payload.get("status") != "ok":
            raise RuntimeError(f"AS{asn}: API response failed")

        count = 0
        for item in payload["data"]["prefixes"]:
            net = ipaddress.ip_network(item["prefix"], strict=True)
            if net.version != 4:
                continue
            if not net.is_global or net.prefixlen < 8:
                raise RuntimeError(f"AS{asn}: suspicious prefix {net}")
            networks.append(net)
            count += 1

        if count == 0:
            raise RuntimeError(f"AS{asn}: no IPv4 prefixes returned")

    result[name] = list(ipaddress.collapse_addresses(networks))

# 不同线路之间不允许前缀覆盖,否则规则顺序可能掩盖更具体路由。
ranges = sorted(
    (
        int(net.network_address),
        int(net.broadcast_address),
        name,
        str(net),
    )
    for name, nets in result.items()
    for net in nets
)
active = []
for start, end, name, text in ranges:
    active = [item for item in active if item[0] >= start]
    for other_end, other_name, other_text in active:
        if other_name != name:
            raise RuntimeError(
                f"cross-group overlap: {text} and {other_text}"
            )
    active.append((end, name, text))

out = pathlib.Path("/etc/asn-policy/staging")
out.mkdir(parents=True, exist_ok=True)
for name, nets in result.items():
    text = "".join(f"{net}\n" for net in nets)
    (out / f"{name}.txt").write_text(text, encoding="ascii")
    print(f"{name}: {len(nets)} prefixes")

运行并检查:

sudo python3 /etc/asn-policy/fetch-prefixes.py
sudo wc -l /etc/asn-policy/staging/*.txt
sudo head /etc/asn-policy/staging/cn2.txt

这里主动拒绝跨组重叠。原因是策略规则先查某张表,一旦该表存在较宽的匹配路由,就不会再去后面的表寻找更具体的路由。

发现重叠时,应暂停导入,明确归属并生成互不重叠的前缀集合;不要简单调整规则顺序来掩盖问题,也不要把一条覆盖大量地址的宽前缀直接删掉。

首次部署审核完成后,将文件放到正式目录:

sudo install -d -m 0750 /etc/asn-policy/prefixes
sudo install -m 0640 /etc/asn-policy/staging/cn2.txt /etc/asn-policy/prefixes/cn2.txt
sudo install -m 0640 /etc/asn-policy/staging/cmi.txt /etc/asn-policy/prefixes/cmi.txt
sudo install -m 0640 /etc/asn-policy/staging/cu9929.txt /etc/asn-policy/prefixes/cu9929.txt

后续更新应先备份整个 prefixes 目录并比较增删量。下载失败时保留现有文件,不能用空集合覆盖线上配置。

3. 设置宽松反向路径检查

严格反向路径检查可能因多出口选路而丢弃合法入站包。这里使用 rp_filter=2,允许按宽松模式检查源地址可达性,不完全关闭检查。

确认 /etc/sysctl.d/90-asn-policy.conf 尚不存在;若已有同名文件,先检查内容,不要覆盖其他业务配置。

sudo tee /etc/sysctl.d/90-asn-policy.conf >/dev/null <<'EOF'
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.eno1.rp_filter = 2
net.ipv4.conf.eno2.rp_filter = 2
net.ipv4.conf.eno3.rp_filter = 2
EOF

sudo sysctl -p /etc/sysctl.d/90-asn-policy.conf

影响范围是本机 IPv4 入站的源地址校验行为,现有防火墙规则不变。回滚时删除本次新增的文件,再加载已保存的参数快照。

4. 安装路由加载脚本

本方案使用六张专用路由表:

二、分步操作:备份、生成前缀、安装策略 / 4. 安装路由加载脚本配图

  • 201~203:包含对应接口的直连路由和默认路由,服务于已确定源 IP 的连接。
  • 101~103:包含对应接口的直连路由及 ASN 目标前缀,不配置默认路由。未命中时继续查询下一张表,最终落到 main。

保存为 /usr/local/sbin/asn-policy-load:

#!/usr/bin/env bash
set -euo pipefail

DIR=/etc/asn-policy/prefixes

# 用实际交付参数替换这些值。
DEV=(eno1 eno2 eno3)
ADDR=(192.0.2.2 198.51.100.2 203.0.113.2)
NET=(192.0.2.0/29 198.51.100.0/29 203.0.113.0/29)
GW=(192.0.2.1 198.51.100.1 203.0.113.1)
DT=(101 102 103)
ST=(201 202 203)
NAME=(cn2 cmi cu9929)

# 先校验全部输入;校验失败时不改路由。
python3 - "$DIR" <<'PY'
import ipaddress
import pathlib
import sys

base = pathlib.Path(sys.argv[1])
ranges = []
for name in ("cn2", "cmi", "cu9929"):
    lines = (base / f"{name}.txt").read_text().splitlines()
    if not lines:
        raise SystemExit(f"{name}: empty prefix file")
    seen = set()
    for line in lines:
        net = ipaddress.ip_network(line, strict=True)
        if net.version != 4 or not net.is_global or net.prefixlen < 8:
            raise SystemExit(f"{name}: invalid prefix {line}")
        if net in seen:
            raise SystemExit(f"{name}: duplicate prefix {line}")
        seen.add(net)
        ranges.append((
            int(net.network_address),
            int(net.broadcast_address),
            name,
            str(net),
        ))

active = []
for start, end, name, text in sorted(ranges):
    active = [item for item in active if item[0] >= start]
    for other_end, other_name, other_text in active:
        if name != other_name:
            raise SystemExit(f"overlap: {text} / {other_text}")
    active.append((end, name, text))
PY

for i in 0 1 2; do
    ip link show dev "${DEV[$i]}" >/dev/null
    ip -4 -o address show dev "${DEV[$i]}" |
        grep -Fq " ${ADDR[$i]}/"
done

# 仅删除本方案的精确规则。重复部署会短暂撤下分流。
for i in 0 1 2; do
    while ip -4 rule del priority "$((100+i))" \
        from "${ADDR[$i]}/32" table "${ST[$i]}" 2>/dev/null; do :; done
    while ip -4 rule del priority "$((200+i))" \
        table "${DT[$i]}" 2>/dev/null; do :; done
done

for i in 0 1 2; do
    ip -4 route flush table "${DT[$i]}"
    ip -4 route flush table "${ST[$i]}"

    ip -4 route add "${NET[$i]}" dev "${DEV[$i]}" \
        scope link src "${ADDR[$i]}" table "${ST[$i]}"
    ip -4 route add default via "${GW[$i]}" dev "${DEV[$i]}" \
        src "${ADDR[$i]}" table "${ST[$i]}"

    ip -4 route add "${NET[$i]}" dev "${DEV[$i]}" \
        scope link src "${ADDR[$i]}" table "${DT[$i]}"

    while IFS= read -r prefix; do
        ip -4 route add "$prefix" via "${GW[$i]}" dev "${DEV[$i]}" \
            src "${ADDR[$i]}" table "${DT[$i]}"
    done < "$DIR/${NAME[$i]}.txt"
done

# 路由全部安装完成后,再启用规则。
for i in 0 1 2; do
    ip -4 rule add priority "$((100+i))" \
        from "${ADDR[$i]}/32" table "${ST[$i]}"
done
for i in 0 1 2; do
    ip -4 rule add priority "$((200+i))" table "${DT[$i]}"
done

授权:

sudo chmod 0750 /usr/local/sbin/asn-policy-load

脚本中的 route flush 只允许作用于已确认专用的六张表,不能改成 table main 或 table all。重载会产生短暂选路变化,不属于无中断更新,应安排维护窗口。

src 是路由的首选源地址,不是 SNAT。应用没有绑定源地址时,可按目标路由选择对应出口 IP;应用若绑定了某个公网 IP,则由前面的源地址规则固定到该 IP 对应出口。

三、结果验证:先人工执行,再配置开机加载

1. 设置自动回滚保护

第一次实施应在带外控制台可用、原 SSH 会话保留的情况下进行。先准备第五节中的回滚脚本,再设置五分钟后自动执行:

sudo systemd-run --unit=asn-policy-rollback \
  --on-active=5min /usr/local/sbin/asn-policy-rollback

随后加载策略:

sudo /usr/local/sbin/asn-policy-load

这五分钟用于核对路由、业务端口和管理连接。时间不够就先回滚,不应只为了延长测试而取消保护。

2. 检查规则与路由选择

ip -4 rule show
ip -4 route show table 201
ip -4 route show table 101

预期顺序为:本地路由规则、三个源地址规则、三个 ASN 表查询规则、系统原有 main 与 default 查询规则。若还有其他规则插在其中,应确认它们不会提前截获流量。

从三个前缀文件中各选一个可实际测试的公网目标,再检查:

ip -4 route get <电信目标IPv4>
ip -4 route get <移动目标IPv4>
ip -4 route get <联通目标IPv4>

未指定源地址时,分别应看到对应接口、网关及首选源地址。再检查源地址约束:

ip -4 route get <移动目标IPv4> from 

此时仍应走 CN2 对应的源地址固定表,而不是 CMI。这个结果说明“回复保持出口一致”的规则优先级正确。

另外选一个不在三个集合中的公网地址,确认它仍走原有默认路由。

3. 验证真实业务,而不只看路由查询

使用自己可控制或已获授权的目标进行连接测试:

traceroute -n -T -p 443 <测试目标IPv4>
sudo tcpdump -ni eno2 'host <测试目标IPv4> and tcp port 443'

从业务请求开始抓包,检查出口接口及源地址是否符合预期。目标不开放 443 时,换用实际业务端口。

还应从外部访问物理机的三个公网 IP,分别确认服务响应正常、回包从对应接口发出。ip route get 只证明内核选路;中间跳出现某个 ASN,或部分跳不回应,也不能单独证明线路等级和端到端质量。

验收成功后取消自动回滚:

sudo systemctl stop asn-policy-rollback.timer
sudo systemctl is-active asn-policy-rollback.timer

若回滚服务已经开始执行,应重新核对当前规则,不要把定时器停止当作部署成功。

4. 配置开机加载

保存为 /etc/systemd/system/asn-policy.service:

[Unit]
Description=IPv4 ASN based policy routing
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/asn-policy-load
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

启用但不立即重复加载:

sudo systemctl daemon-reload
sudo systemctl enable asn-policy.service

network-online.target 是否真正等待三个接口就绪,取决于机器使用的网络管理服务。应核验相应的 wait-online 服务,并在维护窗口重启验证:

systemctl status asn-policy.service
journalctl -u asn-policy.service -b --no-pager

如果加载失败,脚本返回非零状态,不能把“服务已启用”当作路由已经生效。

四、失败处理:按低风险到高风险排查

路由查询没有进入预期表

先检查目标是否确实包含在前缀文件中,再检查 ip rule 的优先级和表内路由。

ASN 数据不覆盖某个目标时,应补充经过审核的归属数据;应用主动绑定源 IP 时,源地址规则优先属于正常现象。不要通过删除源地址规则让回复流量强行服从目标分类。

查路由正确,但 TCP 连接失败

先抓包区分结果:

四、失败处理:按低风险到高风险排查 / 查路由正确,但TCP连接失败配图

  • 没有发出 SYN:检查应用绑定地址、本机防火墙和应用错误。
  • SYN 已从正确接口发出,但没有回复:检查上游源地址许可、出口可达性和远端访问限制。
  • 回复到达接口,却没有被应用接收:检查 rp_filter、本机防火墙和监听地址。
  • SYN 与回复走了不同公网 IP:检查应用是否另行绑定地址,以及是否存在其他路由、地址转换或流量标记规则。

不要用清空防火墙作为验证手段。本方案不需要关闭主机防火墙,也不需要开启 IP 转发。

三个集合都有路由,但结果偏向第一张表

检查跨组重叠、未经校验的宽前缀,以及目的路由表中是否误加了默认路由。101~103 任意一张表存在可用默认路由,都可能让后续表无法被查询。

同一张表内仍遵循最长前缀匹配,但不同表之间不是“先汇总所有路由再取最长匹配”,这是排障时容易混淆的地方。

左侧两条路由位于同表,选择B;右侧A位于先查表101,B位于后查表102,命中A后不再查询B

某条线路中断,为什么没有自动切到其他线路

本文没有配置健康探测和自动故障切换。链路仍显示 UP 而上游黑洞时,路由可能继续命中故障出口。

新建且未绑定源地址的连接,可以通过撤下对应的目的策略规则暂时退回原默认出口;但已使用故障线路公网 IP 的连接,不能随意切换源地址来维持原会话。只有其他出口明确接受该源 IP、且回程也成立时,才具备进一步设计跨线容灾的基础。

五、回滚:撤销专用策略,保留原网络配置

保存为 /usr/local/sbin/asn-policy-rollback:

#!/usr/bin/env bash
set -euo pipefail

ADDR=(192.0.2.2 198.51.100.2 203.0.113.2)
DT=(101 102 103)
ST=(201 202 203)

systemctl disable asn-policy.service 2>/dev/null || true

for i in 0 1 2; do
    while ip -4 rule del priority "$((100+i))" \
        from "${ADDR[$i]}/32" table "${ST[$i]}" 2>/dev/null; do :; done
    while ip -4 rule del priority "$((200+i))" \
        table "${DT[$i]}" 2>/dev/null; do :; done
done

for table in 101 102 103 201 202 203; do
    ip -4 route flush table "$table"
done

# 此文件必须是本次部署新建、且没有其他业务共用的文件。
rm -f /etc/sysctl.d/90-asn-policy.conf
sysctl -p /root/asn-policy-backup/rp-filter.conf

授权及手动执行方式:

sudo chmod 0750 /usr/local/sbin/asn-policy-rollback
sudo /usr/local/sbin/asn-policy-rollback

回滚会撤销本次分流,并恢复部署前保存的反向路径检查参数;不会删除公网地址、改变默认路由或清空防火墙。撤下规则时,正在使用非默认出口源 IP 的连接可能中断,这是回滚的影响范围。

随后与备份核对:

ip -4 rule show
ip -4 route show table main
sysctl net.ipv4.conf.all.rp_filter

若部署期间其他人员调整过同一组参数,应先协调再恢复快照,避免覆盖后续变更。

六、上线验收清单

  • 三条线路均有独立、已验证的接口、地址和网关,服务商允许对应源 IP 出口。
  • 六张路由表和六个规则优先级均为本方案专用,没有覆盖原业务配置。
  • ASN 清单来源、生成时间、前缀数量和人工审核结果已记录。
  • 不同出口的前缀集合没有重叠,目的路由表没有默认路由。
  • 三组目标的新连接分别选择预期接口,未匹配地址继续使用原默认出口。
  • 三个公网 IP 的外部访问及回包均已验证,SSH 管理连接正常。
  • IPv6、应用显式绑定源地址以及未覆盖 ASN 的行为已向业务人员说明。
  • 自动回滚已取消,手动回滚脚本和部署前快照已保留。
  • 重启后服务成功加载,规则、地址和业务端口均通过复验。
  • 后续前缀更新先生成、比较、审核,再在维护窗口加载;不直接把下载结果推入线上。
  • 线路中断不会被误认为已有自动容灾;需要切换时,先确认源地址许可和回程条件。