#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"
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) {
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);
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;
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);
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) {
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 */