From ea7646be892feca8a34a8f4197768df982ef8e13 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 23 Jul 2021 11:32:04 +0100 Subject: [efi] Record cached ProxyDHCPOFFER and PXEBSACK, if present 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. Signed-off-by: Michael Brown --- src/arch/x86/interface/pcbios/bios_cachedhcp.c | 3 +- src/core/cachedhcp.c | 175 ++++++++++++++++++------- src/include/ipxe/cachedhcp.h | 9 +- src/include/ipxe/dhcppkt.h | 2 +- src/interface/efi/efi_cachedhcp.c | 29 +++- 5 files changed, 167 insertions(+), 51 deletions(-) diff --git a/src/arch/x86/interface/pcbios/bios_cachedhcp.c b/src/arch/x86/interface/pcbios/bios_cachedhcp.c index 3d38699f..277c40d6 100644 --- a/src/arch/x86/interface/pcbios/bios_cachedhcp.c +++ b/src/arch/x86/interface/pcbios/bios_cachedhcp.c @@ -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 ) ); diff --git a/src/core/cachedhcp.c b/src/core/cachedhcp.c index 0e7da4bf..2fa9b0c7 100644 --- a/src/core/cachedhcp.c +++ b/src/core/cachedhcp.c @@ -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 */ diff --git a/src/include/ipxe/cachedhcp.h b/src/include/ipxe/cachedhcp.h index 7765c645..39ce7454 100644 --- a/src/include/ipxe/cachedhcp.h +++ b/src/include/ipxe/cachedhcp.h @@ -12,6 +12,13 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include -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 */ diff --git a/src/include/ipxe/dhcppkt.h b/src/include/ipxe/dhcppkt.h index f13dfc93..86075960 100644 --- a/src/include/ipxe/dhcppkt.h +++ b/src/include/ipxe/dhcppkt.h @@ -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 ); } diff --git a/src/interface/efi/efi_cachedhcp.c b/src/interface/efi/efi_cachedhcp.c index 14b531d0..1d4b98fd 100644 --- a/src/interface/efi/efi_cachedhcp.c +++ b/src/interface/efi/efi_cachedhcp.c @@ -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 ); -- cgit v1.2.1