[dhcp] Fall back to using the hardware address to populate the chaddr field
authorMichael Brown <mcb30@etherboot.org>
Tue, 11 Aug 2009 22:44:04 +0000 (23:44 +0100)
committerMichael Brown <mcb30@etherboot.org>
Tue, 11 Aug 2009 23:27:08 +0000 (00:27 +0100)
For IPoIB, the chaddr field is too small (16 bytes) to contain the
20-byte IPoIB link-layer address.  RFC4390 mandates that we should
pass an empty chaddr field and rely on the DHCP client identifier
instead.  This has many problems, not least of which is that a client
identifier containing an IPoIB link-layer address is not very useful
from the point of view of creating DHCP reservations, since the QPN
component is assigned at runtime and may vary between boots.

Leave the DHCP client identifier as-is, to avoid breaking existing
setups as far as possible, but expose the real hardware address (the
port GUID) via the DHCP chaddr field, using the broadcast flag to
instruct the DHCP server not to use this chaddr value as a link-layer
address.

This makes it possible (at least with ISC dhcpd) to create DHCP
reservations using host declarations such as:

    host duckling {
        fixed-address 10.252.252.99;
        hardware unknown-32 00:02:c9:02:00:25:a1:b5;
    }

src/include/gpxe/dhcp.h
src/net/udp/dhcp.c
src/usr/dhcpmgmt.c

index 74741d9..2d9f885 100644 (file)
@@ -601,6 +601,8 @@ struct dhcphdr {
 /** Setting block name used for BootServerDHCP responses */
 #define PXEBS_SETTINGS_NAME "pxebs"
 
+extern void * dhcp_chaddr ( struct net_device *netdev, uint8_t *hlen,
+                           uint16_t *flags );
 extern int dhcp_create_packet ( struct dhcp_packet *dhcppkt,
                                struct net_device *netdev, uint8_t msgtype,
                                const void *options, size_t options_len,
index 8d07225..5f74137 100644 (file)
@@ -826,6 +826,46 @@ static struct dhcp_session_state dhcp_state_pxebs = {
  *
  */
 
+/**
+ * Construct DHCP client hardware address field and broadcast flag
+ *
+ * @v netdev           Network device
+ * @v hlen             DHCP hardware address length to fill in
+ * @v flags            DHCP flags to fill in
+ * @ret chaddr         DHCP client hardware address
+ */
+void * dhcp_chaddr ( struct net_device *netdev, uint8_t *hlen,
+                    uint16_t *flags ) {
+       struct ll_protocol *ll_protocol = netdev->ll_protocol;
+       typeof ( ( ( struct dhcphdr * ) NULL )->chaddr ) chaddr;
+
+       /* If the link-layer address cannot fit into the chaddr field
+        * (as is the case for IPoIB) then try using the hardware
+        * address instead.  If we do this, set the broadcast flag,
+        * since chaddr then does not represent a valid link-layer
+        * address for the return path.
+        *
+        * If even the hardware address is too large, use an empty
+        * chaddr field and set the broadcast flag.
+        *
+        * This goes against RFC4390, but RFC4390 mandates that we use
+        * a DHCP client identifier that conforms with RFC4361, which
+        * we cannot do without either persistent (NIC-independent)
+        * storage, or by eliminating the hardware address completely
+        * from the DHCP packet, which seems unfriendly to users.
+        */
+       if ( ( *hlen = ll_protocol->ll_addr_len ) <= sizeof ( chaddr ) ) {
+               return netdev->ll_addr;
+       }
+       *flags = htons ( BOOTP_FL_BROADCAST );
+       if ( ( *hlen = ll_protocol->hw_addr_len ) <= sizeof ( chaddr ) ) {
+               return netdev->hw_addr;
+       } else {
+               *hlen = 0;
+               return NULL;
+       }
+}
+
 /**
  * Create a DHCP packet
  *
@@ -846,7 +886,7 @@ int dhcp_create_packet ( struct dhcp_packet *dhcppkt,
                         const void *options, size_t options_len,
                         void *data, size_t max_len ) {
        struct dhcphdr *dhcphdr = data;
-       unsigned int hlen;
+       void *chaddr;
        int rc;
 
        /* Sanity check */
@@ -859,16 +899,8 @@ int dhcp_create_packet ( struct dhcp_packet *dhcppkt,
        dhcphdr->magic = htonl ( DHCP_MAGIC_COOKIE );
        dhcphdr->htype = ntohs ( netdev->ll_protocol->ll_proto );
        dhcphdr->op = dhcp_op[msgtype];
-       /* If hardware length exceeds the chaddr field length, don't
-        * use the chaddr field.  This is as per RFC4390.
-        */
-       hlen = netdev->ll_protocol->ll_addr_len;
-       if ( hlen > sizeof ( dhcphdr->chaddr ) ) {
-               hlen = 0;
-               dhcphdr->flags = htons ( BOOTP_FL_BROADCAST );
-       }
-       dhcphdr->hlen = hlen;
-       memcpy ( dhcphdr->chaddr, netdev->ll_addr, hlen );
+       chaddr = dhcp_chaddr ( netdev, &dhcphdr->hlen, &dhcphdr->flags );
+       memcpy ( dhcphdr->chaddr, chaddr, dhcphdr->hlen );
        memcpy ( dhcphdr->options, options, options_len );
 
        /* Initialise DHCP packet structure */
index d98aa9f..aa96985 100644 (file)
@@ -37,6 +37,9 @@ FILE_LICENCE ( GPL2_OR_LATER );
  */
 
 int dhcp ( struct net_device *netdev ) {
+       uint8_t *chaddr;
+       uint8_t hlen;
+       uint16_t flags;
        int rc;
 
        /* Check we can open the interface first */
@@ -48,7 +51,10 @@ int dhcp ( struct net_device *netdev ) {
                return rc;
 
        /* Perform DHCP */
-       printf ( "DHCP (%s %s)", netdev->name, netdev_addr ( netdev ) );
+       chaddr = dhcp_chaddr ( netdev, &hlen, &flags );
+       printf ( "DHCP (%s ", netdev->name );
+       while ( hlen-- )
+               printf ( "%02x%c", *(chaddr++), ( hlen ? ':' : ')' ) );
        if ( ( rc = start_dhcp ( &monojob, netdev ) ) == 0 )
                rc = monojob_wait ( "" );