summaryrefslogtreecommitdiffstats
path: root/src/network/networkd-dhcp6.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 02:25:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 02:25:50 +0000
commit19f4f86bfed21c5326ed2acebe1163f3a83e832b (patch)
treed59b9989ce55ed23693e80974d94c856f1c2c8b1 /src/network/networkd-dhcp6.c
parentInitial commit. (diff)
downloadsystemd-upstream.tar.xz
systemd-upstream.zip
Adding upstream version 241.upstream/241upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/network/networkd-dhcp6.c')
-rw-r--r--src/network/networkd-dhcp6.c702
1 files changed, 702 insertions, 0 deletions
diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c
new file mode 100644
index 0000000..c1fba03
--- /dev/null
+++ b/src/network/networkd-dhcp6.c
@@ -0,0 +1,702 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include <netinet/ether.h>
+#include <linux/if.h>
+#include "sd-radv.h"
+
+#include "sd-dhcp6-client.h"
+
+#include "hashmap.h"
+#include "hostname-util.h"
+#include "missing_network.h"
+#include "network-internal.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+#include "siphash24.h"
+#include "string-util.h"
+#include "radv-internal.h"
+
+static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link);
+
+static bool dhcp6_get_prefix_delegation(Link *link) {
+ if (!link->network)
+ return false;
+
+ return IN_SET(link->network->router_prefix_delegation,
+ RADV_PREFIX_DELEGATION_DHCP6,
+ RADV_PREFIX_DELEGATION_BOTH);
+}
+
+static bool dhcp6_enable_prefix_delegation(Link *dhcp6_link) {
+ Manager *manager;
+ Link *l;
+ Iterator i;
+
+ assert(dhcp6_link);
+
+ manager = dhcp6_link->manager;
+ assert(manager);
+
+ HASHMAP_FOREACH(l, manager->links, i) {
+ if (l == dhcp6_link)
+ continue;
+
+ if (!dhcp6_get_prefix_delegation(l))
+ continue;
+
+ return true;
+ }
+
+ return false;
+}
+
+static int dhcp6_lease_information_acquired(sd_dhcp6_client *client,
+ Link *link) {
+ return 0;
+}
+
+static int dhcp6_pd_prefix_assign(Link *link, struct in6_addr *prefix,
+ uint8_t prefix_len,
+ uint32_t lifetime_preferred,
+ uint32_t lifetime_valid) {
+ sd_radv *radv = link->radv;
+ int r;
+ _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL;
+
+ r = sd_radv_prefix_new(&p);
+ if (r < 0)
+ return r;
+
+ r = sd_radv_prefix_set_prefix(p, prefix, prefix_len);
+ if (r < 0)
+ return r;
+
+ r = sd_radv_prefix_set_preferred_lifetime(p, lifetime_preferred);
+ if (r < 0)
+ return r;
+
+ r = sd_radv_prefix_set_valid_lifetime(p, lifetime_valid);
+ if (r < 0)
+ return r;
+
+ r = sd_radv_stop(radv);
+ if (r < 0)
+ return r;
+
+ r = sd_radv_add_prefix(radv, p, true);
+ if (r < 0 && r != -EEXIST)
+ return r;
+
+ r = manager_dhcp6_prefix_add(link->manager, &p->opt.in6_addr, link);
+ if (r < 0)
+ return r;
+
+ return sd_radv_start(radv);
+}
+
+static int dhcp6_route_remove_handler(sd_netlink *nl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0)
+ log_link_debug_errno(link, r, "Received error on unreachable route removal for DHCPv6 delegated subnetl: %m");
+
+ return 1;
+}
+
+int dhcp6_lease_pd_prefix_lost(sd_dhcp6_client *client, Link* link) {
+ int r;
+ sd_dhcp6_lease *lease;
+ union in_addr_union pd_prefix;
+ uint8_t pd_prefix_len;
+ uint32_t lifetime_preferred, lifetime_valid;
+
+ r = sd_dhcp6_client_get_lease(client, &lease);
+ if (r < 0)
+ return r;
+
+ sd_dhcp6_lease_reset_pd_prefix_iter(lease);
+
+ while (sd_dhcp6_lease_get_pd(lease, &pd_prefix.in6, &pd_prefix_len,
+ &lifetime_preferred,
+ &lifetime_valid) >= 0) {
+ _cleanup_free_ char *buf = NULL;
+ _cleanup_free_ Route *route = NULL;
+
+ if (pd_prefix_len > 64)
+ continue;
+
+ (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
+
+ if (pd_prefix_len < 64) {
+ r = route_new(&route);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Cannot create unreachable route to delete for DHCPv6 delegated subnet %s/%u: %m",
+ strnull(buf),
+ pd_prefix_len);
+ continue;
+ }
+
+ route_add(link, AF_INET6, &pd_prefix, pd_prefix_len,
+ 0, 0, 0, &route);
+ route_update(route, NULL, 0, NULL, NULL, 0, 0,
+ RTN_UNREACHABLE);
+
+ r = route_remove(route, link, dhcp6_route_remove_handler);
+ if (r < 0) {
+ (void) in_addr_to_string(AF_INET6,
+ &pd_prefix, &buf);
+
+ log_link_warning_errno(link, r, "Cannot delete unreachable route for DHCPv6 delegated subnet %s/%u: %m",
+ strnull(buf),
+ pd_prefix_len);
+
+ continue;
+ }
+
+ log_link_debug(link, "Removing unreachable route %s/%u",
+ strnull(buf), pd_prefix_len);
+ }
+ }
+
+ return 0;
+}
+
+static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i,
+ struct in6_addr *pd_prefix,
+ uint8_t pd_prefix_len,
+ uint32_t lifetime_preferred,
+ uint32_t lifetime_valid) {
+ Link *link;
+ Manager *manager = dhcp6_link->manager;
+ union in_addr_union prefix;
+ uint64_t n_prefixes, n_used = 0;
+ _cleanup_free_ char *buf = NULL;
+ _cleanup_free_ char *assigned_buf = NULL;
+ int r;
+
+ assert(manager);
+ assert(pd_prefix_len <= 64);
+
+ prefix.in6 = *pd_prefix;
+
+ r = in_addr_mask(AF_INET6, &prefix, pd_prefix_len);
+ if (r < 0)
+ return r;
+
+ n_prefixes = UINT64_C(1) << (64 - pd_prefix_len);
+
+ (void) in_addr_to_string(AF_INET6, &prefix, &buf);
+ log_link_debug(dhcp6_link, "Assigning up to %" PRIu64 " prefixes from %s/%u",
+ n_prefixes, strnull(buf), pd_prefix_len);
+
+ while (hashmap_iterate(manager->links, i, (void **)&link, NULL)) {
+ Link *assigned_link;
+
+ if (n_used == n_prefixes) {
+ log_link_debug(dhcp6_link, "Assigned %" PRIu64 "/%" PRIu64 " prefixes from %s/%u",
+ n_used, n_prefixes, strnull(buf), pd_prefix_len);
+
+ return -EAGAIN;
+ }
+
+ if (link == dhcp6_link)
+ continue;
+
+ if (!dhcp6_get_prefix_delegation(link))
+ continue;
+
+ assigned_link = manager_dhcp6_prefix_get(manager, &prefix.in6);
+ if (assigned_link != NULL && assigned_link != link)
+ continue;
+
+ (void) in_addr_to_string(AF_INET6, &prefix, &assigned_buf);
+ r = dhcp6_pd_prefix_assign(link, &prefix.in6, 64,
+ lifetime_preferred, lifetime_valid);
+ if (r < 0) {
+ log_link_error_errno(link, r, "Unable to %s prefix %s/64 from %s/%u for link: %m",
+ assigned_link ? "update": "assign",
+ strnull(assigned_buf),
+ strnull(buf), pd_prefix_len);
+
+ if (assigned_link == NULL)
+ continue;
+
+ } else
+ log_link_debug(link, "Assigned prefix %" PRIu64 "/%" PRIu64 " %s/64 from %s/%u to link",
+ n_used + 1, n_prefixes,
+ strnull(assigned_buf),
+ strnull(buf), pd_prefix_len);
+
+ n_used++;
+
+ r = in_addr_prefix_next(AF_INET6, &prefix, 64);
+ if (r < 0 && n_used < n_prefixes)
+ return r;
+ }
+
+ return 0;
+}
+
+static int dhcp6_route_handler(sd_netlink *nl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST)
+ log_link_debug_errno(link, r, "Received error when adding unreachable route for DHCPv6 delegated subnet: %m");
+
+ return 1;
+}
+
+static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) {
+ int r;
+ sd_dhcp6_lease *lease;
+ union in_addr_union pd_prefix;
+ uint8_t pd_prefix_len;
+ uint32_t lifetime_preferred, lifetime_valid;
+ _cleanup_free_ char *buf = NULL;
+ Iterator i = ITERATOR_FIRST;
+
+ r = sd_dhcp6_client_get_lease(client, &lease);
+ if (r < 0)
+ return r;
+
+ sd_dhcp6_lease_reset_pd_prefix_iter(lease);
+
+ while (sd_dhcp6_lease_get_pd(lease, &pd_prefix.in6, &pd_prefix_len,
+ &lifetime_preferred,
+ &lifetime_valid) >= 0) {
+
+ if (pd_prefix_len > 64) {
+ (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
+ log_link_debug(link, "PD Prefix length > 64, ignoring prefix %s/%u",
+ strnull(buf), pd_prefix_len);
+ continue;
+ }
+
+ if (pd_prefix_len < 48) {
+ (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
+ log_link_warning(link, "PD Prefix length < 48, looks unusual %s/%u",
+ strnull(buf), pd_prefix_len);
+ }
+
+ if (pd_prefix_len < 64) {
+ Route *route = NULL;
+
+ (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
+
+ r = route_new(&route);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Cannot create unreachable route for DHCPv6 delegated subnet %s/%u: %m",
+ strnull(buf),
+ pd_prefix_len);
+ continue;
+ }
+
+ route_add(link, AF_INET6, &pd_prefix, pd_prefix_len,
+ 0, 0, 0, &route);
+ route_update(route, NULL, 0, NULL, NULL, 0, 0,
+ RTN_UNREACHABLE);
+
+ r = route_configure(route, link, dhcp6_route_handler);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Cannot configure unreachable route for delegated subnet %s/%u: %m",
+ strnull(buf),
+ pd_prefix_len);
+ route_free(route);
+ continue;
+ }
+
+ route_free(route);
+
+ log_link_debug(link, "Configuring unreachable route for %s/%u",
+ strnull(buf), pd_prefix_len);
+
+ } else
+ log_link_debug(link, "Not adding a blocking route since distributed prefix is /64");
+
+ r = dhcp6_pd_prefix_distribute(link, &i, &pd_prefix.in6,
+ pd_prefix_len,
+ lifetime_preferred,
+ lifetime_valid);
+ if (r < 0 && r != -EAGAIN)
+ return r;
+
+ if (r >= 0)
+ i = ITERATOR_FIRST;
+ }
+
+ return 0;
+}
+
+int dhcp6_request_prefix_delegation(Link *link) {
+ Link *l;
+ Iterator i;
+
+ assert_return(link, -EINVAL);
+ assert_return(link->manager, -EOPNOTSUPP);
+
+ if (dhcp6_get_prefix_delegation(link) <= 0)
+ return 0;
+
+ log_link_debug(link, "Requesting DHCPv6 prefixes to be delegated for new link");
+
+ HASHMAP_FOREACH(l, link->manager->links, i) {
+ int r, enabled;
+
+ if (l == link)
+ continue;
+
+ if (!l->dhcp6_client)
+ continue;
+
+ r = sd_dhcp6_client_get_prefix_delegation(l->dhcp6_client, &enabled);
+ if (r < 0) {
+ log_link_warning_errno(l, r, "Cannot get prefix delegation when adding new link");
+ continue;
+ }
+
+ if (enabled == 0) {
+ r = sd_dhcp6_client_set_prefix_delegation(l->dhcp6_client, 1);
+ if (r < 0) {
+ log_link_warning_errno(l, r, "Cannot enable prefix delegation when adding new link");
+ continue;
+ }
+ }
+
+ r = sd_dhcp6_client_is_running(l->dhcp6_client);
+ if (r <= 0)
+ continue;
+
+ if (enabled != 0) {
+ log_link_debug(l, "Requesting re-assignment of delegated prefixes after adding new link");
+ (void) dhcp6_lease_pd_prefix_acquired(l->dhcp6_client, l);
+
+ continue;
+ }
+
+ r = sd_dhcp6_client_stop(l->dhcp6_client);
+ if (r < 0) {
+ log_link_warning_errno(l, r, "Cannot stop DHCPv6 prefix delegation client after adding new link");
+ continue;
+ }
+
+ r = sd_dhcp6_client_start(l->dhcp6_client);
+ if (r < 0) {
+ log_link_warning_errno(l, r, "Cannot restart DHCPv6 prefix delegation client after adding new link");
+ continue;
+ }
+
+ log_link_debug(l, "Restarted DHCPv6 client to acquire prefix delegations after adding new link");
+ }
+
+ return 0;
+}
+
+static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST) {
+ if (link->rtnl_extended_attrs) {
+ log_link_warning(link, "Could not set extended netlink attributes, reverting to fallback mechanism");
+
+ link->rtnl_extended_attrs = false;
+ dhcp6_lease_address_acquired(link->dhcp6_client, link);
+
+ return 1;
+ }
+
+ log_link_error_errno(link, r, "Could not set DHCPv6 address: %m");
+
+ link_enter_failed(link);
+
+ } else if (r >= 0)
+ manager_rtnl_process_address(rtnl, m, link->manager);
+
+ return 1;
+}
+
+static int dhcp6_address_change(
+ Link *link,
+ struct in6_addr *ip6_addr,
+ uint32_t lifetime_preferred,
+ uint32_t lifetime_valid) {
+
+ _cleanup_(address_freep) Address *addr = NULL;
+ char buffer[INET6_ADDRSTRLEN];
+ int r;
+
+ r = address_new(&addr);
+ if (r < 0)
+ return r;
+
+ addr->family = AF_INET6;
+ memcpy(&addr->in_addr.in6, ip6_addr, sizeof(*ip6_addr));
+
+ addr->flags = IFA_F_NOPREFIXROUTE;
+ addr->prefixlen = 128;
+
+ addr->cinfo.ifa_prefered = lifetime_preferred;
+ addr->cinfo.ifa_valid = lifetime_valid;
+
+ log_link_info(link,
+ "DHCPv6 address %s/%d timeout preferred %d valid %d",
+ inet_ntop(AF_INET6, &addr->in_addr.in6, buffer, sizeof(buffer)),
+ addr->prefixlen, lifetime_preferred, lifetime_valid);
+
+ r = address_configure(addr, link, dhcp6_address_handler, true);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Could not assign DHCPv6 address: %m");
+
+ return r;
+}
+
+static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link) {
+ int r;
+ sd_dhcp6_lease *lease;
+ struct in6_addr ip6_addr;
+ uint32_t lifetime_preferred, lifetime_valid;
+
+ r = sd_dhcp6_client_get_lease(client, &lease);
+ if (r < 0)
+ return r;
+
+ sd_dhcp6_lease_reset_address_iter(lease);
+
+ while (sd_dhcp6_lease_get_address(lease, &ip6_addr,
+ &lifetime_preferred,
+ &lifetime_valid) >= 0) {
+
+ r = dhcp6_address_change(link, &ip6_addr, lifetime_preferred, lifetime_valid);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) {
+ int r;
+ Link *link = userdata;
+
+ assert(link);
+ assert(link->network);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return;
+
+ switch(event) {
+ case SD_DHCP6_CLIENT_EVENT_STOP:
+ case SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE:
+ case SD_DHCP6_CLIENT_EVENT_RETRANS_MAX:
+ if (sd_dhcp6_client_get_lease(client, NULL) >= 0)
+ log_link_warning(link, "DHCPv6 lease lost");
+
+ (void) dhcp6_lease_pd_prefix_lost(client, link);
+ (void) manager_dhcp6_prefix_remove_all(link->manager, link);
+
+ link->dhcp6_configured = false;
+ break;
+
+ case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE:
+ r = dhcp6_lease_address_acquired(client, link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return;
+ }
+
+ r = dhcp6_lease_pd_prefix_acquired(client, link);
+ if (r < 0)
+ log_link_debug(link, "DHCPv6 did not receive prefixes to delegate");
+
+ _fallthrough_;
+ case SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST:
+ r = dhcp6_lease_information_acquired(client, link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return;
+ }
+
+ link->dhcp6_configured = true;
+ break;
+
+ default:
+ if (event < 0)
+ log_link_warning_errno(link, event, "DHCPv6 error: %m");
+ else
+ log_link_warning(link, "DHCPv6 unknown event: %d", event);
+ return;
+ }
+
+ link_check_ready(link);
+}
+
+int dhcp6_request_address(Link *link, int ir) {
+ int r, inf_req, pd;
+ bool running;
+
+ assert(link);
+ assert(link->dhcp6_client);
+ assert(link->network);
+ assert(in_addr_is_link_local(AF_INET6, (const union in_addr_union*)&link->ipv6ll_address) > 0);
+
+ r = sd_dhcp6_client_is_running(link->dhcp6_client);
+ if (r < 0)
+ return r;
+ else
+ running = r;
+
+ r = sd_dhcp6_client_get_prefix_delegation(link->dhcp6_client, &pd);
+ if (r < 0)
+ return r;
+
+ if (pd && ir && link->network->dhcp6_force_pd_other_information) {
+ log_link_debug(link, "Enabling managed mode to request DHCPv6 PD with 'Other Information' set");
+
+ r = sd_dhcp6_client_set_address_request(link->dhcp6_client,
+ false);
+ if (r < 0 )
+ return r;
+
+ ir = false;
+ }
+
+ if (running) {
+ r = sd_dhcp6_client_get_information_request(link->dhcp6_client, &inf_req);
+ if (r < 0)
+ return r;
+
+ if (inf_req == ir)
+ return 0;
+
+ r = sd_dhcp6_client_stop(link->dhcp6_client);
+ if (r < 0)
+ return r;
+ } else {
+ r = sd_dhcp6_client_set_local_address(link->dhcp6_client, &link->ipv6ll_address);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_dhcp6_client_set_information_request(link->dhcp6_client, ir);
+ if (r < 0)
+ return r;
+
+ r = sd_dhcp6_client_start(link->dhcp6_client);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int dhcp6_set_hostname(sd_dhcp6_client *client, Link *link) {
+ _cleanup_free_ char *hostname = NULL;
+ const char *hn;
+ int r;
+
+ assert(link);
+
+ if (!link->network->dhcp_send_hostname)
+ hn = NULL;
+ else if (link->network->dhcp_hostname)
+ hn = link->network->dhcp_hostname;
+ else {
+ r = gethostname_strict(&hostname);
+ if (r < 0 && r != -ENXIO) /* ENXIO: no hostname set or hostname is "localhost" */
+ return r;
+
+ hn = hostname;
+ }
+
+ r = sd_dhcp6_client_set_fqdn(client, hn);
+ if (r == -EINVAL && hostname)
+ /* Ignore error when the machine's hostname is not suitable to send in DHCP packet. */
+ log_link_warning_errno(link, r, "DHCP6 CLIENT: Failed to set hostname from kernel hostname, ignoring: %m");
+ else if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set hostname: %m");
+
+ return 0;
+}
+
+int dhcp6_configure(Link *link) {
+ _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
+ const DUID *duid;
+ int r;
+
+ assert(link);
+ assert(link->network);
+
+ if (link->dhcp6_client)
+ return 0;
+
+ r = sd_dhcp6_client_new(&client);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to create DHCP6 client: %m");
+
+ r = sd_dhcp6_client_attach_event(client, NULL, 0);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to attach event: %m");
+
+ r = sd_dhcp6_client_set_mac(client,
+ (const uint8_t *) &link->mac,
+ sizeof (link->mac), ARPHRD_ETHER);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set MAC address: %m");
+
+ if (link->network->iaid_set) {
+ r = sd_dhcp6_client_set_iaid(client, link->network->iaid);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set IAID: %m");
+ }
+
+ duid = link_get_duid(link);
+ if (duid->type == DUID_TYPE_LLT && duid->raw_data_len == 0)
+ r = sd_dhcp6_client_set_duid_llt(client, duid->llt_time);
+ else
+ r = sd_dhcp6_client_set_duid(client,
+ duid->type,
+ duid->raw_data_len > 0 ? duid->raw_data : NULL,
+ duid->raw_data_len);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set DUID: %m");
+
+ r = dhcp6_set_hostname(client, link);
+ if (r < 0)
+ return r;
+
+ r = sd_dhcp6_client_set_ifindex(client, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set ifindex: %m");
+
+ if (link->network->rapid_commit) {
+ r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_RAPID_COMMIT);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set request flag for rapid commit: %m");
+ }
+
+ r = sd_dhcp6_client_set_callback(client, dhcp6_handler, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set callback: %m");
+
+ if (dhcp6_enable_prefix_delegation(link)) {
+ r = sd_dhcp6_client_set_prefix_delegation(client, true);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set prefix delegation: %m");
+ }
+
+ link->dhcp6_client = TAKE_PTR(client);
+
+ return 0;
+}