LoginSignup
2
1

More than 1 year has passed since last update.

手を動かして学ぶ QEMU とネットワーク

Last updated at Posted at 2021-12-19

このポストは 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 デバイスで通信してみようと思います。

手を動かす

作成する構成

今回は以下のような構成を作成します。

Untitled presentation.png

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

疎通しました! :tada:

Screenshot from 2021-12-18 19-05-44.png

まとめ

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 だったので、変更したら疎通が確認できました。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1