It's sometimes desirable to be able to specify a static IP address for a virtual machine that is using a virtual network bridge. For example, this can be useful when you have additional iptables rules for a specific virtual machines on the host.

With libvirt networks, one way to do this is by configuring the DHCP server to allocate IP addresses to virtual machines based on their MAC address. This doesn't require any special configuration in the virtual machine as it obtains its IP via DHCP.

Configuring this requires the MAC address of the virtual machines network interface, when using libvirt this can be found in the virtual machines configuration.

root@demo ~ # virsh dumpxml guest-1
<domain type='kvm' id='1'>
..
  <devices>
    ..
    <interface type='bridge'>
      <mac address='52:54:00:b3:e8:b7'/>
      <source bridge='virbr0'/>
      <target dev='vnet0'/>
      <model type='virtio'/>
      <alias name='net0'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
    </interface>
    ..
  </devices>
..
</domain>

This shows the MAC address of this virtual machines interface is 52:54:00:b3:e8:b7. As this virtual machine had previously obtained an IP address from the DHCP server it will appear in the net-dhcp-leases for the network.

root@demo ~ # virsh net-dhcp-leases  default
Expiry Time          MAC address        Protocol  IP address                Hostname        Client ID or DUID
-------------------------------------------------------------------------------------------------------------------
2018-11-27 12:04:27  52:54:00:b3:e8:b7  ipv4      192.168.122.14/24         guest-1         -

Currently the virtual machine has been allocated 192.168.122.14.

Within this /24 subnetwork there are 254 available IP addresses, the range 192.168.122.2 to 192.168.122.254 is available to allocate to virtual machines.

To allow both dynamically allocated and static IP addresses, the available range for dynamically allocated IP addresses can be limited to a subset so that the rest are can be allocated to specific MAC addresses.

To do this the default network need to be modified and the dynamic IP range updated. To edit the default network the virsh net-edit default command is used, this will open the configuration in the users default editor.

<network>
  <name>default</name>
  <uuid>4690aefc-dfa3-4a0a-bd4c-e4297126a845</uuid>
  <forward mode='nat'>
    <nat>
      <port start='1024' end='65535'/>
    </nat>
  </forward>
  <bridge name='virbr0' stp='on' delay='0'/>
  <mac address='52:54:00:95:92:6b'/>
  <ip address='192.168.122.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.122.2' end='192.168.122.128'/>
    </dhcp>
  </ip>
</network>

In the above configuration the upper DHCP range has been limited from 192.168.122.254 to 192.168.122.128.

The next task is to assign a specific IP outside this range to a specific MAC address. This done by adding a host element containing the MAC address found earlier, the desired IP address in this example 192.168.122.129 and the name of the virtual machine.

<network>
  <name>default</name>
  <uuid>4690aefc-dfa3-4a0a-bd4c-e4297126a845</uuid>
  <forward mode='nat'>
    <nat>
      <port start='1024' end='65535'/>
    </nat>
  </forward>
  <bridge name='virbr0' stp='on' delay='0'/>
  <mac address='52:54:00:95:92:6b'/>
  <ip address='192.168.122.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.122.2' end='192.168.122.128'/>
      <host mac='52:54:00:b3:e8:b7' name='guest-1' ip='192.168.122.129'/>
    </dhcp>
  </ip>
</network>

Modifying this network configuration has no effect until the network is restarted. To do this the network is destroyed and started again, in virtualisation terminology destroying means stopping. This will cause virtual machines using the network to lose network connectivity.

root@demo ~ # virsh net-destroy default
Network default destroyed

root@demo ~ # virsh net-start default
Network default started

Even after the network is restarted, running virtual machines using the network will not be able to use the new network, they need to be destroyed and restarted for the interface to work again.

root@demo ~ # virsh destroy guest-1
Domain guest-1 destroyed

root@demo ~ # virsh start guest-1
Domain guest-1 started

Looking at the leases again, the virtual machine guest-1 has now been allocated 192.168.122.129.

root@demo ~ # virsh net-dhcp-leases default
Expiry Time          MAC address        Protocol  IP address                Hostname        Client ID or DUID
-------------------------------------------------------------------------------------------------------------------
 2018-11-27 13:49:09  52:54:00:b3:e8:b7  ipv4      192.168.122.129/24        guest-1        -

The VM has the IP address we specified, but taking down the entire network is a real problem, especially if there are other virtual machines using the network.

It is also possible to make some live configurations to the network such as the IP address assigned to a virtual machine, this change is immediate, the interface in the virtual machine doesn't even need to be restarted.

root@demo ~ # virsh net-update default modify ip-dhcp-host \
      '<host mac="52:54:00:b3:e8:b7" name="guest-1" ip="192.168.122.130"/>' \
      --live --config
Updated network default persistent config and live state

Host elements can also be deleted. This won't automatically allocate a new IP in the virtual machine, in fact it will keep using the previously allocated one. When the interface is restarted it will be allocated a new address.

root@demo ~ # virsh net-update default delete ip-dhcp-host \
      '<host mac="52:54:00:b3:e8:b7" name="guest-1" ip="192.168.122.130"/>' \
      --live --config
Updated network default persistent config and live state

Host elements can also be added, the interface in the virtual machine will need to be restarted to obtain the new IP address.

root@demo ~ # virsh net-update default add ip-dhcp-host \
      '<host mac="52:54:00:b3:e8:b7" name="guest-1" ip="192.168.122.131"/>' \
      --live --config
Updated network default persistent config and live state

Modifying the networks with commands is a bit unwieldy but may be preferable than taking down the entire virtual network. It's also possible to specify IPv6 addresses that will be allocated to virtual machines, but I intend to use a physical bridge network for IPv6 networking, so I'm not going to write about it. For more information on that, check out the libvirt networking documentation or this great libvirt tutial by Jamie Nguyen.