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=no
和downscript=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
该脚本用于创建网桥,将网桥与宿主机的网卡绑定,然后将虚拟网卡绑定到网桥上,这样虚拟机就可以通过网桥与宿主机通信,宿主机也可以通过网桥与虚拟机通信。
-device & -netdev (Recommended)
这是新版本的 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 <> VM | VM → HOST | HOST → VM | VM → Internet | Internet → 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_rsa
,id_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.21
和 10.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 中添加了驱动器,则可能会出现此问题。您可以检查是否存在两个具有相同 bus
和 unit
的设备(在此情况下,都是 0)。解决此问题的方法是删除重复设备或更改其配置以包括唯一的 bus
和 unit
。
如果您没有意图添加重复的设备,在运行 QEMU 之前,您可能需要检查您的命令行,以确保正确设置了 -drive
选项。请注意,当使用 -device
添加设备时,您还应该避免使用 -drive
选项,因为它们可能引起冲突。
如果您需要进一步帮助,建议提供完整的 QEMU 命令和参数列表,以便更好地理解问题并提供更详细的建议。
参考资料
- QEMU 网络配置 // 围城
- 理解 Linux 虚拟网卡设备 tun/tap 的一切 | 骏马金龙
- QEMU 网络配置一把梭 | CataLpa’s Site
- qemu 虚拟机与外部网络的通信 li_Jiejun 的博客-CSDN 博客
- 安装 qemu-kvm 以及配置桥接网络
- QEMU 中的网络虚拟化配置_程序猿 Ricky 的日常干货的博客-CSDN 博客
- Nginx Directory
- QEMU 网络配置 - 浙林龙哥 - 博客园
- 【qemu】qemu 网络配置 - 知乎
- QEMU 网络配置 // 围城
- 安装 qemu-kvm 以及配置桥接网络
- 在 qemu 中使用桥接网络 - T^3 Blog
- 为 QEMU 配置网桥上网 | Yanick’s Wiki
- 理解 Linux 虚拟网卡设备 tun/tap 的一切 | 骏马金龙
- QEMU 网络配置一把梭 | CataLpa’s Site