Skip to main content

Setting up UEFI HTTP boot with libvirt

Overcoming the limitations of TFTP when imaging by switching to UEFI and HTTP management
Image
UEFI HTTP Boot with Libvirt

Photo by Ben Breard

I've been a big proponent of network-based provisioning pretty much my entire career. My second job out of college involved imaging ~800 computers multiple times a week. When I was hired, my predecessors used floppy disks to load a small operating system (OS), matching network interface card (NIC) driver, and imaging client (remember Ghost?). The bottom line was it was very time/labor-intensive and a horrible process. Imaging a group of systems took about 30-60 minutes. Long story short, we reduced that time to about five minutes after leveraging a combination of PXE, Wake-on-LAN, Universal Network Device Interface (UNDI) drivers, virtual LANs (VLANs), and IGMP snooping. My second iteration of the solution took the total attended time to less than 30 seconds. It's an amazing technology for provisioning and I even got hired at Red Hat by giving a presentation on the preboot execution environment (PXE). Needless to say, I'm a huge fan.

[ You might also enjoy: Creating a multi-boot Linux desktop system ]

Anyway, the problem here is PXE dates back to the 90s and is quite limited by its reliance on technologies such as trivial file transfer protocol (TFTP). Intel has been threatening to deprecate PXE for years now, and they are finally doing it. Even though PXE is pervasive today, it's likely that over the next 2-10 years, UEFI HTTP boot will become the default for most environments. Other benefits and technical details are outlined here. The TL;DR is PXE that relies on DHCP and TFTP, and Unified Extensible Firmware Interface (UEFI) HTTP boot needs DHCP and HTTP. Sound easy? That's because it is. You might even say it's as Trivial as File Transfer Protocols get. Sorry—network booting humor is pretty difficult to come by.

Switching from PXE to HTTP boot

The best documentation I can find on this topic currently is given below in the References section. Essentially, the need for TFTP is dropped. Next, edit the DHCP options to fetch the NBP from an HTTP/HTTPS endpoint. I use GRUB as the NBP in this article. Environments using dhcpd can simply drop-in the configuration they provide, and everything should just work assuming you alter the filename to point to the correct NBP for your environment:

  class "pxeclients" {
     match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
     next-server 192.168.111.1;
     filename "/bootx64.efi";
   }
   class "httpclients" {
     match if substring (option vendor-class-identifier, 0, 10) = "HTTPClient";
     option vendor-class-identifier "HTTPClient";
     filename "http://www.httpboot.local/sle/EFI/BOOT/bootx64.efi";
   }

This example is nice as it allows UEFI systems with a legacy PXE ROM also to boot.

Apply this to libvirt

I had originally planned to set this up on my home network, but pfSense doesn't yet support this, nor does it allow the dhcpd configuration to be modified directly. I've opened an issue, and hopefully, this will be added in the future. The next simplest option I have for testing is using libvirt and virtual machines (VMs) with the Open Virtual Machine Firmware (OVMF) (Tianocore) UEFI firmware. Also, libvirt works great for creating a self-contained environment for experimenting with technology like this and it's super easy to replicate. All you really need to do is provide the correct DHCP options.

The default libvirt network will look something similar to this:

<network>
 <name>default</name>
 <uuid>75f4d9cd-9af2-4df5-afcc-f8f9145f7e34</uuid>
 <forward mode='nat'/>
 <bridge name='virbr0' zone='trusted' stp='on' delay='0'/>
 <mac address='52:54:00:95:95:84'/>
 <ip address='192.168.122.1' netmask='255.255.255.0'>
   <dhcp>
     <range start='192.168.100.128' end='192.168.100.254' />
   </dhcp>
 </ip>
</network>

Enabling legacy PXE is well documented, but I’m including the changes here in case it’s helpful for anyone reading. You need to define the next-server and filename DHCP options.

<network>
 <name>default</name>
 <uuid>75f4d9cd-9af2-4df5-afcc-f8f9145f7e34</uuid>
 <forward mode='nat'/>
 <bridge name='virbr0' zone='trusted' stp='on' delay='0'/>
 <mac address='52:54:00:95:95:84'/>
 <ip address='192.168.122.1' netmask='255.255.255.0'>
 <tftp root='/var/lib/tftpboot'/>
   <dhcp>
     <range start='192.168.100.128' end='192.168.100.254' />
     <bootp file='pxelinux.0'/>
   </dhcp>
 </ip>
</network>

Unfortunately, the libvirt schema doesn't support a lot of available configuration options for dnsmasq. Luckily, recent versions of libvirt support an XML namespace that will append options directly to the bottom of the generated configuration file. This email shows the only working dnsmasq configuration that I was able to find, as HTTP boot is not well documented for the project. For now, I'm leaving the TFTP information in place so VMs using BIOS can continue to do network installs.

Note: Don’t ignore the first line of this example.

<network xmlns:dnsmasq='http://libvirt.org/schemas/network/dnsmasq/1.0'>
 <name>default</name>
 <uuid>75f4d9cd-9af2-4df5-afcc-f8f9145f7e34</uuid>
 <forward mode='nat'/>
 <bridge name='virbr0' zone='trusted' stp='on' delay='0'/>
 <mac address='52:54:00:95:95:84'/>
 <ip address='192.168.122.1' netmask='255.255.255.0'>
 <tftp root='/var/lib/tftpboot'/>
   <dhcp>
     <range start='192.168.100.128' end='192.168.100.254' />
     <bootp file='pxelinux.0'/>
   </dhcp>
 </ip>
 <dnsmasq:options>
   <dnsmasq:option value='dhcp-vendorclass=set:efi-http,HTTPClient:Arch:00016'/>
   <dnsmasq:option value='dhcp-option-force=tag:efi-http,60,HTTPClient'/>
   <dnsmasq:option value='dhcp-boot=tag:efi-http,&quot;http://192.168.122.1/rhel8/EFI/BOOT/BOOTX64.EFI&quot;'/>
 </dnsmasq:options>
</network>

With the network settings in place, a simple sudo virsh net-destroy default && sudo virsh net-start default will load the new configuration. Next, you need a web server. I'm running httpd on my system. As you can see, it's defined in the above example as 192.168.122.1. One of the major benefits of this setup is the web endpoint can be anywhere, so please use whatever makes sense for your environment. For reasons I don't understand, Silverblue includes httpd; all I needed to do on my system was run systemctl start httpd.

Setting up the boot menu

Since I'm working with the Red Hat Enterprise Linux (RHEL) 8.4 Beta, all that's necessary is to download the minimal boot.iso, mount it, and copy the contents to /var/www/html/rhel8/. Don't mount the ISO directly in this location as you need to modify the GRUB configuration and write the file. Next, you need to tweak the kernel and initrd paths in the default GRUB configuration found under /var/www/html/rhel8/EFI/BOOT/grub.cfg as relative paths won't work. For my setup, it's necessary to add /rhel8 on each line. Pass any other option needed, and you're good to go.

menuentry 'Install Red Hat Enterprise Linux 8.4' --class fedora --class gnu-linux --class gnu --class os {
     linuxefi /rhel8/images/pxeboot/vmlinuz inst.stage2=http://192.168.122.1/rhel8 inst.ks=http://192.168.122.1/ks/84.ks quiet
     initrdefi /rhel8/images/pxeboot/initrd.img
}

Time to boot

Now you're ready to create and boot a VM, which leads to the annoying part. By default, a network boot with OVMF will attempt an IPv4 PXE -> IPv6 PXE -> IPv4 HTTP -> IPv6 HTTP in that order. It takes a long time to let them fail, so you'll want to disrupt the standard boot process by quickly pressing the Escape key repeatedly once the VM console comes up to select the IPv4 HTTP manually. In the screencast linked below, I'm pressing the Escape key quickly to select the correct option. This is less than ideal, and I'm sure there's a better way to configure the firmware to disable the legacy option(s). Please let me know if you know how to do this.

sudo virt-install \
  --name=8.4-uefi-httpboot \
  --ram=2048 \
  --vcpus=1 \
  --os-type=linux \
  --os-variant=rhel8.4 \
  --graphics=vnc \
  --pxe \
  --disk=path=/var/home/bbreard/data/distros/uefi.qcow2 \
  --check path_in_use=off \
  --network=network=default,model=virtio \
  --boot=uefi

[ Free course: Red Hat Satellite Technical Overview. ] 

Wrap up

If you go to my site, you can watch a screencast of the process. It's quick and simple. And, that's all there is to it. I hope you'll join me in a world that's free from the limitations of TFTP.

References:

Topics:   Linux   Linux administration   Hardware   Virtualization  
Author’s photo

Ben Breard

Ben Breard is a principal product manager for Red Hat Enterprise Linux and OpenShift and focuses on immutable operating systems and edge computing. As a true believer in open source, he also enjoys evangelizing containers, systemd, kubernetes, and Linux. More about me

Try Red Hat Enterprise Linux

Download it at no charge from the Red Hat Developer program.