summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Brown2024-03-26 16:17:23 +0100
committerMichael Brown2024-03-26 16:57:58 +0100
commit9bbe77669c6e2b71826449d854f5aa0e2cee7767 (patch)
tree9b396129645d4933c72e9f50497637b7f53a906a
parent[efi] Add efi_path_mac() to parse a MAC address from an EFI device path (diff)
downloadipxe-9bbe77669c6e2b71826449d854f5aa0e2cee7767.tar.gz
ipxe-9bbe77669c6e2b71826449d854f5aa0e2cee7767.tar.xz
ipxe-9bbe77669c6e2b71826449d854f5aa0e2cee7767.zip
[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>
-rw-r--r--src/include/ipxe/settings.h2
-rw-r--r--src/interface/efi/efi_path.c241
2 files changed, 243 insertions, 0 deletions
diff --git a/src/include/ipxe/settings.h b/src/include/ipxe/settings.h
index e042b975..424188de 100644
--- a/src/include/ipxe/settings.h
+++ b/src/include/ipxe/settings.h
@@ -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 );
diff --git a/src/interface/efi/efi_path.c b/src/interface/efi/efi_path.c
index 23f1bb84..ac3c0498 100644
--- a/src/interface/efi/efi_path.c
+++ b/src/interface/efi/efi_path.c
@@ -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>
@@ -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
*
@@ -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,
+};