diff --git a/src/include/gpxe/dhcp.h b/src/include/gpxe/dhcp.h index 74741d96..2d9f8853 100644 --- a/src/include/gpxe/dhcp.h +++ b/src/include/gpxe/dhcp.h @@ -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, diff --git a/src/net/udp/dhcp.c b/src/net/udp/dhcp.c index 8d072250..5f741377 100644 --- a/src/net/udp/dhcp.c +++ b/src/net/udp/dhcp.c @@ -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 */ diff --git a/src/usr/dhcpmgmt.c b/src/usr/dhcpmgmt.c index d98aa9f4..aa969856 100644 --- a/src/usr/dhcpmgmt.c +++ b/src/usr/dhcpmgmt.c @@ -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 ( "" );