KVM (libvirt) 仮想マシンからの通信を IPv4 & IPv6 NAPT (Masquerade)

ホスト OS が Ubuntu 22.04 LTS Desktop の環境において、KVM にインストールした仮想マシンからの通信 IPv4/IPv6 をそれぞれ NAPT する。仮想マシンは IPv4/IPv6 それぞれのプライベートアドレスを持つデュアルスタック構成となる。また、拡張のため物理 NIC に対してもブリッジ接続して別の PC からのインターネット接続も同様に NAPT を行う。

KVM からの Linux Bridge 作成

KVM から Linux Bridge の virbr1 を作成し、 Ubuntu 側で物理 NIC との紐付けなどの細かい編集を行う。初期状態は以下となる。 WiFi でインターネット接続を行っているため、仮想マシンからのインターネット通信はホスト OS の Ubuntu で NAPTを行い、 WiFi からインターネット接続となる。

$ nmcli device
DEVICE          TYPE      STATE                   CONNECTION
wlp6s0          wifi      connected               wlp6s0
virbr0          bridge    connected               virbr0
eth0            ethernet  disconnected            --
p2p-dev-wlp6s0  wifi-p2p  disconnected            --
lo              loopback  unmanaged               --

$ nmcli connection
NAME    UUID                                  TYPE    DEVICE
wlp6s0  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  wifi    wlp6s0
virbr0  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  bridge  virbr0
仮想ネットワーク名guest-virbr1
Spanning Treeoff
Forwarding Delay0
DNS Server (dnsmasq)no
NAT Port Range1024 – 65535
IPv4 Forwarding TypeNAT
IPv4 Address192.168.255.254/24
IPv4 DHCPなし
IPv4 Gateway192.168.255.254
IPv4 RouteNetwork: 192.168.0.0/16
Gateway: 192.168.255.253
IPv6 Forwarding TypeNAT
IPv6 Networkfd00:0:0:ff::fe/64
IPv6 DHCPなし
IPv6 Autoconfなし
IPv6 Gatewayfd00:0:0:ff::fe
IPv6 RouteNetwork: fd00::/8
Gateway: fd00:0:0:ff::fd
virbr1 の設定情報

上記の表のとおりに作成する Linux Bridge (virbr1) 用の XML ファイルを定義する。この時、デフォルトの XML を参考して作成する。また、 libvirt により DNS を明示的に OFF にしないと dnsmasq が動作してしまうため、別途 bind などで DNS サーバにしているときは注意が必要となる。
※UUID や MAC アドレスは自動でアサインされるので省略して良い。

$ cat /usr/share/libvirt/networks/default.xml 
<network>
  <name>default</name>
  <bridge name='virbr0'/>
  <forward/>
  <ip address='192.168.122.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.122.2' end='192.168.122.254'/>
    </dhcp>
  </ip>
</network>

$ cat ~/guest-virbr1.xml
<network>
  <name>guest-virbr1</name>
  <forward mode='nat'>
    <nat ipv6='yes'>
      <port start='1024' end='65535'/>
    </nat>
  </forward>
  <bridge name='virbr1' stp='off' delay='0'/>
  <dns enable='no'/>
  <ip address='192.168.255.254' netmask='255.255.255.0'>
  </ip>
  <ip family='ipv6' address='fd00:0:0:ff::fe' prefix='64'>
  </ip>
  <route address='192.168.0.0' prefix='16' gateway='192.168.255.253'/>
  <route family='ipv6' address='fd00::' prefix='8' gateway='fd00:0:0:ff::fd'/>
</network>

上記で作成した仮想ネットワーク (guest-virbr1) を KVM に定義・有効化することで連動して Linux Bridge (virbr1) が作成される。

$ virsh
virsh # net-list --all
 Name           State    Autostart   Persistent
-------------------------------------------------
 default        active   yes         yes
 
virsh # net-define --file /home/penguin/guest-virbr1.xml 
Network guest-virbr1 defined from /home/penguin/guest-virbr1.xml

virsh # net-list --all
 Name           State      Autostart   Persistent
---------------------------------------------------
 default        active     yes         yes
 guest-virbr1   inactive   no          yes

virsh # net-dumpxml guest-virbr1 
<network>
  <name>guest-virbr1</name>
  <uuid>xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</uuid>
  <forward mode='nat'>
    <nat ipv6='yes'>
      <port start='1024' end='65535'/>
    </nat>
  </forward>
  <bridge name='virbr1' stp='off' delay='0'/>
  <mac address='52:54:00:xx:xx:xx'/>
  <dns enable='no'/>
  <ip address='192.168.255.254' netmask='255.255.255.0'>
  </ip>
  <ip family='ipv6' address='fd00:0:0:ff::fe' prefix='64'>
  </ip>
  <route address='192.168.0.0' prefix='16' gateway='192.168.255.253'/>
  <route family='ipv6' address='fd00::' prefix='8' gateway='fd00:0:0:ff::fd'/>
</network>

定義しただけではまだ動作していないので、仮想ネットワークを有効化することで Linux Bridge を作成する。また、起動時に有効化となるように、自動起動も有効にする。

virsh # net-start --network guest-virbr1 
Network guest-virbr1 started

virsh # net-autostart --network guest-virbr1
Network guest-virbr1 marked as autostarted

virsh # net-list --all
 Name           State    Autostart   Persistent
-------------------------------------------------
 default        active   yes         yes
 guest-virbr1   active   yes         yes

virsh # exit

以下のように Linux Bridge が作成されたことを確認する。また、 nft コマンドより NAPT テーブルが連動して追加されていることを確認する。
※ NAPT の対象は同じネットワークセグメントだけになることに注意(ルーティングしたものは NAPT の対象外)

$ nmcli device
DEVICE          TYPE      STATE                   CONNECTION
wlp6s0          wifi      connected               wlp6s0
virbr0          bridge    connected               virbr0
virbr1          bridge    connected (externally)  virbr1
eth0            ethernet  disconnected            --
p2p-dev-wlp6s0  wifi-p2p  disconnected            --
lo              loopback  unmanaged               --

$ nmcli connection
NAME    UUID                                  TYPE    DEVICE
wlp6s0  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  wifi    wlp6s0 
virbr0  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  bridge  virbr0
virbr1  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  bridge  virbr1

$ brctl show
bridge name	bridge id		STP enabled	interfaces
virbr0		8000.525400xxxxxx	no		
virbr1		8000.525400xxxxxx	no		
$ sudo nft list table ip nat
table ip nat {
	chain LIBVIRT_PRT {
		ip saddr 192.168.255.0/24 ip daddr 224.0.0.0/24 counter packets 3 bytes 406 return
		ip saddr 192.168.255.0/24 ip daddr 255.255.255.255 counter packets 0 bytes 0 return
		meta l4proto tcp ip saddr 192.168.255.0/24 ip daddr != 192.168.240.0/24 counter packets 0 bytes 0 masquerade to :1024-65535 
		meta l4proto udp ip saddr 192.168.255.0/24 ip daddr != 192.168.240.0/24 counter packets 0 bytes 0 masquerade to :1024-65535 
		ip saddr 192.168.255.0/24 ip daddr != 192.168.255.0/24 counter packets 0 bytes 0 masquerade 
		ip saddr 192.168.122.0/24 ip daddr 224.0.0.0/24 counter packets 57 bytes 6371 return
		ip saddr 192.168.122.0/24 ip daddr 255.255.255.255 counter packets 1 bytes 328 return
		meta l4proto tcp ip saddr 192.168.122.0/24 ip daddr != 192.168.122.0/24 counter packets 5884 bytes 311224 masquerade to :1024-65535 
		meta l4proto udp ip saddr 192.168.122.0/24 ip daddr != 192.168.122.0/24 counter packets 1162 bytes 315499 masquerade to :1024-65535 
		ip saddr 192.168.122.0/24 ip daddr != 192.168.122.0/24 counter packets 3 bytes 180 masquerade 
	}

	chain POSTROUTING {
		type nat hook postrouting priority srcnat; policy accept;
		counter packets 31839 bytes 2682246 jump LIBVIRT_PRT
	}
}
$ sudo nft list table ip6 nat
table ip6 nat {
	chain LIBVIRT_PRT {
		ip6 saddr fd00:0:0:ff::/64 ip6 daddr ff02::/16 counter packets 0 bytes 0 return
		meta l4proto tcp ip6 saddr fd00:0:0:ff::/64 ip6 daddr != fd00:0:0:ff::/64 counter packets 0 bytes 0 masquerade to :1024-65535  
		meta l4proto udp ip6 saddr fd00:0:0:ff::/64 ip6 daddr != fd00:0:0:ff::/64 counter packets 0 bytes 0 masquerade to :1024-65535  
		ip6 saddr fd00:0:0:ff::/64 ip6 daddr != fd00:0:0:ff::/64 counter packets 0 bytes 0 masquerade
	}

	chain POSTROUTING {
		type nat hook postrouting priority srcnat; policy accept;
		counter packets 22429 bytes 5412657 jump LIBVIRT_PRT
	}
}

Linux Bridge と 物理 NIC の紐付け

作成した Linux Bridge (virbr1) に KVM で仮想マシンから VNET を割り当てることで IPv4/IPv6 それぞれのインターネット通信が可能となる。これに加えて別の物理 PC からのインターネットも可能とするために、ホスト OS の物理 NIC (eth0)と Linux Bridge (virbr1) を接続する。

$ nmcli connection add type ethernet slave-type bridge master virbr1 ifname eth0
Connection 'bridge-slave-eth0' (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) successfully added.

$ nmcli connection
NAME               UUID                                  TYPE      DEVICE
wlp6s0             xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  wifi      wlp6s0
virbr0             xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  bridge    virbr0
virbr1             xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  bridge    virbr1
bridge-slave-eth0  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  ethernet  eth0

$ nmcli device
DEVICE          TYPE      STATE                   CONNECTION
wlp6s0          wifi      connected               wlp6s0
virbr0          bridge    connected               virbr0
virbr1          bridge    connected (externally)  virbr1
eth0            ethernet  connected               bridge-slave-eth0
p2p-dev-wlp6s0  wifi-p2p  disconnected            --
lo              loopback  unmanaged               --

$ brctl show
bridge name	bridge id		STP enabled	interfaces
virbr0		8000.525400xxxxxx	no		
virbr1		8000.525400xxxxxx	no		eth0

mncli コマンドの設定を反映するため、インタフェースの再起動を ホスト OS 側と KVM 側でそれぞれ行う。
※ KVM 側からだけだと nmcli からの設定がうまく反映されなかった

$ nmcli connection down virbr1; nmcli connection up virbr1
Connection 'virbr1' successfully deactivated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/18)
Connection successfully activated (master waiting for slaves) (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/19)

$ virsh net-destroy --network guest-virbr1
Network guest-virbr1 destroyed

$ virsh net-start --network guest-virbr1
Network guest-virbr1 started

NAPT の動作確認

KVM の仮想マシンを起動してインターネット通信が可能なことを確認する。

$ virsh start FG-01

$ brctl show
bridge name	bridge id		STP enabled	interfaces
virbr0		8000.xxxxxxxxxxxx	no		vnet0
virbr1		8000.xxxxxxxxxxxx	no		eth0
							vnet1

$ virsh list
 Id   Name     State
------------------------
 1    FG-01   running

virsh # domiflist 1
 Interface   Type      Source         Model    MAC
------------------------------------------------------------------
 vnet0       network   default        virtio   52:54:00:xx:xx:xx
 vnet1       bridge    guest-virbr1   virtio   52:54:00:xx:xx:xx

$ virsh console 1
Connected to domain 'FG-01'
Escape character is ^] (Ctrl + ])

FG-01 # execute ping xxxxx.com
PING xxxxx.com (xxx.xxx.xxx.xxx): 56 data bytes
64 bytes from xxx.xxx.xxx.xxx: icmp_seq=0 ttl=46 time=223.7 ms
64 bytes from xxx.xxx.xxx.xxx: icmp_seq=1 ttl=46 time=177.2 ms
64 bytes from xxx.xxx.xxx.xxx: icmp_seq=2 ttl=46 time=996.2 ms
64 bytes from xxx.xxx.xxx.xxx: icmp_seq=3 ttl=46 time=296.1 ms
64 bytes from xxx.xxx.xxx.xxx: icmp_seq=4 ttl=46 time=192.6 ms
--- xxxxx.com ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max = 177.2/377.1/996.2 ms

FG-01 # execute ping6 xxxxx.com
PING xxxxx.com(2001:xxxx:xxxx:xxxx::xxxx) 56 data bytes
64 bytes from 2001:xxxx:xxxx:xxxx::xxxx: icmp_seq=1 ttl=47 time=274 ms
64 bytes from 2001:xxxx:xxxx:xxxx::xxxx: icmp_seq=2 ttl=47 time=282 ms
64 bytes from 2001:xxxx:xxxx:xxxx::xxxx: icmp_seq=3 ttl=47 time=219 ms
64 bytes from 2001:xxxx:xxxx:xxxx::xxxx: icmp_seq=4 ttl=47 time=221 ms
64 bytes from 2001:xxxx:xxxx:xxxx::xxxx: icmp_seq=5 ttl=47 time=240 ms
--- xxxxx.com ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss, time 4002ms
rtt min/avg/max/mdev = 219.957/247.754/282.518/26.269 ms

参考URL
IPv6 NAT based network
Network XML format
Chapter 16. Configuring virtual machine network connections
Disable or change port of dnsmasq service in libvirt
KVM: how do I add host route when VM starts?