通过 .sh 脚本在 Linux 上添加路由
NetRoute Pro 生成包含 ip route add 命令的简单 Bash 脚本。一条命令即可应用。持久化根据发行版需一步额外操作。
命令语法
语法(幂等 — 脚本中使用此形式):
ip route replace <CIDR> via <GATEWAY> dev <VPN_IF>
示例:
ip route replace 1.1.1.0/24 via 10.0.0.1 dev wg0
以 root 身份运行或使用 sudo。NetRoute Pro 将命令生成为 .sh 脚本 — 通过 bash routes.sh 应用。replace 在路由不存在时创建、存在时更新,因此重复运行是安全的;add 在重复时会失败并报 RTNETLINK answers: File exists,配合 set -e 会中止脚本。
准备工作
iproute2(所有现代发行版预装)- root 或 sudo 权限
- 已配置并处于 up 状态的 VPN 接口(WireGuard、OpenVPN、IKEv2 等)
- 已安装 NetRoute Pro Chrome 扩展
步骤 1. 在 NetRoute Pro 中生成 .sh
- 在 Chrome 中打开目标网站
- 启动 NetRoute Pro,选择 Linux 平台
- 设置 VPN 接口的 IP 作为网关(例如
10.8.0.1) - 选择聚合掩码(推荐
/24) - 点击 Analyze Website
- 下载
routes.sh
步骤 2. 应用脚本
chmod +x routes.sh
sudo bash routes.sh
路由立即应用,一直保留到下次重启。要使路由在重启后保留,请执行步骤 3。
步骤 3. 使路由持久化
根据您的发行版选择方法:
systemd-networkd
创建 drop-in 文件 /etc/systemd/network/10-vpn.network.d/routes.conf:
[Route]
Destination=104.21.32.0/24
Gateway=10.8.0.1
[Route]
Destination=172.67.0.0/16
Gateway=10.8.0.1
应用:
sudo systemctl restart systemd-networkd
NetworkManager dispatcher
创建脚本 /etc/NetworkManager/dispatcher.d/99-vpn-routes(不要忘记可执行位):
#!/bin/bash
# $1 — interface, $2 — action
if [ "$1" = "wg0" ] && [ "$2" = "up" ]; then
/usr/local/bin/routes.sh
fi
sudo chmod +x /etc/NetworkManager/dispatcher.d/99-vpn-routes
systemd oneshot 服务(推荐)
适用于任何 systemd 发行版(Ubuntu、Debian、Fedora、Arch、RHEL、openSUSE — 几乎所有现代发行版)。在添加路由前正确等待 VPN 隧道建立 — 没有竞态条件,无需 sleep 黑科技。
sudo mv routes.sh /usr/local/bin/routes.sh
sudo chmod +x /usr/local/bin/routes.sh
sudo tee /etc/systemd/system/vpn-routes.service > /dev/null <<'EOF'
[Unit]
Description=Apply VPN split-tunnel routes
After=network-online.target wg-quick@wg0.service
Wants=network-online.target
Requisite=wg-quick@wg0.service
BindsTo=wg-quick@wg0.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/routes.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now vpn-routes.service
将 wg-quick@wg0.service 替换为你实际的 VPN 单元:
- OpenVPN:
openvpn-client@<name>.service - strongSwan / IPsec:
strongswan.service - 不绑定 VPN 单元:移除
Requisite=和BindsTo=行,只保留After=network-online.target
检查服务:
systemctl status vpn-routes.service
journalctl -u vpn-routes.service
@reboot cron? cron 守护进程通常在 VPN 隧道建立前启动 — ip route add 对尚不存在的接口运行,悄悄失败,你只在下次重启时才发现路由失效。systemd 的 After= 和 Requisite= 会等待 VPN 单元,消除竞态。BindsTo= 也在 VPN 关闭时停止路由。
DNS 泄漏 — 必读
本指南按 IP 路由流量。它不路由 DNS。浏览器仍然向系统解析器(通常是 ISP 的,通过 DHCP 获得)询问 example.com 的 IP — 只有结果 IP 流量才通过 VPN 加密。即使数据加密,ISP 仍能看到你访问哪些站点。
根据你的威胁模型,三种选择:
- 对 ISP 完全隐藏 DNS(split-DNS)。在 systemd-resolved 中为隧道域名配置每接口 DNS:
现在sudo resolvectl dns wg0 10.0.0.1 sudo resolvectl domain wg0 ~example.com ~another-site.comexample.com查询走 VPN,其他走默认解析器。把10.0.0.1替换为你 VPN 提供商的内部 DNS。 - 降低 ISP 可见性(公共 DoH/DoT)。把系统解析器指向公共加密解析器:
ISP 不再看到域名查询;公共解析器会看到。sudo resolvectl dns wg0 1.1.1.1#cloudflare-dns.com sudo resolvectl dnsovertls wg0 yes - 接受泄漏。如果你的目标是访问内容而非规避监控,这没问题 — 数据路径仍然加密,只有查询元数据泄露。
验证当前状态:dnsleaktest.com 或 browserleaks.com/dns — 显示的解析器应属于你的 VPN 或所选公共 DoH,而非 ISP。本地用 resolvectl status 查看每接口 DNS 配置。
IPv6 双栈绕过
如果目标有 AAAA 记录(大多数热门站点都有 — Cloudflare、Google、AWS 都是双栈),系统按 RFC 6724 优先选择 IPv6。本指南添加的路由是仅 IPv4 — IPv6 流量完全绕过 VPN,通过 ISP 默认 v6 路由出去。结果:VPN 看起来在工作,但对双栈目标毫无作用。
VPN 启动后快速检查:
curl -6 -s -o /dev/null -w "remote=%{remote_ip}\n" https://example.com
ip -6 route get $(dig +short AAAA example.com | head -1)
如果 IPv6 路径不经过 VPN 接口,说明在绕过隧道。两种解决方案:
- v6 也走 VPN(如果你的提供商有 IPv6 端点)。在 NetRoute Pro 中生成 IPv6 路由 — 同样流程,不同前缀(
2606:4700::/32等)。 - 系统级禁用 IPv6:
所有流量回退到 v4 并被正确隧道化。sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1 sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1 # 持久化:把同样的行写入 /etc/sysctl.d/99-disable-ipv6.conf
故障关闭(kill switch)
当 VPN 隧道断开(网络切换、服务商问题、睡眠/唤醒)时,内核会移除绑定到死接口的路由 — 流向先前隧道化 CIDR 的流量会回退到 ISP 的默认路由。结果:用户看不到任何错误的静默泄漏。强制故障关闭(阻断而非泄漏):
# nftables(现代 Linux)
sudo nft add table inet vpnkill
sudo nft add chain inet vpnkill output { type filter hook output priority 0 \; }
sudo nft add rule inet vpnkill output ip daddr 1.1.1.0/24 oif != wg0 drop
sudo nft add rule inet vpnkill output ip daddr 8.8.8.0/24 oif != wg0 drop
# 为每个隧道化 CIDR 重复。
iptables 等价规则:
sudo iptables -I OUTPUT ! -o wg0 -d 1.1.1.0/24 -j REJECT
sudo iptables -I OUTPUT ! -o wg0 -d 8.8.8.0/24 -j REJECT
现在流向这些前缀的流量经任何非 wg0 接口都会被丢弃 — VPN 断开 = 阻断,而非泄漏。用户看到连接错误(VPN 不工作的明确信号),而不是静默经 ISP 出去。这是更安全的故障模式。
验证
ip route show | grep 10.8.0.1
您应该看到所有通过 VPN 网关路由的子网。
回滚
脚本在应用更改前会将路由表快照保存到 /tmp/route-table.<timestamp>.txt — 用于诊断变化。要删除 NetRoute Pro 添加的路由,逐个删除前缀:
for cidr in 1.1.1.0/24 8.8.8.0/24 162.159.0.0/16; do
sudo ip route del "$cidr" 2>/dev/null || true
done
或更保守地,从 routes.sh 生成反向列表,将 replace(或 add)替换为 del:
awk '/ip route /{ sub(/^ip route (replace|add)/, "ip route del"); print }' routes.sh | sudo bash
如果出现严重问题,/tmp/route-table.<timestamp>.txt 中的快照显示更改前状态 — 可手动恢复需要的部分。
常见问题
Network is unreachable
VPN 接口未启动或没有 IP。检查:
ip link show
ip addr show
Permission denied
修改路由表需要 root 权限。使用 sudo 运行脚本。
配置文件示例
带注释的可编辑模板。将示例路由替换为 NetRoute Pro 为目标网站生成的输出。
linux-routes.sh— Bash 脚本,使用ip route add命令
#!/bin/bash
# Example Linux routing script for split tunneling.
# Generated by NetRoute Pro: https://alexander2k.github.io/netroute-site/
#
# Run as root or with sudo. Routes added here last until reboot —
# for persistence see the Linux guide (systemd-networkd / NetworkManager).
#
# Idempotent: uses `ip route replace` so re-running the script is safe.
# `add` would fail with "RTNETLINK answers: File exists" on a duplicate;
# combined with `set -e`, the first existing route would abort the script
# and skip the rest. `replace` creates if missing, updates if present.
set -euo pipefail
DEV="wg0" # VPN interface name (wg0, tun0, ppp0, ...)
GW="10.0.0.1" # VPN gateway IP (or use only "dev $DEV" if VPN is point-to-point)
# Snapshot current route table — useful for rollback if something breaks.
SNAPSHOT="/tmp/route-table.$(date +%s).txt"
ip route show > "$SNAPSHOT"
echo "Route table snapshot saved to $SNAPSHOT"
ip route replace 1.1.1.0/24 via "$GW" dev "$DEV"
ip route replace 8.8.8.0/24 via "$GW" dev "$DEV"
ip route replace 162.159.0.0/16 via "$GW" dev "$DEV"
# Verify with: ip route show
# Remove a single route: ip route del <CIDR>
# Full rollback (delete all routes added by this script):
# for cidr in 1.1.1.0/24 8.8.8.0/24 162.159.0.0/16; do
# ip route del "$cidr" 2>/dev/null || true
# done
提示:需要不带注释行的配置?在 NetRoute Pro 选项中取消勾选 「在导出文件中包含注释」,扩展将仅导出路由命令。适用于不支持注释的路由器。
官方文档
- ip-route(8) man page (en)
- systemd.network(5) (en)
- NetworkManager Dispatcher (en)
- Arch Wiki: systemd-networkd (en)