]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/network/networkd-dhcp6.c
tree-wide: drop license boilerplate
[thirdparty/systemd.git] / src / network / networkd-dhcp6.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
5c79bd79
PF
2/***
3 This file is part of systemd.
4
5 Copyright (C) 2014 Intel Corporation. All rights reserved.
5c79bd79
PF
6***/
7
8#include <netinet/ether.h>
9#include <linux/if.h>
76c3246d 10#include "sd-radv.h"
5c79bd79 11
5c79bd79
PF
12#include "sd-dhcp6-client.h"
13
76c3246d 14#include "hashmap.h"
8006aa32 15#include "hostname-util.h"
07630cea 16#include "network-internal.h"
23f53b99
TG
17#include "networkd-link.h"
18#include "networkd-manager.h"
76c3246d
PF
19#include "siphash24.h"
20#include "string-util.h"
21#include "radv-internal.h"
07630cea 22
be3a09b7
PF
23static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link);
24
103b81ee
PF
25static bool dhcp6_verify_link(Link *link) {
26 if (!link->network) {
27 log_link_info(link, "Link is not managed by us");
28 return false;
29 }
30
31 if (!IN_SET(link->network->router_prefix_delegation,
32 RADV_PREFIX_DELEGATION_DHCP6,
33 RADV_PREFIX_DELEGATION_BOTH)) {
34 log_link_debug(link, "Link does not request DHCPv6 prefix delegation");
35 return false;
36 }
37
38 return true;
39}
40
41static bool dhcp6_enable_prefix_delegation(Link *dhcp6_link) {
42 Manager *manager;
43 Link *l;
44 Iterator i;
45
46 assert(dhcp6_link);
47
48 manager = dhcp6_link->manager;
49 assert(manager);
50
51 HASHMAP_FOREACH(l, manager->links, i) {
52 if (l == dhcp6_link)
53 continue;
54
55 if (!dhcp6_verify_link(l))
56 continue;
57
58 return true;
59 }
60
61 return false;
62}
63
c62c4628
PF
64static int dhcp6_lease_information_acquired(sd_dhcp6_client *client,
65 Link *link) {
66 return 0;
67}
68
76c3246d
PF
69static int dhcp6_pd_prefix_assign(Link *link, struct in6_addr *prefix,
70 uint8_t prefix_len,
71 uint32_t lifetime_preferred,
72 uint32_t lifetime_valid) {
73 sd_radv *radv = link->radv;
74 int r;
75 _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL;
76
77 r = sd_radv_prefix_new(&p);
78 if (r < 0)
79 return r;
80
81 r = sd_radv_prefix_set_prefix(p, prefix, prefix_len);
82 if (r < 0)
83 return r;
84
85 r = sd_radv_prefix_set_preferred_lifetime(p, lifetime_preferred);
86 if (r < 0)
87 return r;
88
89 r = sd_radv_prefix_set_valid_lifetime(p, lifetime_valid);
90 if (r < 0)
91 return r;
92
93 r = sd_radv_stop(radv);
94 if (r < 0)
95 return r;
96
97 r = sd_radv_add_prefix(radv, p, true);
98 if (r < 0 && r != -EEXIST)
99 return r;
100
101 r = manager_dhcp6_prefix_add(link->manager, &p->opt.in6_addr, link);
102 if (r < 0)
103 return r;
104
105 return sd_radv_start(radv);
106}
107
108static Network *dhcp6_reset_pd_prefix_network(Link *link) {
109 assert(link);
110 assert(link->manager);
111 assert(link->manager->networks);
112
113 return link->manager->networks;
114}
115
116static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i,
117 struct in6_addr *pd_prefix,
118 uint8_t pd_prefix_len,
119 uint32_t lifetime_preferred,
120 uint32_t lifetime_valid) {
121 Link *link;
122 Manager *manager = dhcp6_link->manager;
123 union in_addr_union prefix;
124 uint8_t n_prefixes, n_used = 0;
125 _cleanup_free_ char *buf = NULL;
126 int r;
127
128 assert(manager);
129 assert(pd_prefix_len <= 64);
130
131 prefix.in6 = *pd_prefix;
132
133 r = in_addr_mask(AF_INET6, &prefix, pd_prefix_len);
134 if (r < 0)
135 return r;
136
137 n_prefixes = 1 << (64 - pd_prefix_len);
138
139 (void) in_addr_to_string(AF_INET6, &prefix, &buf);
140 log_link_debug(dhcp6_link, "Assigning up to %u prefixes from %s/%u",
141 n_prefixes, strnull(buf), pd_prefix_len);
142
143 while (hashmap_iterate(manager->links, i, (void **)&link, NULL)) {
144 Link *assigned_link;
145
146 if (n_used == n_prefixes) {
147 log_link_debug(dhcp6_link, "Assigned %u/%u prefixes from %s/%u",
148 n_used, n_prefixes, strnull(buf), pd_prefix_len);
149
150 return -EAGAIN;
151 }
152
153 if (link == dhcp6_link)
154 continue;
155
156 if (!dhcp6_verify_link(link))
157 continue;
158
159 assigned_link = manager_dhcp6_prefix_get(manager, &prefix.in6);
160 if (assigned_link != NULL && assigned_link != link)
161 continue;
162
163 r = dhcp6_pd_prefix_assign(link, &prefix.in6, 64,
164 lifetime_preferred, lifetime_valid);
165 if (r < 0) {
166 log_link_error_errno(link, r, "Unable to %s prefix %s/%u for link: %m",
167 assigned_link ? "update": "assign",
168 strnull(buf), pd_prefix_len);
169
170 if (assigned_link == NULL)
171 continue;
172
173 } else
174 log_link_debug(link, "Assigned prefix %u/%u %s/64 to link",
175 n_used + 1, n_prefixes, strnull(buf));
176
177 n_used++;
178
179 r = in_addr_prefix_next(AF_INET6, &prefix, pd_prefix_len);
180 if (r < 0 && n_used < n_prefixes)
181 return r;
182 }
183
73922903
PF
184 if (n_used < n_prefixes) {
185 Route *route;
186 int n = n_used;
187
188 r = route_new(&route);
189 if (r < 0)
190 return r;
191
192 while (n < n_prefixes) {
193 route_update(route, &prefix, pd_prefix_len, NULL, NULL,
194 0, 0, RTN_UNREACHABLE);
195
196 r = route_configure(route, link, NULL);
197 if (r < 0) {
198 route_free(route);
199 return r;
200 }
201
202 r = in_addr_prefix_next(AF_INET6, &prefix, pd_prefix_len);
203 if (r < 0)
204 return r;
205 }
206 }
207
76c3246d
PF
208 return n_used;
209}
210
211static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) {
212 int r;
213 sd_dhcp6_lease *lease;
214 struct in6_addr pd_prefix;
215 uint8_t pd_prefix_len;
216 uint32_t lifetime_preferred, lifetime_valid;
217 _cleanup_free_ char *buf = NULL;
218 Iterator i = ITERATOR_FIRST;
219
220 r = sd_dhcp6_client_get_lease(client, &lease);
221 if (r < 0)
222 return r;
223
224 (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &pd_prefix, &buf);
225
226 dhcp6_reset_pd_prefix_network(link);
227 sd_dhcp6_lease_reset_pd_prefix_iter(lease);
228
229 while (sd_dhcp6_lease_get_pd(lease, &pd_prefix, &pd_prefix_len,
230 &lifetime_preferred,
231 &lifetime_valid) >= 0) {
232
233 if (pd_prefix_len > 64) {
234 log_link_debug(link, "PD Prefix length > 64, ignoring prefix %s/%u",
235 strnull(buf), pd_prefix_len);
236 continue;
237 }
238
239 r = dhcp6_pd_prefix_distribute(link, &i, &pd_prefix,
240 pd_prefix_len,
241 lifetime_preferred,
242 lifetime_valid);
243 if (r < 0 && r != -EAGAIN)
244 return r;
245
246 if (r >= 0)
247 i = ITERATOR_FIRST;
248 }
249
250 return 0;
251}
252
1c4baffc 253static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m,
c62c4628
PF
254 void *userdata) {
255 _cleanup_link_unref_ Link *link = userdata;
256 int r;
257
258 assert(link);
259
1c4baffc 260 r = sd_netlink_message_get_errno(m);
c62c4628 261 if (r < 0 && r != -EEXIST) {
be3a09b7
PF
262 if (link->rtnl_extended_attrs) {
263 log_link_warning(link, "Could not set extended netlink attributes, reverting to fallback mechanism");
264
265 link->rtnl_extended_attrs = false;
266 dhcp6_lease_address_acquired(link->dhcp6_client, link);
267
268 return 1;
269 }
270
e53fc357 271 log_link_error_errno(link, r, "Could not set DHCPv6 address: %m");
c62c4628
PF
272
273 link_enter_failed(link);
274
275 } else if (r >= 0)
200a0868 276 manager_rtnl_process_address(rtnl, m, link->manager);
c62c4628
PF
277
278 return 1;
279}
280
1e7a0e21
LP
281static int dhcp6_address_change(
282 Link *link,
283 struct in6_addr *ip6_addr,
284 uint32_t lifetime_preferred,
285 uint32_t lifetime_valid) {
286
c62c4628 287 _cleanup_address_free_ Address *addr = NULL;
1e7a0e21
LP
288 char buffer[INET6_ADDRSTRLEN];
289 int r;
c62c4628 290
f0213e37 291 r = address_new(&addr);
c62c4628
PF
292 if (r < 0)
293 return r;
294
295 addr->family = AF_INET6;
296 memcpy(&addr->in_addr.in6, ip6_addr, sizeof(*ip6_addr));
851c9f82
PF
297
298 addr->flags = IFA_F_NOPREFIXROUTE;
6d8f6b0b 299 addr->prefixlen = 128;
c62c4628
PF
300
301 addr->cinfo.ifa_prefered = lifetime_preferred;
302 addr->cinfo.ifa_valid = lifetime_valid;
303
f2341e0a 304 log_link_info(link,
1e7a0e21
LP
305 "DHCPv6 address %s/%d timeout preferred %d valid %d",
306 inet_ntop(AF_INET6, &addr->in_addr.in6, buffer, sizeof(buffer)),
f2341e0a 307 addr->prefixlen, lifetime_preferred, lifetime_valid);
c62c4628 308
66669078 309 r = address_configure(addr, link, dhcp6_address_handler, true);
c62c4628 310 if (r < 0)
f2341e0a 311 log_link_warning_errno(link, r, "Could not assign DHCPv6 address: %m");
c62c4628
PF
312
313 return r;
314}
315
c62c4628
PF
316static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link) {
317 int r;
318 sd_dhcp6_lease *lease;
319 struct in6_addr ip6_addr;
320 uint32_t lifetime_preferred, lifetime_valid;
c62c4628
PF
321
322 r = sd_dhcp6_client_get_lease(client, &lease);
323 if (r < 0)
324 return r;
325
326 sd_dhcp6_lease_reset_address_iter(lease);
327
328 while (sd_dhcp6_lease_get_address(lease, &ip6_addr,
483d099e
ZJS
329 &lifetime_preferred,
330 &lifetime_valid) >= 0) {
c62c4628 331
6d8f6b0b 332 r = dhcp6_address_change(link, &ip6_addr, lifetime_preferred, lifetime_valid);
c62c4628
PF
333 if (r < 0)
334 return r;
335 }
336
337 return 0;
338}
339
5c79bd79 340static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) {
c62c4628 341 int r;
5c79bd79
PF
342 Link *link = userdata;
343
344 assert(link);
345 assert(link->network);
5c79bd79
PF
346
347 if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
348 return;
349
350 switch(event) {
10c9ce61
DH
351 case SD_DHCP6_CLIENT_EVENT_STOP:
352 case SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE:
353 case SD_DHCP6_CLIENT_EVENT_RETRANS_MAX:
3098562c
TG
354 if (sd_dhcp6_client_get_lease(client, NULL) >= 0)
355 log_link_warning(link, "DHCPv6 lease lost");
18d29550 356
76c3246d
PF
357 (void) manager_dhcp6_prefix_remove_all(link->manager, link);
358
18d29550 359 link->dhcp6_configured = false;
c62c4628
PF
360 break;
361
10c9ce61 362 case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE:
c62c4628
PF
363 r = dhcp6_lease_address_acquired(client, link);
364 if (r < 0) {
365 link_enter_failed(link);
366 return;
367 }
368
76c3246d
PF
369 r = dhcp6_lease_pd_prefix_acquired(client, link);
370 if (r < 0)
371 log_link_debug(link, "DHCPv6 did not receive prefixes to delegate");
372
4831981d 373 _fallthrough_;
10c9ce61 374 case SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST:
c62c4628
PF
375 r = dhcp6_lease_information_acquired(client, link);
376 if (r < 0) {
377 link_enter_failed(link);
378 return;
379 }
5c79bd79 380
18d29550 381 link->dhcp6_configured = true;
5c79bd79
PF
382 break;
383
384 default:
385 if (event < 0)
e53fc357 386 log_link_warning_errno(link, event, "DHCPv6 error: %m");
5c79bd79 387 else
e53fc357 388 log_link_warning(link, "DHCPv6 unknown event: %d", event);
5c79bd79
PF
389 return;
390 }
18d29550 391
8012cd39 392 link_check_ready(link);
5c79bd79
PF
393}
394
720bec40 395int dhcp6_request_address(Link *link, int ir) {
7a695d8e
TG
396 int r, inf_req;
397 bool running;
5c79bd79 398
7a695d8e
TG
399 assert(link);
400 assert(link->dhcp6_client);
720bec40 401 assert(in_addr_is_link_local(AF_INET6, (const union in_addr_union*)&link->ipv6ll_address) > 0);
85bd849f 402
7a695d8e
TG
403 r = sd_dhcp6_client_is_running(link->dhcp6_client);
404 if (r < 0)
405 return r;
406 else
407 running = !!r;
e6604041 408
7a695d8e 409 if (running) {
720bec40
TY
410 r = sd_dhcp6_client_get_information_request(link->dhcp6_client, &inf_req);
411 if (r < 0)
412 return r;
413
414 if (inf_req == ir)
415 return 0;
416
7a695d8e
TG
417 r = sd_dhcp6_client_stop(link->dhcp6_client);
418 if (r < 0)
419 return r;
720bec40
TY
420 } else {
421 r = sd_dhcp6_client_set_local_address(link->dhcp6_client, &link->ipv6ll_address);
422 if (r < 0)
423 return r;
7a695d8e 424 }
85bd849f 425
720bec40
TY
426 r = sd_dhcp6_client_set_information_request(link->dhcp6_client, ir);
427 if (r < 0)
428 return r;
429
430 r = sd_dhcp6_client_start(link->dhcp6_client);
7a695d8e
TG
431 if (r < 0)
432 return r;
85bd849f 433
7a695d8e
TG
434 return 0;
435}
18d29550 436
8006aa32
SA
437static int dhcp6_set_hostname(sd_dhcp6_client *client, Link *link) {
438 _cleanup_free_ char *hostname = NULL;
439 const char *hn;
440 int r;
441
442 assert(link);
443
444 if (!link->network->dhcp_send_hostname)
445 hn = NULL;
446 else if (link->network->dhcp_hostname)
447 hn = link->network->dhcp_hostname;
448 else {
449 r = gethostname_strict(&hostname);
450 if (r < 0 && r != -ENXIO) /* ENXIO: no hostname set or hostname is "localhost" */
451 return r;
452
453 hn = hostname;
454 }
455
456 return sd_dhcp6_client_set_fqdn(client, hn);
457}
458
7a695d8e
TG
459int dhcp6_configure(Link *link) {
460 sd_dhcp6_client *client = NULL;
8341a5c3 461 const DUID *duid;
fb5c8216 462 int r;
7a695d8e
TG
463
464 assert(link);
fb5c8216 465 assert(link->network);
7a695d8e 466
62379e88
TG
467 if (link->dhcp6_client)
468 return 0;
469
7a695d8e
TG
470 r = sd_dhcp6_client_new(&client);
471 if (r < 0)
85bd849f 472 return r;
5c79bd79 473
7a695d8e 474 r = sd_dhcp6_client_attach_event(client, NULL, 0);
5c79bd79 475 if (r < 0)
e6604041 476 goto error;
5c79bd79 477
7a695d8e 478 r = sd_dhcp6_client_set_mac(client,
5c79bd79
PF
479 (const uint8_t *) &link->mac,
480 sizeof (link->mac), ARPHRD_ETHER);
e6604041
PF
481 if (r < 0)
482 goto error;
5c79bd79 483
413708d1
VK
484 r = sd_dhcp6_client_set_iaid(client, link->network->iaid);
485 if (r < 0)
486 goto error;
487
8341a5c3
ZJS
488 duid = link_duid(link);
489 r = sd_dhcp6_client_set_duid(client,
490 duid->type,
491 duid->raw_data_len > 0 ? duid->raw_data : NULL,
492 duid->raw_data_len);
413708d1
VK
493 if (r < 0)
494 goto error;
495
8006aa32 496 r = dhcp6_set_hostname(client, link);
8006aa32 497 if (r < 0)
4a3b54fd 498 goto error;
8006aa32 499
2f8e7633 500 r = sd_dhcp6_client_set_ifindex(client, link->ifindex);
e6604041
PF
501 if (r < 0)
502 goto error;
5c79bd79 503
fb5c8216
SS
504 if (link->network->rapid_commit) {
505 r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_RAPID_COMMIT);
506 if (r < 0)
507 goto error;
508 }
509
7a695d8e 510 r = sd_dhcp6_client_set_callback(client, dhcp6_handler, link);
e6604041
PF
511 if (r < 0)
512 goto error;
5c79bd79 513
103b81ee
PF
514 if (dhcp6_enable_prefix_delegation(link)) {
515 r = sd_dhcp6_client_set_prefix_delegation(client, true);
516 if (r < 0)
517 goto error;
518 }
519
7a695d8e 520 link->dhcp6_client = client;
e6604041 521
7a695d8e 522 return 0;
5c79bd79 523
7a695d8e
TG
524error:
525 sd_dhcp6_client_unref(client);
5c79bd79
PF
526 return r;
527}