[cachedhcp] Allow cached DHCPACK to override a temporary MAC address

When running on a system with an ACPI-provided system-specific MAC
address, iPXE will apply this address to an ECM or NCM USB NIC.  If
iPXE has been chainloaded from a previous stage that does not
understand the ACPI MAC mechanism then this can result in iPXE using a
different MAC address than the previous stage, which is surprising to
users.

Attempt to minimise surprise by allowing the MAC address found in a
cached DHCPACK packet to override a temporary MAC address, if the
DHCPACK MAC address matches the network device's permanent MAC
address.  When a previous stage has chosen to use the network device's
permanent MAC address (e.g. because it does not understand the ACPI
MAC mechanism), this will cause iPXE to make the same choice.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/core/cachedhcp.c b/src/core/cachedhcp.c
index 2fa9b0c..c4ca46e 100644
--- a/src/core/cachedhcp.c
+++ b/src/core/cachedhcp.c
@@ -90,29 +90,62 @@
  */
 static int cachedhcp_apply ( struct cached_dhcp_packet *cache,
 			     struct net_device *netdev ) {
-	struct settings *settings;
+	struct settings *settings = NULL;
+	struct ll_protocol *ll_protocol;
+	const uint8_t *chaddr;
+	uint8_t *hw_addr;
+	uint8_t *ll_addr;
+	size_t ll_addr_len;
 	int rc;
 
 	/* Do nothing if cache is empty */
 	if ( ! cache->dhcppkt )
 		return 0;
+	chaddr = cache->dhcppkt->dhcphdr->chaddr;
 
-	/* Do nothing unless cached packet's MAC address matches this
-	 * network device, if specified.
-	 */
+	/* Handle association with network device, if specified */
 	if ( netdev ) {
-		if ( memcmp ( netdev->ll_addr, cache->dhcppkt->dhcphdr->chaddr,
-			      netdev->ll_protocol->ll_addr_len ) != 0 ) {
+		hw_addr = netdev->hw_addr;
+		ll_addr = netdev->ll_addr;
+		ll_protocol = netdev->ll_protocol;
+		ll_addr_len = ll_protocol->ll_addr_len;
+
+		/* If cached packet's MAC address matches the network
+		 * device's permanent MAC address, then assume that
+		 * the permanent MAC address ought to be the network
+		 * device's current link-layer address.
+		 *
+		 * This situation can arise when the PXE ROM does not
+		 * understand the system-specific mechanism for
+		 * overriding the MAC address, and so uses the
+		 * permanent MAC address instead.  We choose to match
+		 * this behaviour in order to minimise surprise.
+		 */
+		if ( memcmp ( hw_addr, chaddr, ll_addr_len ) == 0 ) {
+			if ( memcmp ( hw_addr, ll_addr, ll_addr_len ) != 0 ) {
+				DBGC ( colour, "CACHEDHCP %s resetting %s MAC "
+				       "%s ", cache->name, netdev->name,
+				       ll_protocol->ntoa ( ll_addr ) );
+				DBGC ( colour, "-> %s\n",
+				       ll_protocol->ntoa ( hw_addr ) );
+			}
+			memcpy ( ll_addr, hw_addr, ll_addr_len );
+		}
+
+		/* Do nothing unless cached packet's MAC address
+		 * matches this network device.
+		 */
+		if ( memcmp ( ll_addr, chaddr, 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 );
+		/* Use network device's settings block */
+		settings = netdev_settings ( netdev );
+	}
 
 	/* Register settings */
 	if ( ( rc = register_settings ( &cache->dhcppkt->settings, settings,