跳转至

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。

存在三个痛点:

  1. 百兆瓶颈:TP-Link 只有 100M,把 em1/em2 拖成百兆(服务器是千兆);
  2. Win 访问 VM 需静态路由:Win 在 31 段、VM 在 0 段,跨网段必须加路由;
  3. 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. 目标

  1. 宿主网络渲染器切换为 systemd-networkd;
  2. 建立 br0(唯一并入 eno1),宿主管理地址 192.168.31.249 落在 br0 上;
  3. eno2(.250)保持独立不动,作为失联兜底入口;
  4. OpenWrt 以单网卡旁路由形态运行:地址 192.168.31.2、网关 192.168.31.1、关闭 DHCP;
  5. OpenWrt 域开机自启;
  6. 网络回归项: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. 后续步骤(本次之外)

  1. 安装 openclash:OpenWrt 起来后经 31.1 出网,opkg update 安装(如需第三方源,按 openclash 官方文档配置);
  2. VM 网关/DNS 指向 192.168.31.2:新建 VM 时在 guest 内固定静态 IP + 网关 31.2 + DNS 31.2;
  3. 验证分流:VM 内 curl -I https://github.com;Win 侧保持网关 31.1(不强制走代理);
  4. 可选:把 eno3 并入 br0 做链路冗余,或保留备用。

来源:/home/rain/Documents/kvm/openwrt_bypass/docs/SOP.md | 最后更新:2026-09-15 01:14(由 build/sync.py 同步,请勿在站点侧修改)