Quick Setup

安装工具

安装两个网络管理工具用于建立网桥以及虚拟网卡:

# 安装虚拟网桥工具
sudo apt install bridge-utils -y
# UML(User-mode linux)工具        
sudo apt install uml-utilities  -y   

配置脚本

qemu-ifup

将下面的脚本保存为文件 qemu-ifup,并赋予可执行权限:

为了方便复制脚本,在 confluence 页面提供了脚本内容,可以直接复制。

mkdir -p /etc/qemu
mv qemu-ifup /etc/qemu && mv qemu-ifdown /etc/qemu 
sudo chmod +x qemu-ifup
sudo chmod +x qemu-ifdown

因为网卡信息不容易定位,可能一台机器有多个网卡,所以不方便用脚本获取,需要手动设置一下。将下面的NIC值修改为宿主机可以上网的网卡名称。可以通过ifconfig命令查看。

#!/bin/bash
# 设置默认网卡信息
NIC=enp2s0
# 设置用户名
USER_NAME=user
# 设置网桥名称
BRIDGE=br0
# 设置网络信息
NIC_IP=$(ifconfig $NIC | grep "inet\b" | awk '{print $2}')

NIC_NETMAST=$(ifconfig $NIC | grep "inet\b" | awk '{print $4}')

NIC_BROADCAST=$(ifconfig $NIC | grep "inet\b" | awk '{print $6}')

NETMASK=255.255.240.0
# 设置默认网关地址
GATEWAY=10.12.192.1

# 获取宿主机网卡MAC地址,因为创建的网桥MAC地址是随机的,
# 无法接入公司,需要从开发机网卡将其MAC地址赋值给网桥
MAC=$(ifconfig $NIC | grep "ether\b" | awk '{print $2}')

# 检查网桥是否已创建,已创建就忽略
function check_bridge() 
{
    echO "Check bridge..."
    if brctl show | grep "^$BRIDGE" &> /dev/null; then
        return 1
    else
        return 0
    fi
}

# 创建网桥
function create_bridge() 
{
    echo "Start Create bridge..."
    brctl addbr "$BRIDGE"
    brctl addif "$BRIDGE"  "$NIC"
    ifconfig br0 0.0.0.0 promisc up
    dhclient $BRIDGE
}

# ifconfig "$BRIDGE" "$NIC_IP" netmask "$NIC_NETMAST" broadcast "$NIC_BROADCAST"  hw ether "$MAC" promisc up

# 启用IP转发
function enable_ip_forward() 
{
    echo 1 > /proc/sys/net/ipv4/ip_forward
}

# 设置网桥
function setup_bridge()
{
    check_bridge "$BRIDGE"
    if [ $? -eq 0 ]; then
        create_bridge
    fi
    enable_ip_forward
}

if [ -n "$1" ]; then
    setup_bridge
    echo "Creating $1..."
    tunctl -t "$1" -u "$USER_NAME"
    ifconfig "$1" 0.0.0.0 up
    echo "Adding $1 to $BRIDGE..."
    brctl addif "$BRIDGE" "$1"
    sleep 5
    ifconfig "$BRIDGE"  hw ether "$MAC" promisc up
    exit 0
else
    echo "Error: no interface specified."
    exit 1
fi

qemu-ifdown

以下是qemu-ifdown脚本,用于在关闭 QEMU 时关闭虚拟网卡,将其从网桥中移除,删除虚拟网卡。

#!/bin/bash
# 设置网桥名称
BRIDGE=br0

if [ -n "$1" ]; then
    # 将tap设备从网桥中移除
	brctl delif ${BRIDGE} $1
	# 关闭tap设备
	ip link link $1 down
    # 删除tap设备
    ip link del "$1"
	tunctl -d "$1"
	exit 0
else
	echo "Error: no interface specified"
	exit 1
fi

将系统镜像复制一份并修改文件名,QEMU 不能同时使用一个镜像启动两个虚拟机。

cp openEuler-22.09-riscv64-qemu.qcow2 openEuler-22.09-riscv64-qemu-vm1.qcow2

需要修改启动脚本中的镜像文件名,以及启动参数,将drive以及cmd变量的内容覆盖为下面的内容,修改mac为分配给自己的虚拟机的 MAC 地址,script为上面的脚本qemu-ifup的路径。

# 该脚本用于启动VM0
drive="openEuler-22.09-riscv64-qemu.qcow2"
....
cmd="qemu-system-riscv64 \
  -nographic -machine virt \
  -smp "$vcpu" -m "$memory"G \
  -bios "$fw" \
  -drive file="$drive",format=qcow2,id=hd0 \
  -object rng-random,filename=/dev/urandom,id=rng0 \
  -device virtio-vga \
  -device virtio-rng-device,rng=rng0 \
  -device virtio-blk-device,drive=hd0 \
  -device virtio-net-device,netdev=tapnet,mac=e0:be:03:88:54:e8 \
  -netdev tap,id=tapnet,script=/etc/qemu/qemu-ifup,downscript=/etc/qemu/qemu-ifdown \
  -device qemu-xhci -usb -device usb-kbd -device usb-tablet"

sudo权限启动脚本:

sudo ./preview_start_vm0.sh

以下为配置 VM1 过程,VM1 的启动脚本与 VM0 的启动脚本类似,只需要修改drive以及MAC,必须保证MAC与 VM0 的MAC不同。

# 该脚本用于启动VM1
drive="openEuler-22.09-riscv64-qemu.qcow2"
....
cmd="qemu-system-riscv64 \
  -nographic -machine virt \
  -smp "$vcpu" -m "$memory"G \
  -bios "$fw" \
  -drive file="$drive",format=qcow2,id=hd0 \
  -object rng-random,filename=/dev/urandom,id=rng0 \
  -device virtio-vga \
  -device virtio-rng-device,rng=rng0 \
  -device virtio-blk-device,drive=hd0 \
  -device virtio-net-device,netdev=tapnet,mac=80:d4:09:62:cd:3c \
  -netdev tap,id=tapnet,script=/etc/qemu/qemu-ifup,downscript=/etc/qemu/qemu-ifdown \
  -device qemu-xhci -usb -device usb-kbd -device usb-tablet"

网络通信测试

当前网络状态如下:

HOST:10.12.192.177
VM0:10.12.193.53
VM1:10.12.193.101

HOST –> VM0

# user @ ubuntu18 in ~/openeuler/openEuler2209 [18:41:54] 
$ ping -c 3 10.12.193.101
PING 10.12.193.101 (10.12.193.101) 56(84) bytes of data.
64 bytes from 10.12.193.101: icmp_seq=1 ttl=64 time=1.37 ms
64 bytes from 10.12.193.101: icmp_seq=2 ttl=64 time=0.897 ms
64 bytes from 10.12.193.101: icmp_seq=3 ttl=64 time=0.890 ms

--- 10.12.193.101 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 0.890/1.055/1.378/0.228 ms

Host –> VM1 的测试结果与 Host –> VM0 的测试结果相同。

VM0 –> HOST

[root@openEuler-riscv64 ~]# ping -c 3 10.12.193.53 
PING 10.12.193.53 (10.12.193.53) 56(84) bytes of data.
64 bytes from 10.12.193.53: icmp_seq=1 ttl=64 time=0.716 ms
64 bytes from 10.12.193.53: icmp_seq=2 ttl=64 time=1.74 ms
64 bytes from 10.12.193.53: icmp_seq=3 ttl=64 time=1.81 ms

--- 10.12.193.53 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2009ms
rtt min/avg/max/mdev = 0.716/1.424/1.812/0.501 ms

VM1 –> Host 的测试结果与 Host –> VM0 的测试结果相同。

VM1 –> VM0

[root@openEuler-riscv64 ~]# ping -c 3 10.12.193.53 
PING 10.12.193.53 (10.12.193.53) 56(84) bytes of data.
64 bytes from 10.12.193.53: icmp_seq=1 ttl=64 time=0.716 ms
64 bytes from 10.12.193.53: icmp_seq=2 ttl=64 time=1.74 ms
64 bytes from 10.12.193.53: icmp_seq=3 ttl=64 time=1.81 ms

--- 10.12.193.53 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2009ms
rtt min/avg/max/mdev = 0.716/1.424/1.812/0.501 ms

VM0 –> VM1 与 VM1 –> VM0 的测试结果相同。

VM0 –> github

[root@openEuler-riscv64 ~]# ping -c 4 github.com
PING github.com (192.30.255.113) 56(84) bytes of data.
64 bytes from lb-192-30-255-113-sea.github.com (192.30.255.113): icmp_seq=1 ttl=46 time=221 ms
64 bytes from lb-192-30-255-113-sea.github.com (192.30.255.113): icmp_seq=2 ttl=46 time=277 ms
64 bytes from lb-192-30-255-113-sea.github.com (192.30.255.113): icmp_seq=3 ttl=46 time=216 ms
64 bytes from lb-192-30-255-113-sea.github.com (192.30.255.113): icmp_seq=4 ttl=46 time=218 ms

--- github.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3014ms
rtt min/avg/max/mdev = 215.984/232.733/276.593/25.374 ms

HOST –> github

# user @ ubuntu18 in ~/openeuler/openEuler2209 [17:59:40] 
$ ping -c 3  github.com
PING github.com (192.30.255.113) 56(84) bytes of data.
64 bytes from lb-192-30-255-113-sea.github.com (192.30.255.113): icmp_seq=1 ttl=46 time=218 ms
64 bytes from lb-192-30-255-113-sea.github.com (192.30.255.113): icmp_seq=2 ttl=46 time=216 ms
64 bytes from lb-192-30-255-113-sea.github.com (192.30.255.113): icmp_seq=3 ttl=46 time=216 ms

--- github.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 216.252/217.087/218.409/0.945 ms

原理探究 (Ongoing)

Step by Step 解析

查看一下网络接口信息:

ifconfig
enp3s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.12.192.173  netmask 255.255.240.0  broadcast 10.12.207.255
        inet6 fe80::a00:27ff:fe32:e709  prefixlen 64  scopeid 0x20<link>
        ether 08:00:27:32:e7:09  txqueuelen 1000  (Ethernet)
        RX packets 6017  bytes 5412928 (5.4 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1979  bytes 179467 (179.4 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 125  bytes 10142 (10.1 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 125  bytes 10142 (10.1 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

创建一个名为br0的网桥

sudo brctl addbr br0

将网桥与宿主机的网卡绑定

sudo brctl addif br0 enp3s0

启用 br0 接口,并从 DHCP 服务器获得 IP 地址

sudo ifconfig br0 0.0.0.0 promisc up
sudo dhclient br0

查看虚拟网桥列表

sudo brctl show br0

bridge name	    bridge id		    STP enabled    interfaces
br0		            8000.e0be0388eec9  no             enp3s0

查看 br0 的各接口信息

sudo brctl showstp br0
br0
 bridge id		8000.e0be0388eec9
 designated root	8000.e0be0388eec9
 root port			0			path cost 0
 max age			20.00s
 forward delay		15.00s
 hello time			2.00s
 ageing time		300.00s
 hello timer		0.00s		<tbd>
 forward timer		0.00s		<tbd>
 ageing timer		0.00s		<tbd>
 
 enp3s0 (1)
 port id			8001			local state forwarding
 designated root	8000.08002732e709
 path cost			100
 designated bridge	8000.08002732e709
 designated port	8001
 forward delay		15.00s
 hello time			2.00s
 max age			20.00s
 ageing time		300.00s
 priority			128

当前网络拓扑:

            +-----------------------------------+
            |          Internet                 |
            +-----------------------------------+
                             |
                             |
                             v
+---------------------------------------------------------+
|                   enp3s0 (Host Interface)               |
|                   IP: 10.12.192.173                     |
+---------------------------------------------------------+
                            |
                            v
+---------------------------------------------------------+
|                         br0 (Bridge)                    |
|                      IP: 10.12.192.173                  |
+---------------------------------------------------------+

创建一个 tap0 接口用于VM0使用,允许 user 用户访问

sudo tunctl -t tap0 -u user       

在虚拟网桥中增加 tap0 接口

sudo brctl addif br0 tap0

启用 tap0 接口,混杂模式

sudo ifconfig tap0 0.0.0.0 promisc up

将网桥的 MAC 地址修改为宿主机的 MAC 地址,这样就可以接入公司网络了。否则因为内网的 MAC 地址过滤,无法接入公司网络。

ifconfig br0  hw ether 08:00:27:32:e7:09  promisc up

查看虚拟网桥列表

$ sudo brctl show br0

bridge name	    bridge id		    STP enabled    interfaces
br0		            8000.08002732e709  no             enp3s0
                                                        tap0

查看当前的网桥状态:

$ sudo brctl showstp br0 
br0
 bridge id		8000.08002732e709
 designated root	8000.08002732e709
 root port		   0			path cost		   0
 max age		  20.00			bridge max age		  20.00
 hello time		   2.00			bridge hello time	   2.00
 forward delay		  15.00			bridge forward delay	  15.00
 ageing time		 300.00
 hello timer		   0.00			tcn timer		   0.00
 topology change timer	   0.00			gc timer		   7.75
 flags			


enp2s0 (1)
 port id		8001			state		     forwarding
 designated root	8000.08002732e709	path cost		   4
 designated bridge	8000.08002732e709	message age timer	   0.00
 designated port	8001			forward delay timer	   0.00
 designated cost	   0			hold timer		   0.00
 flags			

tap0 (2)
 port id		8002			state		     disabled
 designated root	8000.08002732e709	path cost		 100
 designated bridge	8000.08002732e709	message age timer	   0.00
 designated port	8002			forward delay timer	   0.00
 designated cost	   0			hold timer		   0.00
 flags				

tap0可能处于disabled状态,因为还没有虚拟机使用它。启动虚拟机之后会自动切换到forwarding状态。

当前网络拓扑:

            +-----------------------------------+
            |          Internet                 |
            +-----------------------------------+
                             |
                             |
                             v
+---------------------------------------------------------+
|                   enp3s0 (Host Interface)               |
|                   IP: 10.12.192.173                     |
+---------------------------------------------------------+
                            |
                            v
+---------------------------------------------------------+
|                         br0 (Bridge)                    |
|                      IP: 10.12.192.173                  |
|                  +---------------------+                |
|                  |        tap0         |                |
|                  |     IP: 0.0.0.0     |                |
+---------------------------------------------------------+

启动 QEMU

cmd="qemu-system-riscv64 \
  -nographic -machine virt \
  -smp "$vcpu" -m "$memory"G \
  -bios "$fw" \
  -drive file="$drive",format=qcow2,id=hd0 \
  -object rng-random,filename=/dev/urandom,id=rng0 \
  -device virtio-vga \
  -device virtio-rng-device,rng=rng0 \
  -device virtio-blk-device,drive=hd0 \
  -device virtio-net-device,netdev=tapnet,mac=e0:be:03:88:54:e8 \
  -netdev tap,id=tapnet,ifname=tap0,script=no,downscript=no \
  -device qemu-xhci -usb -device usb-kbd -device usb-tablet"

关注这段脚本的网络配置部分:

  -device virtio-net-device,netdev=tapnet,mac=e0:be:03:88:54:e8 \
  -netdev tap,id=tapnet,ifname=tap0,script=no,downscript=no \

详细解释可以查看“QEMU 网络虚拟化章节”,第一个参数 -device virtio-net-device 定义了名为 virtio-net-device 的网络设备,并将其连接到一个名为 tapnet 的网络设备上,指定它的 MAC 地址为 e0:be:03:88:54:e8。第二个参数 -netdev tap 用于指定后端实现,使用tap方式,并且指定唯一 ID 为tapnet-device参数中的子参数netdev使用,指定ifname=tap0,表示使用tap0接口作为虚拟化的后端。script=nodownscript=no表示不使用脚本来启动和关闭tap0接口。

查看当前的网络接口信息ifconfig

br0: flags=4419<UP,BROADCAST,RUNNING,PROMISC,MULTICAST>  mtu 1500
        inet 10.12.192.173  netmask 255.255.240.0  broadcast 10.12.207.255
        inet6 fe80::e2be:3ff:fe88:eec9  prefixlen 64  scopeid 0x20<link>
        ether 08:00:27:32:e7:09  txqueuelen 1000  (Ethernet)
        RX packets 861148  bytes 310707296 (310.7 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 17556062  bytes 1516515693 (1.5 GB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

enp2s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.12.192.173  netmask 255.255.240.0  broadcast 10.12.207.255
        inet6 fe80::4964:61f8:420d:6781  prefixlen 64  scopeid 0x20<link>
        ether 08:00:27:32:e7:09  txqueuelen 1000  (Ethernet)
        RX packets 894523  bytes 325547917 (325.5 MB)
        RX errors 0  dropped 1926  overruns 0  frame 0
        TX packets 17563568  bytes 1516947572 (1.5 GB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 1654925876  bytes 134933568498 (134.9 GB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1654925876  bytes 134933568498 (134.9 GB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

tap0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet6 fe80::f8ae:85ff:fed7:f9cd  prefixlen 64  scopeid 0x20<link>
        ether fa:ae:85:d7:f9:cd  txqueuelen 1000  (Ethernet)
        RX packets 557  bytes 44913 (44.9 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 7165  bytes 832171 (832.1 KB)
        TX errors 0  dropped 55942 overruns 0  carrier 0  collisions 0

当前网络拓扑:

            +-----------------------------------+
            |          Internet                 |
            +-----------------------------------+
                             |
                             |
                             v
+---------------------------------------------------------+
|                   enp3s0 (Host Interface)               |
|                   IP: 10.12.192.173                     |
+---------------------------------------------------------+
                            |
                            v
+---------------------------------------------------------+
|                         br0 (Bridge)                    |
|                      IP: 10.12.192.173                  |
|                  +---------------------+                |
|                  |        tap0         |                |
|                  |     IP: 0.0.0.0     |                |
+---------------------------|-----------------------------+
                            |
                            v
+---------------------------|-----------------------------+
|                  |        eth0         |                |
|                  |     IP:10.12.193.53 |                |
|                  +---------------------+                |
|                     VM0 (QEMU)                          |
+---------------------------------------------------------+

查看当前的网桥状态,可以看到 tap0 已经处于 forwarding 状态:

$ sudo brctl showstp br0 
br0
 bridge id		8000.08002732e709
 designated root	8000.08002732e709


enp2s0 (1)
 port id		8001			state		     forwarding
 designated root	8000.08002732e709	path cost		   4
 designated bridge	8000.08002732e709	message age 		

tap0 (2)
 port id		8002			state		     forwarding
 designated root	8000.08002732e709	path cost		 100
 designated bridge	8000.08002732e709	message age 			

添加 VM1 过程就忽略了,添加后的网络拓扑如下:

            +-----------------------------------+
            |          Internet                 |
            +-----------------------------------+
                             |
                             |
                             v
+---------------------------------------------------------+
|                   enp3s0 (Host Interface)               |
|                   IP: 10.12.192.173                     |
+---------------------------------------------------------+
                            |
                            v
+---------------------------------------------------------+
|                         br0 (Bridge)                    |
|                      IP: 10.12.192.173                  |
|    +---------------------+  +-------------------+       |
|    |        tap0         |  |        tap1       |       |
|    |     IP: 0.0.0.0     |  |     IP: 0.0.0.0   |       |
+---------------|-------------------------|---------------+
                |                         |
                v                         v
+---------------|----------+  +-----------|---------------+
|   |     eth0         |   |  |   |        eth0       |   |
|   |  IP:10.12.193.53 |   |  |   |  IP:10.12.193.101 |   |
|   +---------------------+|  |   +-------------------+   |
|         VM0 (QEMU)       |  |         VM1 (QEMU)        |
+--------------------------+  +---------------------------+

QEMU 网络虚拟化

QEMU 对于网络的虚拟化需要两个参数来指定:

  • 其中一个用于指定网络的前端驱动,也就是 Guest 中的实现
  • 另一个用于指定网络的后端实现,也就是在 Host 中的实现。

QEMU 支持两种方式来实现网络虚拟化,一种是旧版本上使用的参数为 -net 配合 -net ,另一种是在新版本上支持的 -device 配合 -netdev 。QEMU 的发展趋势是倾向于用 -device 一种命令格式来虚拟出不同的设备,其中包括网卡设备。

-net & -net (legacy)

虽然仍然支持,但是逐步被废弃,不推荐使用。

我们以以下命令为例,来说明 -net-net 的使用方法:

vcpu=8
memory=8
drive="openEuler-22.09-V1-riscv64-qemu.qcow2"
fw="fw_payload_oe_qemuvirt.elf"

cmd="qemu-system-riscv64 \
  -nographic -machine virt \
  -smp "$vcpu" -m "$memory"G \
  -kernel "$fw" \
  -bios none \
  -drive file="$drive",format=qcow2,id=hd0 \
  -object rng-random,filename=/dev/urandom,id=rng0 \
  -device virtio-vga \
  -device virtio-rng-device,rng=rng0 \
  -device virtio-blk-device,drive=hd0 \
  -net nic,mac=52:54:00:12:34:56 \
  -net tap,ifname=tap0,script=no,downscript=no \
  -device qemu-xhci -usb -device usb-kbd -device usb-tablet \
  -append 'root=/dev/vda1 rw console=ttyS0 swiotlb=1 loglevel=3 systemd.default_timeout_start_sec=600 selinux=0 highres=off mem="$memory_append"M earlycon' "

其中这两个参数即实现了虚拟化网络:

  -net nic,mac=52:54:00:12:34:56 \
  -net tap,ifname=tap0,script=no,downscript=no \

第一个参数 -net nic 用于指定上述所说的前端驱动,也就是 Guest 中的实现,这里使用的是 默认的驱动,这个驱动是 QEMU 中的一个虚拟网卡设备,指定它的 MAC 地址为 52:54:00:12:34:56

第二个参数 -net tap 用于指定后端实现,也就是 Host 中的实现,这里使用的是 tap 驱动,它的网卡名称为 tap0,并且不执行任何脚本。这两个参数的组合就实现了虚拟化网络。

更多示例:

-net nic,model=virtio \
-net tap,ifname=tap3,script=/ect/qemu/qemu-ifup,downscript=no \

第一个参数 -net nic 用于指定上述所说的前端驱动,也就是 Guest 中的实现,这里使用的是 virtio 驱动,这个驱动是 QEMU 中的一个虚拟网卡设备。第二个参数 -net tap 用于指定后端实现,也就是 Host 中的实现,这里使用的是 tap 驱动,它的网卡名称为 tap3,并且执行脚本 /ect/qemu/qemu-ifup

解释/ect/qemu/qemu-ifup 该脚本用于创建网桥,将网桥与宿主机的网卡绑定,然后将虚拟网卡绑定到网桥上,这样虚拟机就可以通过网桥与宿主机通信,宿主机也可以通过网桥与虚拟机通信。

这是新版本的 QEMU 支持的命令格式,也是 QEMU 未来的发展趋势,我们以以下命令为例,来说明 -device-netdev 的使用方法:

qemu-system-riscv64 \
  -nographic -machine virt \
  -smp "$vcpu" -m "$memory"G \
  -bios "$fw" \
  -drive file="$drive",format=qcow2,id=hd0 \
  -object rng-random,filename=/dev/urandom,id=rng0 \
  -device virtio-vga \
  -device virtio-rng-device,rng=rng0 \
  -device virtio-blk-device,drive=hd0 \
  -device virtio-net-device,netdev=tapnet,mac=e0:be:03:88:54:e8 \
  -netdev tap,id=tapnet,ifname=tap0,script=~/qemu-script/qemu-ifup,downscript=no \
  -device qemu-xhci -usb -device usb-kbd -device usb-tablet

其中这两个参数即实现了虚拟化网络:

  -device virtio-net-device,netdev=tapnet,mac=e0:be:03:88:54:e8 \
  -netdev tap,id=tapnet,ifname=tap0,script=~/qemu-script/qemu-ifup,downscript=no \

第一个参数 -device virtio-net-device 用于指定上述所说的前端驱动,也就是 Guest 中的实现,定义了名为 virtio-net-device 的网络设备,并将其连接到一个名为 tapnet 的网络设备上,指定它的 MAC 地址为 e0:be:03:88:54:e8

第二个参数 -netdev tap 用于指定后端实现,使用tap方式,并且指定唯一 ID 为tapnet-device参数中的子参数netdev使用。网卡名称为tap0并且执行脚本 ~/qemu-script/qemu-ifup

-netdev 参数中 id 的使用 -netdev 参数中的 id 用于指定唯一的 ID,这个 ID 会被 -device 参数中的子参数 netdev 使用,这样 -device 参数就知道要将前端驱动连接到哪个后端实现上了。id 可以自定义任意唯一字符串如-netdev tap,id=test对应-device virtio-net-device,netdev=test

更多示例:

  -device virtio-net-pci,netdev=tapnet,mac=e0:be:03:88:54:e8 \
  -netdev tap,id=tapnet,script=no,downscript=no \

第一个参数 -device virtio-net-pci 定义了名为 virtio-net-pci 的网络设备,并将其连接到一个名为 tapnet 的网络设备上,指定它的 MAC 地址为 e0:be:03:88:54:e8

第二个参数,仔细观察会发现,我们没有定义链接到后端网卡的名称ifname,这是因为以tap模式启动 QEMU 时会自动创建tap设备,具体网卡名称根据当前宿主机的网卡情况而定,默认会创建一个名为tap0的网卡,如果启动了两个虚拟机,那么第二个虚拟机的网卡名称就是tap1,以此类推。

区分 tap 模式与 bridge 模式

我们有时候会用以下的命令进行 QEMU 虚拟机桥接网络的配置:

  -device virtio-net-device,netdev=bridgenet,mac=52:54:00:12:34:57 \
  -netdev bridge,ifname=br0,id=bridgenet

这也能为我们创建一个桥接网络,这是因为它和 -netdev tap 的工作方式是一样的,只是 -netdev bridge 的简化写法,qemu-bridge-helper 在背后替我们做了 tap 设备创建以及将 tap 设备加入桥接口的所有事情。

添加多张网卡

如果了解上述内容,添加多张网卡就十分容易实现了,我们只需要再添加一对 -device-netdev 参数即可,如下所示:

qemu-system-riscv64 \
  -nographic -machine virt \
  -smp "$vcpu" -m "$memory"G \
  -bios "$fw" \
  -drive file="$drive",format=qcow2,id=hd0 \
  -object rng-random,filename=/dev/urandom,id=rng0 \
  -device virtio-vga \
  -device virtio-rng-device,rng=rng0 \
  -device virtio-blk-device,drive=hd0 \
  -device virtio-net-device,netdev=tapnet0,mac=e0:be:03:88:54:e8 \
  -netdev tap,id=tapnet0,script=/etc/qemu/qemu-ifup,downscript=/etc/qemu/qemu-ifdown \
  -device virtio-net-device,netdev=tapnet1,mac=e0:be:03:88:54:e8 \
  -netdev tap,id=tapnet1,script=/etc/qemu/qemu-ifup,downscript=/etc/qemu/qemu-ifdown \
  -device qemu-xhci -usb -device usb-kbd -device usb-tablet

需要注意的是,我们需要为每个 -device 参数指定一个唯一的 ID,这个 ID 会被 -netdev 参数中的子参数 netdev 使用,这样 -device 参数就知道要将前端驱动连接到哪个后端实现上了。并且每个 tap 设备只能被一个虚拟机使用,所以每个虚拟机的 tap 设备名称不能相同。

登录虚拟机查看网卡信息:

[root@openEuler-riscv64 ~]# ifconfig 
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.12.193.53  netmask 255.255.240.0  broadcast 10.12.207.255
        inet6 fe80::9e6:287b:30a2:574d  prefixlen 64  scopeid 0x20<link>
        ether e0:be:03:88:54:e8  txqueuelen 1000  (Ethernet)
        RX packets 81  bytes 9871 (9.6 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 19  bytes 1735 (1.6 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.12.193.101  netmask 255.255.240.0  broadcast 10.12.207.255
        inet6 fe80::4fe0:9e1e:4681:52b7  prefixlen 64  scopeid 0x20<link>
        ether 80:d4:09:62:cd:3c  txqueuelen 1000  (Ethernet)
        RX packets 76  bytes 9471 (9.2 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 15  bytes 1708 (1.6 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

不同网络策略工作方式

  • NAT 网络模式
    • NAT 网络以路由器的 NAT 功能为原理,允许虚拟机通过共享主机的 IP 地址访问互联网,但虚拟机之间不能直接通信。通过端口转发可以实现虚拟机之间的连接。
  • 桥接网络模式
    • 桥接网络模式通过虚拟交换机连接虚拟机和主机,使得虚拟机可以通过局域网访问互联网,并允许虚拟机之间直接通信。
  • 内部网络模式
    • 内部网络模式使得虚拟机可以创建一个完全隔离的网络,虚拟机之间可以直接通信,但无法访问互联网或外部网络。
  • 仅主机网络模式
    • 仅主机网络模式允许虚拟机之间可以通信,并且与主机之间也可以通信,但无法访问互联网或外部网络。
VM <> VMVM → HOSTHOST → VMVM → InternetInternet → VM
网络地址转换 NAT×××
NAT 网络××
Bridged Adapter 桥接网卡

TUN/TAP 网络设备

TAP 属于 Linux 内核支持的一种虚拟化网络设备,还有 TUN 也属于这种设备,它们完全由软件模拟实现,TUN/TAP 负责在内核协议栈和用户进程之间传送协议数据单元。TUN 工作在网络层,而 TAP 工作在数据链路层,TUN 负责与应用程序交换 IP 数据包,而 TAP 与应用程序交换以太网帧。所以 TUN 经常涉及路由,而 TAP 常用于网络桥接。

SSH 远程登录虚拟机

宿主机任意下目录执行:

$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/user/.ssh/id_rsa): host2vm0_id_irsa
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in host2vm0_id_irsa.
Your public key has been saved in host2vm0_id_irsa.pub.
The key fingerprint is:
SHA256:OkWcw+R3x6Z2mzeYQuG033H3N9qIeym3TZKzz6YD8tQ user@ubuntu18
The key's randomart image is:
+---[RSA 2048]----+
|        .        |
|       = .   .   |
|        B .o. +  |
|       . oo.o+   |
|        S  ++ ..o|
|       o ..+.E=o=|
|      o   +..B+=+|
|       .   oo=@o+|
|           o=**= |
+----[SHA256]-----+

一直回车确定,生成公私钥,保存在~/.ssh目录下。

我在宿主机上生成的公私钥名称为,分别是host2vm0_id_rsa,host2vm0_id_rsa.pub方便我记忆。如果一直回车,那么生成的公私钥名称为id_rsaid_rsa.pub

将公钥复制到虚拟机 VM0 上,以当前虚拟机 VM0 的 IP:10.12.193.53 为例。

$ ssh-copy-id 10.12.193.53
# 输入密码
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
user@10.12.193.53's password: 

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh '10.12.193.53'"
and check to make sure that only the key(s) you wanted were added.

然后就可以直接免密码登录了:

ssh user@10.12.193.53

Fixed Problems (Ongoing)

cannot ioctl tunsetiff tap0 device or resource busy (errno=16)

failed to initialize tap device: Operation not permitted

同类型错误:failed to create TAP device: Operation not permitted。因为创建虚拟设备 tap 需要 root 权限,所以需要使用 sudo 命令。执行 QEMU 启动是需要添加 sudo

QEMU 虚拟机启动后网卡处于 DOWN 状态,无法获取 IP

查看是否是 MAC 地址配置错误,使用下面命令检查:

sudo ifconfig eth0 up
SIOCSIFFLAGS: Cannot assign requested address

如果报错,参考下面章节SIOCSIFFLAGS: Cannot assign requested address解决方法进行解决。

虚拟机可以 ping 通外网,宿主机无法 ping 外网

这种情况说明基本网络没有问题,只是 DNS 解析有问题,可以通过修改/etc/resolv.conf文件解决。

海宁 DNS 服务器地址:10.12.2.2110.12.2.22,我的情况是只能 ping 10.12.2.21,可以选择自己能 ping 通的 DNS 服务器地址。如果无法 ping 通,说明问题不在这,需要自行解决。

# 修改 DNS 服务器地址
sudo vim /etc/resolv.conf
# 添加以下内容
nameserver 10.12.2.21
nameserver 10.12.2.22

网络配置错误,如何恢复配置之前的状态

最简单的方式 - 重启,因为所有操作都是命令行配置,都是临时配置,可以直接重启解决。

既然有这一小节,说明肯定有时候不方便直接重启,那么就需要手动恢复配置之前的状态。但是能够恢复的前提是需要记得之前的网卡 IP 地址、子网掩码、网关、广播地址等信息。这些信息在局域网里,可能只有 IP 不同,其他信息如果没记住可以查看其他同事的网卡配置即可。

# 将网桥绑定的网卡从网桥上移除
sudo brctl delif br0 enp2s0
sudo brctl delif br0 tap0
# 配置宿主机网卡信息,必须一字不差,保持和之前一模一样才能恢复
sudo ip addr add 10.12.192.173/20 broadcast 10.12.207.255 dev enp2s0
# 必须设置网关
sudo ip route add default via 10.12.192.1 dev enp2s0
# 重启网络管理器
systemctl restart NetworkManager

-netdev tap,id=tapnet,script=/qemu-script/qemu-ifup,:network script /qemu-script/qemu-ifup failed with status 256

可能原因 1: qemu-ifup 脚本没有执行权限,需要添加执行权限。

chmod +x qemu-ifup

可能原因 2: qemu-ifup 路径不对,必须放到/etc/qemu/目录下。

mkdir -p /etc/qemu
mv qemu-ifup /etc/qemu && mv qemu-ifdown /etc/qemu 
sudo chmod +x qemu-ifup
sudo chmod +x qemu-ifdown

SIOCSIFFLAGS: Cannot assign requested address

sudo ifconfig eth0 up
SIOCSIFFLAGS: Cannot assign requested address

一般由于 MAC 地址配置错误导致,可以通过修改 MAC 地址为多播地址解决。

sudo ifconfig enp2s0 hw ether 00:11:22:33:44:55

重启网卡

sudo ifconfig enp2s0 down
sudo ifconfig enp2s0 up

MAC 地址的第一个字节中的最后一位(即第 7 位)用于标识该地址是单播,多播还是广播地址。如果这个位设置为 0,则表示这是一个单播地址;如果设置为 1,则表示这是一个多播或广播地址。

使用这种方法,我们可以确定上述每个 MAC 地址是否是单播地址:

  • cd:c2:05:84:c8:2c - 单播地址
  • 13:7b:49:fc:a6:aa - 单播地址
  • 8f:aa:42:29:e8:68 - 单播地址

00:11:22:33:44:55 是多播地址。

qemu -device drive with 0 bus=0 unit=0 exists

这个错误通常意味着您尝试在 QEMU VM 中添加一个重复的设备。

如果您已经在 VM 中添加了驱动器,则可能会出现此问题。您可以检查是否存在两个具有相同 busunit 的设备(在此情况下,都是 0)。解决此问题的方法是删除重复设备或更改其配置以包括唯一的 busunit

如果您没有意图添加重复的设备,在运行 QEMU 之前,您可能需要检查您的命令行,以确保正确设置了 -drive 选项。请注意,当使用 -device 添加设备时,您还应该避免使用 -drive 选项,因为它们可能引起冲突。

如果您需要进一步帮助,建议提供完整的 QEMU 命令和参数列表,以便更好地理解问题并提供更详细的建议。

参考资料

  1. QEMU 网络配置 // 围城
  2. 理解 Linux 虚拟网卡设备 tun/tap 的一切 | 骏马金龙
  3. QEMU 网络配置一把梭 | CataLpa’s Site
  4. qemu 虚拟机与外部网络的通信 li_Jiejun 的博客-CSDN 博客
  5. 安装 qemu-kvm 以及配置桥接网络
  6. QEMU 中的网络虚拟化配置_程序猿 Ricky 的日常干货的博客-CSDN 博客
  7. Nginx Directory
  8. QEMU 网络配置 - 浙林龙哥 - 博客园
  9. 【qemu】qemu 网络配置 - 知乎
  10. QEMU 网络配置 // 围城
  11. 安装 qemu-kvm 以及配置桥接网络
  12. 在 qemu 中使用桥接网络 - T^3 Blog
  13. 为 QEMU 配置网桥上网 | Yanick’s Wiki
  14. 理解 Linux 虚拟网卡设备 tun/tap 的一切 | 骏马金龙
  15. QEMU 网络配置一把梭 | CataLpa’s Site

附录