How to configure a Raspberry Pi as a PXE boot server

PXE (Preboot eXecution Environment) is a client-server environment which makes possible to boot and install operating systems without the need of physical media. The core idea is quite simple: in a very early stage, a client gets an IP address from a DHCP server and downloads the files needed to perform the boot process via the tftp protocol (Trivial ftp). In this tutorial we will use the dnsmasq application: it can be used as a primary DHCP server or in proxy DHCP mode if another DHCP server exists in the network; it also provides the tftp service used to transfer files.

In this tutorial you will learn:

  • How to configure pxelinux and create a boot menu
  • How to extract files from an ISO and setup the appropriate file structure
  • How to configure dnsmasq as a standard or proxy DHCP server
  • How to configure the tftp server embed in dnsmasq
  • How to allow traffic through the needed ports using ufw

Raspberry Pi as a PXE boot server

Raspberry Pi as a PXE boot server

Software requirements and conventions used

Software Requirements and Linux Command Line Conventions
Category Requirements, Conventions or Software Version Used
System Raspberry Pi OS (previously known as Raspbian)
Software dnsmasq, pxelinux, syslinux-efi
Other Root permissions
Conventions # – requires given linux-commands to be executed with root privileges either directly as a root user or by use of sudo command
$ – requires given linux-commands to be executed as a regular non-privileged user

Installing packages

The first thing we must do is to install some essential packages:

  • dnsmasq
  • pxelinux
  • syslinux-efi

Dnsmasq provides both the DHCP and the tftp services; pxelinux is a bootloader member of the syslinux family, and is specifically designed for the PXE environment; the syslinux-efi_ package contains libraries needed to support EFI clients. To install the packages on the Raspberry Pi OS, we can run:

$ sudo apt-get update && sudo apt-get install dnsmasq pxelinux syslinux-efi

File structure

Once the needed packages are installed, we can proceed and setup the file structure. For the sake of this tutorial, the root of the whole setup will be the /mnt/data/netboot directory, which will be also used as the tftp root (defined inside the dnsmasq configuration file); all the needed files will be stored inside it.

Syslinux files and modules

We want to be able to support the boot of clients in BIOS and EFI mode, therefore the first thing we need to do is to create two directories named after those architectures inside /mnt/data/netboot:

$ mkdir /mnt/data/netboot/{bios,efi64}


Each architecture needs some specific syslinux libraries to work. We copy them in the appropriate directories:

$ cp \
  /usr/lib/syslinux/modules/bios/{ldlinux,vesamenu,libcom32,libutil}.c32 \
  /usr/lib/PXELINUX/pxelinux.0 \
  /mnt/data/netboot/bios

$ cp \
  /usr/lib/syslinux/modules/efi64/ldlinux.e64 \
  /usr/lib/syslinux/modules/efi64/{vesamenu,libcom32,libutil}.c32 \
  /usr/lib/SYSLINUX.EFI/efi64/syslinux.efi \
  /mnt/data/netboot/efi64

Distribution files

At this point we need to create the directory that will host the distributions we want to make available in our boot menu. Let’s call it boot:

$ mkdir /mnt/data/netboot/boot

In this tutorial, just as an example, we will work with a Debian netinstall image. For convenience, I will suppose a previously verified ISO (take a look at our article about checking the integrity and the signature of a distribution image with gpg if you want to know how to verify the integrity and signature of a distribution image) to be available on the Rpi filesystem in the /mnt/data/isos directory.

We create the appropriate path inside /mnt/data/netboot/boot, naming directories after the architecture, name and version of the system we want to provide in our menu (in this case amd64 – Debian 10):

$ mkdir -p /mnt/data/netboot/boot/amd64/debian/10

This path choice is arbitrary, so feel free to create your own. At this point we must mount the distribution ISO and copy the files into the destination directory. To mount the ISO we run:

$ sudo mount -o loop -t iso9660 /mnt/data/isos/debian-10.4.0-amd64-netinst.iso /media

Once the ISO is mounted, its files will be accessible under /media. I like to use rsync to copy them:

$ sudo rsync -av /media/ /mnt/data/netboot/boot/amd64/debian/10

Once the files are copied, we can unmount the ISO:

$ sudo umount /media

In the next step we will see how to create a boot menu using the syslinux syntax.

Creating the boot menu

Now that we have the distribution files in place, we can create the boot menu. inside our tftp root, (/mnt/data/netboot in our case), we create the pxelinux.cfg directory:

$ mkdir /mnt/data/netboot/pxelinux.cfg

Inside the pxelinux.cfg directory we create a file called default and paste the following configuration inside it:

MENU TITLE  PXE Boot Menu
DEFAULT     vesamenu.c32

    LABEL local
        MENU LABEL Boot from local drive
        LOCALBOOT 0xffff

    MENU BEGIN amd64
    MENU TITLE amd64

        MENU BEGIN Debian
        MENU TITLE Debian

            LABEL installgui
                MENU LABEL ^Graphical install
                KERNEL ::boot/amd64/debian/10/install.amd/vmlinuz
                APPEND vga=788 initrd=::boot/amd64/debian/10/install.amd/gtk/initrd.gz --- quiet


            LABEL install
                MENU LABEL ^Install
                KERNEL ::boot/amd64/debian/10/install.amd/vmlinuz
                APPEND vga=788 initrd=::boot/amd64/debian/10/install.amd/initrd.gz --- quiet

            MENU END

    MENU END

The configuration above will generate a nested menu built following the directory path we created inside the boot directory. Again, the one above is just an example. You can create and structure the menu as you want; all you need to do is to use the appropriate syntax, as explained in the dedicated syslinux wiki page.

The menu contains an entry to let the user boot from the local hard drive, a submenu with the amd64 label, and two entries for the Debian distribution, installgui and install. The former launches the distribution installer in graphical mode, the latter in a textual mode which seems to use ncurses libraries.

How can we know the exact parameters to use in the KERNEL and APPEND lines? We can take a look at the menu configuration which exists inside the distribution content we extracted from the ISO. In our case, for example, /mnt/data/netboot/boot/amd64/debian/10/isolinux/menu.cfg. Unfortunately not all the distributions use the same syntax, therefore we must pay attention and adapt the configuration as needed.

One thing we had to adapt from the original configuration, is the path of the vmlinuz and initrd.gz files. Remember that we are accessing those files via tftp!

Normally, the files path are interpreted as relative to the tftp root directory, but in the configuration above, as you can observe, we used the :: syntax (for example we wrote ::boot/amd64/debian/10/install.amd/vmlinuz to reference the kernel image). Why we did this?

Since we created two directories which hold the libraries providing support for bios and efi64 mode and we want to use the same menu configuration for both, we need to link the pxelinux.cfg directory in both of them, therefore we need to reference the tftp root in an “absolute” way. The :: symbol allows us to do exactly this: it is a way to reference the absolute path to the tftp root.

Supposing our current working directory is /mnt/data/netboot, to link the menu configuration in the directories mentioned above, we can issue the following command:

$ ln -rs pxelinux.cfg bios && ln -rs pxelinux.cfg efi64


Here we used the -r option of the ln command to create relative symbolic links. At this point our directory tree should look like that:

/mnt/data/netboot
├── bios
│   ├── ldlinux.c32
│   ├── libcom32.c32
│   ├── libutil.c32
│   ├── pxelinux.0
│   ├── pxelinux.cfg -> ../pxelinux.cfg
│   └── vesamenu.c32
├── boot
│   └── amd64
│       └── debian
│           └── 10
├── efi64
│   ├── ldlinux.e64
│   ├── libcom32.c32
│   ├── libutil.c32
│   ├── pxelinux.cfg -> ../pxelinux.cfg
│   ├── syslinux.efi
│   └── vesamenu.c32
└── pxelinux.cfg
    └── default

We can now configure dnsmasq.

Configure dnsmasq

The dnsmasq configuration file is /etc/dnsmasq.conf. Some of the parameters that can be set inside of it are commented; more information about them can be found consulting the dnsmasq manual. We will only consider the ones necessary for our setup.

Disabling DNS functionality

The first thing we want to do is to disable the DNS service embedded in dnsmasq: we only need the DHCP and tftp functionalities offered by the application. To reach our goal we can use the port option: it is used to determine what port should be used for DNS; setting its value to 0 disables the service. We can append the instruction at the end of the configuration file.

port=0

Specify the network interface for DHCP requests

The second thing we want to do is to specify the network interface that will be used to listen for DHCP requests. In our case said interface is eth0, so we write:

interface=eth0

If we don’t want to use a specific interface, we can specify an IP address, using the listen-address option instead.

Specifying the IP range/proxy mode

This configuration step is very important and changes depending on our network configuration.

If the DHCP service provided by dnsmasq is the only one in the network, in this step we must simply configure the range of IP addresses that will be assigned to clients, and optionally a lease time for example:

dhcp-range=192.168.0.100,192.168.0.200,12h

In the line above, the range of available IP addresses is defined by separating the lower and higher boundaries by a comma. In this case we defined a range that goes from 192.168.0.100 to 192.168.200; we also set a lease time of 12h.

The second case is probably the most common in a standard/home setup, where usually the DHCP service is provided by a router. If this is the case, dnsmasq should be set to run in proxy mode in order to avoid conflicts. In those cases, we can write:

dhcp-range=192.168.0.0,proxy

We entered two elements separated by a comma: the first one is the address of the subnet (192.168.0.0), the second is the “proxy” keyword.

Enabling the tftp server

At this point we need to enable the dnsmasq embedded tftp server: we will use it to serve the files needed for the clients to boot. All we have to do to accomplish this task is to append the following line to the configuration file:

enable-tftp

We also must set the directory that should be used as the tftp root. This directory, as we already discussed, will host the shared files. In our case we this directory is /mnt/data/netboot (the default one is /var/ftpd):

tftp-root=/mnt/data/netboot

Set boot file based on the client architecture

The pxelinux bootloader is able to work both in EFI and BIOS mode, so we have to find a way to serve the appropriate file depending on the mode used by the client. The question is, how the client communicates such information?

DHCP uses a series of options for information exchange: option 93 (client-arch) is used to pass information about the client architecture. The table below displays the option numeric and string values and the architectures they reference:

Option value String value Architecture
0 x86PC Intel x86PC
1 PC98 NEC/PC98
2 IA64_EFI EFI Itanium
3 Alpha DEC Alpha
4 Arc_x86 Arc x86
5 Intel_Lean_Client Intel Lean Client
6 IA32_EFI EFI IA32
7 BC_EFI EFI BC
8 Xscale_EFI EFI Xscale
9 X86-64_EFI EFI x86-64

To specify what file should be provided for the appropriate mode used by the client we can use the pxe-service option. For x86PC we can enter the following line:

pxe-service=x86PC,"PXELINUX (BIOS)",bios/pxelinux


We provided three values separated by a comma to the option: the first one is the client system type (x86PC), the second is the menu text and the third is the file that will be downloaded by the client to perform the boot. The path of the file is relative to the tftp root. In this case it is found inside the bios directory we created before and is called pxelinux.0: the name must be reported without the .0 extension, as you can see above.

For the EFI x86-64 mode, instead, we add:

pxe-service=x86-64_EFI,"PXELINUX (EFI)",efi64/syslinux.efi

Setup logging

Another thing that is useful to enable is dnsmasq logging, in order to keep track of the DHCP and tftp activity. To accomplish this task, we add the log-queries instruction to our configuration, and set the file that should be used to store the messages with the log-facility instruction:

log-queries
log-facility=/var/log/dnsmasq.log

Save the configuration and service restart

At this point our configuration should look like this:

port=0
interface=eth0
dhcp-range=192.168.0.0,proxy
enable-tftp
tftp-root=/mnt/data/netboot
pxe-service=x86PC,"PXELINUX (BIOS)",bios/pxelinux
pxe-service=x86-64_EFI,"PXELINUX (EFI)",efi64/syslinux.efi
log-queries
log-facility=/var/log/dnsmasq.log

We can save the changes we made to the /etc/dnsmasq.conf file, and finally restart the dnsmasq service:

$ sudo systemctl restart dnsmasq

Firewall setup

In order for our setup to work correctly we must also allow incoming traffic through our firewall via some specific ports. In this tutorial I will assume the use of the ufw frontend. The ports we must allow incoming traffic through are:

  • 67/udp
  • 69/udp
  • 4011/udp

To allow the traffic we can run the following command:

$ sudo ufw allow 67/udp
$ sudo ufw allow 69/udp
$ sudo ufw allow 4011/udp

Booting

At this point, if the client machine is connected to the network via ethernet and the PXE boot option is chosen as boot “source” (make sure the functionality is enabled!), we should be able to see the PXE boot menu:

pxe_boot_menu

The PXE boot menu

Once we select amd64 -> Debian -> Graphical install the appropriate files will be downloaded and the Debian installer should appear:

debian-installer

Debian graphical installer

It’s now possible to proceed with the installation.

Conclusions

In this tutorial we saw how to perform the steps needed to turn a Raspberry Pi in a PXE boot server: we saw how to install and configure dnsmasq and the pxelinux bootloader; we also learned how to create a syslinux menu and the appropriate file structure; finally, we saw what ports to open for the setup to work. Doubts? Questions? Feel free to comment and ask for help!



Comments and Discussions
Linux Forum