]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-dhcp-client: use dhcp_client_parse_message()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 1 Apr 2026 13:45:04 +0000 (22:45 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Sat, 16 May 2026 13:34:25 +0000 (22:34 +0900)
This also unify two IO event source handlers.

src/libsystemd-network/sd-dhcp-client.c

index e2513567a2f58d3ce30c6065f12d508e6238a2b0..afe40ee09afa3c3fa2927f812a163d56e83c72c2 100644 (file)
 #include "dhcp-client-internal.h"
 #include "dhcp-client-send.h"
 #include "dhcp-lease-internal.h"
-#include "dhcp-option.h"
-#include "dhcp-packet.h"
 #include "dns-domain.h"
 #include "errno-util.h"
 #include "event-util.h"
 #include "hostname-util.h"
 #include "iovec-util.h"
 #include "iovec-wrapper.h"
-#include "memory-util.h"
+#include "ip-util.h"
 #include "network-common.h"
 #include "random-util.h"
 #include "set.h"
@@ -131,12 +129,6 @@ int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) {
         return set_ensure_put(&client->req_opts, NULL, UINT8_TO_PTR(option));
 }
 
-static int client_request_contains(sd_dhcp_client *client, uint8_t option) {
-        assert(client);
-
-        return set_contains(client->req_opts, UINT8_TO_PTR(option));
-}
-
 int sd_dhcp_client_set_request_address(
                 sd_dhcp_client *client,
                 const struct in_addr *last_addr) {
@@ -947,195 +939,6 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata)
         return client_timeout_resend(s, usec, userdata);
 }
 
-static int dhcp_option_parse_and_verify(
-                sd_dhcp_client *client,
-                DHCPMessage *message,
-                size_t len,
-                sd_dhcp_lease *lease) {
-
-        _cleanup_free_ char *error_message = NULL;
-        int r;
-
-        assert(client);
-        assert(message);
-        assert(lease);
-
-        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");
-
-        switch (client->state) {
-        case DHCP_STATE_SELECTING:
-                if (r == DHCP_ACK) {
-                        if (!client->rapid_commit)
-                                return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
-                                                             "received unexpected ACK, ignoring.");
-                        if (!lease->rapid_commit)
-                                return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
-                                                             "received rapid ACK without Rapid Commit option, ignoring.");
-                } else if (r == DHCP_OFFER) {
-                        if (lease->rapid_commit) {
-                                /* Some RFC incompliant servers provides an OFFER with a rapid commit option.
-                                 * See https://github.com/systemd/systemd/issues/29904.
-                                 * Let's support such servers gracefully. */
-                                log_dhcp_client(client, "received OFFER with Rapid Commit option, ignoring.");
-                                lease->rapid_commit = false;
-                        }
-                        if (lease->lifetime == 0 && client->fallback_lease_lifetime > 0)
-                                lease->lifetime = client->fallback_lease_lifetime;
-                } else
-                        return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
-                                                     "received unexpected message, ignoring.");
-
-                break;
-
-        case DHCP_STATE_REBOOTING:
-        case DHCP_STATE_REQUESTING:
-        case DHCP_STATE_RENEWING:
-        case DHCP_STATE_REBINDING:
-                if (r == DHCP_NAK) {
-                        if (client->lease && client->lease->server_address != lease->server_address)
-                                    return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
-                                                                 "NAK from unexpected server, ignoring: %s",
-                                                                 strna(error_message));
-                        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.");
-
-        return 0;
-}
-
-static int bootp_option_parse_and_verify(
-                sd_dhcp_client *client,
-                DHCPMessage *message,
-                size_t len,
-                sd_dhcp_lease *lease) {
-
-        int r;
-
-        assert(client);
-        assert(message);
-        assert(lease);
-
-        r = dhcp_option_parse(message, len, dhcp_lease_parse_options, lease, /* ret_error_message= */ NULL);
-        if (r == -ENOMSG)
-                r = DHCP_ACK; /* BOOTP messages don't have a DHCP message type option */
-        else if (r < 0)
-                return log_dhcp_client_errno(client, r, "Failed to parse BOOTP options, ignoring: %m");
-        else
-                return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), "Received unexpected message, ignoring.");
-
-        log_dhcp_client(client, "BOOTP identified, using infinite lease. BOOTP siaddr=(%#x), DHCP Server Identifier=(%#x)",
-                        message->siaddr, lease->server_address);
-
-        lease->lifetime = USEC_INFINITY;
-        lease->address = message->yiaddr;
-        if (lease->server_address == 0)
-                lease->server_address = message->siaddr;
-
-        /* BOOTP protocol does not have any OFFER and REQUEST process. Hence, it is mostly equivalent to
-         * Rapid Commit process in DHCP. */
-        lease->rapid_commit = true;
-
-        if (lease->address == 0)
-                return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), "received lease lacks address, ignoring.");
-
-        return 0;
-}
-
-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;
-        int r;
-
-        assert(client);
-        assert(message);
-        assert(ret);
-
-        r = dhcp_lease_new(&lease);
-        if (r < 0)
-                return r;
-
-        if (sd_dhcp_client_id_is_set(&client->client_id)) {
-                r = dhcp_lease_set_client_id(lease, &client->client_id);
-                if (r < 0)
-                        return r;
-        }
-
-        if (client->bootp)
-                r = bootp_option_parse_and_verify(client, message, len, lease);
-        else
-                r = dhcp_option_parse_and_verify(client, message, len, lease);
-        if (r < 0)
-                return r;
-
-        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.");
-
-        /* RFC 8925 section 3.2
-         * If the client did not include the IPv6-Only Preferred option code in the Parameter Request List in
-         * the DHCPDISCOVER or DHCPREQUEST message, it MUST ignore the IPv6-Only Preferred option in any
-         * messages received from the server. */
-        if (lease->ipv6_only_preferred_usec > 0 &&
-            !client_request_contains(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)) {
-                log_dhcp_client(client, "Received message with unrequested IPv6-only preferred option, ignoring the option.");
-                lease->ipv6_only_preferred_usec = 0;
-        }
-
-        *ret = TAKE_PTR(lease);
-        return 0;
-}
-
-static int client_handle_offer_or_rapid_ack(sd_dhcp_client *client, DHCPMessage *message, size_t len, const triple_timestamp *timestamp) {
-        _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_set_timestamp(lease, timestamp);
-
-        dhcp_lease_unref_and_replace(client->lease, lease);
-
-        if (client->lease->rapid_commit) {
-                log_dhcp_client(client, "ACK");
-                return SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
-        }
-
-        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);
         assert(client->lease);
@@ -1186,32 +989,6 @@ 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 *message, size_t len, const triple_timestamp *timestamp) {
-        _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_set_timestamp(lease, timestamp);
-
-        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;
-
-        dhcp_lease_unref_and_replace(client->lease, lease);
-
-        log_dhcp_client(client, "ACK");
-        return r;
-}
-
 static int client_set_lease_timeouts(sd_dhcp_client *client) {
         usec_t time_now;
         int r;
@@ -1319,14 +1096,32 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
         return 0;
 }
 
-static int client_enter_bound(sd_dhcp_client *client, int notify_event) {
+static int client_enter_bound(sd_dhcp_client *client, sd_dhcp_lease *lease) {
         int r;
 
         assert(client);
-        assert(client->lease);
+        assert(lease);
 
-        if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING))
+        int notify_event;
+        switch (client->state) {
+        case DHCP_STATE_SELECTING:
+        case DHCP_STATE_REQUESTING:
+        case DHCP_STATE_REBOOTING:
                 notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
+                break;
+        case DHCP_STATE_RENEWING:
+        case DHCP_STATE_REBINDING:
+                assert(client->lease);
+                if (lease_equal(client->lease, lease))
+                        notify_event = SD_DHCP_CLIENT_EVENT_RENEW;
+                else
+                        notify_event = SD_DHCP_CLIENT_EVENT_IP_CHANGE;
+                break;
+        default:
+                assert_not_reached();
+        }
+
+        unref_and_replace_new_ref(client->lease, lease, sd_dhcp_lease_ref, sd_dhcp_lease_unref);
 
         client_disable_event_sources(client);
 
@@ -1344,136 +1139,51 @@ static int client_enter_bound(sd_dhcp_client *client, int notify_event) {
         return 0;
 }
 
-static int client_verify_message_header(sd_dhcp_client *client, DHCPMessage *message, size_t len) {
-        const uint8_t *expected_chaddr = NULL;
-        uint8_t expected_hlen = 0;
-
-        assert(client);
-        assert(message);
-
-        if (len < sizeof(DHCPMessage))
-                return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG),
-                                             "Too small to be a DHCP message, ignoring.");
-
-        if (be32toh(message->magic) != DHCP_MAGIC_COOKIE)
-                return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG),
-                                             "Not a DHCP message, ignoring.");
-
-        if (message->op != BOOTREPLY)
-                return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG),
-                                             "Not a BOOTREPLY message, ignoring.");
-
-        if (message->htype != client->arp_type)
-                return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG),
-                                             "Packet type does not match client type, ignoring.");
-
-        if (client->arp_type == ARPHRD_ETHER) {
-                expected_hlen = ETH_ALEN;
-                expected_chaddr = client->hw_addr.bytes;
-        }
-
-        if (message->hlen != expected_hlen)
-                return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG),
-                                             "Received packet hlen (%u) does not match expected (%u), ignoring.",
-                                             message->hlen, expected_hlen);
-
-        if (memcmp_safe(message->chaddr, expected_chaddr, expected_hlen))
-                return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG),
-                                             "Received chaddr does not match expected, ignoring.");
-
-        if (be32toh(message->xid) != client->xid)
-                return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG),
-                                             "Received xid (%u) does not match expected (%u), ignoring.",
-                                             be32toh(message->xid), client->xid);
-
-        return 0;
-}
-
-static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, size_t len, const triple_timestamp *timestamp) {
+static int client_handle_message(sd_dhcp_client *client, const struct iovec *iov, const triple_timestamp *timestamp) {
         DHCP_CLIENT_DONT_DESTROY(client);
         int r;
 
         assert(client);
-        assert(message);
-        assert(timestamp);
-
-        if (client_verify_message_header(client, message, len) < 0)
-                return 0;
+        assert(iov);
 
-        switch (client->state) {
-        case DHCP_STATE_SELECTING:
+        _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+        r = dhcp_client_parse_message(client, iov, &lease);
+        if (ERRNO_IS_NEG_RESOURCE(r))
+                return r;
+        if (r < 0)
+                return 0; /* Ignore all parse errors. */
 
-                r = client_handle_offer_or_rapid_ack(client, message, len, timestamp);
-                if (ERRNO_IS_NEG_RESOURCE(r))
-                        return r;
-                if (r == -EADDRNOTAVAIL)
-                        /* got a rapid NAK, let's restart the client */
-                        return client_restart(client);
-                if (r < 0)
-                        return 0; /* invalid message, let's ignore it */
+        switch (r) {
 
-                if (client->lease->rapid_commit)
-                        /* got a successful rapid commit */
-                        return client_enter_bound(client, r);
+        case DHCP_OFFER:
+                dhcp_lease_set_timestamp(lease, timestamp);
 
+                unref_and_replace_new_ref(client->lease, lease, sd_dhcp_lease_ref, sd_dhcp_lease_unref);
+                if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0)
+                        return 0; /* networkd refused the server, ignoring the message. */
+                if (client->state == DHCP_STATE_STOPPED)
+                        return 0; /* The notify callback stopped the client. */
                 return client_enter_requesting(client);
 
-        case DHCP_STATE_REBOOTING:
-        case DHCP_STATE_REQUESTING:
-        case DHCP_STATE_RENEWING:
-        case DHCP_STATE_REBINDING:
-
-                r = client_handle_ack(client, message, len, timestamp);
-                if (ERRNO_IS_NEG_RESOURCE(r))
-                        return r;
-                if (r == -EADDRNOTAVAIL)
-                        /* got a NAK, let's restart the client */
-                        return client_restart(client);
-                if (r < 0)
-                        return 0; /* invalid message, let's ignore it */
-
-                return client_enter_bound(client, r);
+        case DHCP_ACK:
+                dhcp_lease_set_timestamp(lease, timestamp);
+                return client_enter_bound(client, lease);
 
-        case DHCP_STATE_BOUND:
-                log_dhcp_client(client, "Unexpected DHCP message received in BOUND state, ignoring.");
-                return 0;
-
-        case DHCP_STATE_INIT:
-        case DHCP_STATE_INIT_REBOOT:
-                log_dhcp_client(client, "Unexpectedly receive message without sending any requests, ignoring.");
-                return 0;
+        case DHCP_NAK:
+                return client_restart(client);
 
         default:
                 assert_not_reached();
         }
-
-        return 0;
 }
 
-int client_receive_message_udp(
-                sd_event_source *s,
-                int fd,
-                uint32_t revents,
-                void *userdata) {
-
-        sd_dhcp_client *client = ASSERT_PTR(userdata);
-        _cleanup_free_ DHCPMessage *message = NULL;
-        ssize_t len, buflen;
-        /* This needs to be initialized with zero. See #20741.
-         * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */
-        CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL) control = {};
-        struct iovec iov;
-        struct msghdr msg = {
-                .msg_iov = &iov,
-                .msg_iovlen = 1,
-                .msg_control = &control,
-                .msg_controllen = sizeof(control),
-        };
+static int client_receive_message(sd_dhcp_client *client, int fd, bool raw) {
         int r;
 
-        assert(s);
+        assert(client);
+        assert(fd >= 0);
 
-        buflen = next_datagram_size_fd(fd);
+        ssize_t buflen = next_datagram_size_fd(fd);
         if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen))
                 return 0;
         if (buflen < 0) {
@@ -1481,92 +1191,56 @@ int client_receive_message_udp(
                 return 0;
         }
 
-        message = malloc0(buflen);
-        if (!message)
+        _cleanup_free_ void *buf = malloc0(buflen);
+        if (!buf)
                 return -ENOMEM;
 
-        iov = IOVEC_MAKE(message, buflen);
-
-        len = recvmsg_safe(fd, &msg, MSG_DONTWAIT);
-        if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len))
-                return 0;
-        if (len < 0) {
-                log_dhcp_client_errno(client, len, "Could not receive message from UDP socket, ignoring: %m");
-                return 0;
-        }
-
-        log_dhcp_client(client, "Received message from UDP socket, processing.");
-        r = client_handle_message(client, message, len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg));
-        if (r < 0)
-                client_stop(client, r);
-
-        return 0;
-}
-
-int client_receive_message_raw(
-                sd_event_source *s,
-                int fd,
-                uint32_t revents,
-                void *userdata) {
-
-        sd_dhcp_client *client = ASSERT_PTR(userdata);
-        _cleanup_free_ DHCPPacket *packet = NULL;
         /* This needs to be initialized with zero. See #20741.
          * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */
         CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL +
                          CMSG_SPACE(sizeof(struct tpacket_auxdata))) control = {};
-        struct iovec iov = {};
         struct msghdr msg = {
-                .msg_iov = &iov,
+                .msg_iov = &IOVEC_MAKE(buf, buflen),
                 .msg_iovlen = 1,
                 .msg_control = &control,
                 .msg_controllen = sizeof(control),
         };
-        bool checksum = true;
-        ssize_t buflen, len;
-        int r;
-
-        assert(s);
-
-        buflen = next_datagram_size_fd(fd);
-        if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen))
-                return 0;
-        if (buflen < 0) {
-                log_dhcp_client_errno(client, buflen, "Failed to determine datagram size to read, ignoring: %m");
-                return 0;
-        }
-
-        packet = malloc0(buflen);
-        if (!packet)
-                return -ENOMEM;
 
-        iov = IOVEC_MAKE(packet, buflen);
-
-        len = recvmsg_safe(fd, &msg, 0);
+        ssize_t len = recvmsg_safe(fd, &msg, MSG_DONTWAIT);
         if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len))
                 return 0;
         if (len < 0) {
-                log_dhcp_client_errno(client, len, "Could not receive message from raw socket, ignoring: %m");
+                log_dhcp_client_errno(client, len,
+                                      "Could not receive message from %s socket, ignoring: %m",
+                                      raw ? "RAW" : "UDP");
                 return 0;
         }
 
-        struct tpacket_auxdata *aux = CMSG_FIND_DATA(&msg, SOL_PACKET, PACKET_AUXDATA, struct tpacket_auxdata);
-        if (aux)
-                checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY);
-
-        if (dhcp_packet_verify_headers(packet, len, checksum, client->port) < 0)
-                return 0;
+        struct iovec payload = IOVEC_MAKE(buf, len);
+        if (raw) {
+                struct tpacket_auxdata *aux = CMSG_FIND_DATA(&msg, SOL_PACKET, PACKET_AUXDATA, struct tpacket_auxdata);
+                bool checksum = !aux || !(aux->tp_status & TP_STATUS_CSUMNOTREADY);
 
-        len -= DHCP_IP_UDP_SIZE;
+                if (udp_packet_verify(&payload, client->port, checksum, &payload) < 0)
+                        return 0;
+        }
 
-        log_dhcp_client(client, "Received message from RAW socket, processing.");
-        r = client_handle_message(client, &packet->dhcp, len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg));
+        log_dhcp_client(client, "Received message from %s socket, processing.", raw ? "RAW" : "UDP");
+        r = client_handle_message(client, &payload, TRIPLE_TIMESTAMP_FROM_CMSG(&msg));
         if (r < 0)
                 client_stop(client, r);
 
         return 0;
 }
 
+int client_receive_message_udp(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+        return client_receive_message(userdata, fd, /* raw= */ false);
+}
+
+int client_receive_message_raw(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+        return client_receive_message(userdata, fd, /* raw= */ true);
+}
+
 int sd_dhcp_client_send_renew(sd_dhcp_client *client) {
         if (!sd_dhcp_client_is_running(client) || client->state != DHCP_STATE_BOUND || client->bootp)
                 return 0; /* do nothing */