Set up the client device
You’ll need to add network booting to the boot order of your Raspberry Pi. An easy way to do this on Pi 5s is to use the Raspberry Pi Imager’s “Network Boot” image which’ll handle all the eeprom configuration for you.
You’ll only need to do this once unless you remove network booting from your boot order in the future.
Raspberry Pi network boot flow
DHCP
When the Pi attempts to network boot it will send a DHCP discover request that looks like this:
Frame 6044: 364 bytes on wire (2912 bits), 364 bytes captured (2912 bits) on interface sshdump, id 0
Ethernet II, Src: f6:92:bf:91:13:a6 (f6:92:bf:91:13:a6), Dst: RaspberryPiF_64:46:1c (b8:27:eb:64:46:1c)
Internet Protocol Version 4, Src: 10.0.0.1, Dst: 10.0.0.75
User Datagram Protocol, Src Port: 67, Dst Port: 67
Dynamic Host Configuration Protocol (Discover)
Message type: Boot Request (1)
Hardware type: Ethernet (0x01)
Hardware address length: 6
Hops: 1
Transaction ID: 0x0a485863
Seconds elapsed: 0
Bootp flags: 0x0000 (Unicast)
0... .... .... .... = Broadcast flag: Unicast
.000 0000 0000 0000 = Reserved flags: 0x0000
Client IP address: 0.0.0.0
Your (client) IP address: 0.0.0.0
Next server IP address: 0.0.0.0
Relay agent IP address: 10.0.0.1
Client MAC address: RaspberryPiT_e8:27:1e (d8:3a:dd:e8:27:1e)
Client hardware address padding: 00000000000000000000
Server host name not given
Boot file name not given
Magic cookie: DHCP
Option: (53) DHCP Message Type (Discover)
Length: 1
DHCP: Discover (1)
Option: (55) Parameter Request List
Length: 14
Parameter Request List Item: (1) Subnet Mask
Parameter Request List Item: (3) Router
Parameter Request List Item: (43) Vendor-Specific Information
Parameter Request List Item: (60) Vendor class identifier
Parameter Request List Item: (66) TFTP Server Name
Parameter Request List Item: (67) Bootfile name
Parameter Request List Item: (128) DOCSIS full security server IP [TODO]
Parameter Request List Item: (129) PXE - undefined (vendor specific)
Parameter Request List Item: (130) PXE - undefined (vendor specific)
Parameter Request List Item: (131) PXE - undefined (vendor specific)
Parameter Request List Item: (132) PXE - undefined (vendor specific)
Parameter Request List Item: (133) PXE - undefined (vendor specific)
Parameter Request List Item: (134) PXE - undefined (vendor specific)
Parameter Request List Item: (135) PXE - undefined (vendor specific)
Option: (60) Vendor class identifier
Length: 32
Vendor class identifier: PXEClient:Arch:00000:UNDI:002001
Option: (93) Client System Architecture
Length: 2
Client System Architecture: IA x86 PC (0)
Option: (94) Client Network Device Interface
Length: 3
Major Version: 2
Minor Version: 1
Option: (97) UUID/GUID-based Client Identifier
Length: 17
Client Identifier (UUID): 52506935-4170-00d0-dde8-271e64f2093a
Option: (255) End
Option End: 255
To get it to netboot you’ll need to respond with two particular DHCP options set:
- Option 44 must contain “Raspberry Pi Boot”
- Option 66 must contain the IP address of the TFTP server.
It’s worth noting here that, unlike many other clients, Raspberry Pis ignore the next server and boot file fields in the response.
Here’s an example response from my dhcpd
server:
Frame 6048: 342 bytes on wire (2736 bits), 342 bytes captured (2736 bits) on interface sshdump, id 0
Ethernet II, Src: RaspberryPiF_64:46:1c (b8:27:eb:64:46:1c), Dst: RaspberryPiT_e8:27:1e (d8:3a:dd:e8:27:1e)
Internet Protocol Version 4, Src: 10.0.0.75, Dst: 10.0.0.240
User Datagram Protocol, Src Port: 67, Dst Port: 68
Dynamic Host Configuration Protocol (Offer)
Message type: Boot Reply (2)
Hardware type: Ethernet (0x01)
Hardware address length: 6
Hops: 0
Transaction ID: 0x0a485863
Seconds elapsed: 0
Bootp flags: 0x0000 (Unicast)
0... .... .... .... = Broadcast flag: Unicast
.000 0000 0000 0000 = Reserved flags: 0x0000
Client IP address: 0.0.0.0
Your (client) IP address: 10.0.0.240
Next server IP address: 0.0.0.0
Relay agent IP address: 0.0.0.0
Client MAC address: RaspberryPiT_e8:27:1e (d8:3a:dd:e8:27:1e)
Client hardware address padding: 00000000000000000000
Server host name not given
Boot file name: bootcode.bin
Magic cookie: DHCP
Option: (53) DHCP Message Type (Offer)
Length: 1
DHCP: Offer (2)
Option: (54) DHCP Server Identifier (10.0.0.75)
Length: 4
DHCP Server Identifier: 10.0.0.75
Option: (51) IP Address Lease Time
Length: 4
IP Address Lease Time: 1 day (86400)
Option: (1) Subnet Mask (255.255.255.0)
Length: 4
Subnet Mask: 255.255.255.0
Option: (3) Router
Length: 4
Router: 10.0.0.1
Option: (43) Vendor-Specific Information
Length: 17
Value: 52617370626572727920506920426f6f74
Option: (66) TFTP Server Name
Length: 9
TFTP Server Name: 10.0.0.75
Option: (255) End
Option End: 255
Padding: 0000
TFTP
Now that the Pi knows which TFTP server to talk to it’ll try to fetch the files it needs to boot.
First it’ll look for config.txt
in a subdirectory based on the Pi’s serial number, for example: 3a09f264/config.txt
.
If that file isn’t present it’ll look for config.txt
in the root directory, for example: config.txt
.
From there it will request different files depending on the Pi model and the configuration in config.txt
:
Here’s an example of the TFTP requests from a Pi 5:
TFTP Read Request, File: 3a09f264/config.txt
TFTP Read Request, File: config.txt
TFTP Read Request, File: pieeprom.sig
TFTP Read Request, File: bcm2712-rpi-5-b.dtb
TFTP Read Request, File: /config.txt
TFTP Read Request, File: /config.txt
TFTP Read Request, File: kernel_2712.img
TFTP Read Request, File: bcm2712-rpi-5-b.dtb
TFTP Read Request, File: /overlays/overlay_map.dtb
TFTP Read Request, File: /config.txt
TFTP Read Request, File: /overlays/dwc2.dtbo
TFTP Read Request, File: /cmdline.txt
TFTP Read Request, File: armstub8-2712.bin
TFTP Read Request, File: kernel_2712.img
Configure dhcpd
On my network I still use dhcpd
to handle my IPv4 devices .
Here’s an minimal example of a dhcpd.conf
configuration that’ll netboot Raspberry Pis as well as handling other PXE clients.
# Declare the PXE client class used to detect client architecture.
option pxe-system-type code 93 = unsigned integer 16;
# Declare the vendor-specific option for Raspberry Pis.
option raspberry-pi-boot code 43 = text;
subnet 10.0.0.0 netmask 255.255.255.0 {
option routers 10.0.0.1;
# Put the IP of your TFTP server here.
option tftp-server-name "10.0.0.75";
pool {
range 10.0.0.200 10.0.0.254;
allow unknown-clients;
}
# Class for PXE clients
class "pxeclients" {
# Match PXE client devices
match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
# If the first three bytes are in a block of Raspberry Pi MACs then set the Pi-specific option
if substring (hardware, 1, 3) = d8:3a:dd {
option raspberry-pi-boot "Raspberry Pi Boot";
} else if option pxe-system-type = 00:07 {
# This assumes your tftp server has a boot subdirectory with Grub's EFI files in it.
filename "boot/grub/x86_64-efi/core.efi";
} else if option pxe-system-type = 00:08 {
filename "boot/grub/x86_64-efi/core.efi";
} else if option pxe-system-type = 00:09 {
filename "boot/grub/x86_64-efi/core.efi";
} else if option pxe-system-type = 00:0a {
filename "boot/grub/arm-efi/core.efi";
} else if option pxe-system-type = 00:0b {
filename "boot/grub/aarch64-efi/core.efi";
} else {
# Fall back to assuming an X86 device with only BIOS boot support
filename "boot/grub/x86_64-efi/core.0";
}
}
}