iPXE PHP Scripts - Basic Setup

I've been lucky enough to have a chance to play around with iPXE. iPXE is a replacement PXE firmware intended for burning on to network cards in order to bring their functionality up to speed with more modern PXE firmwares. It can also be chain loaded from existing PXE firmwares.

Part of my job sees me deploying large numbers of servers at once, often with different OSes and build requirements. I've always been extremely lazy innovative, so I've been working on building iPXE scripts for all of the OSes I have been deploying, and booting the correct one for each server using a PHP script that checks the serial number of the server.

Of course this is all possible with iPXE, because it's amazing. The basic flow of how this works is:

  1. PXE firmware on card sends DHCP request
  2. DHCP server is configured to provide an IP address, and then pass a boot file, which in our case is the iPXE undionly.kpxe file used to chain load iPXE
  3. iPXE can be compiled to use a default script, which in my case chain loads a script from the boot server, which is actually a PHP file running on NGINX which returns iPXE commands.

A few tips that will hopefully get you chain loading iPXE and writing your own dynamic scripts, to make your life easier:


iPXE chain loading itself


When you configure DHCP to send out the undionly.kpxe version of iPXE for chain loading, you have to remember that by default, iPXE will do its own DHCP broadcast, and then receive the boot file name of itself (undionly.kpxe), which it will then load

You can break this cycle with a conditional in your DHCP (ISC DHCPd in this example) configuration:

subnet 192.168.1.0 netmask 255.255.255.0 {
    option domain-name-servers 192.168.1.1;
    option routers 192.168.1.1;
    next-server 192.168.1.1;
    if exists user-class and option user-class = "iPXE" {
        filename "default.ipxe";
    } else {
        filename "undionly.kpxe";
    }
}

Obviously you can replace the IPs and filenames with whatever you like, so long as undionly.kpxe refers to the UNDI version of iPXE, and default.ipxe points to your default iPXE script you would like to load once iPXE has been chain loaded.


Using PHP to generate dynamic iPXE scripts


When chain loading an iPXE script using the chain command, the target can be an HTTP path, so long as the returned data is of type text/plain, and the content begins with the line #!ipxe followed by two carriage returns.

An example of this:

<?php
header("Content-type: text/plain");
echo "#!ipxe\n\n";
echo "chain -ar myscript.ipxe\n";
?>

Use HTTP to load everything!**


One thing you will notice when you start using iPXE, and start reading some of the scripts people put together (I will be posting some examples in another post) is that HTTP is used quite often to load things like ISO files, initrd/initramfs files, and in the case of Windows, .WIM files - instead of using the more traditional TFTP protocol, which is often used when PXE booting machines.

This is because HTTP is much, much faster. TFTP uses UDP, which gives no guarantee that packets will arrive in the right order, or at all, and as such has to be a lot more conservative with how large and how furiously transmitted those packets can be. It's also a lot simpler to implement in tiny PXE boot roms, which is why it is used. HTTP however uses TCP, which guarantees delivery of each packet and also the sequence of the packets, at a protocol level, so the packets can be sent as quickly as the underlying switching and network cards will allow.

One example, when booting windows, is the loading of the install.wim file. On 10 gigabit Ethernet, I have had this take 15-20 minutes to transfer across the wire, for a 3GB Windows PE image. This same file with HTTP over the same link takes 2-3 minutes.

To use HTTP, all you have to do is specify an HTTP URL for a file as an argument to any of the iPXE commands which accept files, for example, the chain, imgload, kernel or initrd commands, and iPXE will do the rest. Obviously this means you will need to run an HTTP server such as NGINX (recommended), lighttpd or Apache to server the files in additional to your TFTP server.