海外物理机如何按ASN配置CN2 GIA、CMI与9929策略路由?
目标状态是:一台海外物理机同时具备 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 GIA | eno1 | 192.0.2.2 | 192.0.2.0/29 | 192.0.2.1 | 101 | 201 |
| CMI | eno2 | 198.51.100.2 | 198.51.100.0/29 | 198.51.100.1 | 102 | 202 |
| 9929 | eno3 | 203.0.113.2 | 203.0.113.0/29 | 203.0.113.1 | 103 | 203 |
表中的网关位于直连网段内。如果实际交付是 /32 地址、链路外网关或其他特殊方式,应先按服务商给出的网络配置完成连通性验证,不能直接照搬这组路由命令。
明确 ASN 与线路的对应关系
ASN 表示地址前缀的路由起源,不表示数据包经过的每一个骨干网络。以下是可作为起点的运营商分组,并非完整的中国大陆地址清单:
| 目标前缀起源 ASN | 默认选择出口 | 需要理解的边界 |
|---|---|---|
| AS4134、AS4809 | CN2 GIA | 分别涉及电信相关网络;目标属于这些 ASN,不代表实际路径一定全程经过 AS4809 |
| AS9808、AS58453 | CMI | 包含移动相关国内及国际网络,覆盖范围不只限于大陆终端 |
| AS4837、AS9929 | 9929 | 联通普通网络与相关精品网络的目标都可按业务策略指定走此出口 |
这张表是业务选路策略,不是线路质量证明。其他 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. 安装路由加载脚本
本方案使用六张专用路由表:

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 连接失败
先抓包区分结果:

- 没有发出 SYN:检查应用绑定地址、本机防火墙和应用错误。
- SYN 已从正确接口发出,但没有回复:检查上游源地址许可、出口可达性和远端访问限制。
- 回复到达接口,却没有被应用接收:检查
rp_filter、本机防火墙和监听地址。 - SYN 与回复走了不同公网 IP:检查应用是否另行绑定地址,以及是否存在其他路由、地址转换或流量标记规则。
不要用清空防火墙作为验证手段。本方案不需要关闭主机防火墙,也不需要开启 IP 转发。
三个集合都有路由,但结果偏向第一张表
检查跨组重叠、未经校验的宽前缀,以及目的路由表中是否误加了默认路由。101~103 任意一张表存在可用默认路由,都可能让后续表无法被查询。
同一张表内仍遵循最长前缀匹配,但不同表之间不是“先汇总所有路由再取最长匹配”,这是排障时容易混淆的地方。

某条线路中断,为什么没有自动切到其他线路
本文没有配置健康探测和自动故障切换。链路仍显示 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 的行为已向业务人员说明。
- 自动回滚已取消,手动回滚脚本和部署前快照已保留。
- 重启后服务成功加载,规则、地址和业务端口均通过复验。
- 后续前缀更新先生成、比较、审核,再在维护窗口加载;不直接把下载结果推入线上。
- 线路中断不会被误认为已有自动容灾;需要切换时,先确认源地址许可和回程条件。



