diff --git a/src/libsystemd-network/dhcp-internal.h b/src/libsystemd-network/dhcp-internal.h index 6538f05bdb4..466d8e4b3f1 100644 --- a/src/libsystemd-network/dhcp-internal.h +++ b/src/libsystemd-network/dhcp-internal.h @@ -53,8 +53,8 @@ typedef int (*dhcp_option_callback_t)(uint8_t code, uint8_t len, int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **error_message); int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid, - uint8_t type, uint16_t arp_type, size_t optlen, - size_t *optoffset); + uint8_t type, uint16_t arp_type, uint8_t hlen, const uint8_t *chaddr, + size_t optlen, size_t *optoffset); uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len); diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c index cace916f441..d1a1cf57f3d 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -10,21 +10,44 @@ #include "dhcp-internal.h" #include "dhcp-protocol.h" +#include "memory-util.h" #define DHCP_CLIENT_MIN_OPTIONS_SIZE 312 -int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid, - uint8_t type, uint16_t arp_type, size_t optlen, - size_t *optoffset) { +int dhcp_message_init( + DHCPMessage *message, + uint8_t op, + uint32_t xid, + uint8_t type, + uint16_t arp_type, + uint8_t hlen, + const uint8_t *chaddr, + size_t optlen, + size_t *optoffset) { + size_t offset = 0; int r; assert(IN_SET(op, BOOTREQUEST, BOOTREPLY)); - assert(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND)); + assert(chaddr || hlen == 0); message->op = op; message->htype = arp_type; - message->hlen = (arp_type == ARPHRD_ETHER) ? ETHER_ADDR_LEN : 0; + + /* RFC2131 section 4.1.1: + The client MUST include its hardware address in the ’chaddr’ field, if + necessary for delivery of DHCP reply messages. + + RFC 4390 section 2.1: + A DHCP client, when working over an IPoIB interface, MUST follow the + following rules: + "htype" (hardware address type) MUST be 32 [ARPPARAM]. + "hlen" (hardware address length) MUST be 0. + "chaddr" (client hardware address) field MUST be zeroed. + */ + message->hlen = (arp_type == ARPHRD_INFINIBAND) ? 0 : hlen; + memcpy_safe(message->chaddr, chaddr, message->hlen); + message->xid = htobe32(xid); message->magic = htobe32(DHCP_MAGIC_COOKIE); diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index c89d9f3f2bf..7b296ae4a04 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -832,7 +832,8 @@ static int client_message_init( return -ENOMEM; r = dhcp_message_init(&packet->dhcp, BOOTREQUEST, client->xid, type, - client->arp_type, optlen, &optoffset); + client->arp_type, client->mac_addr_len, client->mac_addr, + optlen, &optoffset); if (r < 0) return r; @@ -862,15 +863,6 @@ static int client_message_init( if (client->request_broadcast || client->arp_type != ARPHRD_ETHER) packet->dhcp.flags = htobe16(0x8000); - /* RFC2131 section 4.1.1: - The client MUST include its hardware address in the ’chaddr’ field, if - necessary for delivery of DHCP reply messages. Non-Ethernet - interfaces will leave 'chaddr' empty and use the client identifier - instead (eg, RFC 4390 section 2.1). - */ - if (client->arp_type == ARPHRD_ETHER) - memcpy(&packet->dhcp.chaddr, &client->mac_addr, ETH_ALEN); - /* If no client identifier exists, construct an RFC 4361-compliant one */ if (client->client_id_len == 0) { size_t duid_len; diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index f00a1754e2b..91069c07828 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -482,14 +482,14 @@ static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret, return -ENOMEM; r = dhcp_message_init(&packet->dhcp, BOOTREPLY, - be32toh(req->message->xid), type, ARPHRD_ETHER, + be32toh(req->message->xid), type, + req->message->htype, req->message->hlen, req->message->chaddr, req->max_optlen, &optoffset); if (r < 0) return r; packet->dhcp.flags = req->message->flags; packet->dhcp.giaddr = req->message->giaddr; - memcpy(&packet->dhcp.chaddr, &req->message->chaddr, ETH_ALEN); *_optoffset = optoffset; *ret = TAKE_PTR(packet); @@ -621,7 +621,7 @@ static int server_send_forcerenew(sd_dhcp_server *server, be32_t address, return -ENOMEM; r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0, - DHCP_FORCERENEW, ARPHRD_ETHER, + DHCP_FORCERENEW, ARPHRD_ETHER, ETH_ALEN, chaddr, DHCP_MIN_OPTIONS_SIZE, &optoffset); if (r < 0) return r; @@ -631,8 +631,6 @@ static int server_send_forcerenew(sd_dhcp_server *server, be32_t address, if (r < 0) return r; - memcpy(&packet->dhcp.chaddr, chaddr, ETH_ALEN); - return dhcp_server_send_udp(server, address, DHCP_PORT_CLIENT, &packet->dhcp, sizeof(DHCPMessage) + optoffset); diff --git a/src/libsystemd-network/test-dhcp-option.c b/src/libsystemd-network/test-dhcp-option.c index 800a6641e35..b01b1f57a3a 100644 --- a/src/libsystemd-network/test-dhcp-option.c +++ b/src/libsystemd-network/test-dhcp-option.c @@ -89,7 +89,8 @@ static void test_message_init(void) { message = malloc0(len); assert_se(dhcp_message_init(message, BOOTREQUEST, 0x12345678, - DHCP_DISCOVER, ARPHRD_ETHER, optlen, &optoffset) >= 0); + DHCP_DISCOVER, ARPHRD_ETHER, ETH_ALEN, (uint8_t[16]){}, + optlen, &optoffset) >= 0); assert_se(message->xid == htobe32(0x12345678)); assert_se(message->op == BOOTREQUEST);