]>
Commit | Line | Data |
---|---|---|
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 |
23 | static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link); |
24 | ||
103b81ee PF |
25 | static 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 | ||
41 | static 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 |
64 | static int dhcp6_lease_information_acquired(sd_dhcp6_client *client, |
65 | Link *link) { | |
66 | return 0; | |
67 | } | |
68 | ||
76c3246d PF |
69 | static 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 | ||
108 | static 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 | ||
116 | static 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 | ||
211 | static 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 | 253 | static 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 |
281 | static 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 |
316 | static 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 | 340 | static 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 | 395 | int 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 |
437 | static 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 |
459 | int 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 |
524 | error: |
525 | sd_dhcp6_client_unref(client); | |
5c79bd79 PF |
526 | return r; |
527 | } |