Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[efi] Extract basic network settings from loaded image device path
The UEFI HTTP boot mechanism is extraordinarily badly designed, even
by the standards of the UEFI specification in general.  It has the
symptoms of a feature that has been designed entirely in terms of user
stories, without any consideration at all being given to the
underlying technical architecture.  It does work, provided that you
are doing precisely and only what was envisioned by the product owner.
If you want to try anything outside the bounds of the product owner's
extremely limited imagination, then you are almost certainly about to
enter a world of pain.

As one very minor example of this: the cached DHCP packet is not
available when using HTTP boot.  The UEFI HTTP boot code does perform
DHCP, but it pointlessly and unhelpfully throws away the DHCP packet
and trashes the network interface configuration before handing over to
the downloaded executable.

Work around this imbecility by parsing and applying the few network
configuration settings that are persisted into the loaded image's
device path.  This is limited to very basic information such as the IP
address, gateway address, and DNS server address, but it does at least
provide enough for a functional routing table.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
  • Loading branch information
mcb30 committed Mar 26, 2024
1 parent 170bbfd commit 9bbe776
Show file tree
Hide file tree
Showing 2 changed files with 243 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/include/ipxe/settings.h
Expand Up @@ -445,6 +445,8 @@ len6_setting __setting ( SETTING_IP6, len6 );
extern const struct setting
gateway6_setting __setting ( SETTING_IP6, gateway6 );
extern const struct setting
dns6_setting __setting ( SETTING_IP6_EXTRA, dns6 );
extern const struct setting
hostname_setting __setting ( SETTING_HOST, hostname );
extern const struct setting
domain_setting __setting ( SETTING_IP_EXTRA, domain );
Expand Down
241 changes: 241 additions & 0 deletions src/interface/efi/efi_path.c
Expand Up @@ -34,6 +34,8 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include <ipxe/fcp.h>
#include <ipxe/ib_srp.h>
#include <ipxe/usb.h>
#include <ipxe/settings.h>
#include <ipxe/dhcp.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/efi_driver.h>
#include <ipxe/efi/efi_path.h>
Expand All @@ -44,6 +46,40 @@ FILE_LICENCE ( GPL2_OR_LATER );
*
*/

/** An EFI device path settings block */
struct efi_path_settings {
/** Settings interface */
struct settings settings;
/** Device path */
EFI_DEVICE_PATH_PROTOCOL *path;
};

/** An EFI device path setting */
struct efi_path_setting {
/** Setting */
const struct setting *setting;
/**
* Fetch setting
*
* @v pathset Path setting
* @v path Device path
* @v data Buffer to fill with setting data
* @v len Length of buffer
* @ret len Length of setting data, or negative error
*/
int ( * fetch ) ( struct efi_path_setting *pathset,
EFI_DEVICE_PATH_PROTOCOL *path,
void *data, size_t len );
/** Path type */
uint8_t type;
/** Path subtype */
uint8_t subtype;
/** Offset within device path */
uint8_t offset;
/** Length (if fixed) */
uint8_t len;
};

/**
* Find next element in device path
*
Expand Down Expand Up @@ -657,3 +693,208 @@ EFI_DEVICE_PATH_PROTOCOL * efi_describe ( struct interface *intf ) {
intf_put ( dest );
return path;
}

/**
* Fetch an EFI device path fixed-size setting
*
* @v pathset Path setting
* @v path Device path
* @v data Buffer to fill with setting data
* @v len Length of buffer
* @ret len Length of setting data, or negative error
*/
static int efi_path_fetch_fixed ( struct efi_path_setting *pathset,
EFI_DEVICE_PATH_PROTOCOL *path,
void *data, size_t len ) {

/* Copy data */
if ( len > pathset->len )
len = pathset->len;
memcpy ( data, ( ( ( void * ) path ) + pathset->offset ), len );

return pathset->len;
}

/**
* Fetch an EFI device path DNS setting
*
* @v pathset Path setting
* @v path Device path
* @v data Buffer to fill with setting data
* @v len Length of buffer
* @ret len Length of setting data, or negative error
*/
static int efi_path_fetch_dns ( struct efi_path_setting *pathset,
EFI_DEVICE_PATH_PROTOCOL *path,
void *data, size_t len ) {
DNS_DEVICE_PATH *dns = container_of ( path, DNS_DEVICE_PATH, Header );
unsigned int count;
unsigned int i;
size_t frag_len;

/* Check applicability */
if ( ( !! dns->IsIPv6 ) !=
( pathset->setting->type == &setting_type_ipv6 ) )
return -ENOENT;

/* Calculate number of addresses */
count = ( ( ( ( path->Length[1] << 8 ) | path->Length[0] ) -
pathset->offset ) / sizeof ( dns->DnsServerIp[0] ) );

/* Copy data */
for ( i = 0 ; i < count ; i++ ) {
frag_len = len;
if ( frag_len > pathset->len )
frag_len = pathset->len;
memcpy ( data, &dns->DnsServerIp[i], frag_len );
data += frag_len;
len -= frag_len;
}

return ( count * pathset->len );
}

/** EFI device path settings */
static struct efi_path_setting efi_path_settings[] = {
{ &ip_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH,
MSG_IPv4_DP, offsetof ( IPv4_DEVICE_PATH, LocalIpAddress ),
sizeof ( struct in_addr ) },
{ &netmask_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH,
MSG_IPv4_DP, offsetof ( IPv4_DEVICE_PATH, SubnetMask ),
sizeof ( struct in_addr ) },
{ &gateway_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH,
MSG_IPv4_DP, offsetof ( IPv4_DEVICE_PATH, GatewayIpAddress ),
sizeof ( struct in_addr ) },
{ &ip6_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH,
MSG_IPv6_DP, offsetof ( IPv6_DEVICE_PATH, LocalIpAddress ),
sizeof ( struct in6_addr ) },
{ &len6_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH,
MSG_IPv6_DP, offsetof ( IPv6_DEVICE_PATH, PrefixLength ),
sizeof ( uint8_t ) },
{ &gateway6_setting, efi_path_fetch_fixed, MESSAGING_DEVICE_PATH,
MSG_IPv6_DP, offsetof ( IPv6_DEVICE_PATH, GatewayIpAddress ),
sizeof ( struct in6_addr ) },
{ &dns_setting, efi_path_fetch_dns, MESSAGING_DEVICE_PATH,
MSG_DNS_DP, offsetof ( DNS_DEVICE_PATH, DnsServerIp ),
sizeof ( struct in_addr ) },
{ &dns6_setting, efi_path_fetch_dns, MESSAGING_DEVICE_PATH,
MSG_DNS_DP, offsetof ( DNS_DEVICE_PATH, DnsServerIp ),
sizeof ( struct in6_addr ) },
};

/**
* Fetch value of EFI device path setting
*
* @v settings Settings block
* @v setting Setting to fetch
* @v data Buffer to fill with setting data
* @v len Length of buffer
* @ret len Length of setting data, or negative error
*/
static int efi_path_fetch ( struct settings *settings, struct setting *setting,
void *data, size_t len ) {
struct efi_path_settings *pathsets =
container_of ( settings, struct efi_path_settings, settings );
EFI_DEVICE_PATH_PROTOCOL *path = pathsets->path;
EFI_DEVICE_PATH_PROTOCOL *next;
struct efi_path_setting *pathset;
unsigned int i;
int ret;

/* Find matching path setting, if any */
for ( i = 0 ; i < ( sizeof ( efi_path_settings ) /
sizeof ( efi_path_settings[0] ) ) ; i++ ) {

/* Check for a matching setting */
pathset = &efi_path_settings[i];
if ( setting_cmp ( setting, pathset->setting ) != 0 )
continue;

/* Find matching device path element, if any */
for ( ; ( next = efi_path_next ( path ) ) ; path = next ) {

/* Check for a matching path type */
if ( ( path->Type != pathset->type ) ||
( path->SubType != pathset->subtype ) )
continue;

/* Fetch value */
if ( ( ret = pathset->fetch ( pathset, path,
data, len ) ) < 0 )
return ret;

/* Apply default type, if not already set */
if ( ! setting->type )
setting->type = pathset->setting->type;

return ret;
}
break;
}

return -ENOENT;
}

/** EFI device path settings operations */
static struct settings_operations efi_path_settings_operations = {
.fetch = efi_path_fetch,
};

/**
* Create per-netdevice EFI path settings
*
* @v netdev Network device
* @v priv Private data
* @ret rc Return status code
*/
static int efi_path_net_probe ( struct net_device *netdev, void *priv ) {
struct efi_path_settings *pathsets = priv;
struct settings *settings = &pathsets->settings;
EFI_DEVICE_PATH_PROTOCOL *path = efi_loaded_image_path;
unsigned int vlan;
void *mac;
int rc;

/* Check applicability */
pathsets->path = path;
mac = efi_path_mac ( path );
vlan = efi_path_vlan ( path );
if ( ( mac == NULL ) ||
( memcmp ( mac, netdev->ll_addr,
netdev->ll_protocol->ll_addr_len ) != 0 ) ||
( vlan != vlan_tag ( netdev ) ) ) {
DBGC ( settings, "EFI path %s does not apply to %s\n",
efi_devpath_text ( path ), netdev->name );
return 0;
}

/* Never override a real DHCP settings block */
if ( find_child_settings ( netdev_settings ( netdev ),
DHCP_SETTINGS_NAME ) ) {
DBGC ( settings, "EFI path %s not overriding %s DHCP "
"settings\n", efi_devpath_text ( path ), netdev->name );
return 0;
}

/* Initialise and register settings */
settings_init ( settings, &efi_path_settings_operations,
&netdev->refcnt, NULL );
if ( ( rc = register_settings ( settings, netdev_settings ( netdev ),
DHCP_SETTINGS_NAME ) ) != 0 ) {
DBGC ( settings, "EFI path %s could not register for %s: %s\n",
efi_devpath_text ( path ), netdev->name,
strerror ( rc ) );
return rc;
}
DBGC ( settings, "EFI path %s registered for %s\n",
efi_devpath_text ( path ), netdev->name );

return 0;
}

/** EFI path settings per-netdevice driver */
struct net_driver efi_path_net_driver __net_driver = {
.name = "EFI path",
.priv_len = sizeof ( struct efi_path_settings ),
.probe = efi_path_net_probe,
};

0 comments on commit 9bbe776

Please sign in to comment.