From f7fe2b319e8acab643a238dc42aeaa58dc5687f5 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 3 Mar 2026 11:04:18 +0000 Subject: [PATCH] [cachedhcp] Set current working URI to cached DHCP filename For a UEFI HTTP boot, we set the current working URI based on the loaded image device path. The autoexec.ipxe script will be fetched from the same directory as the iPXE binary itself. For a BIOS or UEFI PXE boot, we do not explicitly set a current working URI, but rely on the fact that registering the cached DHCP settings block will cause the TFTP code to set the current working URI to "tftp://${next-server}/". The autoexec.ipxe script will therefore be fetched from the default directory (which is most probably the root directory) of the TFTP server. When using a UEFI shim, the shim will always fetch iPXE from the same directory as the shim itself. This leads to a somewhat unintuitive requirement for a UEFI PXE boot: the shim and iPXE must be placed in the same directory, but the corresponding autoexec.ipxe script must be placed in the root directory. As with the loaded image device path for a UEFI HTTP boot, the existence of a cached DHCP packet gives us a way to construct the URI of our own binary. We can therefore choose to use this to set the current working URI, so that the autoexec.ipxe script may be placed in the same directory as the iPXE binary itself. This is the least surprising location, and avoids the need for lengthy explanations in documentation. Choose to set the current working URI at the point that the cached DHCP packet is recorded, rather than the point at which it is applied and registered as a settings block. This avoids some awkward corner cases (such as failing to find a matching network device for the DHCPACK), and naturally ensures that we retrieve the next-server address and filename from the same DHCP packet. We rely on the order in which cached DHCP packets are recorded to impose a priority ordering: later packets (e.g. PxeBSACK) will override earlier ones. To avoid breaking existing setups that do place the autoexec.ipxe script in the root directory, we modify the fetching logic to first attempt to retrieve autoexec.ipxe from the current working URI, then from the root directory of that URI. As with commit a69afd7 ("[tftp] Use TFTP server URI only if no other working URI is set"), this is technically a breaking change in behaviour, but the new behaviour is almost certainly less surprising than the existing behaviour. Scripts that rely on the current working URI being set to the root of the TFTP server can use absolute URIs (i.e. add an initial slash): this is more explicit and will work on iPXE builds both before and after this change. Signed-off-by: Michael Brown --- src/core/cachedhcp.c | 52 ++++++++++++++++++++++++++++++++ src/interface/efi/efi_autoexec.c | 13 +++++--- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/core/cachedhcp.c b/src/core/cachedhcp.c index 21cb382ad..6518cc2b1 100644 --- a/src/core/cachedhcp.c +++ b/src/core/cachedhcp.c @@ -33,6 +33,7 @@ FILE_SECBOOT ( PERMITTED ); #include #include #include +#include #include /** @file @@ -194,6 +195,50 @@ static int cachedhcp_apply ( struct cached_dhcp_packet *cache, return 0; } +/** + * Get URI from cached DHCP packet + * + * @v cache Cached DHCP packet + * @ret uri URI, or NULL if not defined + */ +static struct uri * cachedhcp_uri ( struct cached_dhcp_packet *cache ) { + struct dhcp_packet *dhcppkt = cache->dhcppkt; + struct settings *settings = &dhcppkt->settings; + struct uri *uri = NULL; + union { + struct sockaddr sa; + struct sockaddr_in sin; + } next_server; + char *filename; + + /* Fetch next-server address */ + memset ( &next_server, 0, sizeof ( next_server ) ); + next_server.sin.sin_family = AF_INET; + fetch_ipv4_setting ( settings, &next_server_setting, + &next_server.sin.sin_addr ); + if ( next_server.sin.sin_addr.s_addr ) { + DBGC ( colour, "CACHEDHCP %s has next-server %s\n", + cache->name, inet_ntoa ( next_server.sin.sin_addr ) ); + } + + /* Fetch filename */ + fetch_string_setting_copy ( settings, &filename_setting, &filename ); + if ( ! filename ) + goto err_filename; + DBGC ( colour, "CACHEDHCP %s has filename %s\n", + cache->name, filename ); + + /* Construct URI */ + uri = pxe_uri ( &next_server.sa, filename ); + if ( ! uri ) + goto err_uri; + + err_uri: + free ( filename ); + err_filename: + return uri; +} + /** * Record cached DHCP packet * @@ -208,6 +253,7 @@ int cachedhcp_record ( struct cached_dhcp_packet *cache, unsigned int vlan, struct dhcp_packet *dhcppkt; struct dhcp_packet *tmp; struct dhcphdr *dhcphdr; + struct uri *uri; unsigned int i; size_t len; @@ -261,6 +307,12 @@ int cachedhcp_record ( struct cached_dhcp_packet *cache, unsigned int vlan, cache->dhcppkt = dhcppkt; cache->vlan = vlan; + /* Set current working URI, if defined in this packet */ + uri = cachedhcp_uri ( cache ); + if ( uri ) + churi ( uri ); + uri_put ( uri ); + return 0; } diff --git a/src/interface/efi/efi_autoexec.c b/src/interface/efi/efi_autoexec.c index b63ac1602..4bb5a7a11 100644 --- a/src/interface/efi/efi_autoexec.c +++ b/src/interface/efi/efi_autoexec.c @@ -131,17 +131,20 @@ static int efi_autoexec_network ( EFI_HANDLE handle, struct image **image ) { goto err_open; } - /* Attempt download */ - rc = imgacquire ( EFI_AUTOEXEC_NAME, EFI_AUTOEXEC_TIMEOUT, image ); - if ( rc != 0 ) { - DBGC ( device, "EFI %s could not download %s: %s\n", + /* Attempt download from current working URI, then from root */ + if ( ( rc = imgacquire ( EFI_AUTOEXEC_NAME, EFI_AUTOEXEC_TIMEOUT, + image ) != 0 ) && + ( rc = imgacquire ( "/" EFI_AUTOEXEC_NAME, EFI_AUTOEXEC_TIMEOUT, + image ) != 0 ) ) { + DBGC ( device, "EFI %s could not download [/]%s: %s\n", efi_handle_name ( device ), EFI_AUTOEXEC_NAME, strerror ( rc ) ); + goto err_acquire; } + err_acquire: /* Ensure network exchanges have completed */ sync ( EFI_AUTOEXEC_SYNC_TIMEOUT ); - err_open: err_cwuri: mnptemp_destroy ( netdev );