diff --git a/src/libsystemd-network/dhcp-server-lease-internal.h b/src/libsystemd-network/dhcp-server-lease-internal.h index 6d7eb4c213..024279ccbd 100644 --- a/src/libsystemd-network/dhcp-server-lease-internal.h +++ b/src/libsystemd-network/dhcp-server-lease-internal.h @@ -6,6 +6,7 @@ #include "dhcp-client-id-internal.h" #include "dhcp-server-internal.h" #include "dhcp-server-request.h" +#include "ether-addr-util.h" #include "sd-forward.h" typedef struct sd_dhcp_server_lease { @@ -16,10 +17,9 @@ typedef struct sd_dhcp_server_lease { sd_dhcp_client_id client_id; uint8_t htype; /* e.g. ARPHRD_ETHER */ - uint8_t hlen; /* e.g. ETH_ALEN */ + struct hw_addr_data hw_addr; be32_t address; be32_t gateway; - uint8_t chaddr[16]; usec_t expiration; char *hostname; } sd_dhcp_server_lease; diff --git a/src/libsystemd-network/dhcp-server-request.c b/src/libsystemd-network/dhcp-server-request.c index 39c11e69c8..768677e65e 100644 --- a/src/libsystemd-network/dhcp-server-request.c +++ b/src/libsystemd-network/dhcp-server-request.c @@ -54,8 +54,6 @@ int dhcp_request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_ } static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) { - int r; - assert(req); assert(message); @@ -64,41 +62,26 @@ static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMes if (message->hlen > sizeof(message->chaddr)) return -EBADMSG; - /* set client id based on MAC address if client did not send an explicit one */ - if (!sd_dhcp_client_id_is_set(&req->client_id)) { - if (!client_id_data_size_is_valid(message->hlen)) - return -EBADMSG; + req->hw_addr.length = req->message->hlen; + memcpy_safe(req->hw_addr.bytes, message->chaddr, message->hlen); - r = sd_dhcp_client_id_set(&req->client_id, /* type= */ 1, message->chaddr, message->hlen); - if (r < 0) - return r; - } + /* Fake client ID generated from the DHCP header. + * The client ID type 0 and 255 are special. So do not use if htype is 0 or 255. + * Note, Some hardware type (e.g. Infiniband) may not set chaddr field. */ + if (!IN_SET(req->message->htype, 0, UINT8_MAX)) + (void) sd_dhcp_client_id_set( + &req->client_id_by_header, + req->message->htype, + req->message->chaddr, + req->message->hlen); - if (message->hlen == 0 || memeqzero(message->chaddr, message->hlen)) { - uint8_t type; - const void *data; - size_t size; + /* If Client Identifier option is unspecified, use the generated one. */ + if (!sd_dhcp_client_id_is_set(&req->client_id)) + req->client_id = req->client_id_by_header; - /* 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 (!sd_dhcp_client_id_is_set(&req->client_id)) - return -EBADMSG; - - r = sd_dhcp_client_id_get(&req->client_id, &type, &data, &size); - if (r < 0) - return r; - - if (type != 1) - return -EBADMSG; - - if (size > sizeof(message->chaddr)) - return -EBADMSG; - - memcpy(message->chaddr, data, size); - message->hlen = size; - } + /* We manage bound leases by client ID. Hence, at least one of them is necessary. */ + if (!sd_dhcp_client_id_is_set(&req->client_id)) + return -EBADMSG; if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE) req->max_optlen = DHCP_MIN_OPTIONS_SIZE; diff --git a/src/libsystemd-network/dhcp-server-request.h b/src/libsystemd-network/dhcp-server-request.h index f1e827a0e1..c9ffe2aba8 100644 --- a/src/libsystemd-network/dhcp-server-request.h +++ b/src/libsystemd-network/dhcp-server-request.h @@ -3,6 +3,7 @@ #include "dhcp-client-id-internal.h" #include "dhcp-protocol.h" +#include "ether-addr-util.h" #include "sd-forward.h" #include "sparse-endian.h" #include "time-util.h" @@ -10,10 +11,13 @@ typedef struct DHCPRequest { /* received message */ DHCPMessage *message; + /* sender hardware address, may not be set for non-ethernet interface */ + struct hw_addr_data hw_addr; triple_timestamp timestamp; /* options */ sd_dhcp_client_id client_id; + sd_dhcp_client_id client_id_by_header; size_t max_optlen; be32_t server_id; be32_t requested_ip; diff --git a/src/libsystemd-network/dhcp-server-send.c b/src/libsystemd-network/dhcp-server-send.c index a7b827f56e..3fd548392d 100644 --- a/src/libsystemd-network/dhcp-server-send.c +++ b/src/libsystemd-network/dhcp-server-send.c @@ -44,8 +44,7 @@ static int server_acquire_raw_socket(sd_dhcp_server *server) { static int dhcp_server_send_unicast_raw( sd_dhcp_server *server, - uint8_t hlen, - const uint8_t *chaddr, + const struct hw_addr_data *hw_addr, DHCPPacket *packet, size_t len) { @@ -54,8 +53,7 @@ static int dhcp_server_send_unicast_raw( assert(server); assert(server->ifindex > 0); assert(server->address != 0); - assert(hlen > 0); - assert(chaddr); + assert(hw_addr); assert(packet); assert(len > sizeof(DHCPPacket)); @@ -81,10 +79,10 @@ static int dhcp_server_send_unicast_raw( .ll.sll_family = AF_PACKET, .ll.sll_protocol = htobe16(ETH_P_IP), .ll.sll_ifindex = server->ifindex, - .ll.sll_halen = hlen, + .ll.sll_halen = hw_addr->length, }; - memcpy(sa.ll.sll_addr, chaddr, hlen); + memcpy_safe(sa.ll.sll_addr, hw_addr->bytes, hw_addr->length); struct msghdr mh = { .msg_name = &sa.sa, @@ -192,7 +190,7 @@ static int dhcp_server_send_message( * InfiniBand. In that case, we cannot unicast in the below, so need to broadcast. Also, broadcast * the message if 'yiaddr' is zero.) */ if (FLAGS_SET(be16toh(req->message->flags), 0x8000) || - req->message->hlen == 0 || memeqzero(req->message->chaddr, req->message->hlen) || + hw_addr_is_null(&req->hw_addr) || packet->dhcp.yiaddr == INADDR_ANY) return dhcp_server_send_udp( server, @@ -205,8 +203,7 @@ static int dhcp_server_send_message( * unicasts DHCPOFFER and DHCPACK messages to the client’s hardware address and ’yiaddr’ address. */ return dhcp_server_send_unicast_raw( server, - req->message->hlen, - req->message->chaddr, + &req->hw_addr, packet, sizeof(DHCPPacket) + optoffset); } @@ -269,7 +266,7 @@ static int server_message_init( r = dhcp_message_init(&packet->dhcp, BOOTREPLY, be32toh(req->message->xid), - req->message->htype, req->message->hlen, req->message->chaddr, + req->message->htype, req->hw_addr.length, req->hw_addr.bytes, type, req->max_optlen, &optoffset); if (r < 0) return r; @@ -534,28 +531,23 @@ int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequ return DHCP_NAK; } -static int server_send_forcerenew( +static int dhcp_server_send_forcerenew( sd_dhcp_server *server, - be32_t address, - be32_t gateway, - uint8_t htype, - uint8_t hlen, - const uint8_t *chaddr) { + sd_dhcp_server_lease *lease) { _cleanup_free_ DHCPPacket *packet = NULL; size_t optoffset = 0; int r; assert(server); - assert(address != INADDR_ANY); - assert(chaddr); + assert(lease); packet = malloc0(sizeof(DHCPPacket) + DHCP_MIN_OPTIONS_SIZE); if (!packet) return -ENOMEM; r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0, - htype, hlen, chaddr, DHCP_FORCERENEW, + lease->htype, lease->hw_addr.length, lease->hw_addr.bytes, DHCP_FORCERENEW, DHCP_MIN_OPTIONS_SIZE, &optoffset); if (r < 0) return r; @@ -565,7 +557,7 @@ static int server_send_forcerenew( if (r < 0) return r; - return dhcp_server_send_udp(server, address, DHCP_PORT_CLIENT, + return dhcp_server_send_udp(server, lease->address, DHCP_PORT_CLIENT, &packet->dhcp, sizeof(DHCPMessage) + optoffset); } @@ -579,8 +571,6 @@ int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { log_dhcp_server(server, "FORCERENEW"); HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) - RET_GATHER(r, - server_send_forcerenew(server, lease->address, lease->gateway, - lease->htype, lease->hlen, lease->chaddr)); + RET_GATHER(r, dhcp_server_send_forcerenew(server, lease)); return r; } diff --git a/src/libsystemd-network/fuzz-dhcp-server.c b/src/libsystemd-network/fuzz-dhcp-server.c index ac006bc2aa..34c1f8371a 100644 --- a/src/libsystemd-network/fuzz-dhcp-server.c +++ b/src/libsystemd-network/fuzz-dhcp-server.c @@ -27,10 +27,10 @@ static int add_lease(sd_dhcp_server *server, const struct in_addr *server_addres *lease = (sd_dhcp_server_lease) { .n_ref = 1, .address = htobe32(UINT32_C(10) << 24 | i), - .chaddr = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, + .hw_addr.length = ETH_ALEN, + .hw_addr.bytes = { 3, 3, 3, 3, 3, 3, }, .expiration = usec_add(now(CLOCK_BOOTTIME), USEC_PER_DAY), .gateway = server_address->s_addr, - .hlen = ETH_ALEN, .htype = ARPHRD_ETHER, .client_id.size = 2, diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c index a7d9947c29..58b5831e44 100644 --- a/src/libsystemd-network/sd-dhcp-server-lease.c +++ b/src/libsystemd-network/sd-dhcp-server-lease.c @@ -16,7 +16,6 @@ #include "iovec-util.h" #include "json-util.h" #include "mkdir.h" -#include "string-util.h" #include "tmpfile-util.h" static sd_dhcp_server_lease* dhcp_server_lease_free(sd_dhcp_server_lease *lease) { @@ -103,12 +102,11 @@ int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *r .address = address, .client_id = req->client_id, .htype = req->message->htype, - .hlen = req->message->hlen, .gateway = req->message->giaddr, .expiration = expiration, }; - memcpy(lease->chaddr, req->message->chaddr, req->message->hlen); + lease->hw_addr = req->hw_addr; if (req->hostname) { lease->hostname = strdup(req->hostname); @@ -146,27 +144,17 @@ int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server) { sd_dhcp_server_lease* dhcp_server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req) { sd_dhcp_server_lease *static_lease; - sd_dhcp_client_id client_id; assert(server); assert(req); static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id); - if (static_lease) - goto verify; - - /* when no lease is found based on the client id fall back to chaddr */ - if (!client_id_data_size_is_valid(req->message->hlen)) - return NULL; - - if (sd_dhcp_client_id_set(&client_id, /* type= */ 1, req->message->chaddr, req->message->hlen) < 0) - return NULL; - - static_lease = hashmap_get(server->static_leases_by_client_id, &client_id); + if (!static_lease && sd_dhcp_client_id_is_set(&req->client_id_by_header)) + /* when no lease is found, fall back to use the fake client ID generated from the header. */ + static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id_by_header); if (!static_lease) return NULL; -verify: /* Check if the address is in the same subnet. */ if ((static_lease->address & server->netmask) != server->subnet) return NULL; @@ -247,8 +235,8 @@ static int dhcp_server_lease_build_json(sd_dhcp_server_lease *lease, sd_json_var JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING_NON_NULL("Address", &(struct in_addr) { .s_addr = lease->address }), JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", lease->hostname), SD_JSON_BUILD_PAIR_UNSIGNED("HardwareAddressType", lease->htype), - SD_JSON_BUILD_PAIR_UNSIGNED("HardwareAddressLength", lease->hlen), - SD_JSON_BUILD_PAIR_BYTE_ARRAY("HardwareAddress", lease->chaddr, sizeof(lease->chaddr))); + SD_JSON_BUILD_PAIR_UNSIGNED("HardwareAddressLength", lease->hw_addr.length), + SD_JSON_BUILD_PAIR_BYTE_ARRAY("HardwareAddress", lease->hw_addr.bytes, lease->hw_addr.length)); } int dhcp_server_bound_leases_append_json(sd_dhcp_server *server, sd_json_variant **v) { @@ -376,70 +364,67 @@ int dhcp_server_save_leases(sd_dhcp_server *server) { return 0; } -static int json_dispatch_chaddr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { - uint8_t* address = ASSERT_PTR(userdata); - _cleanup_(iovec_done) struct iovec iov = {}; - int r; +typedef struct LeaseParam { + sd_dhcp_client_id client_id; + uint8_t htype; + uint8_t hlen; + struct iovec hw_addr; + struct in_addr address; + usec_t exp_b; + usec_t exp_r; + char *hostname; +} LeaseParam; - r = json_dispatch_byte_array_iovec(name, variant, flags, &iov); - if (r < 0) - return r; +static void lease_param_done(LeaseParam *p) { + assert(p); - if (iov.iov_len != 16) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name)); - - memcpy(address, iov.iov_base, iov.iov_len); - return 0; + iovec_done(&p->hw_addr); + free(p->hostname); } static int json_dispatch_dhcp_lease(sd_dhcp_server *server, sd_json_variant *v, bool use_boottime) { - static const sd_json_dispatch_field dispatch_table_boottime[] = { - { "ClientId", SD_JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(sd_dhcp_server_lease, client_id), SD_JSON_MANDATORY }, - { "Address", SD_JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(sd_dhcp_server_lease, address), SD_JSON_MANDATORY }, - { "Hostname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(sd_dhcp_server_lease, hostname), 0 }, - { "HardwareAddressType", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, htype), 0 }, - { "HardwareAddressLength", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, hlen), 0 }, - { "HardwareAddress", SD_JSON_VARIANT_ARRAY, json_dispatch_chaddr, offsetof(sd_dhcp_server_lease, chaddr), 0 }, - { "ExpirationUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(sd_dhcp_server_lease, expiration), SD_JSON_MANDATORY }, - { "ExpirationRealtimeUSec", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, SD_JSON_MANDATORY }, - {} - }, dispatch_table_realtime[] = { - { "ClientId", SD_JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(sd_dhcp_server_lease, client_id), SD_JSON_MANDATORY }, - { "Address", SD_JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(sd_dhcp_server_lease, address), SD_JSON_MANDATORY }, - { "Hostname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(sd_dhcp_server_lease, hostname), 0 }, - { "HardwareAddressType", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, htype), 0 }, - { "HardwareAddressLength", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, hlen), 0 }, - { "HardwareAddress", SD_JSON_VARIANT_ARRAY, json_dispatch_chaddr, offsetof(sd_dhcp_server_lease, chaddr), 0 }, - { "ExpirationUSec", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, SD_JSON_MANDATORY }, - { "ExpirationRealtimeUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(sd_dhcp_server_lease, expiration), SD_JSON_MANDATORY }, + static const sd_json_dispatch_field dispatch_table[] = { + { "ClientId", SD_JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(LeaseParam, client_id), SD_JSON_MANDATORY }, + { "HardwareAddressType", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(LeaseParam, htype), 0 }, + { "HardwareAddressLength", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(LeaseParam, hlen), 0 }, + { "HardwareAddress", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(LeaseParam, hw_addr), 0 }, + { "Address", SD_JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(LeaseParam, address), SD_JSON_MANDATORY }, + { "AddressString", SD_JSON_VARIANT_STRING, NULL, 0, 0 }, + { "ExpirationUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LeaseParam, exp_b), SD_JSON_MANDATORY }, + { "ExpirationRealtimeUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LeaseParam, exp_r), SD_JSON_MANDATORY }, + { "Hostname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(LeaseParam, hostname), 0 }, {} }; - _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL; - usec_t now_b; int r; assert(server); assert(v); - lease = new(sd_dhcp_server_lease, 1); - if (!lease) - return -ENOMEM; - - *lease = (sd_dhcp_server_lease) { - .n_ref = 1, - }; - - r = sd_json_dispatch(v, use_boottime ? dispatch_table_boottime : dispatch_table_realtime, SD_JSON_ALLOW_EXTENSIONS, lease); + _cleanup_(lease_param_done) LeaseParam p = {}; + r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); if (r < 0) return r; + if (p.hlen > HW_ADDR_MAX_SIZE) + return -EINVAL; + + if (p.hlen > p.hw_addr.iov_len) + return -EINVAL; + + if (!in4_addr_is_set(&p.address)) + return -EINVAL; + + if (!sd_dhcp_client_id_is_set(&p.client_id)) + return -EINVAL; + + usec_t now_b; r = sd_event_now(server->event, CLOCK_BOOTTIME, &now_b); if (r < 0) return r; if (use_boottime) { - if (lease->expiration < now_b) + if (p.exp_b < now_b) return 0; /* already expired */ } else { usec_t now_r; @@ -448,12 +433,29 @@ static int json_dispatch_dhcp_lease(sd_dhcp_server *server, sd_json_variant *v, if (r < 0) return r; - if (lease->expiration < now_r) + if (p.exp_r < now_r) return 0; /* already expired */ - lease->expiration = map_clock_usec_raw(lease->expiration, now_r, now_b); + p.exp_b = map_clock_usec_raw(p.exp_r, now_r, now_b); } + _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = new(sd_dhcp_server_lease, 1); + if (!lease) + return -ENOMEM; + + *lease = (sd_dhcp_server_lease) { + .n_ref = 1, + + .client_id = p.client_id, + .htype = p.htype, + .hw_addr.length = p.hlen, + .address = p.address.s_addr, + .expiration = p.exp_b, + .hostname = TAKE_PTR(p.hostname), + }; + + memcpy_safe(lease->hw_addr.bytes, p.hw_addr.iov_base, p.hlen); + r = dhcp_server_put_lease(server, lease, /* is_static= */ false); if (r == -EEXIST) return 0; diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index bc9ab1e9ac..a4f025bd41 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -152,8 +152,9 @@ TEST(dhcp_server_handle_message) { test.header.op = BOOTREQUEST; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); + /* Neither client ID nor hardware type is set. There is no way to manage the bound lease for the request. */ test.header.htype = 0; - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); + ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), EBADMSG); test.header.htype = ARPHRD_ETHER; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); diff --git a/src/network/networkd-dhcp-server-bus.c b/src/network/networkd-dhcp-server-bus.c index 58e0a26f22..d5829b6dfb 100644 --- a/src/network/networkd-dhcp-server-bus.c +++ b/src/network/networkd-dhcp-server-bus.c @@ -55,7 +55,7 @@ static int property_get_leases( if (r < 0) return r; - r = sd_bus_message_append_array(reply, 'y', &lease->chaddr, sizeof(lease->chaddr)); + r = sd_bus_message_append_array(reply, 'y', &lease->hw_addr.bytes, lease->hw_addr.length); if (r < 0) return r;