The Preboot eXecution Environment (PXE, also known as Pre-Execution Environment; sometimes pronounced "pixie") is an environment to boot computers using a network interface independently of data storage devices (like hard disks) or installed operating systems. -Wikipedia
PXE allows computers to boot from a binary image stored on a server, rather than the local hardware. Broadly speaking, a DHCP server informs a client of the TFTP server and filename from which to boot.
In the standard DHCP mode, the server has been implemented from RFC2131, RFC2132, and the DHCP Wikipedia Entry.
The DHCP server is in charge of assigning new clients with IP addresses, and informing them of the location of the TFTP server and filename they should look for. The top half of the DHCP request, as seen on the Wikipedia entry, consists of some network information, followed by 192 legacy null bytes, and then the magic cookie.
After the magic cookie, there are optional fields. These fields are defined by the field ID, a length byte and then the option data. Every DHCP packet has an option 53 field. This field specifies the packet type, be it DISCOVERY, OFFER, REQUEST or ACKNOWLEDGEMENT.
The DISCOVERY and REQUEST packets are those sent by the client to the broadcast address (IPv4: 255.255.255.255) where the client sends this data on port 68 and the server receives it on port 68. The OFFER and ACKNOWLEDGEMENT packets are sent from the server to the broadcast address where the server sends this data on port 67 and the client receives it on port 68. These four packets make up the four-way handshake.
The OFFER and ACKNOWLEDGEMENT packets both contain the same option data for each client. This can include the router, subnet mask, lease time, and DHCP server IP address.
Also included in these options are our PXE options. The minimum required option fields are option 66 and option 67. Option 66 denotes the IP of the TFTP server, and option 67 denotes the filename of the file to retrieve and boot.
Once the four way handshake is complete, the client will send a TFTP read request to the given fileserver IP address requesting the given filename.
By default only requests declaring the 'PXEClient' value dhcp option 60 are served, this is defined by PXE specifications If you're using PyPXE as a library you can change this behavior extending the DHCP class and overwriting the validateReq method.
ProxyDHCP mode is useful for when you either cannot (or do not want to) change the main DHCP server on a network. The bulk of ProxyDHCP information can be found in the Intel PXE spec. The main idea behind ProxyDHCP is that the main network DHCP server can hand out the IP leases while the ProxyDHCP server hands out the PXE information to each client. Therefore, slightly different information is sent in the ProxyDHCP packets.
There are multiple ways to implement ProxyDHCP: broadcast, multicast, unicast or lookback. Lookback is the simplest implementation and this is what we have chosen to use. When we receive a DHCP DISCOVER from a client, we respond with a DHCP OFFER but the OFFER packet is sent without a few fields we would normally send in standard DHCP mode (this includes an offered IP address, along with any other network information such as router, DNS server(s), etc.). What we include in this OFFER packet (which isn't in a normal DHCP packet), is a vendor-class identifier of 'PXEClient' - this string identifies the packet as being relevant to PXE booting.
There are a few vendor-specific options under the DHCP option 43:
- The first of these options is PXE discovery control; this is a bitmask defined in the PXE spec. When bit 3 is set, the PXE client will look backwards in the packet for the filename. The filename is located in the standard 'DHCP Boot Filename' area, previously referred to as 'legacy'. This is a NULL-terminated string located at offset 150 in the DHCP packet (before the DHCP magic cookie).
- The second vendor-specific option that is used is the PXE menu prompt. Although not visible, this is required by the spec and causes problems if it is missing. The main use for this is for the other DISCOVERY modes.
The client should receive two DHCP OFFER packets in ProxyDHCP mode: the first from the main DHCP server and the second from the ProxyDHCP server. Once both are received, the client will continue on with the DHCP handshake and, after it is complete, the client will boot using the settings in the DHCP OFFER from the ProxyDHCP server.
We have only implemented the read OPCODE for the TFTP server, as PXE does not use write. Only octet transfer mode is supported. The main TFTP protocol is defined in RFC1350
The blksize option, as defined in RFC2348 allows the client to specify the block size for each transfer packet. The blksize option is passed along with the read opcode, following the filename and mode. The format is blksize, followed by a null byte, followed by the ASCII base-10 representation of the blksize (i.e 512 rather than 0x200), followed by another null byte.
We have implemented GET and HEAD, as there is no requirement for any other methods. The referenced RFCs are RFC2616 and RFC7230.
The HEAD method is used by some PXE ROMs to find the Content-Length before the GET is sent.
NBD is similar to NFS in that it can act as a root device for Linux systems. Defined in the specification, NBD allows access to block devices over the network by performing read and write requests on the block device itself.
This is different to NFS as it does not act as a filesystem, merely a single file. NBD supports read/write access along with copy-on-write support, both in memory and on disk. Read/write without copy-on-write is potentially dangerous if the file-system layer does not support multiple systems accessing it at the same time. Copy-on-write alleviates these potential problems by providing a volatile layer in which per-client changes are saved. Both the on-disk and in-memory configurations delete changes after the client disconnects, but the in-memory configuration may offer a speed increase as the changes are stored in the system RAM.
WARNING: The use of this option can potentially consume a large amount of RAM; up to the size of the disk image multiplied by the number of connected clients may be used. The same can be said for the on-disk configuration, where this configuration uses disk space rather than memory space. A further configuration option to store the original disk image in memory is provided to potentially allow for read/write speed up.
The PyPXE library provides the following services for the purpose of creating a Python-based PXE environment: TFTP, HTTP, DHCP, and NBD. Each service must be imported independently as such:
from pypxe import tftp
orimport pypxe.tftp
imports the TFTP servicefrom pypxe import dhcp
orimport pypxe.dhcp
imports the DHCP servicefrom pypxe import http
orimport pypxe.http
imports the HTTP servicefrom pypxe import nbd
orimport pypxe.nbd
imports the NBD service
See pypxe/server.py
for example usage on how to call, define, and setup the services. When running any Python script that uses these classes, it should be run as a user with root privileges as they bind to interfaces and without root privileges the services will most likely fail to bind properly.
The TFTP service can be imported one of the following two ways:
from pypxe import tftp
import pypxe.tftp
The TFTP server class, TFTPD()
, is constructed with the following keyword arguments:
Keyword Argument | Description | Default | Type |
---|---|---|---|
ip |
This is the IP address that the TFTP server will bind to. | '0.0.0.0' (so that it binds to all available interfaces) |
string |
port |
This it the port that the TFTP server will run on. | 69 (default port for TFTP) |
int |
netboot_directory |
This is the directory that the TFTP server will serve files from similarly to that of tftpboot . |
'.' (current directory) |
string |
mode_debug |
This indicates whether or not the TFTP server should be started in debug mode or not. | False |
bool |
mode_verbose |
This indicates whether or not the TFTP server should be started in verbose mode or not. | False |
bool |
logger |
A Logger object used for logging messages, if None a local StreamHandler instance will be created. |
None |
Logger |
default_retries |
The number of data retransmissions before dropping a connection. | 3 |
int |
timeout |
The time in seconds before re-sending an un-acknowledged data block. | 5 |
int |
The DHCP service can be imported one of the following two ways:
from pypxe import dhcp
import pypxe.dhcp
The DHCP server class, DHCPD()
, is constructed with the following keyword arguments:
Keyword Argument | Description | Default | Type |
---|---|---|---|
ip |
This is the IP address that the DHCP server itself binds to. | '192.168.2.2' |
string |
port |
This it the port that the TFTP server will run on. | 67 (default port to listen for DHCP requests) |
int |
offer_from |
This specifies the beginning of the range of IP addresses that the DHCP server will hand out to clients. | '192.168.2.100' |
string |
offer_to |
This specifies the end of the range of IP addresses that the DHCP server will hand out to clients. | '192.168.2.150' |
string |
subnet_mask |
This specifies the subnet mask that the DHCP server will specify to clients. | '255.255.255.0' |
string |
router |
This specifies the IP address of the router that the DHCP server will specify to clients. | '192.168.2.1' |
string |
dns_server |
This specifies the DNS server that the DHCP server will specify to clients. Only one DNS server can be passed. | '8.8.8.8' (Google Public DNS) |
string |
broadcast |
This specifies the broadcast address the DHCP will broadcast packets to. It is derived from ip and subnet_mask , unless set explicitly in CLI or in JSON config file. This is necessary in selecting the right NIC for broadcast when you have multiple. |
'' |
string |
file_server |
This is the IP address of the file server containing network boot files that the DHCP server will specify to clients. | '192.168.2.2' |
string |
file_name |
This specifies the file name that the client should look for on the remote server. | 'pxelinux.0' |
string |
use_ipxe |
This indicates whether or not iPXE is being used and adjusts itself accordingly. | False |
bool |
use_http |
This indicates whether or not the built-in HTTP server is being used and adjusts itself accordingly. | False |
bool |
mode_proxy |
This indicates whether or not the DHCP server should be started in ProxyDHCP mode or not. | False |
bool |
static_config |
This specifies a static configuration dictionary so that it can give specific leases to specific MAC addresses. | {} |
dict |
whitelist |
This indicates whether or not the DHCP server should use the static configuration dictionary as a whitelist; effectively, the DHCP server will only give out leases to those specified in the static_config dictionary. |
False |
bool |
mode_debug |
This indicates whether or not the DHCP server should be started in debug mode or not. | False |
bool |
mode_verbose |
This indicates whether or not the DHCP server should be started in verbose mode or not. | False |
bool |
logger |
A Logger object used for logging messages, if None a local StreamHandler instance will be created. |
None |
Logger |
The HTTP service can be imported one of the following two ways:
from pypxe import http
import pypxe.http
The HTTP server class, HTTPD()
, is constructed with the following keyword arguments:
Keyword Argument | Description | Default | Type |
---|---|---|---|
ip |
This is the IP address that the HTTP server will bind to. | '0.0.0.0' (so that it binds to all available interfaces) |
string |
port |
This it the port that the HTTP server will run on. | 80 (default port for HTTP) |
int |
netboot_directory |
This is the directory that the HTTP server will serve files from similarly to that of tftpboot . |
'.' (current directory) |
string |
mode_debug |
This indicates whether or not the HTTP server should be started in debug mode or not. | False |
bool |
mode_verbose |
This indicates whether or not the HTTP server should be started in verbose mode or not. | False |
bool |
logger |
A Logger object used for logging messages, if None a local StreamHandler instance will be created. |
None |
Logger |
The NBD service can be imported one of the following two ways:
from pypxe import http
import pypxe.nbd
The NBD server class, NBD()
, is constructed with the following keyword arguments:
Keyword Argument | Description | Default | Type |
---|---|---|---|
ip |
This is the IP address that the NBD server will bind to. | '0.0.0.0' (so that it binds to all available interfaces) |
string |
port |
This it the port that the NBD server will run on. | 10809 (default port for NBD) |
int |
block_device |
The filename of the block device to be used as the root device. | '' |
string |
write |
Enable write support on the block device. | False |
bool |
cow |
Enable copy-on-write support on the block device. | True |
bool |
in_mem |
Enable in-memory copy-on-write support on the block device. False causes changes to be stored on disk. |
False |
bool |
copy_to_ram |
Copy the disk image to RAM when the service starts. | False |
bool |
mode_debug |
This indicates whether or not the NBD server should be started in debug mode or not. | False |
bool |
mode_verbose |
This indicates whether or not the NBD server should be started in verbose mode or not. | False |
bool |
logger |
A Logger object used for logging messages, if None a local StreamHandler instance will be created. |
None |
Logger |
- The function
chr(0)
is used in multiple places throughout the servers. This denotes aNULL
byte, or\x00
- Python 2.6 does not include the
argparse
module, it is included in the standard library as of 2.7 and newer. Theargparse
module is required to take in command line arguments andpypxe.server
will not run without it. - The TFTP server currently does not support transfer of large files, this is a known issue (see #35). Instead of using TFTP to transfer large files (roughly 33MB or greater) it is recommended that you use the HTTP server to do so. iPXE supports direct boot from HTTP and certain kernels (once you've booted into
pxelinux.0
via TFTP) support fetching files via HTTP as well.