diff --git a/src/libsystemd-network/dhcp-internal.h b/src/libsystemd-network/dhcp-internal.h index 6538f05bdb..466d8e4b3f 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 cace916f44..d1a1cf57f3 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/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index 13d8cd77b4..51510b9d22 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -26,7 +26,7 @@ typedef enum DHCPRawOption { typedef struct DHCPClientId { size_t length; - void *data; + uint8_t *data; } DHCPClientId; typedef struct DHCPLease { @@ -34,6 +34,8 @@ typedef struct DHCPLease { DHCPClientId client_id; + uint8_t htype; /* e.g. ARPHRD_ETHER */ + uint8_t hlen; /* e.g. ETH_ALEN */ be32_t address; be32_t gateway; uint8_t chaddr[16]; diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index c33be947b7..7b296ae4a0 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; @@ -848,7 +849,7 @@ static int client_message_init( secs = ((time_now - client->start_time) / USEC_PER_SEC) ? : 1; packet->dhcp.secs = htobe16(secs); - /* RFC2132 section 4.1 + /* RFC2131 section 4.1 A client that cannot receive unicast IP datagrams until its protocol software has been configured with an IP address SHOULD set the BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or @@ -862,15 +863,6 @@ static int client_message_init( if (client->request_broadcast || client->arp_type != ARPHRD_ETHER) packet->dhcp.flags = htobe16(0x8000); - /* RFC2132 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 ea98060ea2..38c202975f 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -15,6 +15,7 @@ #include "fd-util.h" #include "in-addr-util.h" #include "io-util.h" +#include "memory-util.h" #include "network-common.h" #include "ordered-set.h" #include "siphash24.h" @@ -294,22 +295,29 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) { return 0; } -static int dhcp_server_send_unicast_raw(sd_dhcp_server *server, - DHCPPacket *packet, size_t len) { +static int dhcp_server_send_unicast_raw( + sd_dhcp_server *server, + uint8_t hlen, + const uint8_t *chaddr, + DHCPPacket *packet, + size_t len) { + union sockaddr_union link = { .ll.sll_family = AF_PACKET, .ll.sll_protocol = htobe16(ETH_P_IP), .ll.sll_ifindex = server->ifindex, - .ll.sll_halen = ETH_ALEN, + .ll.sll_halen = hlen, }; assert(server); assert(server->ifindex > 0); assert(server->address); + assert(hlen > 0); + assert(chaddr); assert(packet); assert(len > sizeof(DHCPPacket)); - memcpy(&link.ll.sll_addr, &packet->dhcp.chaddr, ETH_ALEN); + memcpy(link.ll.sll_addr, chaddr, hlen); dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER, packet->dhcp.yiaddr, @@ -378,8 +386,16 @@ static bool requested_broadcast(DHCPMessage *message) { return message->flags & htobe16(0x8000); } -static int dhcp_server_send(sd_dhcp_server *server, be32_t destination, uint16_t destination_port, - DHCPPacket *packet, size_t optoffset, bool l2_broadcast) { +static int dhcp_server_send( + sd_dhcp_server *server, + uint8_t hlen, + const uint8_t *chaddr, + be32_t destination, + uint16_t destination_port, + DHCPPacket *packet, + size_t optoffset, + bool l2_broadcast) { + if (destination != INADDR_ANY) return dhcp_server_send_udp(server, destination, destination_port, &packet->dhcp, @@ -392,7 +408,7 @@ static int dhcp_server_send(sd_dhcp_server *server, be32_t destination, uint16_t /* we cannot send UDP packet to specific MAC address when the address is not yet configured, so must fall back to raw packets */ - return dhcp_server_send_unicast_raw(server, packet, + return dhcp_server_send_unicast_raw(server, hlen, chaddr, packet, sizeof(DHCPPacket) + optoffset); } @@ -462,7 +478,8 @@ int dhcp_server_send_packet(sd_dhcp_server *server, destination = req->message->ciaddr; bool l2_broadcast = requested_broadcast(req->message) || type == DHCP_NAK; - return dhcp_server_send(server, destination, destination_port, packet, optoffset, l2_broadcast); + return dhcp_server_send(server, req->message->hlen, req->message->chaddr, + destination, destination_port, packet, optoffset, l2_broadcast); } static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret, @@ -482,14 +499,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); @@ -591,11 +608,7 @@ static int server_send_offer_or_ack( return r; } - r = dhcp_server_send_packet(server, req, packet, type, offset); - if (r < 0) - return r; - - return 0; + return dhcp_server_send_packet(server, req, packet, type, offset); } static int server_send_nak(sd_dhcp_server *server, DHCPRequest *req) { @@ -610,8 +623,14 @@ static int server_send_nak(sd_dhcp_server *server, DHCPRequest *req) { return dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset); } -static int server_send_forcerenew(sd_dhcp_server *server, be32_t address, - be32_t gateway, const uint8_t chaddr[]) { +static int server_send_forcerenew( + sd_dhcp_server *server, + be32_t address, + be32_t gateway, + uint8_t htype, + uint8_t hlen, + const uint8_t *chaddr) { + _cleanup_free_ DHCPPacket *packet = NULL; size_t optoffset = 0; int r; @@ -625,7 +644,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, htype, hlen, chaddr, DHCP_MIN_OPTIONS_SIZE, &optoffset); if (r < 0) return r; @@ -635,15 +654,9 @@ static int server_send_forcerenew(sd_dhcp_server *server, be32_t address, if (r < 0) return r; - memcpy(&packet->dhcp.chaddr, chaddr, ETH_ALEN); - - r = dhcp_server_send_udp(server, address, DHCP_PORT_CLIENT, - &packet->dhcp, - sizeof(DHCPMessage) + optoffset); - if (r < 0) - return r; - - return 0; + return dhcp_server_send_udp(server, address, DHCP_PORT_CLIENT, + &packet->dhcp, + sizeof(DHCPMessage) + optoffset); } static int parse_request(uint8_t code, uint8_t len, const void *option, void *userdata) { @@ -675,8 +688,7 @@ static int parse_request(uint8_t code, uint8_t len, const void *option, void *us if (!data) return -ENOMEM; - free(req->client_id.data); - req->client_id.data = data; + free_and_replace(req->client_id.data, data); req->client_id.length = len; } @@ -712,22 +724,45 @@ static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMes req->message = message; - /* set client id based on MAC address if client did not send an explicit - one */ + /* set client id based on MAC address if client did not send an explicit one */ if (!req->client_id.data) { - void *data; + uint8_t *data; - data = malloc0(ETH_ALEN + 1); + if (message->hlen == 0) + return -EBADMSG; + + data = new0(uint8_t, message->hlen + 1); if (!data) return -ENOMEM; - ((uint8_t*) data)[0] = 0x01; - memcpy((uint8_t*) data + 1, &message->chaddr, ETH_ALEN); + data[0] = 0x01; + memcpy(data + 1, message->chaddr, message->hlen); - req->client_id.length = ETH_ALEN + 1; + req->client_id.length = message->hlen + 1; req->client_id.data = data; } + if (message->hlen > sizeof(message->chaddr)) + return -EBADMSG; + + if (message->hlen == 0 || memeqzero(message->chaddr, message->hlen)) { + /* See RFC2131 section 4.1.1. + * hlen and chaddr may not be set for non-ethernet interface. + * Let's try to retrieve it from the client ID. */ + + if (!req->client_id.data) + return -EBADMSG; + + if (req->client_id.length <= 1 || req->client_id.length > sizeof(message->chaddr) + 1) + return -EBADMSG; + + if (req->client_id.data[0] != 0x01) + return -EBADMSG; + + message->hlen = req->client_id.length - 1; + memcpy(message->chaddr, req->client_id.data + 1, message->hlen); + } + if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE) req->max_optlen = DHCP_MIN_OPTIONS_SIZE; @@ -782,6 +817,10 @@ static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *messag assert(message); assert(sd_dhcp_server_is_in_relay_mode(server)); + if (message->hlen == 0 || message->hlen > sizeof(message->chaddr) || memeqzero(message->chaddr, message->hlen)) + return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EBADMSG), + "(relay agent) received message without/invalid hardware address, discarding."); + if (message->op == BOOTREQUEST) { log_dhcp_server(server, "(relay agent) BOOTREQUEST (0x%x)", be32toh(message->xid)); if (message->hops >= 16) @@ -821,7 +860,7 @@ static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *messag bool l2_broadcast = requested_broadcast(message) || message_type == DHCP_NAK; const be32_t destination = message_type == DHCP_NAK ? INADDR_ANY : message->ciaddr; - return dhcp_server_send(server, destination, DHCP_PORT_CLIENT, packet, opt_length, l2_broadcast); + return dhcp_server_send(server, message->hlen, message->chaddr, destination, DHCP_PORT_CLIENT, packet, opt_length, l2_broadcast); } return -EBADMSG; } @@ -830,7 +869,9 @@ static int prepare_new_lease( DHCPLease **ret_lease, be32_t address, const DHCPClientId *client_id, - const uint8_t chaddr[static ETH_ALEN], + uint8_t htype, + uint8_t hlen, + const uint8_t *chaddr, be32_t gateway, usec_t expiration) { @@ -843,6 +884,8 @@ static int prepare_new_lease( *lease = (DHCPLease) { .address = address, .client_id.length = client_id->length, + .htype = htype, + .hlen = hlen, .gateway = gateway, .expiration = expiration, }; @@ -850,7 +893,7 @@ static int prepare_new_lease( if (!lease->client_id.data) return -ENOMEM; - memcpy(&lease->chaddr, chaddr, ETH_ALEN); + memcpy(lease->chaddr, chaddr, hlen); *ret_lease = TAKE_PTR(lease); @@ -868,12 +911,11 @@ static int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server) { if (r < 0) return r; - HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) { + HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) if (lease->expiration < time_now) { log_dhcp_server(server, "CLEAN (0x%x)", be32toh(lease->address)); dhcp_lease_free(lease); } - } return 0; } @@ -900,9 +942,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz assert(server); assert(message); - if (message->op != BOOTREQUEST || - message->htype != ARPHRD_ETHER || - message->hlen != ETHER_ADDR_LEN) + if (message->op != BOOTREQUEST) return 0; req = new0(DHCPRequest, 1); @@ -915,7 +955,6 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz r = ensure_sane_request(server, req, message); if (r < 0) - /* this only fails on critical errors */ return r; r = dhcp_server_cleanup_expired_leases(server); @@ -1054,6 +1093,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz expiration = usec_add(req->lifetime * USEC_PER_SEC, time_now); r = prepare_new_lease(&lease, static_lease->address, &req->client_id, + req->message->htype, req->message->hlen, req->message->chaddr, req->message->giaddr, expiration); if (r < 0) return r; @@ -1097,6 +1137,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz if (!existing_lease) { r = prepare_new_lease(&new_lease, address, &req->client_id, + req->message->htype, req->message->hlen, req->message->chaddr, req->message->giaddr, expiration); if (r < 0) return r; @@ -1222,7 +1263,7 @@ static int server_receive_message(sd_event_source *s, int fd, if ((size_t) len < sizeof(DHCPMessage)) return 0; - CMSG_FOREACH(cmsg, &msg) { + CMSG_FOREACH(cmsg, &msg) if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO && cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) { @@ -1235,7 +1276,6 @@ static int server_receive_message(sd_event_source *s, int fd, break; } - } if (sd_dhcp_server_is_in_relay_mode(server)) { r = dhcp_server_relay_message(server, message, len - sizeof(DHCPMessage), buflen); @@ -1326,7 +1366,8 @@ int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { log_dhcp_server(server, "FORCERENEW"); HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) { - k = server_send_forcerenew(server, lease->address, lease->gateway, lease->chaddr); + k = server_send_forcerenew(server, lease->address, lease->gateway, + lease->htype, lease->hlen, lease->chaddr); if (k < 0) r = k; } @@ -1532,22 +1573,16 @@ int sd_dhcp_server_set_static_lease( assert_return(server, -EINVAL); assert_return(client_id, -EINVAL); - assert_return(client_id_size == ETH_ALEN + 1, -EINVAL); assert_return(!sd_dhcp_server_is_running(server), -EBUSY); /* Static lease with an empty or omitted address is a valid entry, - * the server removes any static lease with the specified mac address. */ + * the server removes any static lease with the specified mac address. */ if (!address || address->s_addr == 0) { - _cleanup_free_ void *data = NULL; DHCPClientId c; - data = memdup(client_id, client_id_size); - if (!data) - return -ENOMEM; - c = (DHCPClientId) { .length = client_id_size, - .data = data, + .data = client_id, }; dhcp_lease_free(hashmap_remove(server->static_leases_by_client_id, &c)); diff --git a/src/libsystemd-network/test-dhcp-option.c b/src/libsystemd-network/test-dhcp-option.c index 800a6641e3..b01b1f57a3 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); diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index a5c9bfeafb..b0e37352c3 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -137,12 +137,12 @@ static void test_message_handler(void) { assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER); test.message.htype = 0; - assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER); test.message.htype = ARPHRD_ETHER; assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER); test.message.hlen = 0; - assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == -EBADMSG); test.message.hlen = ETHER_ADDR_LEN; assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);