]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/libsystemd-network/sd-dhcp-client.c
dhcp: fix comparison with previous lease
[thirdparty/systemd.git] / src / libsystemd-network / sd-dhcp-client.c
index f39af0d323948485f56e1bd13134ab19ac7255b8..b0f0f849379e622ebb825c636e30e975e5407ece 100644 (file)
 #include "event-util.h"
 #include "hostname-util.h"
 #include "io-util.h"
+#include "memory-util.h"
 #include "random-util.h"
 #include "string-util.h"
 #include "strv.h"
-#include "util.h"
 
 #define MAX_CLIENT_ID_LEN (sizeof(uint32_t) + MAX_DUID_LEN)  /* Arbitrary limit */
 #define MAX_MAC_ADDR_LEN CONST_MAX(INFINIBAND_ALEN, ETH_ALEN)
@@ -88,7 +88,8 @@ struct sd_dhcp_client {
         uint32_t mtu;
         uint32_t xid;
         usec_t start_time;
-        unsigned attempt;
+        uint64_t attempt;
+        uint64_t max_attempts;
         usec_t request_sent;
         sd_event_source *timeout_t1;
         sd_event_source *timeout_t2;
@@ -118,19 +119,19 @@ static const uint8_t default_req_opts[] = {
 */
 /* NOTE: using PRL options that Windows 10 RFC7844 implementation uses */
 static const uint8_t default_req_opts_anonymize[] = {
-       SD_DHCP_OPTION_SUBNET_MASK,                     /* 1 */
-       SD_DHCP_OPTION_ROUTER,                          /* 3 */
-       SD_DHCP_OPTION_DOMAIN_NAME_SERVER,              /* 6 */
-       SD_DHCP_OPTION_DOMAIN_NAME,                     /* 15 */
-       SD_DHCP_OPTION_ROUTER_DISCOVER,                 /* 31 */
-       SD_DHCP_OPTION_STATIC_ROUTE,                    /* 33 */
-       SD_DHCP_OPTION_VENDOR_SPECIFIC,                 /* 43 */
-       SD_DHCP_OPTION_NETBIOS_NAMESERVER,              /* 44 */
-       SD_DHCP_OPTION_NETBIOS_NODETYPE,                /* 46 */
-       SD_DHCP_OPTION_NETBIOS_SCOPE,                   /* 47 */
-       SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,          /* 121 */
-       SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE,  /* 249 */
-       SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,     /* 252 */
+        SD_DHCP_OPTION_SUBNET_MASK,                     /* 1 */
+        SD_DHCP_OPTION_ROUTER,                          /* 3 */
+        SD_DHCP_OPTION_DOMAIN_NAME_SERVER,              /* 6 */
+        SD_DHCP_OPTION_DOMAIN_NAME,                     /* 15 */
+        SD_DHCP_OPTION_ROUTER_DISCOVER,                 /* 31 */
+        SD_DHCP_OPTION_STATIC_ROUTE,                    /* 33 */
+        SD_DHCP_OPTION_VENDOR_SPECIFIC,                 /* 43 */
+        SD_DHCP_OPTION_NETBIOS_NAMESERVER,              /* 44 */
+        SD_DHCP_OPTION_NETBIOS_NODETYPE,                /* 46 */
+        SD_DHCP_OPTION_NETBIOS_SCOPE,                   /* 47 */
+        SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,          /* 121 */
+        SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE,  /* 249 */
+        SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,     /* 252 */
 };
 
 static int client_receive_message_raw(
@@ -353,10 +354,10 @@ static int dhcp_client_set_iaid_duid_internal(
         size_t len;
 
         assert_return(client, -EINVAL);
-        assert_return(duid_len == 0 || duid != NULL, -EINVAL);
+        assert_return(duid_len == 0 || duid, -EINVAL);
 
-        if (duid != NULL) {
-                r = dhcp_validate_duid_len(duid_type, duid_len);
+        if (duid) {
+                r = dhcp_validate_duid_len(duid_type, duid_len, true);
                 if (r < 0)
                         return r;
         }
@@ -377,7 +378,7 @@ static int dhcp_client_set_iaid_duid_internal(
                 }
         }
 
-        if (duid != NULL) {
+        if (duid) {
                 client->client_id.ns.duid.type = htobe16(duid_type);
                 memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len);
                 len = sizeof(client->client_id.ns.duid.type) + duid_len;
@@ -520,10 +521,18 @@ int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) {
         return 0;
 }
 
+int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempts) {
+        assert_return(client, -EINVAL);
+
+        client->max_attempts = max_attempts;
+
+        return 0;
+}
+
 int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
         assert_return(client, -EINVAL);
 
-        if (!IN_SET(client->state, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING))
+        if (!IN_SET(client->state, DHCP_STATE_SELECTING, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING))
                 return -EADDRNOTAVAIL;
 
         if (ret)
@@ -532,11 +541,13 @@ int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
         return 0;
 }
 
-static void client_notify(sd_dhcp_client *client, int event) {
+static int client_notify(sd_dhcp_client *client, int event) {
         assert(client);
 
         if (client->callback)
-                client->callback(client, event, client->userdata);
+                return client->callback(client, event, client->userdata);
+
+        return 0;
 }
 
 static int client_initialize(sd_dhcp_client *client) {
@@ -551,7 +562,7 @@ static int client_initialize(sd_dhcp_client *client) {
         (void) event_source_disable(client->timeout_t2);
         (void) event_source_disable(client->timeout_expire);
 
-        client->attempt = 1;
+        client->attempt = 0;
 
         client->state = DHCP_STATE_INIT;
         client->xid = 0;
@@ -595,7 +606,7 @@ static int client_message_init(
         assert(ret);
         assert(_optlen);
         assert(_optoffset);
-        assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST));
+        assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE));
 
         optlen = DHCP_MIN_OPTIONS_SIZE;
         size = sizeof(DHCPPacket) + optlen;
@@ -686,7 +697,7 @@ static int client_message_init(
            MAY contain the Parameter Request List option. */
         /* NOTE: in case that there would be an option to do not send
          * any PRL at all, the size should be checked before sending */
-        if (client->req_opts_size > 0) {
+        if (client->req_opts_size > 0 && type != DHCP_RELEASE) {
                 r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
                                        SD_DHCP_OPTION_PARAMETER_REQUEST_LIST,
                                        client->req_opts_size, client->req_opts);
@@ -718,7 +729,7 @@ static int client_message_init(
          */
         /* RFC7844 section 3:
            SHOULD NOT contain any other option. */
-        if (!client->anonymize) {
+        if (!client->anonymize && type != DHCP_RELEASE) {
                 max_size = htobe16(size);
                 r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0,
                                        SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE,
@@ -850,6 +861,41 @@ static int client_send_discover(sd_dhcp_client *client) {
         return 0;
 }
 
+static int client_send_release(sd_dhcp_client *client) {
+        _cleanup_free_ DHCPPacket *release = NULL;
+        size_t optoffset, optlen;
+        int r;
+
+        assert(client);
+        assert(!IN_SET(client->state, DHCP_STATE_STOPPED));
+
+        r = client_message_init(client, &release, DHCP_RELEASE,
+                                &optlen, &optoffset);
+        if (r < 0)
+                return r;
+
+        /* Fill up release IP and MAC */
+        release->dhcp.ciaddr = client->lease->address;
+        memcpy(&release->dhcp.chaddr, &client->mac_addr, client->mac_addr_len);
+
+        r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0,
+                               SD_DHCP_OPTION_END, 0, NULL);
+        if (r < 0)
+                return r;
+
+        r = dhcp_network_send_udp_socket(client->fd,
+                                         client->lease->server_address,
+                                         DHCP_PORT_SERVER,
+                                         &release->dhcp,
+                                         sizeof(DHCPMessage) + optoffset);
+        if (r < 0)
+                return r;
+
+        log_dhcp_client(client, "RELEASE");
+
+        return 0;
+}
+
 static int client_send_request(sd_dhcp_client *client) {
         _cleanup_free_ DHCPPacket *request = NULL;
         size_t optoffset, optlen;
@@ -1050,10 +1096,12 @@ static int client_timeout_resend(
         case DHCP_STATE_REQUESTING:
         case DHCP_STATE_BOUND:
 
-                if (client->attempt < 64)
-                        client->attempt *= 2;
+                if (client->attempt < client->max_attempts)
+                        client->attempt++;
+                else
+                        goto error;
 
-                next_timeout = time_now + (client->attempt - 1) * USEC_PER_SEC;
+                next_timeout = time_now + ((UINT64_C(1) << MIN(client->attempt, (uint64_t) 6)) - 1) * USEC_PER_SEC;
 
                 break;
 
@@ -1077,17 +1125,15 @@ static int client_timeout_resend(
                 r = client_send_discover(client);
                 if (r >= 0) {
                         client->state = DHCP_STATE_SELECTING;
-                        client->attempt = 1;
-                } else {
-                        if (client->attempt >= 64)
-                                goto error;
-                }
+                        client->attempt = 0;
+                } else if (client->attempt >= client->max_attempts)
+                        goto error;
 
                 break;
 
         case DHCP_STATE_SELECTING:
                 r = client_send_discover(client);
-                if (r < 0 && client->attempt >= 64)
+                if (r < 0 && client->attempt >= client->max_attempts)
                         goto error;
 
                 break;
@@ -1097,7 +1143,7 @@ static int client_timeout_resend(
         case DHCP_STATE_RENEWING:
         case DHCP_STATE_REBINDING:
                 r = client_send_request(client);
-                if (r < 0 && client->attempt >= 64)
+                if (r < 0 && client->attempt >= client->max_attempts)
                          goto error;
 
                 if (client->state == DHCP_STATE_INIT_REBOOT)
@@ -1249,7 +1295,7 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata)
         client->fd = asynchronous_close(client->fd);
 
         client->state = DHCP_STATE_REBINDING;
-        client->attempt = 1;
+        client->attempt = 0;
 
         r = dhcp_network_bind_raw_socket(client->ifindex, &client->link,
                                          client->xid, client->mac_addr,
@@ -1269,7 +1315,7 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata)
         DHCP_CLIENT_DONT_DESTROY(client);
 
         client->state = DHCP_STATE_RENEWING;
-        client->attempt = 1;
+        client->attempt = 0;
 
         return client_initialize_time_events(client);
 }
@@ -1319,6 +1365,9 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, size_
         sd_dhcp_lease_unref(client->lease);
         client->lease = TAKE_PTR(lease);
 
+        if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0)
+                return -ENOMSG;
+
         log_dhcp_client(client, "OFFER");
 
         return 0;
@@ -1336,6 +1385,23 @@ static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force,
         return 0;
 }
 
+static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) {
+        if (a->address != b->address)
+                return false;
+
+        if (a->subnet_mask != b->subnet_mask)
+                return false;
+
+        if (a->router_size != b->router_size)
+                return false;
+
+        for (size_t i = 0; i < a->router_size; i++)
+                if (a->router[i].s_addr != b->router[i].s_addr)
+                        return false;
+
+        return true;
+}
+
 static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t len) {
         _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
         _cleanup_free_ char *error_message = NULL;
@@ -1388,12 +1454,10 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t le
 
         r = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
         if (client->lease) {
-                if (client->lease->address != lease->address ||
-                    client->lease->subnet_mask != lease->subnet_mask ||
-                    client->lease->router != lease->router) {
-                        r = SD_DHCP_CLIENT_EVENT_IP_CHANGE;
-                } else
+                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);
         }
@@ -1553,7 +1617,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
                 if (r >= 0) {
 
                         client->state = DHCP_STATE_REQUESTING;
-                        client->attempt = 1;
+                        client->attempt = 0;
 
                         r = event_reset_time(client->event, &client->timeout_resend,
                                              clock_boottime_or_monotonic(),
@@ -1588,7 +1652,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
                                 notify_event = r;
 
                         client->state = DHCP_STATE_BOUND;
-                        client->attempt = 1;
+                        client->attempt = 0;
 
                         client->last_addr = client->lease->address;
 
@@ -1676,8 +1740,7 @@ static int client_receive_message_udp(
 
         sd_dhcp_client *client = userdata;
         _cleanup_free_ DHCPMessage *message = NULL;
-        const struct ether_addr zero_mac = {};
-        const struct ether_addr *expected_chaddr = NULL;
+        const uint8_t *expected_chaddr = NULL;
         uint8_t expected_hlen = 0;
         ssize_t len, buflen;
 
@@ -1685,6 +1748,12 @@ static int client_receive_message_udp(
         assert(client);
 
         buflen = next_datagram_size_fd(fd);
+        if (buflen == -ENETDOWN) {
+                /* the link is down. Don't return an error or the I/O event
+                   source will be disconnected and we won't be able to receive
+                   packets again when the link comes back. */
+                return 0;
+        }
         if (buflen < 0)
                 return buflen;
 
@@ -1694,7 +1763,8 @@ static int client_receive_message_udp(
 
         len = recv(fd, message, buflen, 0);
         if (len < 0) {
-                if (IN_SET(errno, EAGAIN, EINTR))
+                /* see comment above for why we shouldn't error out on ENETDOWN. */
+                if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN))
                         return 0;
 
                 return log_dhcp_client_errno(client, errno,
@@ -1722,11 +1792,7 @@ static int client_receive_message_udp(
 
         if (client->arp_type == ARPHRD_ETHER) {
                 expected_hlen = ETH_ALEN;
-                expected_chaddr = (const struct ether_addr *) &client->mac_addr;
-        } else {
-               /* Non-Ethernet links expect zero chaddr */
-               expected_hlen = 0;
-               expected_chaddr = &zero_mac;
+                expected_chaddr = &client->mac_addr[0];
         }
 
         if (message->hlen != expected_hlen) {
@@ -1734,7 +1800,7 @@ static int client_receive_message_udp(
                 return 0;
         }
 
-        if (memcmp(&message->chaddr[0], expected_chaddr, ETH_ALEN)) {
+        if (expected_hlen > 0 && memcmp(&message->chaddr[0], expected_chaddr, expected_hlen)) {
                 log_dhcp_client(client, "Received chaddr does not match expected: ignoring");
                 return 0;
         }
@@ -1776,6 +1842,8 @@ static int client_receive_message_raw(
         assert(client);
 
         buflen = next_datagram_size_fd(fd);
+        if (buflen == -ENETDOWN)
+                return 0;
         if (buflen < 0)
                 return buflen;
 
@@ -1787,7 +1855,7 @@ static int client_receive_message_raw(
 
         len = recvmsg(fd, &msg, 0);
         if (len < 0) {
-                if (IN_SET(errno, EAGAIN, EINTR))
+                if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN))
                         return 0;
 
                 return log_dhcp_client_errno(client, errno,
@@ -1795,7 +1863,7 @@ static int client_receive_message_raw(
         } else if ((size_t)len < sizeof(DHCPPacket))
                 return 0;
 
-        CMSG_FOREACH(cmsg, &msg) {
+        CMSG_FOREACH(cmsg, &msg)
                 if (cmsg->cmsg_level == SOL_PACKET &&
                     cmsg->cmsg_type == PACKET_AUXDATA &&
                     cmsg->cmsg_len == CMSG_LEN(sizeof(struct tpacket_auxdata))) {
@@ -1804,7 +1872,6 @@ static int client_receive_message_raw(
                         checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY);
                         break;
                 }
-        }
 
         r = dhcp_packet_verify_headers(packet, len, checksum, client->port);
         if (r < 0)
@@ -1841,6 +1908,14 @@ int sd_dhcp_client_start(sd_dhcp_client *client) {
         return r;
 }
 
+int sd_dhcp_client_send_release(sd_dhcp_client *client) {
+        assert_return(client, -EINVAL);
+
+        client_send_release(client);
+
+        return 0;
+}
+
 int sd_dhcp_client_stop(sd_dhcp_client *client) {
         DHCP_CLIENT_DONT_DESTROY(client);
 
@@ -1924,10 +1999,10 @@ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) {
                 .state = DHCP_STATE_INIT,
                 .ifindex = -1,
                 .fd = -1,
-                .attempt = 1,
                 .mtu = DHCP_DEFAULT_MIN_SIZE,
                 .port = DHCP_PORT_CLIENT,
                 .anonymize = !!anonymize,
+                .max_attempts = (uint64_t) -1,
         };
         /* NOTE: this could be moved to a function. */
         if (anonymize) {