KVM: Creating a bridged network with NetPlan on Ubuntu 18.04 bionic

UPDATE September 2022: New article for bridged networks written for Ubuntu 22.04

In order to expose KVM virtual machines on the same network as your Host, you need to enable bridged networking.

In this article, I’ll show how to implement KVM bridged networking on Ubuntu 18.04 bionic using Netplan.  This bridged network will expose the KVM Guest OS as a peer on the upstream network, with no limits on ingress/egress.

Prerequisites

If you haven’t installed KVM on Ubuntu, you can follow the instructions in my article here.

Although we don’t directly require it for NetPlan, install the network bridge utilities package for debugging.

sudo apt install bridge-utils -y

Create host bridge using NetPlan

By creating a bridged network, you can have guest VMs share the network connection of the Host machine.

As an operator, this means you need to use the tools provided at the OS level to create a network bridge.  On older versions of Ubuntu, you would use ‘brctl‘, but on bionic you use Netplan.

Look at existing NetPlan

Go into the “/etc/netplan” directory and you should see a file named “01-netcfg.yaml” or “50-cloud-init.yml” that looks something like:

network:
  version: 2
  renderer: networkd

  ethernets:
    enp1s0:
      dhcp4: true
      dhcp6: false 
      addresses: [192.168.1.239/24]
      gateway4: 192.168.1.1
      mtu: 1500
      nameservers:
        addresses: [8.8.8.8]

This is for a host with a single physical NIC named ‘enp1s0’ (yours might be ‘eth0’), static IP of 192.168.1.239 on the 192.168.1.1 network and using Google DNS servers at 8.8.8.8.

If you do not have this file, then your system (which might be an upgrade from an older Ubuntu version) is still relying on ‘/etc/network/interfaces’.  You can bring it under netplan control by creating a file using the template above.

Created a bridge with Netplan

To create a bridged network, you need to disable the specific settings on the physical network, and instead apply them to the bridge.  Make a backup of your old file before modifying.

cd /etc/netplan

# make backup
sudo cp 50-cloud-init.yaml 50-cloud-init.yaml.orig

# modify, add bridge
sudo vi 50-cloud-init.yaml

Below is an example showing how we take the physical network example above (physical interfaced named ‘enp1s0’, might be ‘eth0’ for your system), and created a bridge named ‘br0’ that has the same properties.

network:
  version: 2
  renderer: networkd

  ethernets:
    enp1s0:
      dhcp4: false 
      dhcp6: false 
      #addresses: [192.168.1.239/24]
      #gateway4: 192.168.1.1
      #mtu: 1500
      #nameservers:
      #  addresses: [8.8.8.8]

  bridges:
    br0:
      interfaces: [enp1s0]
      addresses: [192.168.1.239/24]
      gateway4: 192.168.2.1
      mtu: 1500
      nameservers:
        addresses: [8.8.8.8]
      parameters:
        stp: true
        forward-delay: 4
      dhcp4: no
      dhcp6: no

Then apply the new netplan with bridged network with the commands below.  But be sure you have physical access to the host in case network connectivity needs to be investigated.

sudo netplan generate 
sudo netplan --debug apply

You may temporarily lost network connectivity if you are connected over ssh after running the apply command.  So be sure you have physical access to the console.

You can see the network entities at the OS level by using these commands:

# bridge control
brctl show

# network control
networkctl
networkctl status br0

# ip list
ip a show br0
# show host routes
ip route

# show arp table (IP to MAC)
arp -n

Configure libvirt network to use existing bridge

Now with the network bridge created at the OS level, you can configure a libvirt network to use this bridge.  Create a file named “host-bridge.xml” containing the following:

<network>
  <name>host-bridge</name>
  <forward mode="bridge"/>
  <bridge name="br0"/>
</network>

Then create the network using these commands:

# create libvirt network using existing host bridge
virsh net-define host-bridge.xml
virsh net-start host-bridge
virsh net-autostart host-bridge

# state should be active, autostart, and persistent
virsh net-list --all

VM assigned to this network will be treated just like any other peer host.  For example, if your upstream gateway router is DHCP enabled, new VM will be assigned an IP from that pool if the guest OS is configured to use DHCP.  Likewise, the guest OS can be configured to use a static IP from the same subnet as the host bridge.

KVM Guest OS

The last step is to configure a KVM Guest to use this network bridge.

From virt-manager, select the network source ‘Virtual network host-bridge: Bridge network’ as shown above.  Or if you ‘virsh edit’ the KVM guest, then set the <interface type=’network’> and <source network=’host-bridge’/> using similar values to below.

<interface type='network'>
    <source network='host-bridge'/>
    <model type='virtio'/>
    <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>

Note that this only assigns the interface at the firmware level.  You still have to ensure proper configuration at the operating system level in order to use DHCP or static IP.

Read my article on cloning Ubuntu guest VMs for tips, configuring older Ubuntu versions is well understood at this point but newer Ubuntu versions that use systemd for networking do not use the MAC address as the DHCP id which can lead to issues when cloning.

 

REFERENCES

libvirt docs, Use an existing host bridge

KVM networking, creating NAT and host-only networks using virsh

KVM networking, bridged

libvirt wiki, VirtualNetworking docs

StackExchange, create libvirt network from bridged network defined in netplan

Bridged networking on KVM, Ubuntu bionic

Netplan reference, with explanation of params for bridging (stp, forward-delay)

Create private bridged network using virsh net-create

NAT bridge with DCHP and also set of static IP

libvirt xml definition for network section

libvirt, use nat, routed, isolated, pre-existing host bridge, macvtap

jamielinux, define dhcp bridge with static IP

jamielinux, bridged network for public IP

linuxtechi, Linux bridge for KVM on Bionic

thegeekstuff, brctl commands

jamielinux, bridge in /etc/networking/interfaces

jamielinux, virtual bridge with brctl, need to forward packets on host

RHEL turn off nework manager, create bridge

changing machine-id to ensure unique DHCP lease on Ubuntu with systemd

systemd uses different method to generate DUID, which is why we must use machine-id

built in networking of Ubuntu 18.04 bionic no long uses NIC MAC address as default id for DHCP requests, instead uses machine-id, can use ‘dhcp-identifier’ in netplan

docs, Netplan yaml confirmation [1,2]

KVM vs QEMU vs Libvirt, how they fit together

Ubuntu 18 installer type determines NetPlan filename, 01-netcfg.yaml or 50-cloud-init.yaml (server=01,live-server=50-cloud-init)

NOTES

Add/remove IP address from network interface

ip a
sudo ip addr add 192.168.2.238/24 dev enp0s3
ip a
sudo ip addr del 192.168.2.238/24 dev enp0s3
ip a

Default location for libvirt qemu network definitions, /etc/libvirt/qemu/networks

restart network

virsh net-destroy default && virsh net-start default

show IP/MAC of bridge

arp -n | grep virbr0

DHCP status

virsh net-dhcp-leases –network default

dhcp conf file, cat /var/lib/libvirt/dnsmasq/<networkName>.conf

dhcp leases, cat /var/lib/libvirt/dnsmasq/<networkName>.hostsfile

delete network

virsh net-autostart networkname --disablevirsh net-delete networknamevirsh net-undefine networkname

MAC address uniqueness not enough to ensure unique IP lease on Ubuntu

# remove old ids
sudo rm /etc/machine-id /var/lib/dbus/machine-id
# recreate new ids
sudo dbus-uuidgen --ensure && cp /var/lib/dbus/machine-id /etc/.

OS level bridge could also be used directly without needing libvirt network definition

<interface type='bridge'>
  <mac address='52:54:00:2d:0d:fb'/>
  <source bridge='br0'/>
  <model type='virtio'/>
  <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>