Remote booting of a device usually works in the following steps.
- Client device boots up and requests an IP address from the DHCP server in the local network. This is done by means of DHCPDISCOVER and DHCPREQUEST messages that the client sends out. Mostly via broadcast messages.
- DHCP server answers to the request with DHCPOFFER / DHCP ACK messages. Along with that comes the IP address that the DHCP server is assigning to the requesting client. Plus additional information such as the address of the TFTP server and the file to load. This is stored in options as part of the DHCP messages. The options are: NEXTSERVER and OPTION_66 for the TFTP server address, BOOTFILE for the file to load. Sometimes other options are needed but that depends on the client.
- Once the client has booted so far (from ROM) that he needs the actual boot file, he is contacting the TFTP server. The address of the TFTP server has been given to the client as part of Step 2. The TFTP server is requested to send the file specified as the boot file (again: see Step 2). This transfers one file from the server to the client.
- The boot file may not be the only file needed on the client side to finish booting. What usually happens is once the boot file is on the client side, the ROM based first boot phase is finished and the operating system included in the boot file is executed. Now a different OS than the one acting from ROM is starting and a different TCP/IP stack is also running. Therefore the client starts again with step 1 – requesting an IP address …
- More files may be fetched from the TFTP server based on whatever the boot file OS may be doing now.
The following INI file is the setup for a pxe boot environment for pxelinux. It assumes 192.168.0.2 as the server IP address.
[Settings] Trace=1 IgnoreUnknownClients=1 ConfigureUnknownClients=1 AssociateBindsToPools=1 IPBIND_2=192.168.0.2 IPPOOL_2=192.168.0.3-134 [General] LEASETIME=86400 ; lease time of 1 day NEXTSERVER=192.168.0.2 ; tftp server (clients look in option 66 and/or in NEXTSERVER field) OPTION_66=192.168.0.2 ; tftp server BOOTFILE=pxelinux.0 SUBNETMASK=255.255.255.0 DOMAINNAME=mydomain.local DNS_1=192.168.0.2 [TFTP-Settings] ; settings for the integrated tftp server EnableTFTP=1 Root=D:\tftproot\ [DNS-Settings] ; settings for the integrated dns server EnableDNS=1
This configuration works under the assumption that a server side path d:\tftproot\ exists with the needed pxelinux files stored, i.e. pxelinux.0. The integrated DNS is not really needed for a pxe boot environment to work. Please note that the integrated TFTP server (new in V1.9) is configured. If you are already using another TFTP server and want to continue to do so, then please set EnableTFTP=0 and make sure that the external TFTP server is setup accordingly.
The same works with Windows 7 and Windows PE boot. If you for example set up a Windows 7 network boot as it is described in the german computer magazin c’t 5/2011 page 176ff, then the following INI file will do the job:
[SETTINGS] IPPOOL_1=192.168.0.3-254 IPBIND_1=192.168.0.2 AssociateBindsToPools=1 [GENERAL] LEASETIME=86400 NODETYPE=8 SUBNETMASK=255.255.255.0 BOOTFILE=wdsnbp.0 NEXTSERVER=192.168.0.2 [TFTP-SETTINGS] EnableTFTP=1 ROOT=D:\pxe
PXE booting UEFI client
The PXE booting of a UEFI client follows the same principle 5 steps as above. The UEFI PXE specification however makes it mandatory to have more INI file settings as described in the previous example. Here is an example INI file for UEFI PXE specification:
[SETTINGS]
IPPOOL_1=192.168.0.3-254
IPBIND_1=192.168.0.2
AssociateBindsToPools=1
IPSCOPE_1=if(OPTION_60~="PXEClient*", "PXEClient", "General") ;
; use [PXEClient] section if OPTION_60 starts with "PXEClient"
[PXEClient]
OPTION_60="PXEClient"
OPTION_97=@97 ; copy of option 97 from request
OPTION_61=@97 ; copy of option 97 from request
OPTION_43=06 (0A) 08 (00 00 01 192.168.0.2) 09 (00 00 ("Server")) 0A (0F "Select") FF ;
; PXE_DISCOVERY_CONTROL: 06, set this to 0A to not prompt
; PXE_BOOT_SERVERS: 08, set IP address of server
; PXE_BOOT_MENU: 09, set boot menu
; PXE_MENU_PROMPT: 0A, set prompt
OPTION_66="192.168.0.2"
BOOTFILE="BOOTX64.EFI"
[GENERAL]
LEASETIME=86400
NODETYPE=8
SUBNETMASK=255.255.255.0
NEXTSERVER=192.168.0.2
[TFTP-SETTINGS]
EnableTFTP=1
ROOT=D:\pxe
Booting BIOS and UEFI clients from the same server
The example above assumes that every PXE client is a UEFI client and gets the same boot file. In a mixed network this does not work, because a legacy BIOS client cannot execute an EFI boot file and a UEFI client cannot execute a BIOS boot file. Both client types announce their processor architecture in the vendor class (option 60), so IPSCOPE_n can be used to send each of them to a different scope section.
Processor architecture numbers
The client sends the architecture as a five digit decimal number as part of its vendor class, for example PXEClient:Arch:00007:UNDI:003000. These are the values in practical use:
| PXEClient:Arch:00000 | Legacy BIOS (x86) | pxelinux.0, undionly.kpxe |
| PXEClient:Arch:00006 | UEFI IA32 (32 bit) | bootia32.efi, ipxe-i386.efi |
| PXEClient:Arch:00007 | UEFI x64 | bootx64.efi, ipxe-x86_64.efi |
| PXEClient:Arch:00009 | UEFI x64 (alternative) | bootx64.efi, ipxe-x86_64.efi |
| PXEClient:Arch:00011 | UEFI ARM64 | bootaa64.efi, ipxe-arm64.efi |
Most x64 UEFI firmware reports 00007, a few report 00009. Both mean the same thing in practice, so a configuration for 64 bit UEFI should accept both.
Example INI file
The following INI file serves legacy BIOS clients, 32 bit UEFI clients and 64 bit UEFI clients. It also covers iPXE chainloading, where the client first boots a small iPXE image over TFTP and then comes back with a user class of “iPXE” to fetch its boot script over HTTP. It assumes 192.168.0.2 as the server IP address.
[SETTINGS] IPPOOL_1=192.168.0.3-254 IPBIND_1=192.168.0.2 AssociateBindsToPools=1 IPSCOPE_1=if(userclass ~~ "iPXE*", "iPXE", if(vendorclass ~~ "PXEClient:Arch:00007*" || vendorclass ~~ "PXEClient:Arch:00009*", "PXE-UEFI-X64", if(vendorclass ~~ "PXEClient:Arch:00006*", "PXE-UEFI-IA32", if(vendorclass ~~ "PXEClient:Arch:00000*", "PXE-BIOS", "General")))) ; [GENERAL] LEASETIME=86400 NODETYPE=8 SUBNETMASK=255.255.255.0 NEXTSERVER=$(IPBIND_1) OPTION_66=$(IPBIND_1) [PXE-BIOS] BOOTFILE=undionly.kpxe OPTION_60="PXEClient" OPTION_97=@97 OPTION_43=06 (0A) FF [PXE-UEFI-X64] BOOTFILE=ipxe-x86_64.efi OPTION_60="PXEClient" OPTION_97=@97 OPTION_43=06 (0A) FF [PXE-UEFI-IA32] BOOTFILE=ipxe-i386.efi OPTION_60="PXEClient" OPTION_97=@97 OPTION_43=06 (0A) FF [iPXE] BOOTFILE=http://$(IPBIND_1)/autoexec.ipxe [TFTP-SETTINGS] EnableTFTP=1 ROOT=D:\pxe [HTTP-SETTINGS] EnableHTTP=1 ROOT=D:\pxe
The scope sections only contain what differs between the client types. Everything the clients have in common stays in [GENERAL], where every client picks it up. $(IPBIND_1) is replaced by the value of IPBIND_1, so the server address has to be written only once.
If you do not use iPXE, drop the [iPXE] section, the [HTTP-SETTINGS] section and the first if() test, and set BOOTFILE in the PXE sections to the boot loader you want to load directly, for example bootx64.efi for UEFI x64 and pxelinux.0 for BIOS.
Details that are easy to miss
- The whole IPSCOPE_1 expression has to be on one single line. A setting is not continued on the next line. This is the most frequent mistake when a configuration is copied out of an e-mail or a chat window, because those tend to wrap long lines.
- The last if() returns “General” and not null. A scope that results to null does not match at all, so a client that is not booting over the network would end up without any address. Returning the name of a regular section instead makes those clients fall back to a normal lease.
- OPTION_60, OPTION_97 and OPTION_43 are needed in every PXE scope section. A UEFI client checks that the answer really comes from a PXE server before it accepts the boot file. Without these options most UEFI firmware silently ignores the offer and runs into a timeout.
- OPTION_43 belongs into the PXE scope sections only, not into the [iPXE] section. Once iPXE is running, boot server discovery is over and the option can send the client back into a discovery loop.
- The order of the tests matters. The user class is checked first, so that a client which has already been chainloaded into iPXE is not sent the iPXE image a second time, which would result in an endless boot loop.
If a client falls through all tests and ends up in the fallback section, it receives an address but no boot file. On the client side this usually shows up as a PXE timeout. Switching on Trace=1 shows the vendor class the client actually sent, which is the fastest way to find out which architecture number is missing from the configuration.