]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[efi] Record cached ProxyDHCPOFFER and PXEBSACK, if present
authorMichael Brown <mcb30@ipxe.org>
Fri, 23 Jul 2021 10:32:04 +0000 (11:32 +0100)
committerMichael Brown <mcb30@ipxe.org>
Tue, 27 Jul 2021 12:50:36 +0000 (13:50 +0100)
Commit cd3de55 ("[efi] Record cached DHCPACK from loaded image's
device handle, if present") added the ability for a chainloaded UEFI
iPXE to reuse an IPv4 address and DHCP options previously obtained by
a built-in PXE stack, without needing to perform a second DHCP
request.

Extend this to also record the cached ProxyDHCPOFFER and PXEBSACK
obtained from the EFI_PXE_BASE_CODE_PROTOCOL instance installed on the
loaded image's device handle, if present.

This allows a chainloaded UEFI iPXE to reuse a boot filename or other
options that were provided via a ProxyDHCP or PXE boot server
mechanism, rather than by standard DHCP.

Tested-by: Andreas Hammarskjöld <junior@2PintSoftware.com>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/arch/x86/interface/pcbios/bios_cachedhcp.c
src/core/cachedhcp.c
src/include/ipxe/cachedhcp.h
src/include/ipxe/dhcppkt.h
src/interface/efi/efi_cachedhcp.c

index 3d38699f7564d26937556507f7070b2bc5348541..277c40d6fd49aeb57148611f751e50b1a2d70659 100644 (file)
@@ -59,7 +59,8 @@ static void cachedhcp_init ( void ) {
        }
 
        /* Record cached DHCPACK */
-       if ( ( rc = cachedhcp_record ( phys_to_user ( cached_dhcpack_phys ),
+       if ( ( rc = cachedhcp_record ( &cached_dhcpack,
+                                      phys_to_user ( cached_dhcpack_phys ),
                                       sizeof ( BOOTPLAYER_t ) ) ) != 0 ) {
                DBGC ( colour, "CACHEDHCP could not record DHCPACK: %s\n",
                       strerror ( rc ) );
index 0e7da4bf2a63f155b9fe9017ab70f56a026452fb..2fa9b0c7301bd2c46921a6d9a2bef2b380499275 100644 (file)
@@ -37,29 +37,121 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
  *
  */
 
+/** A cached DHCP packet */
+struct cached_dhcp_packet {
+       /** Settings block name */
+       const char *name;
+       /** DHCP packet (if any) */
+       struct dhcp_packet *dhcppkt;
+};
+
 /** Cached DHCPACK */
-static struct dhcp_packet *cached_dhcpack;
+struct cached_dhcp_packet cached_dhcpack = {
+       .name = DHCP_SETTINGS_NAME,
+};
+
+/** Cached ProxyDHCPOFFER */
+struct cached_dhcp_packet cached_proxydhcp = {
+       .name = PROXYDHCP_SETTINGS_NAME,
+};
+
+/** Cached PXEBSACK */
+struct cached_dhcp_packet cached_pxebs = {
+       .name = PXEBS_SETTINGS_NAME,
+};
+
+/** List of cached DHCP packets */
+static struct cached_dhcp_packet *cached_packets[] = {
+       &cached_dhcpack,
+       &cached_proxydhcp,
+       &cached_pxebs,
+};
 
 /** Colour for debug messages */
 #define colour &cached_dhcpack
 
 /**
- * Record cached DHCPACK
+ * Free cached DHCP packet
  *
+ * @v cache            Cached DHCP packet
+ */
+static void cachedhcp_free ( struct cached_dhcp_packet *cache ) {
+
+       dhcppkt_put ( cache->dhcppkt );
+       cache->dhcppkt = NULL;
+}
+
+/**
+ * Apply cached DHCP packet settings
+ *
+ * @v cache            Cached DHCP packet
+ * @v netdev           Network device, or NULL
+ * @ret rc             Return status code
+ */
+static int cachedhcp_apply ( struct cached_dhcp_packet *cache,
+                            struct net_device *netdev ) {
+       struct settings *settings;
+       int rc;
+
+       /* Do nothing if cache is empty */
+       if ( ! cache->dhcppkt )
+               return 0;
+
+       /* Do nothing unless cached packet's MAC address matches this
+        * network device, if specified.
+        */
+       if ( netdev ) {
+               if ( memcmp ( netdev->ll_addr, cache->dhcppkt->dhcphdr->chaddr,
+                             netdev->ll_protocol->ll_addr_len ) != 0 ) {
+                       DBGC ( colour, "CACHEDHCP %s does not match %s\n",
+                              cache->name, netdev->name );
+                       return 0;
+               }
+               DBGC ( colour, "CACHEDHCP %s is for %s\n",
+                      cache->name, netdev->name );
+       }
+
+       /* Select appropriate parent settings block */
+       settings = ( netdev ? netdev_settings ( netdev ) : NULL );
+
+       /* Register settings */
+       if ( ( rc = register_settings ( &cache->dhcppkt->settings, settings,
+                                       cache->name ) ) != 0 ) {
+               DBGC ( colour, "CACHEDHCP %s could not register settings: %s\n",
+                      cache->name, strerror ( rc ) );
+               return rc;
+       }
+
+       /* Free cached DHCP packet */
+       cachedhcp_free ( cache );
+
+       return 0;
+}
+
+/**
+ * Record cached DHCP packet
+ *
+ * @v cache            Cached DHCP packet
  * @v data             DHCPACK packet buffer
  * @v max_len          Maximum possible length
  * @ret rc             Return status code
  */
-int cachedhcp_record ( userptr_t data, size_t max_len ) {
+int cachedhcp_record ( struct cached_dhcp_packet *cache, userptr_t data,
+                      size_t max_len ) {
        struct dhcp_packet *dhcppkt;
        struct dhcp_packet *tmp;
        struct dhcphdr *dhcphdr;
+       unsigned int i;
        size_t len;
 
+       /* Free any existing cached packet */
+       cachedhcp_free ( cache );
+
        /* Allocate and populate DHCP packet */
        dhcppkt = zalloc ( sizeof ( *dhcppkt ) + max_len );
        if ( ! dhcppkt ) {
-               DBGC ( colour, "CACHEDHCP could not allocate copy\n" );
+               DBGC ( colour, "CACHEDHCP %s could not allocate copy\n",
+                      cache->name );
                return -ENOMEM;
        }
        dhcphdr = ( ( ( void * ) dhcppkt ) + sizeof ( *dhcppkt ) );
@@ -80,10 +172,26 @@ int cachedhcp_record ( userptr_t data, size_t max_len ) {
        dhcphdr = ( ( ( void * ) dhcppkt ) + sizeof ( *dhcppkt ) );
        dhcppkt_init ( dhcppkt, dhcphdr, len );
 
-       /* Store as cached DHCPACK, and mark original copy as consumed */
-       DBGC ( colour, "CACHEDHCP found cached DHCPACK at %#08lx+%#zx/%#zx\n",
+       /* Discard duplicate packets, since some PXE stacks (including
+        * iPXE itself) will report the DHCPACK packet as the PXEBSACK
+        * if no separate PXEBSACK exists.
+        */
+       for ( i = 0 ; i < ( sizeof ( cached_packets ) /
+                           sizeof ( cached_packets[0] ) ) ; i++ ) {
+               tmp = cached_packets[i]->dhcppkt;
+               if ( tmp && ( dhcppkt_len ( tmp ) == len ) &&
+                    ( memcmp ( tmp->dhcphdr, dhcppkt->dhcphdr, len ) == 0 ) ) {
+                       DBGC ( colour, "CACHEDHCP %s duplicates %s\n",
+                              cache->name, cached_packets[i]->name );
+                       dhcppkt_put ( dhcppkt );
+                       return -EEXIST;
+               }
+       }
+
+       /* Store as cached packet */
+       DBGC ( colour, "CACHEDHCP %s at %#08lx+%#zx/%#zx\n", cache->name,
               user_to_phys ( data, 0 ), len, max_len );
-       cached_dhcpack = dhcppkt;
+       cache->dhcppkt = dhcppkt;
 
        return 0;
 }
@@ -94,14 +202,20 @@ int cachedhcp_record ( userptr_t data, size_t max_len ) {
  */
 static void cachedhcp_startup ( void ) {
 
-       /* If cached DHCP packet was not claimed by any network device
-        * during startup, then free it.
-        */
-       if ( cached_dhcpack ) {
-               DBGC ( colour, "CACHEDHCP freeing unclaimed cached DHCPACK\n" );
-               dhcppkt_put ( cached_dhcpack );
-               cached_dhcpack = NULL;
+       /* Apply cached ProxyDHCPOFFER, if any */
+       cachedhcp_apply ( &cached_proxydhcp, NULL );
+
+       /* Apply cached PXEBSACK, if any */
+       cachedhcp_apply ( &cached_pxebs, NULL );
+
+       /* Free any remaining cached packets */
+       if ( cached_dhcpack.dhcppkt ) {
+               DBGC ( colour, "CACHEDHCP %s unclaimed\n",
+                      cached_dhcpack.name );
        }
+       cachedhcp_free ( &cached_dhcpack );
+       cachedhcp_free ( &cached_proxydhcp );
+       cachedhcp_free ( &cached_pxebs );
 }
 
 /** Cached DHCPACK startup function */
@@ -117,38 +231,9 @@ struct startup_fn cachedhcp_startup_fn __startup_fn ( STARTUP_LATE ) = {
  * @ret rc             Return status code
  */
 static int cachedhcp_probe ( struct net_device *netdev ) {
-       struct ll_protocol *ll_protocol = netdev->ll_protocol;
-       int rc;
 
-       /* Do nothing unless we have a cached DHCPACK */
-       if ( ! cached_dhcpack )
-               return 0;
-
-       /* Do nothing unless cached DHCPACK's MAC address matches this
-        * network device.
-        */
-       if ( memcmp ( netdev->ll_addr, cached_dhcpack->dhcphdr->chaddr,
-                     ll_protocol->ll_addr_len ) != 0 ) {
-               DBGC ( colour, "CACHEDHCP cached DHCPACK does not match %s\n",
-                      netdev->name );
-               return 0;
-       }
-       DBGC ( colour, "CACHEDHCP cached DHCPACK is for %s\n", netdev->name );
-
-       /* Register as DHCP settings for this network device */
-       if ( ( rc = register_settings ( &cached_dhcpack->settings,
-                                       netdev_settings ( netdev ),
-                                       DHCP_SETTINGS_NAME ) ) != 0 ) {
-               DBGC ( colour, "CACHEDHCP could not register settings: %s\n",
-                      strerror ( rc ) );
-               return rc;
-       }
-
-       /* Claim cached DHCPACK */
-       dhcppkt_put ( cached_dhcpack );
-       cached_dhcpack = NULL;
-
-       return 0;
+       /* Apply cached DHCPACK to network device, if applicable */
+       return cachedhcp_apply ( &cached_dhcpack, netdev );
 }
 
 /** Cached DHCP packet network device driver */
index 7765c6455dc1de151acbe99ee6018bf23be30f0f..39ce745433ae66cacab1ff37479216dde2665951 100644 (file)
@@ -12,6 +12,13 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #include <stddef.h>
 #include <ipxe/uaccess.h>
 
-extern int cachedhcp_record ( userptr_t data, size_t max_len );
+struct cached_dhcp_packet;
+
+extern struct cached_dhcp_packet cached_dhcpack;
+extern struct cached_dhcp_packet cached_proxydhcp;
+extern struct cached_dhcp_packet cached_pxebs;
+
+extern int cachedhcp_record ( struct cached_dhcp_packet *cache, userptr_t data,
+                             size_t max_len );
 
 #endif /* _IPXE_CACHEDHCP_H */
index f13dfc93db9a56426474ed39f8138aed25895b6b..86075960aeb0ca37e3747f99a78c01868f2e5a83 100644 (file)
@@ -56,7 +56,7 @@ dhcppkt_put ( struct dhcp_packet *dhcppkt ) {
  * @v dhcppkt          DHCP packet
  * @ret len            Used length
  */
-static inline int dhcppkt_len ( struct dhcp_packet *dhcppkt ) {
+static inline size_t dhcppkt_len ( struct dhcp_packet *dhcppkt ) {
        return ( offsetof ( struct dhcphdr, options ) +
                 dhcppkt->options.used_len );
 }
index 14b531d091dfbffcc9b48c0fa3be6838ba7a3f17..1d4b98fd6881845c840a9af819d6a6323b1107f6 100644 (file)
@@ -75,17 +75,40 @@ int efi_cachedhcp_record ( EFI_HANDLE device ) {
 
        /* Record DHCPACK, if present */
        if ( mode->DhcpAckReceived &&
-            ( ( rc = cachedhcp_record ( virt_to_user ( &mode->DhcpAck ),
+            ( ( rc = cachedhcp_record ( &cached_dhcpack,
+                                        virt_to_user ( &mode->DhcpAck ),
                                         sizeof ( mode->DhcpAck ) ) ) != 0 ) ) {
                DBGC ( device, "EFI %s could not record DHCPACK: %s\n",
                       efi_handle_name ( device ), strerror ( rc ) );
-               goto err_record;
+               goto err_dhcpack;
+       }
+
+       /* Record ProxyDHCPOFFER, if present */
+       if ( mode->ProxyOfferReceived &&
+            ( ( rc = cachedhcp_record ( &cached_proxydhcp,
+                                        virt_to_user ( &mode->ProxyOffer ),
+                                        sizeof ( mode->ProxyOffer ) ) ) != 0)){
+               DBGC ( device, "EFI %s could not record ProxyDHCPOFFER: %s\n",
+                      efi_handle_name ( device ), strerror ( rc ) );
+               goto err_proxydhcp;
+       }
+
+       /* Record PxeBSACK, if present */
+       if ( mode->PxeReplyReceived &&
+            ( ( rc = cachedhcp_record ( &cached_pxebs,
+                                        virt_to_user ( &mode->PxeReply ),
+                                        sizeof ( mode->PxeReply ) ) ) != 0)){
+               DBGC ( device, "EFI %s could not record PXEBSACK: %s\n",
+                      efi_handle_name ( device ), strerror ( rc ) );
+               goto err_pxebs;
        }
 
        /* Success */
        rc = 0;
 
- err_record:
+ err_pxebs:
+ err_proxydhcp:
+ err_dhcpack:
  err_ipv6:
        bs->CloseProtocol ( device, &efi_pxe_base_code_protocol_guid,
                            efi_image_handle, NULL );