通过树莓派的 QEMU 运行 OpenWrt

给吃灰了几个月的树莓派找点活干

前言

最近在研究路由器,但是没有合适的实验场地,正好家里有个吃灰的树莓派,跑个 OpenWrt 虚拟机,当个旁路由应该不成问题。[1]

网上找了很多资料,大部分教程都是用的 PVE 或者 ESXi 来做,但是这两个东西树莓派基本没法跑,我就决定自己摸索。一路上踩了不少坑,我把它记录了下来,避免以后再走弯路。

本文中树莓派的 shell 中始终使用 pi 用户,我会在需要 root 权限的命令前面加上 sudo

我的配置环境

硬件:

  • 树莓派 3B+
  • 家里的一个硬路由做主路由

树莓派上的一些信息:

  • 操作系统 debian 11 arm64
  • 网卡物理接口 eth0
  • 网关地址 192.168.0.1

完整的过程

准备工作

先装一下必要的软件包。

1
2
sudo apt update # 更新软件包列表
sudo apt install qemu-system-arm screen bridge-utils iproute2 gzip telnet
  • qemu-system-arm 我用 qemu 命令行启动虚拟机
  • screen 让虚拟机后台运行
  • bridge-utils brctl 命令,用来管理网桥
  • iproute2 ip 命令,应该都预装了
  • gzip gzip 命令,用来解压 .gz 文件,应该都预装了
  • telnet telnet 命令,用于连接 OpenWrt 的串口

pi 用户加入 kvm 组,使 pi 不用 sudo 就能使用 kvm 。

1
sudo usermod -a -G kvm pi

ip addr show eth0 可以看到当前 eth0 的 IP 地址是 192.168.0.102

1
2
3
4
5
6
7
% ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether c6:0d:62:d6:04:df brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.102/24 brd 192.168.0.255 scope global dynamic noprefixroute eth0
       valid_lft 7175sec preferred_lft 6275sec
    inet6 fe80::1f9d:bfa:758b:7b17/64 scope link
       valid_lft forever preferred_lft forever

网桥的配置

创建网桥

创建一个名为 br0 的网桥。

1
sudo brctl addbr br0

sudo brctl show 查看网桥,网桥还没有连接任何接口。

1
2
3
% sudo brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.000000000000       no

现在打开该网桥,否则网络连接会断开。如果不在意,可以稍后再打开网桥。

1
sudo ip link set br0 up

eth0 接口加入桥中。如果网桥没有打开,进行这一步将会断开网络连接。

1
sudo brctl addif br0 eth0

sudo brctl show 查看网桥。

1
2
3
% sudo brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.b827eb1a6228       no              eth0

获取网桥 IP 地址

关闭 dhcpcd 服务。[2]

1
sudo systemctl stop dhcpcd

获取网桥的 IP 地址。

1
sudo dhclient br0

ip addr show br0 查看网桥的 IP 地址,不出意外的话,网桥获取的 IP 地址跟 eth0 的 IP 地址是一样的。

1
2
3
4
5
6
7
% ip addr show br0
6: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether c6:0d:62:d6:04:df brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.102/24 brd 192.168.0.255 scope global dynamic br0
       valid_lft 6919sec preferred_lft 6919sec
    inet6 fe80::80e:23ff:fe89:59d/64 scope link
       valid_lft forever preferred_lft forever

网桥有了 IP 地址之后, eth0 的 IP 地址就不是很必要了,用以下命令清除 eth0 的 IP 地址。

1
sudo ip addr flush eth0

ip addr show eth0 可以看到当前 eth0 的 IP 地址没有了。

1
2
3
% ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master br-lan state UP group default qlen 1000
    link/ether c6:0d:62:d6:04:df brd ff:ff:ff:ff:ff:ff

创建 tap 接口

创建一个名为 tap0 的 tap 接口并打开。

1
2
3
sudo modprobe tun # 加载 tun 模块
sudo ip tuntap add mode tap tap0 # 创建接口
sudo ip link set tap0 up # 打开接口

ip link show tap0 可以看到该接口,虽然写着 DOWN ,但是实际上它已经可用了。

1
2
3
% ip link show tap0
5: tap0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast master br0 state DOWN mode DEFAULT group default qlen 1000
    link/ether 56:a3:7e:8e:cb:9e brd ff:ff:ff:ff:ff:ff

tap0 接口加入桥中。

1
sudo brctl addif br0 tap0

sudo brctl show 查看网桥。

1
2
3
4
% sudo brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.b827eb1a6228       no              eth0
                                                        tap0

至此,网桥就配置好了。

启动虚拟机

新建个 openwrt 目录来存放虚拟机的文件。

1
2
mkdir openwrt
cd openwrt

下载最新版的 OpenWrt 内核和 rootfs 。

1
2
3
wget https://mirrors.bfsu.edu.cn/openwrt/releases/22.03.3/targets/armvirt/64/openwrt-22.03.3-armvirt-64-Image # 下载内核
wget https://mirrors.bfsu.edu.cn/openwrt/releases/22.03.3/targets/armvirt/64/openwrt-22.03.3-armvirt-64-rootfs-ext4.img.gz # 下载 rootfs 镜像的压缩包
gzip -d openwrt-22.03.3-armvirt-64-rootfs-ext4.img.gz # 解压 rootfs 镜像

这样就有了这两个文件:

1
2
3
% ls
openwrt-22.03.3-armvirt-64-Image
openwrt-22.03.3-armvirt-64-rootfs-ext4.img

可以启动虚拟机了。

1
screen qemu-system-aarch64 -M virt -accel kvm -cpu host -m 128M -nographic -kernel openwrt-22.03.3-armvirt-64-Image -append "root=fe00" -serial telnet::8023,server,nowait -drive file=./openwrt-22.03.3-armvirt-64-rootfs-ext4.img,if=virtio -netdev tap,id=net0,ifname=tap0,script=no,downscript=no -device virtio-net,netdev=net0

命令很长,拆开来解释下:

  • screen 让 QEMU 不被打断,按下 Ctrl-AD 让 QEMU 后台运行(如果命令执行后立马退出,可能是参数有错误)
  • qemu-system-aarch64 运行 arm64 虚拟机的命令
  • -M virt 模拟叫做 virt 的机器
  • -accel kvm 开启 KVM 加速
  • -cpu host 模拟与宿主机相同的 CPU
  • -m 128M 分给虚拟机 128M 内存,实际上 64M 就够了
  • -nographic 不开启图形输出,反正开了也没显示
  • -kernel openwrt-22.03.3-armvirt-64-Image 指定内核,换成你下载的内核
  • -append "root=fe00" 告诉内核根分区在哪里,这一条大概是 OpenWrt 虚拟机通用的
  • -serial telnet::8023,server,nowait 通过 telnet 访问虚拟机的串口
  • -drive file=./openwrt-22.03.3-armvirt-64-rootfs-ext4.img,if=virtio 指定 rootfs 镜像,换成你下载的镜像
  • -netdev tap,id=net0,ifname=tap0,script=no,downscript=no -device virtio-net,netdev=net0 模拟一个 virtio 网络设备,使用 tap 方式,连接到 tap0 接口(刚才接进网桥的接口)

配置 OpenWrt

进入 shell

虚拟机启动后,按下 Ctrl-AD 后回到刚才的 shell ,就可以用 telnet 连接到 OpenWrt 虚拟机了。

1
telnet 127.0.0.1 8023

就像这样:

% telnet 127.0.0.1 8023
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

再次按下回车即可进入 OpenWrt 的 shell :

% telnet 127.0.0.1 8023
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
<按回车>


BusyBox v1.35.0 (2023-01-03 00:24:21 UTC) built-in shell (ash)

  _______                     ________        __
 |       |.-----.-----.-----.|  |  |  |.----.|  |_
 |   -   ||  _  |  -__|     ||  |  |  ||   _||   _|
 |_______||   __|_____|__|__||________||__|  |____|
          |__| W I R E L E S S   F R E E D O M
 -----------------------------------------------------
 OpenWrt 22.03.3, r20028-43d71ad93e
 -----------------------------------------------------
root@OpenWrt:/#

再往下就是正常的 OpenWrt 配置过程了。

设置静态 IP 地址

vi 编辑 /etc/config/network

1
vi /etc/config/network

里面有这样一些内容:

...(其它的东西)

config interface 'lan'
        option device 'br-lan'
        option proto 'static'
        option ipaddr '192.168.1.1'
        option netmask '255.255.255.0'
        option ip6assign '60'

option ipaddr '192.168.1.1' 改成 option ipaddr 'OpenWrt IP 地址' 。OpenWrt IP 地址跟同网段下的 IP 地址不冲突即可,比如我选择 192.168.0.120

新加一行 option gateway '网关地址' ,经测试,没有这一行 OpenWrt 就上不了网。

其它地方不用动,下面是我的例子:

...(其它的东西)

config interface 'lan'
        option device 'br-lan'
        option proto 'static'
        option ipaddr '192.168.0.120'
        option gateway '192.168.0.1'
        option netmask '255.255.255.0'
        option ip6assign '60'

重启 OpenWrt 的网络。

1
/etc/init.d/network restart

更改软件源

国内访问 OpenWrt 的官方源比较慢,装点软件比较费劲,干脆直接换成国内源。

1
2
3
sed -i 's_downloads.openwrt.org_mirrors.bfsu.edu.cn/openwrt_' /etc/opkg/distfeeds.conf # 换源
echo "nameserver 114.114.114.114" >>/etc/resolv.conf # 设置临时 DNS (重启失效)
opkg update # 更新软件包列表

设置密码

设置的密码将会作为 OpenWrt 的管理密码,输入密码时没有回显,合着眼输就行了。

1
passwd

启用 LuCI

默认情况下 LuCI 是关闭的,现在可以把它打开。

1
2
/etc/init.d/uhttpd start # 运行 LuCI
/etc/init.d/uhttpd enable # 设为开机自启

登录管理页面

在浏览器里输入 OpenWrt 的 IP 地址就可以进入管理页面了,比如我的是 http://192.168.0.120/

如果设置了密码就输入密码,没设置密码就直接点击 Login

其它操作如安装中文语言包、设置 DNS 、安装插件等,我就不再赘述了。

关闭虚拟机

先用 telnet 登录 OpenWrt 的 shell ,把 OpenWrt 关机。

1
poweroff

回到树莓派的 shell ,删除 tap 接口和网桥。

1
2
sudo ip link del tap0
sudo ip link del br0

踩过的坑

我也不知道为什么,自从我写完这篇文章之后,之前遇到的坑都没法复现了,等以后再补充吧! ૮₍ ˶•ᴗ•˶₎ა


  1. 经过测试 tap0 接口的速率只有 10Mbps ,用来试验还可以,接入家庭网络就有点不太合适了。 

  2. 树莓派的原版系统中默认开启了 dhcpcd networking 两个网络管理服务。如果不关闭 dhcpcd 服务将导致 eth0 不断地获取到 IP 地址,对下面的操作有影响。 

updatedupdated2023-02-042023-02-04