diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index 04a9893876..7c20e52209 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -7,6 +7,7 @@ #include "sd-dhcp-client.h" +#include "alloc-util.h" #include "dhcp-internal.h" #include "dhcp-protocol.h" #include "list.h" @@ -90,3 +91,6 @@ int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const vo int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease); int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len); + +#define dhcp_lease_unref_and_replace(a, b) \ + unref_and_replace_full(a, b, sd_dhcp_lease_ref, sd_dhcp_lease_unref) diff --git a/src/libsystemd-network/fuzz-dhcp-client.c b/src/libsystemd-network/fuzz-dhcp-client.c index d2bbf660b0..9b9e741e53 100644 --- a/src/libsystemd-network/fuzz-dhcp-client.c +++ b/src/libsystemd-network/fuzz-dhcp-client.c @@ -73,6 +73,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { res = sd_dhcp_client_start(client); assert_se(IN_SET(res, 0, -EINPROGRESS)); client->xid = 2; + client->state = DHCP_STATE_SELECTING; (void) client_handle_offer(client, (DHCPMessage*) data, size); diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 36b87fe84e..cfdee5d6b4 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1546,10 +1546,20 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) return client_initialize_time_events(client); } -static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, size_t len) { +static int client_parse_message( + sd_dhcp_client *client, + DHCPMessage *message, + size_t len, + sd_dhcp_lease **ret) { + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + _cleanup_free_ char *error_message = NULL; int r; + assert(client); + assert(message); + assert(ret); + r = dhcp_lease_new(&lease); if (r < 0) return r; @@ -1562,42 +1572,89 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, size_ return r; } - r = dhcp_option_parse(offer, len, dhcp_lease_parse_options, lease, NULL); - if (r != DHCP_OFFER) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received message was not an OFFER, ignoring"); + r = dhcp_option_parse(message, len, dhcp_lease_parse_options, lease, &error_message); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to parse DHCP options, ignoring: %m"); - lease->next_server = offer->siaddr; - lease->address = offer->yiaddr; + switch (client->state) { + case DHCP_STATE_SELECTING: + if (r != DHCP_OFFER) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), + "received message was not an OFFER, ignoring."); - if (lease->lifetime == 0 && client->fallback_lease_lifetime > 0) - lease->lifetime = client->fallback_lease_lifetime; + if (lease->lifetime == 0 && client->fallback_lease_lifetime > 0) + lease->lifetime = client->fallback_lease_lifetime; + + break; + + case DHCP_STATE_REBOOTING: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + if (r == DHCP_NAK) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EADDRNOTAVAIL), + "NAK: %s", strna(error_message)); + if (r != DHCP_ACK) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), + "received message was not an ACK, ignoring."); + break; + + default: + assert_not_reached(); + } + + lease->next_server = message->siaddr; + lease->address = message->yiaddr; if (lease->address == 0 || lease->server_address == 0 || lease->lifetime == 0) return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received lease lacks address, server address or lease lifetime, ignoring"); + "received lease lacks address, server address or lease lifetime, ignoring."); - if (!lease->have_subnet_mask) { - r = dhcp_lease_set_default_subnet_mask(lease); - if (r < 0) - return log_dhcp_client_errno( - client, SYNTHETIC_ERRNO(ENOMSG), - "received lease lacks subnet mask, and a fallback one cannot be generated, ignoring"); - } + r = dhcp_lease_set_default_subnet_mask(lease); + if (r < 0) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), + "received lease lacks subnet mask, and a fallback one cannot be generated, ignoring."); - sd_dhcp_lease_unref(client->lease); - client->lease = TAKE_PTR(lease); + *ret = TAKE_PTR(lease); + return 0; +} + +static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *message, size_t len) { + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + int r; + + assert(client); + assert(message); + + r = client_parse_message(client, message, len, &lease); + if (r < 0) + return r; + + dhcp_lease_unref_and_replace(client->lease, lease); if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0) return -ENOMSG; log_dhcp_client(client, "OFFER"); - return 0; } +static int client_enter_requesting(sd_dhcp_client *client) { + assert(client); + + client_set_state(client, DHCP_STATE_REQUESTING); + client->attempt = 0; + + return event_reset_time_relative(client->event, &client->timeout_resend, + CLOCK_BOOTTIME, + 0, 0, + client_timeout_resend, client, + client->event_priority, "dhcp4-resend-timer", + /* force_reset = */ true); +} + static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, size_t len) { int r; @@ -1634,64 +1691,27 @@ static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) { return true; } -static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t len) { +static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *message, size_t len) { _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; - _cleanup_free_ char *error_message = NULL; int r; - r = dhcp_lease_new(&lease); + assert(client); + assert(message); + + r = client_parse_message(client, message, len, &lease); if (r < 0) return r; - if (client->client_id_len > 0) { - r = dhcp_lease_set_client_id(lease, - (uint8_t *) &client->client_id, - client->client_id_len); - if (r < 0) - return r; - } + if (!client->lease) + r = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; + else if (lease_equal(client->lease, lease)) + r = SD_DHCP_CLIENT_EVENT_RENEW; + else + r = SD_DHCP_CLIENT_EVENT_IP_CHANGE; - r = dhcp_option_parse(ack, len, dhcp_lease_parse_options, lease, &error_message); - if (r == DHCP_NAK) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "NAK: %s", strna(error_message)); - - if (r != DHCP_ACK) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received message was not an ACK, ignoring"); - - lease->next_server = ack->siaddr; - - lease->address = ack->yiaddr; - - if (lease->address == INADDR_ANY || - lease->server_address == INADDR_ANY || - lease->lifetime == 0) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received lease lacks address, server address or lease lifetime, ignoring"); - - if (lease->subnet_mask == INADDR_ANY) { - r = dhcp_lease_set_default_subnet_mask(lease); - if (r < 0) - return log_dhcp_client_errno( - client, SYNTHETIC_ERRNO(ENOMSG), - "received lease lacks subnet mask, and a fallback one cannot be generated, ignoring"); - } - - r = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; - if (client->lease) { - if (lease_equal(client->lease, lease)) - r = SD_DHCP_CLIENT_EVENT_RENEW; - else - r = SD_DHCP_CLIENT_EVENT_IP_CHANGE; - - client->lease = sd_dhcp_lease_unref(client->lease); - } - - client->lease = TAKE_PTR(lease); + dhcp_lease_unref_and_replace(client->lease, lease); log_dhcp_client(client, "ACK"); - return r; } @@ -1798,9 +1818,48 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { return 0; } +static int client_enter_bound(sd_dhcp_client *client, int notify_event) { + int r; + + assert(client); + + if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING)) + notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; + + client->start_delay = 0; + (void) event_source_disable(client->timeout_resend); + + client_set_state(client, DHCP_STATE_BOUND); + client->attempt = 0; + + client->last_addr = client->lease->address; + + r = client_set_lease_timeouts(client); + if (r < 0) + log_dhcp_client_errno(client, r, "could not set lease timeouts: %m"); + + r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type); + if (r < 0) + return log_dhcp_client_errno(client, r, "could not bind UDP socket: %m"); + + client->receive_message = sd_event_source_disable_unref(client->receive_message); + close_and_replace(client->fd, r); + client_initialize_io_events(client, client_receive_message_udp); + + if (IN_SET(client->state, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING) && + notify_event == SD_DHCP_CLIENT_EVENT_IP_ACQUIRE) + /* FIXME: hmm, maybe this is a bug... */ + log_dhcp_client(client, "client_handle_ack() returned SD_DHCP_CLIENT_EVENT_IP_ACQUIRE while DHCP client is %s the address, skipping callback.", + client->state == DHCP_STATE_RENEWING ? "renewing" : "rebinding"); + else + client_notify(client, notify_event); + + return 0; +} + static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, int len) { DHCP_CLIENT_DONT_DESTROY(client); - int r, notify_event; + int r; assert(client); assert(client->event); @@ -1810,19 +1869,12 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i case DHCP_STATE_SELECTING: r = client_handle_offer(client, message, len); - if (r == -ENOMSG) - return 0; /* invalid message, let's ignore it */ - if (r < 0) + if (ERRNO_IS_NEG_RESOURCE(r)) goto error; + if (r < 0) + return 0; /* invalid message, let's ignore it */ - client_set_state(client, DHCP_STATE_REQUESTING); - client->attempt = 0; - - r = event_reset_time(client->event, &client->timeout_resend, - CLOCK_BOOTTIME, - 0, 0, - client_timeout_resend, client, - client->event_priority, "dhcp4-resend-timer", true); + r = client_enter_requesting(client); break; case DHCP_STATE_REBOOTING: @@ -1831,8 +1883,8 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i case DHCP_STATE_REBINDING: r = client_handle_ack(client, message, len); - if (r == -ENOMSG) - return 0; /* invalid message, let's ignore it */ + if (ERRNO_IS_NEG_RESOURCE(r)) + goto error; if (r == -EADDRNOTAVAIL) { /* got a NAK, let's restart the client */ client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); @@ -1852,46 +1904,9 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i return 0; } if (r < 0) - goto error; + return 0; /* invalid message, let's ignore it */ - if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING)) - notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; - else - notify_event = r; - - client->start_delay = 0; - (void) event_source_disable(client->timeout_resend); - client->receive_message = sd_event_source_disable_unref(client->receive_message); - client->fd = safe_close(client->fd); - - client_set_state(client, DHCP_STATE_BOUND); - client->attempt = 0; - - client->last_addr = client->lease->address; - - r = client_set_lease_timeouts(client); - if (r < 0) { - log_dhcp_client(client, "could not set lease timeouts"); - goto error; - } - - r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type); - if (r < 0) { - log_dhcp_client(client, "could not bind UDP socket"); - goto error; - } - - client->fd = r; - - client_initialize_io_events(client, client_receive_message_udp); - - if (IN_SET(client->state, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING) && - notify_event == SD_DHCP_CLIENT_EVENT_IP_ACQUIRE) - /* FIXME: hmm, maybe this is a bug... */ - log_dhcp_client(client, "client_handle_ack() returned SD_DHCP_CLIENT_EVENT_IP_ACQUIRE while DHCP client is %s the address, skipping callback.", - client->state == DHCP_STATE_RENEWING ? "renewing" : "rebinding"); - else - client_notify(client, notify_event); + r = client_enter_bound(client, r); break; case DHCP_STATE_BOUND: diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index e80425a34e..db8ac7947a 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -1490,6 +1490,9 @@ int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) { assert(lease); + if (lease->have_subnet_mask) + return 0; + if (lease->address == 0) return -ENODATA;