iPXE boot for FreeBSD with an UEFI BIOS
Mark Elvers
2 min read

Categories

  • FreeBSD,UEFI,iPXE

Tags

  • tunbury.org

I had assumed that booting FreeBSD over the network using iPXE would be pretty simple. There is even a freebsd.ipxe file included with Netboot.xyz. However, I quickly realised that most of the Internet wisdom on this process centred around legacy BIOS rather than UEFI. When booting with UEFI, the Netboot.xyz menu omits the FreeBSD option as it only supports legacy BIOS. Even in legacy mode, it uses memdisk from the Syslinux project rather than a FreeBSD loader.

FreeBSD expects to use loader.efi to boot and to mount the root directory over NFS based upon the DHCP scope option root-path. I didn’t want to provide an NFS server just for this process, but even when I gave in and set one up, it still didn’t work. I’m pleased that, in the final configuration, I didn’t need an NFS server.

Much of the frustration around doing this came from setting the root-path option. FreeBSD’s loader.efi sends its own DHCP request to the DHCP server, ignoring the options set root-path or set dhcp.root-path configured in iPXE.

Many dhcpd.conf snippets suggest a block similar to below, but usually with the comment that it doesn’t work. Most authors proceed by setting root-path for the entire scope.

if exists user-class and option user-class = "FreeBSD" {
    option root-path "your-path";
}

I used dhcpdump -i br0 to examine the DHCP packets. This showed an ASCII BEL character (0x07) before FreeBSD in the user-class string.

  TIME: 2025-05-07 08:51:03.811
    IP: 0.0.0.0 (2:0:0:0:0:22) > 255.255.255.255 (ff:ff:ff:ff:ff:ff)
    OP: 1 (BOOTPREQUEST)
 HTYPE: 1 (Ethernet)
  HLEN: 6
  HOPS: 0
   XID: 00000001
  SECS: 0
 FLAGS: 0
CIADDR: 0.0.0.0
YIADDR: 0.0.0.0
SIADDR: 0.0.0.0
GIADDR: 0.0.0.0
CHADDR: 02:00:00:00:00:22:00:00:00:00:00:00:00:00:00:00
 SNAME: .
 FNAME: .
OPTION:  53 (  1) DHCP message type         3 (DHCPREQUEST)
OPTION:  50 (  4) Request IP address        x.y.z.250
OPTION:  54 (  4) Server identifier         x.y.z.1
OPTION:  51 (  4) IP address leasetime      300 (5m)
OPTION:  60 (  9) Vendor class identifier   PXEClient
OPTION:  77 (  8) User-class Identification 0746726565425344 .FreeBSD
OPTION:  55 (  7) Parameter Request List     17 (Root path)
					     12 (Host name)
					     16 (Swap server)
					      3 (Routers)
					      1 (Subnet mask)
					     26 (Interface MTU)
					     54 (Server identifier)

There is a substring command, so I was able to set the root-path like this successfully:

if exists user-class and substring ( option user-class, 1, 7 ) = "FreeBSD" {
    option root-path "your-path";
}

The situation is further complicated as we are using a Ubiquiti Edge router. This requires the command to be encoded as a subnet-parameters, which is injected into /opt/vyatta/etc/dhcpd.conf.

set service dhcp-server shared-network-name lab subnet x.y.z.0/24 subnet-parameters 'if exists user-class and substring( option user-class, 1, 7 ) = "FreeBSD" { option root-path "tftp://x.y.z.240/freebsd14";}'

The FreeBSD 14.2 installation ISO contains the required boot/loader.efi, but we cannot use the extracted ISO as a root file system.

Stage loader.efi on a TFTP server; in my case, the TFTP root is /netbootxyz/config/menus. The IPXE file only needs to contain the chain command.

#!ipxe
chain loader.efi

Download mfsBSD, and extract the contents to a subfolder on the TFTP server. I went freebsd14. This ISO contains the kernel, loader.conf and the a minimal root file system, mfsroot.gz.

With the content of mfsBSD ISO staged on the TFTP server and the modification to the DHCP scope options, the machine will boot into FreeBSD. Sign in with root/mfsroot and invoke bsdinstall.

On real hardware, rather than QEMU, I found that I needed to explicitly set the serial console by adding these lines to the end of boot/loader.conf/

# Serial console
console="comconsole"
comconsole_port="0x2f8"
comconsole_speed="115200"