libvirtのnetworkでDHCPが同じIPを振り続ける

dnsmasqは悪くなかった。ごめんよ。

ホスト：**Ubuntu 19.10** / **libvirtd 5.4.0**
VM：**Ubuntu 20.04 LTS**

# 事象
- libvirt(KVM)でUbuntu VMを作り、**default network**に接続する。
- `virt-clone`する。
- 起動する。

### 結果
同じIPが振られる。

---
default networkの例。


```xml:default（例）
<network>
  <name>default</name>
  <uuid>df3e2c4f-3071-427c-a032-8c340a72407e</uuid>
  <forward mode='nat'/>
  <bridge name='virbr0' stp='off' delay='0'/>
  <mac address='52:54:00:f4:35:24'/>
  <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>
```
こんな感じにcloneする。

```bash:clone
$ virt-clone --original vm1 --name dhcp-test2 --file /var/lib/libvirt/images/dhcp-test2.qcow2
```

で、cloneしたVM2台を起動すると、**同じIPが振られる。**

**🥺ぴえんぱおん🥺**


# 惑ったところ
- `virt-clone`はVMインターフェイスのMACアドレスを変更してくれる。
 - ログを見ると、「異なるMACからのDISCOVERに同じIPを返す」ような謎挙動に見える。

```bash:libvirtdのjournal
May 27 10:59:26 dnsmasq-dhcp[1740]: DHCPDISCOVER(virbr0) 52:54:00:be:82:59
May 27 10:59:26 dnsmasq-dhcp[1740]: DHCPOFFER(virbr0) 192.168.122.188 52:54:00:be:82:59
May 27 10:59:26 dnsmasq-dhcp[1740]: DHCPREQUEST(virbr0) 192.168.122.188 52:54:00:be:82:59
May 27 10:59:26 dnsmasq-dhcp[1740]: DHCPACK(virbr0) 192.168.122.188 52:54:00:be:82:59 testing
May 27 10:59:29 dnsmasq-dhcp[1740]: DHCPDISCOVER(virbr0) 52:54:00:8f:6a:81
May 27 10:59:29 dnsmasq-dhcp[1740]: DHCPOFFER(virbr0) 192.168.122.188 52:54:00:8f:6a:81
May 27 10:59:29 dnsmasq-dhcp[1740]: DHCPREQUEST(virbr0) 192.168.122.188 52:54:00:8f:6a:81
May 27 10:59:29 dnsmasq-dhcp[1740]: DHCPACK(virbr0) 192.168.122.188 52:54:00:8f:6a:81 testing

```

- この点から「lease fileに書き込めていない？」「systemd-networkdと干渉している？」と思うなどした。
 - 間違い。

# 答え

- DHCPのleaseは**MAC Address**ではなく、**Client ID**（**DUID**）で管理されている。
- `virt-clone`したUbuntu VMでは**Client ID**が同じになるため、LeaseからIPを返す。

この結果、cloneしたVMには同じIPを振り続けてしまう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547524/4507a9e7-17eb-be44-e66f-30e96d45afdf.png)
1台目、Option61に**Client Identifier**がある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547524/0340b822-5807-7024-769a-26004e754378.png)
2台目、同じ値である！

leaseを見ても、同じClient IDであることがわかる。
場所は`/var/lib/libvirt/dnsmasq/<interface>.status`、libvirt用の形式で通常のdnsmasqとは異なる。（[この辺の事情](https://bugzilla.redhat.com/show_bug.cgi?id=1228631)）

1台目起動。

```json:/var/lib/libvirt/dnsmasq/virbr0.status
[
  {
    "ip-address": "192.168.122.188",
    "mac-address": "52:54:00:be:82:59",
    "hostname": "testing",
    "client-id": "ff:b5:5e:67:ff:00:02:00:00:ab:11:37:05:50:e0:d8:7b:8c:01",
    "expiry-time": 1590582921
  }
]
```

2台目起動。

```json:/var/lib/libvirt/dnsmasq/virbr0.status
[
  {
    "ip-address": "192.168.122.188",
    "mac-address": "52:54:00:8f:6a:81",
    "hostname": "testing",
    "client-id": "ff:b5:5e:67:ff:00:02:00:00:ab:11:37:05:50:e0:d8:7b:8c:01",
    "expiry-time": 1590583031
  }
]
```

MACが変わっているが、同じレコードとして扱われている。

（気楽に見るなら、`virsh net-dhcp-leases default` でも。）

同じIP振られるのも納得。

# なぜ同じIDになるのか

Ubuntu、というか`systemd`がDHCPに使うClient IDを**machine-id**をもとに計算するため。
virt-cloneはそこまで変えない。

以下manページ抜粋（networkd.conf(5)）

```bash:networkd.conf(5)
       DUIDType=
           Specifies how the DUID should be generated. See RFC 3315[1] for a description of all the options.

           The following values are understood:

           vendor
               If "DUIDType=vendor", then the DUID value will be generated using "43793" as the vendor
               identifier (systemd) and hashed contents of machine-id(5). This is the default if DUIDType= is
               not specified.
```

ここで`machine-id`は`/etc/machine-id`に保存されていて、**再起動しても変わらない**。

以下manページ抜粋（machine-id(5)）


```bash:machine-id(5)
       The machine ID is usually generated from a random source during system installation or first boot and
       stays constant for all subsequent boots. Optionally, for stateless systems, it is generated during
       runtime during early boot if necessary.
```

# どうすればよいのか

以下の方法でとりあえず別のIPをもらえる。

## Client IDをMACにする

ゲストOSでネットワーク設定を変更。
`dhcp-identifier`を追加。

```yml:/etc/netplan/00-installer-config.yaml
# This is the network config written by 'subiquity'
network:
  ethernets:
    ens3:
      dhcp4: true
      dhcp-identifier: mac
  version: 2
```

これにより、Client IDがMACになり、別のIPをもらえる。

```json:/var/lib/libvirt/dnsmasq/virbr0.status
[
  {
    "ip-address": "192.168.122.188",
    "mac-address": "52:54:00:8f:6a:81",
    "hostname": "testing",
    "client-id": "ff:b5:5e:67:ff:00:02:00:00:ab:11:37:05:50:e0:d8:7b:8c:01",
    "expiry-time": 1590583031
  },
  {
    "ip-address": "192.168.122.189",
    "mac-address": "52:54:00:be:82:59",
    "hostname": "testing",
    "client-id": "01:52:54:00:be:82:59",
    "expiry-time": 1590584091
  }
]
```

MACがユニークならこれでもよさそう…？

## machine-idを変更する

ゲストで`/etc/machine-id`を空にすると新しいmachine-idで起動するみたい。
manページ参照。

```bash:machine-id
$ sudo bash -c "echo -n > /etc/machine-id"
$ cat /etc/machine-id 
$
```

再起動後のlease。抜粋。

```json:
[
  {
    "ip-address": "192.168.122.188",
    "mac-address": "52:54:00:8f:6a:81",
    "hostname": "testing",
    "client-id": "ff:b5:5e:67:ff:00:02:00:00:ab:11:37:05:50:e0:d8:7b:8c:01",
    "expiry-time": 1590583031
  },
  {
    "ip-address": "192.168.122.190",
    "mac-address": "52:54:00:be:82:59",
    "hostname": "testing",
    "client-id": "ff:b5:5e:67:ff:00:02:00:00:ab:11:fc:e8:d0:34:46:7c:85:57",
    "expiry-time": 1590584416
  }
]
```
Client IDが変わるため、別のIPをもらうことができる。

# 終わりに

DHCPの仕組みだから多分有名なんだろうが、全然知らなくて無知を晒してしまった。

Clone手法については、もっとシンプルな方法があるのかも。