#include <errno.h>
#include "alloc-util.h"
+#include "dhcp6-internal.h"
#include "dhcp6-lease-internal.h"
-#include "dhcp6-protocol.h"
#include "strv.h"
-#include "util.h"
+
+#define IRT_DEFAULT (1 * USEC_PER_DAY)
+#define IRT_MINIMUM (600 * USEC_PER_SEC)
+
+static void dhcp6_lease_set_timestamp(sd_dhcp6_lease *lease, const triple_timestamp *timestamp) {
+ assert(lease);
+
+ if (timestamp && triple_timestamp_is_set(timestamp))
+ lease->timestamp = *timestamp;
+ else
+ triple_timestamp_get(&lease->timestamp);
+}
int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_t *ret) {
assert_return(lease, -EINVAL);
return 0;
}
-int sd_dhcp6_lease_get_server_address(sd_dhcp6_lease *lease, struct in6_addr *ret) {
- assert_return(lease, -EINVAL);
- assert_return(ret, -EINVAL);
-
- *ret = lease->server_address;
- return 0;
+static usec_t sec2usec(uint32_t sec) {
+ return sec == UINT32_MAX ? USEC_INFINITY : sec * USEC_PER_SEC;
}
-int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire) {
- DHCP6Address *addr;
- uint32_t valid = 0, t;
+static void dhcp6_lease_set_lifetime(sd_dhcp6_lease *lease) {
+ uint32_t t1 = UINT32_MAX, t2 = UINT32_MAX, min_valid_lt = UINT32_MAX;
- assert(ia);
- assert(expire);
+ assert(lease);
+ assert(lease->ia_na || lease->ia_pd);
- LIST_FOREACH(addresses, addr, ia->addresses) {
- t = be32toh(addr->iaaddr.lifetime_valid);
- if (valid < t)
- valid = t;
+ if (lease->ia_na) {
+ t1 = MIN(t1, be32toh(lease->ia_na->header.lifetime_t1));
+ t2 = MIN(t2, be32toh(lease->ia_na->header.lifetime_t2));
+
+ LIST_FOREACH(addresses, a, lease->ia_na->addresses)
+ min_valid_lt = MIN(min_valid_lt, be32toh(a->iaaddr.lifetime_valid));
}
- t = be32toh(ia->header.lifetime_t2);
- if (t > valid)
- return -EINVAL;
+ if (lease->ia_pd) {
+ t1 = MIN(t1, be32toh(lease->ia_pd->header.lifetime_t1));
+ t2 = MIN(t2, be32toh(lease->ia_pd->header.lifetime_t2));
+
+ LIST_FOREACH(addresses, a, lease->ia_pd->addresses)
+ min_valid_lt = MIN(min_valid_lt, be32toh(a->iapdprefix.lifetime_valid));
+ }
+
+ if (t2 == 0 || t2 > min_valid_lt) {
+ /* If T2 is zero or longer than the minimum valid lifetime of the addresses or prefixes,
+ * then adjust lifetime with it. */
+ t1 = min_valid_lt / 2;
+ t2 = min_valid_lt / 10 * 8;
+ }
- *expire = valid - t;
+ lease->lifetime_valid = sec2usec(min_valid_lt);
+ lease->lifetime_t1 = sec2usec(t1);
+ lease->lifetime_t2 = sec2usec(t2);
+}
+
+int dhcp6_lease_get_lifetime(sd_dhcp6_lease *lease, usec_t *ret_t1, usec_t *ret_t2, usec_t *ret_valid) {
+ assert(lease);
+
+ if (!lease->ia_na && !lease->ia_pd)
+ return -ENODATA;
+ if (ret_t1)
+ *ret_t1 = lease->lifetime_t1;
+ if (ret_t2)
+ *ret_t2 = lease->lifetime_t2;
+ if (ret_valid)
+ *ret_valid = lease->lifetime_valid;
return 0;
}
-void dhcp6_ia_clear_addresses(DHCP6IA *ia) {
- DHCP6Address *a, *n;
+static void dhcp6_lease_set_server_address(sd_dhcp6_lease *lease, const struct in6_addr *server_address) {
+ assert(lease);
+ if (server_address)
+ lease->server_address = *server_address;
+ else
+ lease->server_address = (struct in6_addr) {};
+}
+
+int sd_dhcp6_lease_get_server_address(sd_dhcp6_lease *lease, struct in6_addr *ret) {
+ assert_return(lease, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ *ret = lease->server_address;
+ return 0;
+}
+
+void dhcp6_ia_clear_addresses(DHCP6IA *ia) {
assert(ia);
- LIST_FOREACH_SAFE(addresses, a, n, ia->addresses)
+ LIST_FOREACH(addresses, a, ia->addresses)
free(a);
ia->addresses = NULL;
if (r < 0)
return r;
- switch(subopt) {
+ switch (subopt) {
case DHCP6_NTP_SUBOPTION_SRV_ADDR:
case DHCP6_NTP_SUBOPTION_MC_ADDR:
if (sublen != 16)
- return 0;
+ return -EINVAL;
r = dhcp6_option_parse_addresses(subval, sublen, &lease->ntp, &lease->ntp_count);
if (r < 0)
return 0;
}
+int dhcp6_lease_set_captive_portal(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
+ _cleanup_free_ char *uri = NULL;
+ int r;
+
+ assert(lease);
+ assert(optval || optlen == 0);
+
+ r = dhcp6_option_parse_string(optval, optlen, &uri);
+ if (r < 0)
+ return r;
+
+ if (uri && !in_charset(uri, URI_VALID))
+ return -EINVAL;
+
+ return free_and_replace(lease->captive_portal, uri);
+}
+
+int sd_dhcp6_lease_get_captive_portal(sd_dhcp6_lease *lease, const char **ret) {
+ assert_return(lease, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!lease->captive_portal)
+ return -ENODATA;
+
+ *ret = lease->captive_portal;
+ return 0;
+}
+
+static int dhcp6_lease_parse_message(
+ sd_dhcp6_client *client,
+ sd_dhcp6_lease *lease,
+ const DHCP6Message *message,
+ size_t len) {
+
+ usec_t irt = IRT_DEFAULT;
+ int r;
+
+ assert(client);
+ assert(lease);
+ assert(message);
+ assert(len >= sizeof(DHCP6Message));
+
+ len -= sizeof(DHCP6Message);
+ for (size_t offset = 0; offset < len;) {
+ uint16_t optcode;
+ size_t optlen;
+ const uint8_t *optval;
+
+ if (len - offset < offsetof(DHCP6Option, data)) {
+ log_dhcp6_client(client, "Ignoring %zu invalid byte(s) at the end of the packet", len - offset);
+ break;
+ }
+
+ r = dhcp6_option_parse(message->options, len, &offset, &optcode, &optlen, &optval);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r,
+ "Failed to parse option header at offset %zu of total length %zu: %m",
+ offset, len);
+
+ switch (optcode) {
+ case SD_DHCP6_OPTION_CLIENTID:
+ if (dhcp6_lease_get_clientid(lease, NULL, NULL) >= 0)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple client IDs",
+ dhcp6_message_type_to_string(message->type));
+
+ r = dhcp6_lease_set_clientid(lease, optval, optlen);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set client ID: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_SERVERID:
+ if (dhcp6_lease_get_serverid(lease, NULL, NULL) >= 0)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "%s contains multiple server IDs",
+ dhcp6_message_type_to_string(message->type));
+
+ r = dhcp6_lease_set_serverid(lease, optval, optlen);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set server ID: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_PREFERENCE:
+ if (optlen != 1)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "Received invalid length for preference.");
+
+ r = dhcp6_lease_set_preference(lease, optval[0]);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set preference: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_STATUS_CODE: {
+ _cleanup_free_ char *msg = NULL;
+
+ r = dhcp6_option_parse_status(optval, optlen, &msg);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to parse status code: %m");
+ if (r > 0)
+ return log_dhcp6_client_errno(client, dhcp6_message_status_to_errno(r),
+ "Received %s message with non-zero status%s%s",
+ dhcp6_message_type_to_string(message->type),
+ isempty(msg) ? "." : ": ", strempty(msg));
+ break;
+ }
+ case SD_DHCP6_OPTION_IA_NA: {
+ _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL;
+
+ if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+ log_dhcp6_client(client, "Ignoring IA NA option in information requesting mode.");
+ break;
+ }
+
+ r = dhcp6_option_parse_ia(client, client->ia_na.header.id, optcode, optlen, optval, &ia);
+ if (r == -ENOMEM)
+ return log_oom_debug();
+ if (r < 0) {
+ log_dhcp6_client_errno(client, r, "Failed to parse IA_NA option, ignoring: %m");
+ continue;
+ }
+
+ if (lease->ia_na) {
+ log_dhcp6_client(client, "Received duplicate matching IA_NA option, ignoring.");
+ continue;
+ }
+
+ dhcp6_ia_free(lease->ia_na);
+ lease->ia_na = TAKE_PTR(ia);
+ break;
+ }
+ case SD_DHCP6_OPTION_IA_PD: {
+ _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL;
+
+ if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+ log_dhcp6_client(client, "Ignoring IA PD option in information requesting mode.");
+ break;
+ }
+
+ r = dhcp6_option_parse_ia(client, client->ia_pd.header.id, optcode, optlen, optval, &ia);
+ if (r == -ENOMEM)
+ return log_oom_debug();
+ if (r < 0) {
+ log_dhcp6_client_errno(client, r, "Failed to parse IA_PD option, ignoring: %m");
+ continue;
+ }
+
+ if (lease->ia_pd) {
+ log_dhcp6_client(client, "Received duplicate matching IA_PD option, ignoring.");
+ continue;
+ }
+
+ dhcp6_ia_free(lease->ia_pd);
+ lease->ia_pd = TAKE_PTR(ia);
+ break;
+ }
+ case SD_DHCP6_OPTION_RAPID_COMMIT:
+ if (optlen != 0)
+ log_dhcp6_client(client, "Received rapid commit option with an invalid length (%zu), ignoring.", optlen);
+
+ r = dhcp6_lease_set_rapid_commit(lease);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "Failed to set rapid commit flag: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_DNS_SERVER:
+ r = dhcp6_lease_add_dns(lease, optval, optlen);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r, "Failed to parse DNS server option, ignoring: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_DOMAIN:
+ r = dhcp6_lease_add_domains(lease, optval, optlen);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r, "Failed to parse domain list option, ignoring: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_NTP_SERVER:
+ r = dhcp6_lease_add_ntp(lease, optval, optlen);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r, "Failed to parse NTP server option, ignoring: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_SNTP_SERVER:
+ r = dhcp6_lease_add_sntp(lease, optval, optlen);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r, "Failed to parse SNTP server option, ignoring: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_CAPTIVE_PORTAL:
+ r = dhcp6_lease_set_captive_portal(lease, optval, optlen);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r, "Failed to parse captive portal option, ignoring: %m");
+ break;
+
+ case SD_DHCP6_OPTION_CLIENT_FQDN:
+ r = dhcp6_lease_set_fqdn(lease, optval, optlen);
+ if (r < 0)
+ log_dhcp6_client_errno(client, r, "Failed to parse FQDN option, ignoring: %m");
+
+ break;
+
+ case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME:
+ if (optlen != 4)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received information refresh time option with an invalid length (%zu).", optlen);
+
+ irt = unaligned_read_be32(optval) * USEC_PER_SEC;
+ break;
+ }
+ }
+
+ uint8_t *clientid;
+ size_t clientid_len;
+ if (dhcp6_lease_get_clientid(lease, &clientid, &clientid_len) < 0)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "%s message does not contain client ID. Ignoring.",
+ dhcp6_message_type_to_string(message->type));
+
+ if (memcmp_nn(clientid, clientid_len, &client->duid, client->duid_len) != 0)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "The client ID in %s message does not match. Ignoring.",
+ dhcp6_message_type_to_string(message->type));
+
+ if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
+ client->information_refresh_time_usec = MAX(irt, IRT_MINIMUM);
+ log_dhcp6_client(client, "New information request will be refused in %s.",
+ FORMAT_TIMESPAN(client->information_refresh_time_usec, USEC_PER_SEC));
+
+ } else {
+ r = dhcp6_lease_get_serverid(lease, NULL, NULL);
+ if (r < 0)
+ return log_dhcp6_client_errno(client, r, "%s has no server id",
+ dhcp6_message_type_to_string(message->type));
+
+ if (!lease->ia_na && !lease->ia_pd)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "No IA_PD prefix or IA_NA address received. Ignoring.");
+
+ dhcp6_lease_set_lifetime(lease);
+ }
+
+ return 0;
+}
+
static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) {
if (!lease)
return NULL;
dhcp6_ia_free(lease->ia_pd);
free(lease->dns);
free(lease->fqdn);
+ free(lease->captive_portal);
strv_free(lease->domains);
free(lease->ntp);
strv_free(lease->ntp_fqdn);
assert(ret);
- lease = new0(sd_dhcp6_lease, 1);
+ lease = new(sd_dhcp6_lease, 1);
if (!lease)
return -ENOMEM;
- lease->n_ref = 1;
+ *lease = (sd_dhcp6_lease) {
+ .n_ref = 1,
+ };
*ret = lease;
return 0;
}
+
+int dhcp6_lease_new_from_message(
+ sd_dhcp6_client *client,
+ const DHCP6Message *message,
+ size_t len,
+ const triple_timestamp *timestamp,
+ const struct in6_addr *server_address,
+ sd_dhcp6_lease **ret) {
+
+ _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL;
+ int r;
+
+ assert(client);
+ assert(message);
+ assert(len >= sizeof(DHCP6Message));
+ assert(ret);
+
+ r = dhcp6_lease_new(&lease);
+ if (r < 0)
+ return r;
+
+ dhcp6_lease_set_timestamp(lease, timestamp);
+ dhcp6_lease_set_server_address(lease, server_address);
+
+ r = dhcp6_lease_parse_message(client, lease, message, len);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(lease);
+ return 0;
+}