- 1. 网络结构
- 2. 环境说明
- 3. 先创建隔离网络
- 4. 准备iStoreOS磁盘镜像
- 5. 复用HAOS已经验证过的UEFI
- 6. 创建iStoreOS虚拟机
- 7. 在隔离网络中完成首次配置
- 8. 把虚拟网卡切换到家庭局域网
- 9. 获取家庭局域网地址
- 10. 配置单网口旁路由NAT
- 10.1 清理fw4警告
- 11. 禁用ICMP Redirect
- 12. 检查iStoreOS DNS
- 13. 用Windows验证旁路由链路
- 14. 安装Nikki透明代理
- 15. 添加订阅
- 16. Nikki代理配置
- 16.1 只代理一台设备
- 16.2 手动改网关的设备全部可代理
- 17. 首次启动卡在GeoIP和GeoSite下载
- 17.1 手动下载geoip.metadb
- 17.2 手动下载GeoSite.dat
- 18. 手动测试Mihomo配置
- 19. 检查Nikki是否成功运行
- 20. 节点返回429和503
- 21. 最终使用方式
- 22. 常用管理命令
- 23. 总结
前面已经在树莓派5的 Debian 13 宿主机上,通过 KVM/QEMU/libvirt 跑起了 Home Assistant OS。
这次继续折腾,把 iStoreOS 也装进 KVM 虚拟机,做成一个单网口旁路由。
最终效果是:
|
1 2 3 4 5 |
主路由: 192.168.110.1 Debian宿主机: 192.168.110.48 HAOS虚拟机: 192.168.110.28 iStoreOS虚拟机:192.168.110.54 物理网络: eth0 → br0 |
主路由继续负责 DHCP,默认不会让全家设备自动经过旁路由。
只有手动把设备的网关和 DNS 改成:
|
1 2 |
网关:192.168.110.54 DNS: 192.168.110.54 |
这台设备才会经过 iStoreOS 和 Nikki 透明代理。
这种方式对现有网络改动最小,即使旁路由停机,其他没有手动改网关的设备也不受影响。
下面记录完整安装和排障过程。
1. 网络结构
|
1 2 3 4 5 6 7 |
eth0 │ ▼ br0 ├── Debian宿主机:192.168.110.48 ├── HAOS虚拟机: 192.168.110.28 └── iStoreOS虚拟机:192.168.110.54 |
iStoreOS 采用单网口旁路由结构:
|
1 2 3 4 5 6 7 8 9 10 |
客户端 │ 网关/DNS指向192.168.110.54 ▼ iStoreOS │ ▼ 主路由192.168.110.1 │ ▼ Internet |
2. 环境说明
|
1 2 3 4 5 6 7 8 9 |
设备:Raspberry Pi 5 8GB 宿主系统:Debian 13 Trixie ARM64 虚拟化:KVM / QEMU / libvirt 宿主机桥接:br0 家庭网段:192.168.110.0/24 主路由:192.168.110.1 iStoreOS:24.10.7 armsr / armv8 虚拟磁盘:8GB QCOW2 虚拟机资源:2 vCPU / 1GB RAM |
基础旁路由使用 1GB 内存已经够用。安装多个代理、Docker 或其他大型插件时,可以再增加到 2GB。
3. 先创建隔离网络
iStoreOS 首次启动时,不能确定它是否会向局域网发送 DHCP,所以先接入 libvirt 的 default 隔离网络。
|
1 |
sudo virsh net-list --all |
我这里原本是:
|
1 2 3 4 |
Name State Autostart Persistent ------------------------------------------------- default inactive no yes lan-bridge active yes yes |
启动并设置自动启动:
|
1 2 |
sudo virsh net-start default sudo virsh net-autostart default |
确认:
|
1 2 |
sudo virsh net-list --all ip -br addr show virbr0 |
正常类似:
|
1 2 3 4 |
default active yes yes lan-bridge active yes yes virbr0 DOWN 192.168.122.1/24 |
在还没有虚拟机接入时,virbr0 显示 DOWN 是正常的。
4. 准备iStoreOS磁盘镜像
从 iStoreOS 官方固件目录下载 ARM64 通用 EFI 镜像,文件类型选择:
|
1 |
armsr-squashfs-combined-efi.img.gz |
下载后保存为:
|
1 |
/tmp/istoreos-armsr.img.gz |
检查文件:
|
1 2 |
ls -lh /tmp/istoreos-armsr.img.gz gzip -t /tmp/istoreos-armsr.img.gz |
我这里出现:
|
1 |
decompression OK, trailing garbage ignored |
但镜像可以完整解压,后续 qemu-img 检查也正常,因此没有影响使用。
创建镜像目录:
|
1 |
sudo mkdir -p /var/lib/libvirt/images/istoreos |
解压为 RAW 镜像:
|
1 2 3 4 5 6 |
gzip -dc /tmp/istoreos-armsr.img.gz | sudo dd \ of=/var/lib/libvirt/images/istoreos/istoreos.raw \ bs=4M \ status=progress \ conv=fsync |
转换为 QCOW2:
|
1 2 3 4 5 6 |
sudo qemu-img convert \ -p \ -f raw \ -O qcow2 \ /var/lib/libvirt/images/istoreos/istoreos.raw \ /var/lib/libvirt/images/istoreos/istoreos.qcow2 |
扩容到 8GB:
|
1 2 3 |
sudo qemu-img resize \ /var/lib/libvirt/images/istoreos/istoreos.qcow2 \ 8G |
设置权限:
|
1 2 3 4 5 |
sudo chown libvirt-qemu:kvm \ /var/lib/libvirt/images/istoreos/istoreos.qcow2 sudo chmod 660 \ /var/lib/libvirt/images/istoreos/istoreos.qcow2 |
检查:
|
1 2 |
sudo qemu-img info \ /var/lib/libvirt/images/istoreos/istoreos.qcow2 |
正常应包含:
|
1 2 3 |
file format: qcow2 virtual size: 8 GiB corrupt: false |
转换成功后删除 RAW 文件:
|
1 2 |
sudo rm \ /var/lib/libvirt/images/istoreos/istoreos.raw |
5. 复用HAOS已经验证过的UEFI
|
1 2 |
sudo virsh dumpxml haos | grep -E "type arch=|loader|nvram|machine" |
我这里是:
|
1 2 3 |
<type arch='aarch64' machine='virt-10.0'>hvm</type> <loader readonly='yes' type='pflash' format='raw'>/usr/share/AAVMF/AAVMF_CODE.no-secboot.fd</loader> <nvram template='/usr/share/AAVMF/AAVMF_VARS.fd' ...> |
6. 创建iStoreOS虚拟机
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
sudo virt-install \ --name istoreos \ --arch aarch64 \ --machine virt-10.0 \ --virt-type kvm \ --cpu host-passthrough \ --vcpus 2 \ --memory 1024 \ --osinfo linux2024 \ --import \ --disk path=/var/lib/libvirt/images/istoreos/istoreos.qcow2,format=qcow2,bus=virtio,cache=none \ --network network=default,model=virtio \ --boot loader=/usr/share/AAVMF/AAVMF_CODE.no-secboot.fd,loader.readonly=yes,loader.type=pflash,nvram.template=/usr/share/AAVMF/AAVMF_VARS.fd \ --graphics none \ --console pty,target_type=serial \ --noautoconsole |
新版 virt-install 会强制要求 --osinfo。iStoreOS 没有专属条目,使用通用的:
|
1 |
--osinfo linux2024 |
创建成功后检查:
|
1 2 3 4 |
sudo virsh list --all sudo virsh domiflist istoreos sudo virsh domblklist istoreos ip -br addr show virbr0 |
我这里的虚拟网卡 MAC 是:
|
1 |
52:54:00:a5:50:06 |
7. 在隔离网络中完成首次配置
进入串口控制台:
|
1 |
sudo virsh console istoreos |
按几次回车进入 Shell。退出控制台使用:
|
1 |
Ctrl + ] |
检查网络:
|
1 2 |
uci show network uci show dhcp.lan |
这个镜像默认配置已经比较适合旁路由:
|
1 2 3 4 5 6 |
network.lan.device='br-lan' network.lan.proto='dhcp' dhcp.lan.dhcpv4='disabled' dhcp.lan.dhcpv6='disabled' dhcp.lan.ra='disabled' |
也就是说,IPv4 地址会自动获取,而且默认没有开启 LAN DHCP。
为了保险,再明确忽略 DHCP 服务:
|
1 2 3 4 5 |
uci set dhcp.lan.ignore='1' uci commit dhcp /etc/init.d/dnsmasq restart /etc/init.d/odhcpd restart |
暂时关闭 LAN IPv6 客户端:
|
1 2 |
uci set network.lan6.proto='none' uci commit network |
最终关键配置:
|
1 2 3 4 5 6 |
network.lan.proto='dhcp' network.lan6.proto='none' dhcp.lan.ignore='1' dhcp.lan.dhcpv4='disabled' dhcp.lan.dhcpv6='disabled' dhcp.lan.ra='disabled' |
然后关机:
|
1 2 |
sync poweroff |
8. 把虚拟网卡切换到家庭局域网
确认虚拟机关机后:
|
1 |
sudo virsh domstate istoreos |
编辑虚拟机:
|
1 |
sudo virsh edit istoreos |
找到:
|
1 2 3 4 5 |
<interface type='network'> <mac address='52:54:00:a5:50:06'/> <source network='default'/> <model type='virtio'/> </interface> |
只把:
|
1 |
<source network='default'/> |
改成:
|
1 |
<source network='lan-bridge'/> |
MAC 地址不要改变。
启动并设置开机启动:
|
1 2 |
sudo virsh start istoreos sudo virsh autostart istoreos |
9. 获取家庭局域网地址
进入控制台:
|
1 |
sudo virsh console istoreos |
检查:
|
1 2 |
ip -4 -br addr ip route |
我这里自动获得:
|
1 2 3 |
br-lan UP 192.168.110.54/24 default via 192.168.110.1 dev br-lan |
测试网络:
|
1 2 3 |
ping -c 3 192.168.110.1 ping -c 3 1.1.1.1 nslookup openwrt.org |
三项都成功,说明 VirtIO 网卡、DHCP、默认路由和 DNS 均正常。
随后在主路由中做 DHCP 静态租约:
|
1 2 3 |
名称:iStoreOS MAC:52:54:00:a5:50:06 IP:192.168.110.54 |
10. 配置单网口旁路由NAT
检查防火墙:
|
1 2 |
uci show firewall sysctl net.ipv4.ip_forward |
默认已经有:
|
1 2 3 4 5 6 |
firewall.@zone[0].name='lan' firewall.@zone[0].input='ACCEPT' firewall.@zone[0].output='ACCEPT' firewall.@zone[0].forward='ACCEPT' net.ipv4.ip_forward = 1 |
因为单网口旁路由的流量从 br-lan 进入,又从同一个 br-lan 发给主路由,所以需要在 LAN 区域开启 NAT:
|
1 2 3 4 5 6 |
cp /etc/config/firewall \ "/etc/config/firewall.bak.$(date +%Y%m%d-%H%M%S)" uci set firewall.@zone[0].masq='1' uci commit firewall /etc/init.d/firewall restart |
检查:
|
1 2 3 4 5 6 7 |
fw4 check echo "fw4 check exit code: $?" uci get firewall.@zone[0].masq nft list ruleset | sed -n '/chain srcnat_lan/,/^ }/p' |
正常应看到:
|
1 2 3 4 5 |
Ruleset passes nftables check. fw4 check exit code: 0 1 meta nfproto ipv4 masquerade fullcone |
10.1 清理fw4警告
我这里重启防火墙时出现:
|
1 |
Section docker_nat (DockerNAT) option 'extra' is not supported by fw4 |
这是旧式 fw3/iptables 配置项,fw4 会忽略它。
只删除无效字段:
|
1 2 3 4 5 |
uci -q delete firewall.docker_nat.extra uci commit firewall /etc/init.d/firewall restart fw4 check |
删除后警告消失,Docker 的实际 NAT 规则仍然保留。
11. 禁用ICMP Redirect
客户端与主路由、旁路由都在同一网段,Linux 可能向客户端发送 ICMP Redirect,让客户端绕过 iStoreOS 直接访问主路由。
创建持久配置:
|
1 2 3 4 5 |
cat >/etc/sysctl.d/99-bypass-router.conf <<'EOF' net.ipv4.conf.all.send_redirects=0 net.ipv4.conf.default.send_redirects=0 net.ipv4.conf.br-lan.send_redirects=0 EOF |
立即应用:
|
1 |
sysctl -p /etc/sysctl.d/99-bypass-router.conf |
检查:
|
1 2 3 4 |
sysctl \ net.ipv4.ip_forward \ net.ipv4.conf.all.send_redirects \ net.ipv4.conf.br-lan.send_redirects |
正常:
|
1 2 3 |
net.ipv4.ip_forward = 1 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.br-lan.send_redirects = 0 |
12. 检查iStoreOS DNS
这个镜像没有预装 ss,可以使用 BusyBox 的 netstat:
|
1 2 3 4 |
netstat -lntu | grep -E '(:53[[:space:]]|:53$)' ps w | grep '[d]nsmasq' |
我这里看到:
|
1 2 3 |
192.168.110.54:53 127.0.0.1:53 172.17.0.1:53 |
说明 dnsmasq 已经监听家庭局域网地址。
Debian 宿主机安装 DNS 测试工具:
|
1 2 |
sudo apt update sudo apt install -y bind9-dnsutils |
测试 UDP 和 TCP DNS:
|
1 2 3 4 5 |
nslookup openwrt.org 192.168.110.54 dig @192.168.110.54 openwrt.org A +short dig @192.168.110.54 openwrt.org A +tcp +short |
返回地址并显示:
|
1 2 |
status: NOERROR SERVER: 192.168.110.54#53 |
13. 用Windows验证旁路由链路
先只选择一台 Windows 电脑测试。
|
1 2 3 4 5 6 |
IP地址:192.168.110.209 子网前缀长度:24 网关:192.168.110.54 首选DNS:192.168.110.54 备用DNS:留空 IPv6:关闭 |
保存后执行:
|
1 2 3 4 5 |
ipconfig /flushdns ping 192.168.110.54 ping 1.1.1.1 nslookup openwrt.org tracert -d 1.1.1.1 |
我这里 tracert 的前两跳是:
|
1 2 |
1 192.168.110.54 2 192.168.110.1 |
说明链路已经变成:
|
1 2 3 4 |
Windows → iStoreOS → 主路由 → Internet |
Windows 的 IPv6 需要暂时关闭,否则系统可能继续使用主路由通过 IPv6 下发的 DNS,造成 DNS 绕过 iStoreOS。
14. 安装Nikki透明代理
先更新软件源:
|
1 |
opkg update |
安装依赖:
|
1 |
opkg install ca-bundle curl wget-ssl procd-ujail |
注意,iStoreOS 这版 opkg 不支持:
|
1 |
opkg install -y ... |
会提示:
|
1 |
opkg: unrecognized option: y |
添加 Nikki 软件源:
|
1 2 3 |
wget -O - \ https://github.com/nikkinikki-org/OpenWrt-nikki/raw/refs/heads/main/feed.sh | ash |
刷新并安装:
|
1 2 3 4 5 |
opkg update opkg install nikki opkg install luci-app-nikki opkg install luci-i18n-nikki-zh-cn |
重启 LuCI:
|
1 |
/etc/init.d/uhttpd restart |
15. 添加订阅
进入:
|
1 2 3 4 |
Nikki → 配置文件 → 订阅 → 添加 |
填写:
|
1 2 3 4 |
订阅名称:my_sub 订阅链接:服务商提供的Mihomo/Clash Meta订阅 用户代理:clash.meta 优先:远程 |
订阅名称要手动输入。灰色的 default 可能只是占位符,如果没有真正输入,日志会显示:
|
1 |
Update subscription: . |
正常应该显示:
|
1 |
Update subscription: my_sub. |
订阅链接属于敏感凭据,不要截图或公开。
16. Nikki代理配置
在“代理配置”中:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
TCP代理模式:Redirect UDP代理模式:TPROXY IPv4代理:开启 IPv6代理:关闭 IPv4 DNS劫持:开启 IPv6 DNS劫持:关闭 局域网代理:开启 入站接口:lan 路由器代理:关闭 |
这里的入站接口必须选择:
|
1 |
lan |
不是 br-lan。
lan 是 OpenWrt 逻辑网络接口,底层设备才是:
|
1 |
network.lan.device='br-lan' |
16.1 只代理一台设备
测试阶段在访问控制中添加:
|
1 2 3 |
IPv4:192.168.110.209 DNS:开启 代理:开启 |
这样只有 Windows 测试机进入 Nikki。
16.2 手动改网关的设备全部可代理
最终我没有让主路由统一下发旁路由网关,而是继续采用手动方式。
在 Nikki 中删除只匹配 192.168.110.209 的限制,改成“全部”模式;如果界面是逐条规则,就添加一条匹配字段全部留空、只开启 DNS 和代理的规则。
这样所有到达 iStoreOS 的局域网流量都可以进入 Nikki。
但设备是否真正经过 iStoreOS,仍由设备自己的网关决定:
|
1 2 3 4 5 6 7 |
需要代理: 网关 192.168.110.54 DNS 192.168.110.54 不需要代理: 网关 192.168.110.1 DNS 192.168.110.1 |
这种方式不用修改主路由 DHCP,控制最灵活。
17. 首次启动卡在GeoIP和GeoSite下载
Nikki 的配置检查已经通过,但首次启动一直卡住:
|
1 |
Can't find MMDB, start download |
最后报:
|
1 2 3 |
can't download MMDB context deadline exceeded rules[...] [GEOIP,CN,DIRECT] error |
原因是 iStoreOS 无法直连 GitHub 下载 GeoIP 数据。
17.1 手动下载geoip.metadb
|
1 2 3 4 5 6 7 8 |
rm -f /tmp/geoip.metadb curl -4 -L --fail --show-error \ --retry 2 \ --connect-timeout 15 \ --max-time 300 \ -o /tmp/geoip.metadb \ 'https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb' |
检查:
|
1 2 |
ls -lh /tmp/geoip.metadb wc -c /tmp/geoip.metadb |
复制到 Nikki 工作目录:
|
1 2 3 4 5 |
cp /tmp/geoip.metadb \ /etc/nikki/run/geoip.metadb chmod 600 \ /etc/nikki/run/geoip.metadb |
17.2 手动下载GeoSite.dat
|
1 2 3 4 5 6 7 8 |
rm -f /tmp/geosite.dat curl -4 -L --fail --show-error \ --retry 2 \ --connect-timeout 15 \ --max-time 300 \ -o /tmp/geosite.dat \ 'https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat' |
复制并保持日志要求的大小写:
|
1 2 3 4 5 |
cp /tmp/geosite.dat \ /etc/nikki/run/GeoSite.dat chmod 600 \ /etc/nikki/run/GeoSite.dat |
目录中至少应包含:
|
1 2 3 |
/etc/nikki/run/config.yaml /etc/nikki/run/geoip.metadb /etc/nikki/run/GeoSite.dat |
18. 手动测试Mihomo配置
iStoreOS 没有预装 timeout,所以不要使用:
|
1 |
timeout 180 mihomo ... |
否则返回码 127 只表示找不到 timeout。
直接执行:
|
1 2 3 4 5 6 7 |
/usr/bin/mihomo \ -t \ -d /etc/nikki/run \ -f /etc/nikki/run/config.yaml RC=$? echo "配置测试返回码:$RC" |
看到:
|
1 |
Load GeoSite rule: cn |
不代表卡死,首次载入规则需要等待一段时间,不要立即按 Ctrl+C。
正常最后应为:
|
1 2 |
configuration file /etc/nikki/run/config.yaml test is successful 配置测试返回码:0 |
如果 Nikki 已经在运行,再手动使用同一个运行目录测试,可能看到:
|
1 |
[CacheFile] can't open cache file: timeout |
这通常是运行中的核心和手动测试同时访问缓存文件。此时先检查进程,不要重复测试。
19. 检查Nikki是否成功运行
不要只依赖可能卡住的:
|
1 |
/etc/init.d/nikki status |
可以直接检查:
|
1 2 3 4 5 6 7 |
pgrep -af mihomo ip -br addr show nikki nft list ruleset | grep -i -n nikki | head -n 50 |
成功后会看到:
|
1 2 3 |
nikki接口:198.18.0.1 nftables:table inet nikki mihomo进程:存在 |
核心日志可以查看:
|
1 |
tail -f /var/log/nikki/core.log |
20. 节点返回429和503
透明代理已经成功后,日志中出现:
|
1 2 |
429 Too Many Requests 503 Service Unavailable |
例如:
|
1 2 3 |
192.168.110.209 → www.google.com:443 match DomainKeyword/google connect error: unexpected status: 429 Too Many Requests |
这反而证明:
|
1 2 3 |
Windows流量已经进入iStoreOS Nikki访问控制已经匹配 Mihomo规则已经匹配 |
真正的问题是当前代理节点被限流、过载或服务异常。
进入 Nikki 面板,对节点测速,手动切换到一个延迟正常的具体节点,不要继续使用返回 429/503 的节点。
如果全部节点都失败,就需要更新订阅、等待服务商恢复或更换线路,而不是重装旁路由。
21. 最终使用方式
主路由继续保持原样:
|
1 2 |
DHCP网关:192.168.110.1 DHCP DNS: 192.168.110.1 |
需要经过旁路由的设备手动设置:
|
1 2 3 4 5 |
IP:设备自己的固定地址 前缀长度:24 网关:192.168.110.54 DNS:192.168.110.54 IPv6:关闭或单独正确配置 |
不需要经过旁路由的设备继续保持 DHCP 自动配置。
建议始终直连主路由的基础设施:
|
1 2 3 |
主路由:192.168.110.1 Debian宿主机:192.168.110.48 iStoreOS:192.168.110.54 |
尤其是 Debian 宿主机不要依赖运行在自己上面的 iStoreOS VM 作为默认网关,避免形成启动依赖。
HAOS 后续可以手动改成:
|
1 2 |
网关:192.168.110.54 DNS:192.168.110.54 |
这样安装应用和拉取 Docker 镜像速度就快了。
22. 常用管理命令
查看虚拟机:
|
1 2 3 |
sudo virsh list --all sudo virsh domiflist istoreos sudo virsh domblklist istoreos |
进入控制台:
|
1 |
sudo virsh console istoreos |
启动、关闭和开机启动:
|
1 2 3 |
sudo virsh start istoreos sudo virsh shutdown istoreos sudo virsh autostart istoreos |
检查路由:
|
1 2 |
ip -4 -br addr ip route |
检查防火墙:
|
1 2 |
fw4 check nft list ruleset |
检查 Nikki:
|
1 2 3 4 |
pgrep -af mihomo ip -br addr show nikki nft list table inet nikki tail -f /var/log/nikki/core.log |
检查 DNS:
|
1 2 3 4 |
netstat -lntu | grep -E '(:53[[:space:]]|:53$)' nslookup openwrt.org |
23. 总结
至此,树莓派5上的虚拟化结构变成:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
Raspberry Pi 5 └── Debian 13 ├── KVM / QEMU / libvirt ├── br0 ├── HAOS VM │ └── 192.168.110.28 └── iStoreOS VM ├── 192.168.110.54 ├── 单网口旁路由 ├── LAN NAT ├── dnsmasq └── Nikki / Mihomo |
最终网络策略:
|
1 2 3 4 5 |
普通设备: 网关/DNS → 192.168.110.1 需要代理的设备: 网关/DNS → 192.168.110.54 |
这种方式不需要修改主路由的全局 DHCP,不会一次性影响所有设备。
即使 iStoreOS 虚拟机停机,Debian 宿主机和其他未改网关的设备仍能正常使用主路由。
记录一下,给同样在树莓派5上使用 Debian、KVM、HAOS 和 iStoreOS 的朋友做个参考~





最新评论
牛牛牛
同样
站长您好,亚马逊云咨询推广资源,望建立联系,可邮件,谢谢。
换友情链接吗?
看你的站做的挺不错的
恭喜!!太强了,硕博连读啊
雁过留毛,人过留名。
看不懂但大受震撼