手を動かして学ぶ QEMU とネットワーク
このポストは Linux Advent Calendar 2021 の 19日目の記事です。
Linux 成分があんまり入れられなかったですが、強い気持ちで見てもらうことにします。
QEMU で起動させた VM がどのような設定で通信するのか、思いを馳せたことはないでしょうか。
今回は、オーソドックスな設定である Linux Bridge + TAP デバイスによる接続をすべて自分の手で構築し、VM 同士が通信できるようになるまでやってみます。
QEMU プロセスは設定が多機能すぎる
QEMU は、動作させるために必要な設定が多岐にわたるため、運用する上で直接 qemu コマンドを実行して VM を起動することはありません。
OpenStack, Vagrant for kvm, kubevirt など、ほとんどのプラットフォームは libvirt という仮想化に関する機能を包括した api を提供してくれるソフトウェアを使用します。
例えば、uvtool という、ubuntu 上で簡単に VM を起動できるツールがあり、そちらも libvirt を経由して VM を起動します。
VM を起動した際に実際に起動するプロセスは以下のようになります。
libvirt+ 291694 100 0.6 1257616 98960 ? Sl 17:03 0:11 /usr/bin/qemu-system-x86_64 -name guest=test,debug-threads=on -S -object secret,id=masterKey0,format=raw,file=/var/lib/libvirt/qemu/domain-1-test/master-key.aes -machine pc-q35-focal,accel=kvm,usb=off,dump-guest-core=off -cpu qemu64 -m 512 -overcommit mem-lock=off -smp 1,sockets=1,cores=1,threads=1 -uuid 6ecbc830-38d4-479a-a319-65e1d71e854e -no-user-config -nodefaults -chardev socket,id=charmonitor,fd=32,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=utc -no-shutdown -boot strict=on -device pcie-root-port,port=0x10,chassis=1,id=pci.1,bus=pcie.0,multifunction=on,addr=0x2 -device pcie-root-port,port=0x11,chassis=2,id=pci.2,bus=pcie.0,addr=0x2.0x1 -device pcie-root-port,port=0x12,chassis=3,id=pci.3,bus=pcie.0,addr=0x2.0x2 -device pcie-root-port,port=0x13,chassis=4,id=pci.4,bus=pcie.0,addr=0x2.0x3 -device pcie-root-port,port=0x14,chassis=5,id=pci.5,bus=pcie.0,addr=0x2.0x4 -device pcie-root-port,port=0x15,chassis=6,id=pci.6,bus=pcie.0,addr=0x2.0x5 -device pcie-root-port,port=0x16,chassis=7,id=pci.7,bus=pcie.0,addr=0x2.0x6 -device qemu-xhci,id=usb,bus=pci.2,addr=0x0 -device virtio-serial-pci,id=virtio-serial0,bus=pci.3,addr=0x0 -blockdev {"driver":"file","filename":"/var/lib/uvtool/libvirt/images/x-uvt-b64-Y29tLnVidW50dS5jbG91ZDpzZXJ2ZXI6MTguMDQ6YW1kNjQgMjAyMTExMjk=","node-name":"libvirt-3-storage","auto-read-only":true,"discard":"unmap"} -blockdev {"node-name":"libvirt-3-format","read-only":true,"driver":"qcow2","file":"libvirt-3-storage","backing":null} -blockdev {"driver":"file","filename":"/var/lib/uvtool/libvirt/images/test.qcow","node-name":"libvirt-2-storage","auto-read-only":true,"discard":"unmap"} -blockdev {"node-name":"libvirt-2-format","read-only":false,"driver":"qcow2","file":"libvirt-2-storage","backing":"libvirt-3-format"} -device virtio-blk-pci,scsi=off,bus=pci.4,addr=0x0,drive=libvirt-2-format,id=virtio-disk0,bootindex=1 -blockdev {"driver":"file","filename":"/var/lib/uvtool/libvirt/images/test-ds.qcow","node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"} -blockdev {"node-name":"libvirt-1-format","read-only":false,"driver":"qcow2","file":"libvirt-1-storage","backing":null} -device virtio-blk-pci,scsi=off,bus=pci.5,addr=0x0,drive=libvirt-1-format,id=virtio-disk1 -netdev tap,fd=34,id=hostnet0,vhost=on,vhostfd=35 -device virtio-net-pci,netdev=hostnet0,id=net0,mac=52:54:00:b6:4f:14,bus=pci.1,addr=0x0 -chardev pty,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 -chardev socket,id=charchannel0,fd=37,server,nowait -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=org.qemu.guest_agent.0 -vnc 127.0.0.1:0 -spice port=5901,addr=127.0.0.1,disable-ticketing,seamless-migration=on -device qxl-vga,id=video0,ram_size=67108864,vram_size=67108864,vram64_size_mb=0,vgamem_mb=16,max_outputs=1,bus=pcie.0,addr=0x1 -device virtio-balloon-pci,id=balloon0,bus=pci.6,addr=0x0 -sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny -msg timestamp=on
便利機能をいろいろデフォルトで載せてくれているのですが、少々分かりづらいですね。
今回は手動で qemu-system-x86_64 バイナリを叩いて VM を起動することにします。
QEMU を通信させるためには
QEMU のネットワークにはどんなものが使えるのか、以下のドキュメントに載っています。
この中で、今回は Linux Advent Calendar ということで、Linux Bridge + Tap デバイスで通信してみようと思います。
手を動かす
作成する構成
今回は以下のような構成を作成します。
Linux bridge に対して2本の veth が刺さっており、2台の VM が TAP デバイスでつながっている構成です。IP が2つほど飛んでいるのは趣味です。
この VM に IP をつけて、お互いに疎通できるかどうか試してみます。
Bridge を作成する
Linux Bridge を作成します。みんな大好き brctl を使うなり、 iproute2 を使うなり、お好きにどうぞ。僕は iproute2 を使います。
$ sudo ip link add br-test type bridge
できました
11: br-test: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 42:91:b9:ae:3d:41 brd ff:ff:ff:ff:ff:ff
TAP デバイスを作成する
作成します。ip tuntap コマンドで作成できます。
[owner@mobile] ~
% sudo ip tuntap add dev tap-test0 mode tap
[owner@mobile] ~
% sudo ip tuntap add dev tap-test1 mode tap
[owner@mobile] ~
% ip a
12: tap-test0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether a2:e8:69:11:27:4e brd ff:ff:ff:ff:ff:ff
13: tap-test1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether f6:d8:f2:f1:be:71 brd ff:ff:ff:ff:ff:ff
できました。
TAP デバイスを Bridge に接続する
ip link set tap-test0 master br-test
TAP デバイスを指定して VM を起動する
このくらいオプションをつければ良さそうでした。 -machine に与えられる引数は環境によって異なると思います。
なお、TAP デバイスを操作するため管理者権限が必要です。
/usr/bin/qemu-system-x86_64 -machine pc-q35-focal,accel=kvm -netdev tap,id=mynet0,ifname=tap-test0,script=no,downscript=no -device e1000,netdev=mynet0
イメージを作る
起動できそうということがわかったので、ブートイメージを作成します。
今回は cirros をバッキングファイルにした qcow2 イメージを作成して利用します。
qemu-img create -f qcow2 -b cirros-0.5.2-x86_64-disk.img test0.qcow2 2G
以下の引数をつけると、イメージを VM にアタッチできます。
-drive file=./test0.qcow2,index=0,format=qcow2
完成したコマンドは以下となります。
/usr/bin/qemu-system-x86_64 -machine pc-q35-focal,accel=kvm -netdev tap,id=mynet0,ifname=tap-test0,script=no,downscript=no -device e1000,netdev=mynet0 -drive file=./test0.qcow2,index=0,format=qcow2
test0 に IP をつける
cirros でブートした VM にログインして、IP をつけます。
ip a add 10.50.0.3/24 dev eth0
ping を飛ばしてみる
ping を飛ばして、tap の様子を tcpdump で見てみます。
% sudo tcpdump -i tap-test0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tap-test0, link-type EN10MB (Ethernet), capture size 262144 bytes
18:50:59.744654 ARP, Request who-has 10.50.0.4 tell 10.50.0.3, length 46
18:51:00.768584 ARP, Request who-has 10.50.0.4 tell 10.50.0.3, length 46
18:51:02.728406 ARP, Request who-has 10.50.0.4 tell 10.50.0.3, length 46
arp を飛ばしていることがわかります。
test1 に IP をつけて、L2 でつながっていたら通信できそうということがわかります。
test1 に IP をつける
さあどうなるでしょうか?
ip a add 10.50.0.4/24 dev eth0
疎通しました!
まとめ
API がよしなにやってくれる作業を手動で実行することにより、システムへの理解がより深まりますね。
NAT の仕組みを自分で作って、VM を外部疎通させるのも面白そうと思いました。
参考資料
https://i-beam.org/2020/03/01/go-modern-bootserver-02/
https://gist.github.com/extremecoders-re/e8fd8a67a515fee0c873dcafc81d811c
おまけ
kvm が使われているのか確認した
strace したところ、きちんと KVM を使っていそうということが確認できました。
kvm は大体の命令を ioctl システムコールで実行するため、これだけ見れば大丈夫です。
KVM_CREATE_VM
なんていいですね。
% sudo strace -e ioctl /usr/bin/qemu-system-x86_64 -machine pc-q35-focal,accel=kvm -netdev tap,id=tap-test0
ioctl(12, KVM_GET_API_VERSION, 0) = 12
ioctl(12, KVM_CHECK_EXTENSION, KVM_CAP_IMMEDIATE_EXIT) = 1
ioctl(12, KVM_CHECK_EXTENSION, KVM_CAP_NR_MEMSLOTS) = 509
ioctl(12, KVM_CHECK_EXTENSION, KVM_CAP_MULTI_ADDRESS_SPACE) = 2
ioctl(12, KVM_CREATE_VM, 0) = 13
ioctl(13, KVM_CHECK_EXTENSION, KVM_CAP_NR_VCPUS) = 240
ioctl(12, KVM_CHECK_EXTENSION, KVM_CAP_MAX_VCPUS) = 288
ioctl(12, KVM_CHECK_EXTENSION, KVM_CAP_USER_MEMORY) = 1
ioctl(12, KVM_CHECK_EXTENSION, KVM_CAP_DESTROY_MEMORY_REGION_WORKS) = 1
ioctl(12, KVM_CHECK_EXTENSION, KVM_CAP_JOIN_MEMORY_REGIONS_WORKS) = 1
...
ハマったこと
デフォルトの mac address がかぶる
tap-test1 では arp reply が返っているのに、tap-test0 では受け取っていない。。。
% sudo tcpdump -i tap-test1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tap-test1, link-type EN10MB (Ethernet), capture size 262144 bytes
18:58:40.322845 ARP, Request who-has 10.50.0.4 tell 10.50.0.3, length 46
18:58:40.323759 ARP, Reply 10.50.0.4 is-at 52:54:00:12:34:56 (oui Unknown), length 46
18:58:41.347097 ARP, Request who-has 10.50.0.4 tell 10.50.0.3, length 46
18:58:41.347928 ARP, Reply 10.50.0.4 is-at 52:54:00:12:34:56 (oui Unknown), length 46
18:58:43.297178 ARP, Request who-has 10.50.0.4 tell 10.50.0.3, length 46
18:58:43.298373 ARP, Reply 10.50.0.4 is-at 52:54:00:12:34:56 (oui Unknown), length 46
デフォルトの mac address が設定されており、両方の VM が共に 52:54:00:12:34:56
だったので、変更したら疎通が確認できました。
Author And Source
この問題について(手を動かして学ぶ QEMU とネットワーク), 我々は、より多くの情報をここで見つけました https://qiita.com/takutakahashi/items/315626ae479f0a6206ef著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .