SOP:OpenWrt 旁路由改造(dell-R620)¶
变更类型:C 类(生产变更) 目标主机:
dell-R620(单机,无冗余) 实现体:本工程 Python 部署程序(入口main.py,业务类deployer.py) 恢复预案:recovery-plan.md(执行前务必先读,断线时靠它接管) 边界声明:本次会重建宿主网络(切 systemd-networkd + 建 br0),执行期间远程 SSH 必然短暂中断
1. 背景¶
原环境为「OpenWrt 双层路由」:宿主 em1 → 老旧 TP-Link(百兆、纯二层)承载 192.168.0.0/24 LAN,OpenWrt 的 WAN 经 em3 上行到小米路由 192.168.31.0/24。
存在三个痛点:
- 百兆瓶颈:TP-Link 只有 100M,把
em1/em2拖成百兆(服务器是千兆); - Win 访问 VM 需静态路由:Win 在
31段、VM 在0段,跨网段必须加路由; - OpenWrt 的核心价值是 openclash:让 VM 能直连 GitHub 等外部资源。
改为旁路由(bypass router)后:全网统一到 192.168.31.0/24,OpenWrt 只作"策略网关",Win 与 VM 同二层直接互通,百兆设备淘汰。
变更前实测状态:
| 项 | 实测 |
|---|---|
| 网卡(Ubuntu 命名) | eno1=.249(默认路由)、eno2=.250(SSH 入口)、eno3=.251、eno4 未接线;均 1000Mb/s |
| netplan | 01-network-manager-all.yaml + 50-cloud-init.yaml + 99-static-ips.yaml;渲染器 NetworkManager |
| 网桥 | 无(仅 docker0、virbr0) |
| KVM | libvirt 10.0.0 + QEMU 8.2.2 已就绪;存储池 hdd-pool(/data/kvm/img)、ssd-pool(/kvm/img) |
2. 目标¶
- 宿主网络渲染器切换为 systemd-networkd;
- 建立 br0(唯一并入
eno1),宿主管理地址192.168.31.249落在 br0 上; eno2(.250)保持独立不动,作为失联兜底入口;- OpenWrt 以单网卡旁路由形态运行:地址
192.168.31.2、网关192.168.31.1、关闭 DHCP; - OpenWrt 域开机自启;
- 网络回归项:
eno2地址与可达性不变、宿主可访问上游网关。
3. 前置条件¶
| 项 | 要求 |
|---|---|
| 权限 | sudo,脚本以 root 运行(不静默提权) |
| 🔴 物理接触 | 必须能在显示器/键盘前直接操作宿主(iDRAC 192.168.0.120 当前不可达,无法作为带外兜底) |
| 上游 | 小米路由 DHCP 池已排除 31.2 与 31.10~31.99 |
| 工具 | virsh、qemu-img、netplan、systemd-run(工程会自动校验) |
| 镜像 | ../openwrt/openwrt-24.10.0.qcow2 存在 |
| 磁盘 | /data 剩余 ≥ 25G(raw 镜像约 21G) |
准备命令:
cd openwrt_bypass
python3 -m venv .venv
.venv/bin/python -c "import sysconfig, pathlib; p = pathlib.Path(sysconfig.get_paths()['purelib']) / 'py_public_modules.pth'; p.write_text('/home/rain/py_public_modules', encoding='utf-8')"
.venv/bin/python -m pip install -r requirements.txt
cp config/deploy.yaml.example config/deploy.yaml # 按环境核对
执行前必须先做完的第 1 章动作(备份 netplan)——工程会在 backup() 阶段自动完成,但若手工执行请照做:
sudo rm -rf /root/net-backup && sudo mkdir -p /root/net-backup
sudo cp -av /etc/netplan/. /root/net-backup/
sudo ip -br addr | sudo tee /root/net-backup/state-before.txt
sudo ip route | sudo tee -a /root/net-backup/state-before.txt
4. 影响范围¶
| 维度 | 说明 |
|---|---|
| 主机 | 仅 dell-R620 一台(无备机) |
| 🔴 网络 | 会重建宿主网络:eno1 并入 br0、渲染器切 networkd;远程 SSH 必定短暂中断(配错则持续中断,需控制台接管) |
| 是否停服 | 不停服,但网络变更瞬间宿主出网中断数秒 |
| VM 影响 | 现有 VM 均为关机状态,不受影响;后续新 VM 接 br0 |
| 磁盘 | /data/kvm/img 新增约 21G 镜像;不改动其它数据 |
| 窗口期 | 建议 15–30 分钟;期间需在机器旁待命 |
| 兜底 | eno2 全程不动 + systemd-run 定时自动回滚(默认 300s) |
5. 分步操作¶
以下命令序列与实现体
deployer.py中OpenwrtBypassDeployer.execute()的五个子步骤一一对应,供人工复核、手工复现与断线接管使用;以实现体为准。
5.1 自动执行(推荐)¶
cd openwrt_bypass
sudo .venv/bin/python main.py --dry-run --config config/deploy.yaml # 先预演
sudo .venv/bin/python main.py --config config/deploy.yaml # 正式执行
5.2 等价的手工序列(断线时在本地控制台执行)¶
# ---------- 步骤 1:禁用 cloud-init 网络接管(实现:_ensure_cloud_init)----------
echo 'network: {config: disabled}' | sudo tee /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
# ---------- 步骤 2:重建宿主网络(实现:_rebuild_network,高风险)----------
# 2.1 先布置自动回滚保险:300s 内未确认则自动还原
sudo tee /root/net-rollback-guard.sh >/dev/null <<'EOF'
#!/bin/bash
set -x
BACKUP_DIR="/root/net-backup"; NETPLAN_DIR="/etc/netplan"; NEW_FILE="/etc/netplan/99-openwrt-bypass.yaml"
[ "$NETPLAN_DIR" = "/etc/netplan" ] || exit 1
[ -d "$BACKUP_DIR" ] || exit 1
[ -n "$(ls -A "$BACKUP_DIR" 2>/dev/null)" ] || exit 1
rm -f "$NEW_FILE"
cp -av "$BACKUP_DIR"/. "$NETPLAN_DIR"/
netplan apply
{ ip -br addr; ip route; } > /tmp/net-rollback-result.txt 2>&1
EOF
sudo chmod 700 /root/net-rollback-guard.sh
sudo systemd-run --on-active=300s --unit=net-rollback-guard /root/net-rollback-guard.sh
# 2.2 清理冲突的旧配置(已备份),写入目标 netplan
sudo rm -f /etc/netplan/01-network-manager-all.yaml \
/etc/netplan/50-cloud-init.yaml \
/etc/netplan/99-static-ips.yaml
sudo tee /etc/netplan/99-openwrt-bypass.yaml >/dev/null <<'EOF'
network:
version: 2
renderer: networkd
ethernets:
eno1:
match: { macaddress: "c8:1f:66:f2:23:b0" } # 并入 br0
set-name: eno1
dhcp4: false
eno2:
match: { macaddress: "c8:1f:66:f2:23:b1" } # 兜底口,保持独立
set-name: eno2
dhcp4: false
addresses: [192.168.31.250/24]
eno3:
match: { macaddress: "c8:1f:66:f2:23:b2" }
set-name: eno3
dhcp4: false
addresses: [192.168.31.251/24]
bridges:
br0:
interfaces: [eno1]
addresses: [192.168.31.249/24]
routes:
- to: default
via: 192.168.31.1
metric: 100
nameservers:
addresses: [192.168.31.1, 223.5.5.5]
parameters: { stp: false, forward-delay: 0 }
EOF
sudo chmod 600 /etc/netplan/99-openwrt-bypass.yaml
# 2.3 应用并就地验证(本机执行,不依赖远程通道)
sudo netplan apply
sleep 5
ip -br addr show br0 # 应出现 192.168.31.249/24
ip route | grep default # 应经由 br0
ping -c2 192.168.31.1 # 上游可达
# 2.4 验证通过 → 解除保险(务必执行,否则 5 分钟后会被自动还原)
sudo systemctl stop net-rollback-guard.timer
sudo systemctl reset-failed net-rollback-guard.service 2>/dev/null || true
# ---------- 步骤 3:镜像转换(实现:_ensure_image)----------
sudo qemu-img convert -O raw \
../openwrt/openwrt-24.10.0.qcow2 \
/data/kvm/img/openwrt-24.10.0.img
# ---------- 步骤 4:注入旁路由配置(实现:_ensure_router_config)----------
# 【高危】挂载镜像改写 /etc/config;写入前确认 VM 处于关机状态
sudo losetup -Pf --show /data/kvm/img/openwrt-24.10.0.img # 记下输出的 /dev/loopN
# 假设得到 /dev/loop0,rootfs 为 /dev/loop0p2
sudo mount /dev/loop0p2 /mnt/openwrt-rootfs
sudo tee /mnt/openwrt-rootfs/etc/config/network >/dev/null <<'EOF'
config interface 'loopback'
option device 'lo'
option proto 'static'
option ipaddr '127.0.0.1'
option netmask '255.0.0.0'
config device
option name 'br-lan'
option type 'bridge'
list ports 'eth0'
config interface 'lan'
option device 'br-lan'
option proto 'static'
option ipaddr '192.168.31.2'
option netmask '255.255.255.0'
option gateway '192.168.31.1'
list dns '192.168.31.1'
list dns '223.5.5.5'
EOF
sudo sed -i "s/^config dhcp 'lan'/config dhcp 'lan'/" /mnt/openwrt-rootfs/etc/config/dhcp
# 关键:关闭 DHCP(用户采用全静态)
sudo sh -c "printf \"\\nconfig dhcp 'lan'\\n\\toption interface 'lan'\\n\\toption ignore '1'\\n\" >> /mnt/openwrt-rootfs/etc/config/dhcp"
# 关键:开启 masq、关闭 syn_flood(避免回包绕过旁路由 / 缺模块导致流量异常)
sudo tee /mnt/openwrt-rootfs/etc/config/firewall >/dev/null <<'EOF'
config defaults
option syn_flood '0'
option input 'ACCEPT'
option output 'ACCEPT'
option forward 'ACCEPT'
config zone
option name 'lan'
option input 'ACCEPT'
option output 'ACCEPT'
option forward 'ACCEPT'
option masq '1'
option mtu_fix '1'
list network 'lan'
EOF
sync && sudo umount /mnt/openwrt-rootfs && sudo losetup -d /dev/loop0
# ---------- 步骤 5:定义并启动域(实现:_ensure_domain)----------
sudo virsh --connect qemu:///system define /data/kvm/img/openwrt-bypass.xml
sudo virsh --connect qemu:///system start openwrt
sudo virsh --connect qemu:///system autostart openwrt
⚠️ 步骤 2 是唯一可能失联的环节。若 SSH 中断且未恢复,立即转到本地控制台,按
recovery-plan.md处置(首选等 5 分钟自动回滚)。
6. 验证¶
# 1) 宿主网络
ip -br addr show br0 # 预期 192.168.31.249/24
ip -br addr show eno2 # 预期 192.168.31.250/24(兜底口)
ip route | grep default # 预期 default via 192.168.31.1 dev br0
bridge link show # 预期 eno1 挂在 br0 下
# 2) 域状态
virsh --connect qemu:///system domstate openwrt # 预期 running
virsh --connect qemu:///system dominfo openwrt | grep -i autostart
# 3) 旁路由可达(OpenWrt 启动约需 30–60s)
ping -c3 192.168.31.2
# 4) 从 Win(同网段)验证直连效果
ping 192.168.31.2
# 5) VM 走代理的实证(在任一 VM 内,网关/DNS 均为 192.168.31.2)
curl -m10 -sI https://github.com | head -1
幂等验证(必做):连续执行两次,第二次应全部跳过。
sudo .venv/bin/python main.py --config config/deploy.yaml
sudo .venv/bin/python main.py --config config/deploy.yaml # 预期:状态巡检全达标、跳过备份与执行
7. 回滚¶
7.1 备份方式¶
| 对象 | 备份位置 |
|---|---|
| netplan 全部配置 | /root/net-backup/(backup() 阶段自动生成,固定路径) |
| 变更前网络状态 | /root/net-backup/state-before.txt |
| 自动回滚脚本 | /root/net-rollback-guard.sh(由工程生成) |
| 镜像源文件 | ../openwrt/openwrt-24.10.0.qcow2 始终保留,可重新转换 |
7.2 回滚触发判据(量化,任一命中即回滚)¶
netplan apply返回非 0;- apply 后 90 秒内 br0 未持有
192.168.31.249/24,或上游网关 ping 不通; eno2(192.168.31.250)失联;- 校验阶段出现任一
❌ 校验未通过。
7.3 回滚步骤¶
# ① 还原 netplan(自动)
sudo .venv/bin/python main.py --rollback --config config/deploy.yaml
# ② 或手工还原
sudo rm -f /etc/netplan/99-openwrt-bypass.yaml
sudo cp -av /root/net-backup/. /etc/netplan/
sudo netplan apply
# ③ 停掉旁路由(不影响 Win 上网,它走小米)
virsh --connect qemu:///system destroy openwrt
virsh --connect qemu:///system undefine openwrt
# ④ 如需彻底退回 NM 渲染器
sudo systemctl enable --now NetworkManager
sudo netplan apply
7.4 回滚后验证¶
ip -br addr show eno1 # 预期 192.168.31.249/24 回到物理口
ip route | grep default # 预期 default via 192.168.31.1 dev eno1
ping -c2 192.168.31.1
ssh rain@192.168.31.250 # 兜底口仍可用
8. 风险说明¶
| 风险点 | 说明 | 缓解措施 |
|---|---|---|
| 🔴 远程通道中断 | 重建网络必然切断当前 SSH,配错则持续中断 | eno2 全程不动;systemd-run 300s 自动回滚;执行时人在机器旁(见 recovery-plan.md) |
| 🔴 iDRAC 不可达 | 192.168.0.120 与当前网段不同二层,无法作为带外兜底 |
已确认可显示器直连;改造完成后再评估是否恢复 iDRAC 访问 |
| 🟠 小米 DHCP 池冲突 | 若 31.2 或 VM 段被 DHCP 分出去,旁路由失效 |
用户已确认池范围已排除 31.2 |
| 🟠 DNAT 回包不对称 | 旁路由未开 masq 时,VM 回包会被主路由二层直送回 VM、绕过 OpenWrt,导致「去程走 clash、回程不走」 | 防火墙模板强制 masq '1' |
| 🟠 Docker FORWARD DROP | Docker 会改写 iptables FORWARD 策略,可能阻断 br0 转发 |
验证阶段专门检查;异常时排查 iptables -L FORWARD -n -v |
| 🟠 挂载镜像改配置 | 步骤 4 挂载 loop 设备改写镜像,误操作可能损坏镜像 | 源 qcow2 保留,可重新转换;工程按「读→比→写」实现,内容一致即跳过 |
| 🟡 cloud-init 覆盖网络 | 未禁用时可能在重启后改回网络配置 | 步骤 1 先行禁用 |
| 🟡 OpenWrt 首次启动慢 | 首次启动约 30–60s,期间 31.2 不可达 |
校验阶段最长等待 120s |
| 🟡 openclash 未包含 | 本次不自动安装 openclash(需联网 opkg),仅完成旁路由底座 | 见 §9 后续步骤 |
9. 后续步骤(本次之外)¶
- 安装 openclash:OpenWrt 起来后经
31.1出网,opkg update安装(如需第三方源,按 openclash 官方文档配置); - VM 网关/DNS 指向
192.168.31.2:新建 VM 时在 guest 内固定静态 IP + 网关31.2+ DNS31.2; - 验证分流:VM 内
curl -I https://github.com;Win 侧保持网关31.1(不强制走代理); - 可选:把
eno3并入 br0 做链路冗余,或保留备用。
来源:
/home/rain/Documents/kvm/openwrt_bypass/docs/SOP.md| 最后更新:2026-09-15 01:14(由build/sync.py同步,请勿在站点侧修改)