]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkd-dhcp6.c
Merge pull request #11827 from keszybz/pkgconfig-variables
[thirdparty/systemd.git] / src / network / networkd-dhcp6.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 Copyright © 2014 Intel Corporation. All rights reserved.
4 ***/
5
6 #include <netinet/ether.h>
7 #include <linux/if.h>
8 #include "sd-radv.h"
9
10 #include "sd-dhcp6-client.h"
11
12 #include "hashmap.h"
13 #include "hostname-util.h"
14 #include "missing_network.h"
15 #include "network-internal.h"
16 #include "networkd-link.h"
17 #include "networkd-manager.h"
18 #include "siphash24.h"
19 #include "string-util.h"
20 #include "radv-internal.h"
21
22 static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link);
23
24 static bool dhcp6_get_prefix_delegation(Link *link) {
25 if (!link->network)
26 return false;
27
28 return IN_SET(link->network->router_prefix_delegation,
29 RADV_PREFIX_DELEGATION_DHCP6,
30 RADV_PREFIX_DELEGATION_BOTH);
31 }
32
33 static bool dhcp6_enable_prefix_delegation(Link *dhcp6_link) {
34 Manager *manager;
35 Link *l;
36 Iterator i;
37
38 assert(dhcp6_link);
39
40 manager = dhcp6_link->manager;
41 assert(manager);
42
43 HASHMAP_FOREACH(l, manager->links, i) {
44 if (l == dhcp6_link)
45 continue;
46
47 if (!dhcp6_get_prefix_delegation(l))
48 continue;
49
50 return true;
51 }
52
53 return false;
54 }
55
56 static int dhcp6_lease_information_acquired(sd_dhcp6_client *client,
57 Link *link) {
58 return 0;
59 }
60
61 static int dhcp6_pd_prefix_assign(Link *link, struct in6_addr *prefix,
62 uint8_t prefix_len,
63 uint32_t lifetime_preferred,
64 uint32_t lifetime_valid) {
65 sd_radv *radv = link->radv;
66 int r;
67 _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL;
68
69 r = sd_radv_prefix_new(&p);
70 if (r < 0)
71 return r;
72
73 r = sd_radv_prefix_set_prefix(p, prefix, prefix_len);
74 if (r < 0)
75 return r;
76
77 r = sd_radv_prefix_set_preferred_lifetime(p, lifetime_preferred);
78 if (r < 0)
79 return r;
80
81 r = sd_radv_prefix_set_valid_lifetime(p, lifetime_valid);
82 if (r < 0)
83 return r;
84
85 r = sd_radv_stop(radv);
86 if (r < 0)
87 return r;
88
89 r = sd_radv_add_prefix(radv, p, true);
90 if (r < 0 && r != -EEXIST)
91 return r;
92
93 r = manager_dhcp6_prefix_add(link->manager, &p->opt.in6_addr, link);
94 if (r < 0)
95 return r;
96
97 return sd_radv_start(radv);
98 }
99
100 static int dhcp6_route_remove_handler(sd_netlink *nl, sd_netlink_message *m, Link *link) {
101 int r;
102
103 assert(link);
104
105 r = sd_netlink_message_get_errno(m);
106 if (r < 0)
107 log_link_debug_errno(link, r, "Received error on unreachable route removal for DHCPv6 delegated subnetl: %m");
108
109 return 1;
110 }
111
112 int dhcp6_lease_pd_prefix_lost(sd_dhcp6_client *client, Link* link) {
113 int r;
114 sd_dhcp6_lease *lease;
115 union in_addr_union pd_prefix;
116 uint8_t pd_prefix_len;
117 uint32_t lifetime_preferred, lifetime_valid;
118
119 r = sd_dhcp6_client_get_lease(client, &lease);
120 if (r < 0)
121 return r;
122
123 sd_dhcp6_lease_reset_pd_prefix_iter(lease);
124
125 while (sd_dhcp6_lease_get_pd(lease, &pd_prefix.in6, &pd_prefix_len,
126 &lifetime_preferred,
127 &lifetime_valid) >= 0) {
128 _cleanup_free_ char *buf = NULL;
129 _cleanup_free_ Route *route = NULL;
130
131 if (pd_prefix_len > 64)
132 continue;
133
134 (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
135
136 if (pd_prefix_len < 64) {
137 r = route_new(&route);
138 if (r < 0) {
139 log_link_warning_errno(link, r, "Cannot create unreachable route to delete for DHCPv6 delegated subnet %s/%u: %m",
140 strnull(buf),
141 pd_prefix_len);
142 continue;
143 }
144
145 r = route_add(link, AF_INET6, &pd_prefix, pd_prefix_len, 0, 0, 0, &route);
146 if (r < 0) {
147 log_link_warning_errno(link, r, "Failed to add unreachable route to delete for DHCPv6 delegated subnet %s/%u: %m",
148 strnull(buf),
149 pd_prefix_len);
150 continue;
151 }
152
153 route_update(route, NULL, 0, NULL, NULL, 0, 0, RTN_UNREACHABLE);
154
155 r = route_remove(route, link, dhcp6_route_remove_handler);
156 if (r < 0) {
157 (void) in_addr_to_string(AF_INET6,
158 &pd_prefix, &buf);
159
160 log_link_warning_errno(link, r, "Cannot delete unreachable route for DHCPv6 delegated subnet %s/%u: %m",
161 strnull(buf),
162 pd_prefix_len);
163
164 continue;
165 }
166
167 log_link_debug(link, "Removing unreachable route %s/%u",
168 strnull(buf), pd_prefix_len);
169 }
170 }
171
172 return 0;
173 }
174
175 static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i,
176 struct in6_addr *pd_prefix,
177 uint8_t pd_prefix_len,
178 uint32_t lifetime_preferred,
179 uint32_t lifetime_valid) {
180 Link *link;
181 Manager *manager = dhcp6_link->manager;
182 union in_addr_union prefix;
183 uint64_t n_prefixes, n_used = 0;
184 _cleanup_free_ char *buf = NULL;
185 _cleanup_free_ char *assigned_buf = NULL;
186 int r;
187
188 assert(manager);
189 assert(pd_prefix_len <= 64);
190
191 prefix.in6 = *pd_prefix;
192
193 r = in_addr_mask(AF_INET6, &prefix, pd_prefix_len);
194 if (r < 0)
195 return r;
196
197 n_prefixes = UINT64_C(1) << (64 - pd_prefix_len);
198
199 (void) in_addr_to_string(AF_INET6, &prefix, &buf);
200 log_link_debug(dhcp6_link, "Assigning up to %" PRIu64 " prefixes from %s/%u",
201 n_prefixes, strnull(buf), pd_prefix_len);
202
203 while (hashmap_iterate(manager->links, i, (void **)&link, NULL)) {
204 Link *assigned_link;
205
206 if (n_used == n_prefixes) {
207 log_link_debug(dhcp6_link, "Assigned %" PRIu64 "/%" PRIu64 " prefixes from %s/%u",
208 n_used, n_prefixes, strnull(buf), pd_prefix_len);
209
210 return -EAGAIN;
211 }
212
213 if (link == dhcp6_link)
214 continue;
215
216 if (!dhcp6_get_prefix_delegation(link))
217 continue;
218
219 assigned_link = manager_dhcp6_prefix_get(manager, &prefix.in6);
220 if (assigned_link != NULL && assigned_link != link)
221 continue;
222
223 (void) in_addr_to_string(AF_INET6, &prefix, &assigned_buf);
224 r = dhcp6_pd_prefix_assign(link, &prefix.in6, 64,
225 lifetime_preferred, lifetime_valid);
226 if (r < 0) {
227 log_link_error_errno(link, r, "Unable to %s prefix %s/64 from %s/%u for link: %m",
228 assigned_link ? "update": "assign",
229 strnull(assigned_buf),
230 strnull(buf), pd_prefix_len);
231
232 if (assigned_link == NULL)
233 continue;
234
235 } else
236 log_link_debug(link, "Assigned prefix %" PRIu64 "/%" PRIu64 " %s/64 from %s/%u to link",
237 n_used + 1, n_prefixes,
238 strnull(assigned_buf),
239 strnull(buf), pd_prefix_len);
240
241 n_used++;
242
243 r = in_addr_prefix_next(AF_INET6, &prefix, 64);
244 if (r < 0 && n_used < n_prefixes)
245 return r;
246 }
247
248 return 0;
249 }
250
251 static int dhcp6_route_handler(sd_netlink *nl, sd_netlink_message *m, Link *link) {
252 int r;
253
254 assert(link);
255
256 r = sd_netlink_message_get_errno(m);
257 if (r < 0 && r != -EEXIST)
258 log_link_debug_errno(link, r, "Received error when adding unreachable route for DHCPv6 delegated subnet: %m");
259
260 return 1;
261 }
262
263 static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) {
264 int r;
265 sd_dhcp6_lease *lease;
266 union in_addr_union pd_prefix;
267 uint8_t pd_prefix_len;
268 uint32_t lifetime_preferred, lifetime_valid;
269 _cleanup_free_ char *buf = NULL;
270 Iterator i = ITERATOR_FIRST;
271
272 r = sd_dhcp6_client_get_lease(client, &lease);
273 if (r < 0)
274 return r;
275
276 sd_dhcp6_lease_reset_pd_prefix_iter(lease);
277
278 while (sd_dhcp6_lease_get_pd(lease, &pd_prefix.in6, &pd_prefix_len,
279 &lifetime_preferred,
280 &lifetime_valid) >= 0) {
281
282 if (pd_prefix_len > 64) {
283 (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
284 log_link_debug(link, "PD Prefix length > 64, ignoring prefix %s/%u",
285 strnull(buf), pd_prefix_len);
286 continue;
287 }
288
289 if (pd_prefix_len < 48) {
290 (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
291 log_link_warning(link, "PD Prefix length < 48, looks unusual %s/%u",
292 strnull(buf), pd_prefix_len);
293 }
294
295 if (pd_prefix_len < 64) {
296 _cleanup_(route_freep) Route *route = NULL;
297 uint32_t table;
298
299 (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
300
301 r = route_new(&route);
302 if (r < 0) {
303 log_link_warning_errno(link, r, "Cannot create unreachable route for DHCPv6 delegated subnet %s/%u: %m",
304 strnull(buf),
305 pd_prefix_len);
306 continue;
307 }
308
309 table = link_get_dhcp_route_table(link);
310
311 r = route_add(link, AF_INET6, &pd_prefix, pd_prefix_len, 0, 0, table, &route);
312 if (r < 0) {
313 log_link_warning_errno(link, r, "Failed to add unreachable route for DHCPv6 delegated subnet %s/%u: %m",
314 strnull(buf),
315 pd_prefix_len);
316 continue;
317 }
318
319 route_update(route, NULL, 0, NULL, NULL, 0, 0, RTN_UNREACHABLE);
320
321 r = route_configure(route, link, dhcp6_route_handler);
322 if (r < 0) {
323 log_link_warning_errno(link, r, "Cannot configure unreachable route for delegated subnet %s/%u: %m",
324 strnull(buf),
325 pd_prefix_len);
326 continue;
327 }
328
329 log_link_debug(link, "Configuring unreachable route for %s/%u",
330 strnull(buf), pd_prefix_len);
331
332 } else
333 log_link_debug(link, "Not adding a blocking route since distributed prefix is /64");
334
335 r = dhcp6_pd_prefix_distribute(link, &i, &pd_prefix.in6,
336 pd_prefix_len,
337 lifetime_preferred,
338 lifetime_valid);
339 if (r < 0 && r != -EAGAIN)
340 return r;
341
342 if (r >= 0)
343 i = ITERATOR_FIRST;
344 }
345
346 return 0;
347 }
348
349 int dhcp6_request_prefix_delegation(Link *link) {
350 Link *l;
351 Iterator i;
352
353 assert_return(link, -EINVAL);
354 assert_return(link->manager, -EOPNOTSUPP);
355
356 if (dhcp6_get_prefix_delegation(link) <= 0)
357 return 0;
358
359 log_link_debug(link, "Requesting DHCPv6 prefixes to be delegated for new link");
360
361 HASHMAP_FOREACH(l, link->manager->links, i) {
362 int r, enabled;
363
364 if (l == link)
365 continue;
366
367 if (!l->dhcp6_client)
368 continue;
369
370 r = sd_dhcp6_client_get_prefix_delegation(l->dhcp6_client, &enabled);
371 if (r < 0) {
372 log_link_warning_errno(l, r, "Cannot get prefix delegation when adding new link");
373 continue;
374 }
375
376 if (enabled == 0) {
377 r = sd_dhcp6_client_set_prefix_delegation(l->dhcp6_client, 1);
378 if (r < 0) {
379 log_link_warning_errno(l, r, "Cannot enable prefix delegation when adding new link");
380 continue;
381 }
382 }
383
384 r = sd_dhcp6_client_is_running(l->dhcp6_client);
385 if (r <= 0)
386 continue;
387
388 if (enabled != 0) {
389 log_link_debug(l, "Requesting re-assignment of delegated prefixes after adding new link");
390 (void) dhcp6_lease_pd_prefix_acquired(l->dhcp6_client, l);
391
392 continue;
393 }
394
395 r = sd_dhcp6_client_stop(l->dhcp6_client);
396 if (r < 0) {
397 log_link_warning_errno(l, r, "Cannot stop DHCPv6 prefix delegation client after adding new link");
398 continue;
399 }
400
401 r = sd_dhcp6_client_start(l->dhcp6_client);
402 if (r < 0) {
403 log_link_warning_errno(l, r, "Cannot restart DHCPv6 prefix delegation client after adding new link");
404 continue;
405 }
406
407 log_link_debug(l, "Restarted DHCPv6 client to acquire prefix delegations after adding new link");
408 }
409
410 return 0;
411 }
412
413 static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
414 int r;
415
416 assert(link);
417
418 r = sd_netlink_message_get_errno(m);
419 if (r < 0 && r != -EEXIST) {
420 if (link->rtnl_extended_attrs) {
421 log_link_warning(link, "Could not set extended netlink attributes, reverting to fallback mechanism");
422
423 link->rtnl_extended_attrs = false;
424 dhcp6_lease_address_acquired(link->dhcp6_client, link);
425
426 return 1;
427 }
428
429 log_link_error_errno(link, r, "Could not set DHCPv6 address: %m");
430
431 link_enter_failed(link);
432
433 } else if (r >= 0)
434 manager_rtnl_process_address(rtnl, m, link->manager);
435
436 return 1;
437 }
438
439 static int dhcp6_address_change(
440 Link *link,
441 struct in6_addr *ip6_addr,
442 uint32_t lifetime_preferred,
443 uint32_t lifetime_valid) {
444
445 _cleanup_(address_freep) Address *addr = NULL;
446 char buffer[INET6_ADDRSTRLEN];
447 int r;
448
449 r = address_new(&addr);
450 if (r < 0)
451 return r;
452
453 addr->family = AF_INET6;
454 memcpy(&addr->in_addr.in6, ip6_addr, sizeof(*ip6_addr));
455
456 addr->flags = IFA_F_NOPREFIXROUTE;
457 addr->prefixlen = 128;
458
459 addr->cinfo.ifa_prefered = lifetime_preferred;
460 addr->cinfo.ifa_valid = lifetime_valid;
461
462 log_link_info(link,
463 "DHCPv6 address %s/%d timeout preferred %d valid %d",
464 inet_ntop(AF_INET6, &addr->in_addr.in6, buffer, sizeof(buffer)),
465 addr->prefixlen, lifetime_preferred, lifetime_valid);
466
467 r = address_configure(addr, link, dhcp6_address_handler, true);
468 if (r < 0)
469 log_link_warning_errno(link, r, "Could not assign DHCPv6 address: %m");
470
471 return r;
472 }
473
474 static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link) {
475 int r;
476 sd_dhcp6_lease *lease;
477 struct in6_addr ip6_addr;
478 uint32_t lifetime_preferred, lifetime_valid;
479
480 r = sd_dhcp6_client_get_lease(client, &lease);
481 if (r < 0)
482 return r;
483
484 sd_dhcp6_lease_reset_address_iter(lease);
485
486 while (sd_dhcp6_lease_get_address(lease, &ip6_addr,
487 &lifetime_preferred,
488 &lifetime_valid) >= 0) {
489
490 r = dhcp6_address_change(link, &ip6_addr, lifetime_preferred, lifetime_valid);
491 if (r < 0)
492 return r;
493 }
494
495 return 0;
496 }
497
498 static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) {
499 int r;
500 Link *link = userdata;
501
502 assert(link);
503 assert(link->network);
504
505 if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
506 return;
507
508 switch(event) {
509 case SD_DHCP6_CLIENT_EVENT_STOP:
510 case SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE:
511 case SD_DHCP6_CLIENT_EVENT_RETRANS_MAX:
512 if (sd_dhcp6_client_get_lease(client, NULL) >= 0)
513 log_link_warning(link, "DHCPv6 lease lost");
514
515 (void) dhcp6_lease_pd_prefix_lost(client, link);
516 (void) manager_dhcp6_prefix_remove_all(link->manager, link);
517
518 link->dhcp6_configured = false;
519 break;
520
521 case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE:
522 r = dhcp6_lease_address_acquired(client, link);
523 if (r < 0) {
524 link_enter_failed(link);
525 return;
526 }
527
528 r = dhcp6_lease_pd_prefix_acquired(client, link);
529 if (r < 0)
530 log_link_debug(link, "DHCPv6 did not receive prefixes to delegate");
531
532 _fallthrough_;
533 case SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST:
534 r = dhcp6_lease_information_acquired(client, link);
535 if (r < 0) {
536 link_enter_failed(link);
537 return;
538 }
539
540 link->dhcp6_configured = true;
541 break;
542
543 default:
544 if (event < 0)
545 log_link_warning_errno(link, event, "DHCPv6 error: %m");
546 else
547 log_link_warning(link, "DHCPv6 unknown event: %d", event);
548 return;
549 }
550
551 link_check_ready(link);
552 }
553
554 int dhcp6_request_address(Link *link, int ir) {
555 int r, inf_req, pd;
556 bool running;
557
558 assert(link);
559 assert(link->dhcp6_client);
560 assert(link->network);
561 assert(in_addr_is_link_local(AF_INET6, (const union in_addr_union*)&link->ipv6ll_address) > 0);
562
563 r = sd_dhcp6_client_is_running(link->dhcp6_client);
564 if (r < 0)
565 return r;
566 else
567 running = r;
568
569 r = sd_dhcp6_client_get_prefix_delegation(link->dhcp6_client, &pd);
570 if (r < 0)
571 return r;
572
573 if (pd && ir && link->network->dhcp6_force_pd_other_information) {
574 log_link_debug(link, "Enabling managed mode to request DHCPv6 PD with 'Other Information' set");
575
576 r = sd_dhcp6_client_set_address_request(link->dhcp6_client,
577 false);
578 if (r < 0 )
579 return r;
580
581 ir = false;
582 }
583
584 if (running) {
585 r = sd_dhcp6_client_get_information_request(link->dhcp6_client, &inf_req);
586 if (r < 0)
587 return r;
588
589 if (inf_req == ir)
590 return 0;
591
592 r = sd_dhcp6_client_stop(link->dhcp6_client);
593 if (r < 0)
594 return r;
595 } else {
596 r = sd_dhcp6_client_set_local_address(link->dhcp6_client, &link->ipv6ll_address);
597 if (r < 0)
598 return r;
599 }
600
601 r = sd_dhcp6_client_set_information_request(link->dhcp6_client, ir);
602 if (r < 0)
603 return r;
604
605 r = sd_dhcp6_client_start(link->dhcp6_client);
606 if (r < 0)
607 return r;
608
609 return 0;
610 }
611
612 static int dhcp6_set_hostname(sd_dhcp6_client *client, Link *link) {
613 _cleanup_free_ char *hostname = NULL;
614 const char *hn;
615 int r;
616
617 assert(link);
618
619 if (!link->network->dhcp_send_hostname)
620 hn = NULL;
621 else if (link->network->dhcp_hostname)
622 hn = link->network->dhcp_hostname;
623 else {
624 r = gethostname_strict(&hostname);
625 if (r < 0 && r != -ENXIO) /* ENXIO: no hostname set or hostname is "localhost" */
626 return r;
627
628 hn = hostname;
629 }
630
631 r = sd_dhcp6_client_set_fqdn(client, hn);
632 if (r == -EINVAL && hostname)
633 /* Ignore error when the machine's hostname is not suitable to send in DHCP packet. */
634 log_link_warning_errno(link, r, "DHCP6 CLIENT: Failed to set hostname from kernel hostname, ignoring: %m");
635 else if (r < 0)
636 return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set hostname: %m");
637
638 return 0;
639 }
640
641 int dhcp6_configure(Link *link) {
642 _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
643 const DUID *duid;
644 int r;
645
646 assert(link);
647 assert(link->network);
648
649 if (link->dhcp6_client)
650 return 0;
651
652 r = sd_dhcp6_client_new(&client);
653 if (r == -ENOMEM)
654 return log_oom();
655 if (r < 0)
656 return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to create DHCP6 client: %m");
657
658 r = sd_dhcp6_client_attach_event(client, NULL, 0);
659 if (r < 0)
660 return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to attach event: %m");
661
662 r = sd_dhcp6_client_set_mac(client,
663 (const uint8_t *) &link->mac,
664 sizeof (link->mac), ARPHRD_ETHER);
665 if (r < 0)
666 return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set MAC address: %m");
667
668 if (link->network->iaid_set) {
669 r = sd_dhcp6_client_set_iaid(client, link->network->iaid);
670 if (r < 0)
671 return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set IAID: %m");
672 }
673
674 duid = link_get_duid(link);
675 if (duid->type == DUID_TYPE_LLT && duid->raw_data_len == 0)
676 r = sd_dhcp6_client_set_duid_llt(client, duid->llt_time);
677 else
678 r = sd_dhcp6_client_set_duid(client,
679 duid->type,
680 duid->raw_data_len > 0 ? duid->raw_data : NULL,
681 duid->raw_data_len);
682 if (r < 0)
683 return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set DUID: %m");
684
685 r = dhcp6_set_hostname(client, link);
686 if (r < 0)
687 return r;
688
689 r = sd_dhcp6_client_set_ifindex(client, link->ifindex);
690 if (r < 0)
691 return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set ifindex: %m");
692
693 if (link->network->rapid_commit) {
694 r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_RAPID_COMMIT);
695 if (r < 0)
696 return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set request flag for rapid commit: %m");
697 }
698
699 r = sd_dhcp6_client_set_callback(client, dhcp6_handler, link);
700 if (r < 0)
701 return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set callback: %m");
702
703 if (dhcp6_enable_prefix_delegation(link)) {
704 r = sd_dhcp6_client_set_prefix_delegation(client, true);
705 if (r < 0)
706 return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set prefix delegation: %m");
707 }
708
709 link->dhcp6_client = TAKE_PTR(client);
710
711 return 0;
712 }