]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
5c79bd79 | 2 | /*** |
810adae9 | 3 | Copyright © 2014 Intel Corporation. All rights reserved. |
5c79bd79 PF |
4 | ***/ |
5 | ||
9aa5d8ba | 6 | #include <netinet/in.h> |
5c79bd79 | 7 | #include <linux/if.h> |
8f815e8b | 8 | #include <linux/if_arp.h> |
5c79bd79 | 9 | |
5c79bd79 PF |
10 | #include "sd-dhcp6-client.h" |
11 | ||
76c3246d | 12 | #include "hashmap.h" |
f963f895 | 13 | #include "hostname-setup.h" |
8006aa32 | 14 | #include "hostname-util.h" |
bffaa49e | 15 | #include "in-addr-prefix-util.h" |
aeed8332 | 16 | #include "missing_network.h" |
f09a4747 | 17 | #include "networkd-address-generation.h" |
093e3533 | 18 | #include "networkd-address.h" |
ca5ad760 | 19 | #include "networkd-dhcp6.h" |
23f53b99 TG |
20 | #include "networkd-link.h" |
21 | #include "networkd-manager.h" | |
76c5a0f2 | 22 | #include "networkd-queue.h" |
1d596fde | 23 | #include "networkd-radv.h" |
3b6a3bde | 24 | #include "networkd-route.h" |
76c3246d | 25 | #include "siphash24.h" |
838d39af | 26 | #include "string-table.h" |
76c3246d PF |
27 | #include "string-util.h" |
28 | #include "radv-internal.h" | |
07630cea | 29 | |
8cd37e43 YW |
30 | bool link_dhcp6_with_address_enabled(Link *link) { |
31 | if (!link_dhcp6_enabled(link)) | |
32 | return false; | |
33 | ||
34 | return link->network->dhcp6_use_address; | |
35 | } | |
36 | ||
2ffd6d73 YW |
37 | bool link_dhcp6_pd_is_enabled(Link *link) { |
38 | assert(link); | |
39 | ||
40 | if (!link->network) | |
41 | return false; | |
42 | ||
e502f94d | 43 | return link->network->dhcp6_pd; |
2ffd6d73 YW |
44 | } |
45 | ||
1633c457 YW |
46 | static bool dhcp6_lease_has_pd_prefix(sd_dhcp6_lease *lease) { |
47 | uint32_t lifetime_preferred, lifetime_valid; | |
b8ce3b44 | 48 | struct in6_addr pd_prefix; |
1633c457 YW |
49 | uint8_t pd_prefix_len; |
50 | ||
51 | if (!lease) | |
52 | return false; | |
53 | ||
54 | sd_dhcp6_lease_reset_pd_prefix_iter(lease); | |
55 | ||
b8ce3b44 | 56 | return sd_dhcp6_lease_get_pd(lease, &pd_prefix, &pd_prefix_len, &lifetime_preferred, &lifetime_valid) >= 0; |
1633c457 YW |
57 | } |
58 | ||
cde09c34 YW |
59 | static void link_remove_dhcp6_pd_prefix(Link *link, const struct in6_addr *prefix) { |
60 | void *key; | |
1633c457 | 61 | |
cde09c34 YW |
62 | assert(link); |
63 | assert(link->manager); | |
3b6a3bde | 64 | assert(prefix); |
1633c457 | 65 | |
cde09c34 YW |
66 | if (hashmap_get(link->manager->links_by_dhcp6_pd_prefix, prefix) != link) |
67 | return; | |
1633c457 | 68 | |
cde09c34 YW |
69 | hashmap_remove2(link->manager->links_by_dhcp6_pd_prefix, prefix, &key); |
70 | free(key); | |
71 | } | |
1633c457 | 72 | |
cde09c34 YW |
73 | static int link_add_dhcp6_pd_prefix(Link *link, const struct in6_addr *prefix) { |
74 | _cleanup_free_ struct in6_addr *copy = NULL; | |
75 | int r; | |
1633c457 | 76 | |
cde09c34 YW |
77 | assert(link); |
78 | assert(prefix); | |
1633c457 | 79 | |
cde09c34 YW |
80 | copy = newdup(struct in6_addr, prefix, 1); |
81 | if (!copy) | |
82 | return -ENOMEM; | |
1633c457 | 83 | |
cde09c34 YW |
84 | r = hashmap_ensure_put(&link->manager->links_by_dhcp6_pd_prefix, &in6_addr_hash_ops_free, copy, link); |
85 | if (r < 0) | |
86 | return r; | |
87 | if (r > 0) | |
88 | TAKE_PTR(copy); | |
89 | ||
90 | return 0; | |
1633c457 YW |
91 | } |
92 | ||
cde09c34 YW |
93 | static int link_get_by_dhcp6_pd_prefix(Manager *manager, const struct in6_addr *prefix, Link **ret) { |
94 | Link *link; | |
95 | ||
96 | assert(manager); | |
97 | assert(prefix); | |
98 | ||
99 | link = hashmap_get(manager->links_by_dhcp6_pd_prefix, prefix); | |
100 | if (!link) | |
101 | return -ENODEV; | |
1633c457 | 102 | |
cde09c34 YW |
103 | if (ret) |
104 | *ret = link; | |
105 | return 0; | |
1633c457 YW |
106 | } |
107 | ||
3b6a3bde | 108 | static int dhcp6_pd_get_assigned_prefix(Link *link, const struct in6_addr *pd_prefix, uint8_t pd_prefix_len, struct in6_addr *ret) { |
1633c457 | 109 | assert(link); |
3b6a3bde | 110 | assert(pd_prefix); |
1633c457 | 111 | |
3b6a3bde YW |
112 | if (!link_dhcp6_pd_is_enabled(link)) |
113 | return -ENOENT; | |
1633c457 | 114 | |
3b6a3bde YW |
115 | if (link->network->dhcp6_pd_assign) { |
116 | Address *address; | |
1633c457 | 117 | |
3b6a3bde YW |
118 | SET_FOREACH(address, link->addresses) { |
119 | if (address->source != NETWORK_CONFIG_SOURCE_DHCP6PD) | |
120 | continue; | |
121 | assert(address->family == AF_INET6); | |
1633c457 | 122 | |
d205851d | 123 | if (in6_addr_prefix_covers(pd_prefix, pd_prefix_len, &address->in_addr.in6) <= 0) |
3b6a3bde | 124 | continue; |
1633c457 | 125 | |
3b6a3bde | 126 | if (ret) { |
d205851d | 127 | struct in6_addr prefix = address->in_addr.in6; |
1633c457 | 128 | |
d205851d YW |
129 | in6_addr_mask(&prefix, 64); |
130 | *ret = prefix; | |
3b6a3bde YW |
131 | } |
132 | return 0; | |
133 | } | |
134 | } else { | |
135 | Route *route; | |
1633c457 | 136 | |
3b6a3bde YW |
137 | SET_FOREACH(route, link->routes) { |
138 | if (route->source != NETWORK_CONFIG_SOURCE_DHCP6PD) | |
139 | continue; | |
140 | assert(route->family == AF_INET6); | |
1633c457 | 141 | |
d205851d | 142 | if (in6_addr_prefix_covers(pd_prefix, pd_prefix_len, &route->dst.in6) > 0) { |
3b6a3bde YW |
143 | if (ret) |
144 | *ret = route->dst.in6; | |
145 | return 0; | |
146 | } | |
147 | } | |
148 | } | |
1633c457 | 149 | |
3b6a3bde | 150 | return -ENOENT; |
1633c457 YW |
151 | } |
152 | ||
3b6a3bde | 153 | int dhcp6_pd_remove(Link *link, bool only_marked) { |
1633c457 YW |
154 | Address *address; |
155 | Route *route; | |
1633c457 YW |
156 | int k, r = 0; |
157 | ||
158 | assert(link); | |
159 | assert(link->manager); | |
160 | ||
3b6a3bde | 161 | if (!link_dhcp6_pd_is_enabled(link)) |
1633c457 YW |
162 | return 0; |
163 | ||
3b6a3bde YW |
164 | if (!only_marked) |
165 | link->dhcp6_pd_configured = false; | |
1633c457 | 166 | |
3b6a3bde YW |
167 | SET_FOREACH(route, link->routes) { |
168 | if (route->source != NETWORK_CONFIG_SOURCE_DHCP6PD) | |
169 | continue; | |
170 | if (only_marked && !route_is_marked(route)) | |
171 | continue; | |
1633c457 | 172 | |
3b6a3bde YW |
173 | if (link->radv) |
174 | (void) sd_radv_remove_prefix(link->radv, &route->dst.in6, 64); | |
1633c457 | 175 | |
cde09c34 YW |
176 | link_remove_dhcp6_pd_prefix(link, &route->dst.in6); |
177 | ||
3b6a3bde | 178 | k = route_remove(route); |
1633c457 YW |
179 | if (k < 0) |
180 | r = k; | |
181 | ||
3b6a3bde | 182 | route_cancel_request(route); |
1633c457 YW |
183 | } |
184 | ||
3b6a3bde | 185 | SET_FOREACH(address, link->addresses) { |
cde09c34 YW |
186 | struct in6_addr prefix; |
187 | ||
3b6a3bde YW |
188 | if (address->source != NETWORK_CONFIG_SOURCE_DHCP6PD) |
189 | continue; | |
190 | if (only_marked && !address_is_marked(address)) | |
191 | continue; | |
192 | ||
cde09c34 YW |
193 | prefix = address->in_addr.in6; |
194 | in6_addr_mask(&prefix, 64); | |
3b6a3bde | 195 | |
cde09c34 YW |
196 | if (link->radv) |
197 | (void) sd_radv_remove_prefix(link->radv, &prefix, 64); | |
198 | ||
199 | link_remove_dhcp6_pd_prefix(link, &prefix); | |
3b6a3bde YW |
200 | |
201 | k = address_remove(address); | |
1633c457 YW |
202 | if (k < 0) |
203 | r = k; | |
3b6a3bde YW |
204 | |
205 | address_cancel_request(address); | |
1633c457 YW |
206 | } |
207 | ||
208 | return r; | |
209 | } | |
210 | ||
3b6a3bde | 211 | static int dhcp6_pd_check_ready(Link *link); |
1633c457 | 212 | |
3b6a3bde YW |
213 | static int dhcp6_pd_address_ready_callback(Address *address) { |
214 | Address *a; | |
2ffd6d73 | 215 | |
3b6a3bde YW |
216 | assert(address); |
217 | assert(address->link); | |
1633c457 | 218 | |
3b6a3bde YW |
219 | SET_FOREACH(a, address->link->addresses) |
220 | if (a->source == NETWORK_CONFIG_SOURCE_DHCP6PD) | |
221 | a->callback = NULL; | |
1633c457 | 222 | |
3b6a3bde YW |
223 | return dhcp6_pd_check_ready(address->link); |
224 | } | |
1633c457 | 225 | |
3b6a3bde | 226 | static int dhcp6_pd_check_ready(Link *link) { |
3b6a3bde | 227 | int r; |
1633c457 | 228 | |
3b6a3bde | 229 | assert(link); |
06d1105e | 230 | assert(link->network); |
1633c457 | 231 | |
3b6a3bde YW |
232 | if (link->dhcp6_pd_messages > 0) { |
233 | log_link_debug(link, "%s(): DHCPv6PD addresses and routes are not set.", __func__); | |
234 | return 0; | |
1633c457 YW |
235 | } |
236 | ||
06d1105e YW |
237 | if (link->network->dhcp6_pd_assign) { |
238 | bool has_ready = false; | |
239 | Address *address; | |
240 | ||
241 | SET_FOREACH(address, link->addresses) { | |
242 | if (address->source != NETWORK_CONFIG_SOURCE_DHCP6PD) | |
243 | continue; | |
244 | if (address_is_ready(address)) { | |
245 | has_ready = true; | |
246 | break; | |
247 | } | |
3b6a3bde | 248 | } |
1633c457 | 249 | |
06d1105e YW |
250 | if (!has_ready) { |
251 | SET_FOREACH(address, link->addresses) | |
252 | if (address->source == NETWORK_CONFIG_SOURCE_DHCP6PD) | |
253 | address->callback = dhcp6_pd_address_ready_callback; | |
1633c457 | 254 | |
06d1105e YW |
255 | log_link_debug(link, "%s(): no DHCPv6PD address is ready.", __func__); |
256 | return 0; | |
257 | } | |
3b6a3bde | 258 | } |
1633c457 | 259 | |
3b6a3bde | 260 | link->dhcp6_pd_configured = true; |
1633c457 | 261 | |
3b6a3bde | 262 | log_link_debug(link, "DHCPv6 PD addresses and routes set."); |
1633c457 | 263 | |
3b6a3bde YW |
264 | r = dhcp6_pd_remove(link, /* only_marked = */ true); |
265 | if (r < 0) | |
5a07fa9d | 266 | return r; |
1633c457 | 267 | |
3b6a3bde | 268 | link_check_ready(link); |
1633c457 YW |
269 | return 1; |
270 | } | |
271 | ||
3b6a3bde | 272 | static int dhcp6_pd_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { |
76c5a0f2 YW |
273 | int r; |
274 | ||
3b6a3bde YW |
275 | assert(link); |
276 | assert(link->dhcp6_pd_messages > 0); | |
76c5a0f2 | 277 | |
3b6a3bde | 278 | link->dhcp6_pd_messages--; |
76c5a0f2 | 279 | |
3b6a3bde YW |
280 | r = route_configure_handler_internal(rtnl, m, link, "Failed to add DHCPv6 Prefix Delegation route"); |
281 | if (r <= 0) | |
282 | return r; | |
76c5a0f2 | 283 | |
3b6a3bde YW |
284 | r = dhcp6_pd_check_ready(link); |
285 | if (r < 0) | |
286 | link_enter_failed(link); | |
76c5a0f2 | 287 | |
3b6a3bde | 288 | return 1; |
76c5a0f2 YW |
289 | } |
290 | ||
3b6a3bde | 291 | static int dhcp6_pd_request_route(Link *link, const struct in6_addr *prefix) { |
1633c457 | 292 | _cleanup_(route_freep) Route *route = NULL; |
3b6a3bde | 293 | Route *existing; |
1633c457 YW |
294 | int r; |
295 | ||
296 | assert(link); | |
3b6a3bde | 297 | assert(link->network); |
1633c457 | 298 | assert(prefix); |
3b6a3bde YW |
299 | |
300 | if (link->network->dhcp6_pd_assign) | |
301 | return 0; | |
1633c457 YW |
302 | |
303 | r = route_new(&route); | |
304 | if (r < 0) | |
305 | return r; | |
306 | ||
3b6a3bde | 307 | route->source = NETWORK_CONFIG_SOURCE_DHCP6PD; |
1633c457 | 308 | route->family = AF_INET6; |
b8ce3b44 | 309 | route->dst.in6 = *prefix; |
1633c457 | 310 | route->dst_prefixlen = 64; |
d9d6a10b | 311 | route->protocol = RTPROT_DHCP; |
9fe0b7b4 | 312 | route->priority = link->network->dhcp6_pd_route_metric; |
1633c457 | 313 | |
3b6a3bde YW |
314 | if (route_get(NULL, link, route, &existing) < 0) |
315 | link->dhcp6_pd_configured = false; | |
316 | else | |
317 | route_unmark(existing); | |
1633c457 | 318 | |
3b6a3bde YW |
319 | r = link_request_route(link, TAKE_PTR(route), true, &link->dhcp6_pd_messages, |
320 | dhcp6_pd_route_handler, NULL); | |
1633c457 | 321 | if (r < 0) |
76c5a0f2 | 322 | return log_link_error_errno(link, r, "Failed to request DHCPv6 prefix route: %m"); |
1633c457 | 323 | |
1633c457 YW |
324 | return 0; |
325 | } | |
326 | ||
327 | static int dhcp6_pd_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { | |
328 | int r; | |
329 | ||
330 | assert(link); | |
3b6a3bde | 331 | assert(link->dhcp6_pd_messages > 0); |
1633c457 | 332 | |
3b6a3bde | 333 | link->dhcp6_pd_messages--; |
1633c457 | 334 | |
5a07fa9d YW |
335 | r = address_configure_handler_internal(rtnl, m, link, "Could not set DHCPv6 delegated prefix address"); |
336 | if (r <= 0) | |
337 | return r; | |
1633c457 | 338 | |
3b6a3bde YW |
339 | r = dhcp6_pd_check_ready(link); |
340 | if (r < 0) | |
341 | link_enter_failed(link); | |
1633c457 YW |
342 | |
343 | return 1; | |
344 | } | |
345 | ||
baad6421 | 346 | static void log_dhcp6_pd_address(Link *link, const Address *address) { |
baad6421 YW |
347 | _cleanup_free_ char *buffer = NULL; |
348 | int log_level; | |
349 | ||
b8ce3b44 YW |
350 | assert(address); |
351 | assert(address->family == AF_INET6); | |
352 | ||
baad6421 YW |
353 | log_level = address_get(link, address, NULL) >= 0 ? LOG_DEBUG : LOG_INFO; |
354 | ||
355 | if (log_level < log_get_max_level()) | |
356 | return; | |
357 | ||
b8ce3b44 | 358 | (void) in6_addr_prefix_to_string(&address->in_addr.in6, address->prefixlen, &buffer); |
baad6421 | 359 | |
0fd97a25 | 360 | log_link_full(link, log_level, "DHCPv6-PD address %s (valid %s, preferred %s)", |
baad6421 | 361 | strna(buffer), |
0fd97a25 YW |
362 | FORMAT_LIFETIME(address->cinfo.ifa_valid), |
363 | FORMAT_LIFETIME(address->cinfo.ifa_prefered)); | |
baad6421 YW |
364 | } |
365 | ||
76c5a0f2 | 366 | static int dhcp6_pd_request_address( |
8b76ee89 | 367 | Link *link, |
b8ce3b44 | 368 | const struct in6_addr *prefix, |
8b76ee89 YW |
369 | uint32_t lifetime_preferred, |
370 | uint32_t lifetime_valid) { | |
1633c457 | 371 | |
f5960e0a YW |
372 | _cleanup_set_free_ Set *addresses = NULL; |
373 | struct in6_addr *a; | |
1633c457 YW |
374 | int r; |
375 | ||
376 | assert(link); | |
377 | assert(link->network); | |
378 | assert(prefix); | |
379 | ||
99e015e2 | 380 | if (!link->network->dhcp6_pd_assign) |
1633c457 YW |
381 | return 0; |
382 | ||
f5960e0a | 383 | r = dhcp6_pd_generate_addresses(link, prefix, &addresses); |
1633c457 | 384 | if (r < 0) |
f5960e0a | 385 | return log_link_warning_errno(link, r, "Failed to generate addresses for acquired DHCPv6 delegated prefix: %m"); |
1633c457 | 386 | |
f5960e0a YW |
387 | SET_FOREACH(a, addresses) { |
388 | _cleanup_(address_freep) Address *address = NULL; | |
389 | Address *existing; | |
1633c457 | 390 | |
f5960e0a YW |
391 | r = address_new(&address); |
392 | if (r < 0) | |
393 | return log_link_error_errno(link, r, "Failed to allocate address for DHCPv6 delegated prefix: %m"); | |
394 | ||
395 | address->source = NETWORK_CONFIG_SOURCE_DHCP6PD; | |
396 | address->family = AF_INET6; | |
397 | address->in_addr.in6 = *a; | |
398 | address->prefixlen = 64; | |
399 | address->cinfo.ifa_prefered = lifetime_preferred; | |
400 | address->cinfo.ifa_valid = lifetime_valid; | |
401 | SET_FLAG(address->flags, IFA_F_MANAGETEMPADDR, link->network->dhcp6_pd_manage_temporary_address); | |
402 | address->route_metric = link->network->dhcp6_pd_route_metric; | |
403 | ||
404 | log_dhcp6_pd_address(link, address); | |
405 | ||
406 | if (address_get(link, address, &existing) < 0) | |
407 | link->dhcp6_pd_configured = false; | |
408 | else | |
409 | address_unmark(existing); | |
410 | ||
411 | r = link_request_address(link, TAKE_PTR(address), true, &link->dhcp6_pd_messages, | |
412 | dhcp6_pd_address_handler, NULL); | |
413 | if (r < 0) | |
414 | return log_link_error_errno(link, r, "Failed to request DHCPv6 delegated prefix address: %m"); | |
415 | } | |
1633c457 YW |
416 | |
417 | return 0; | |
418 | } | |
419 | ||
8b76ee89 YW |
420 | static int dhcp6_pd_assign_prefix( |
421 | Link *link, | |
b8ce3b44 | 422 | const struct in6_addr *prefix, |
8b76ee89 YW |
423 | uint32_t lifetime_preferred, |
424 | uint32_t lifetime_valid) { | |
425 | ||
1633c457 YW |
426 | int r; |
427 | ||
428 | assert(link); | |
4afd9867 | 429 | assert(link->network); |
1633c457 YW |
430 | assert(prefix); |
431 | ||
4afd9867 | 432 | if (link->network->dhcp6_pd_announce) { |
b8ce3b44 | 433 | r = radv_add_prefix(link, prefix, 64, lifetime_preferred, lifetime_valid); |
4afd9867 YW |
434 | if (r < 0) |
435 | return r; | |
436 | } | |
1633c457 | 437 | |
3b6a3bde | 438 | r = dhcp6_pd_request_route(link, prefix); |
1633c457 YW |
439 | if (r < 0) |
440 | return r; | |
441 | ||
76c5a0f2 | 442 | r = dhcp6_pd_request_address(link, prefix, lifetime_preferred, lifetime_valid); |
1633c457 YW |
443 | if (r < 0) |
444 | return r; | |
445 | ||
cde09c34 | 446 | return link_add_dhcp6_pd_prefix(link, prefix); |
1633c457 YW |
447 | } |
448 | ||
1633c457 | 449 | static bool link_has_preferred_subnet_id(Link *link) { |
02e9e34b AR |
450 | if (!link->network) |
451 | return false; | |
452 | ||
99e015e2 | 453 | return link->network->dhcp6_pd_subnet_id >= 0; |
02e9e34b AR |
454 | } |
455 | ||
456 | static int dhcp6_get_preferred_delegated_prefix( | |
02e9e34b | 457 | Link *link, |
3b6a3bde | 458 | const struct in6_addr *pd_prefix, |
02e9e34b | 459 | uint8_t pd_prefix_len, |
b8ce3b44 | 460 | struct in6_addr *ret) { |
1633c457 YW |
461 | |
462 | /* We start off with the original PD prefix we have been assigned and iterate from there */ | |
463 | union in_addr_union prefix; | |
464 | uint64_t n_prefixes; | |
465 | Link *assigned_link; | |
120b5c0b SS |
466 | int r; |
467 | ||
120b5c0b | 468 | assert(link); |
1633c457 | 469 | assert(link->manager); |
3b6a3bde | 470 | assert(pd_prefix); |
1633c457 YW |
471 | assert(pd_prefix_len <= 64); |
472 | ||
473 | n_prefixes = UINT64_C(1) << (64 - pd_prefix_len); | |
3b6a3bde | 474 | prefix.in6 = *pd_prefix; |
1633c457 YW |
475 | |
476 | if (link_has_preferred_subnet_id(link)) { | |
99e015e2 | 477 | uint64_t subnet_id = link->network->dhcp6_pd_subnet_id; |
02e9e34b | 478 | |
02e9e34b | 479 | /* If the link has a preference for a particular subnet id try to allocate that */ |
1633c457 YW |
480 | if (subnet_id >= n_prefixes) |
481 | return log_link_warning_errno(link, SYNTHETIC_ERRNO(ERANGE), | |
482 | "subnet id %" PRIu64 " is out of range. Only have %" PRIu64 " subnets.", | |
483 | subnet_id, n_prefixes); | |
02e9e34b AR |
484 | |
485 | r = in_addr_prefix_nth(AF_INET6, &prefix, 64, subnet_id); | |
486 | if (r < 0) | |
1633c457 YW |
487 | return log_link_warning_errno(link, r, |
488 | "subnet id %" PRIu64 " is out of range. Only have %" PRIu64 " subnets.", | |
489 | subnet_id, n_prefixes); | |
02e9e34b AR |
490 | |
491 | /* Verify that the prefix we did calculate fits in the pd prefix. | |
492 | * This should not fail as we checked the prefix size beforehand */ | |
d205851d | 493 | assert_se(in6_addr_prefix_covers(pd_prefix, pd_prefix_len, &prefix.in6) > 0); |
02e9e34b | 494 | |
cde09c34 | 495 | if (link_get_by_dhcp6_pd_prefix(link->manager, &prefix.in6, &assigned_link) >= 0 && |
3b6a3bde | 496 | assigned_link != link) { |
1633c457 | 497 | _cleanup_free_ char *assigned_buf = NULL; |
02e9e34b | 498 | |
b8ce3b44 | 499 | (void) in6_addr_to_string(&prefix.in6, &assigned_buf); |
1633c457 YW |
500 | return log_link_warning_errno(link, SYNTHETIC_ERRNO(EAGAIN), |
501 | "The requested prefix %s is already assigned to another link.", | |
502 | strna(assigned_buf)); | |
503 | } | |
02e9e34b | 504 | |
b8ce3b44 | 505 | *ret = prefix.in6; |
02e9e34b | 506 | return 0; |
1419ff04 | 507 | } |
02e9e34b | 508 | |
1419ff04 | 509 | for (uint64_t n = 0; n < n_prefixes; n++) { |
1633c457 | 510 | /* If we do not have an allocation preference just iterate |
1419ff04 | 511 | * through the address space and return the first free prefix. */ |
cde09c34 | 512 | if (link_get_by_dhcp6_pd_prefix(link->manager, &prefix.in6, &assigned_link) < 0 || |
3b6a3bde | 513 | assigned_link == link) { |
b8ce3b44 | 514 | *ret = prefix.in6; |
1419ff04 | 515 | return 0; |
02e9e34b AR |
516 | } |
517 | ||
1419ff04 YW |
518 | r = in_addr_prefix_next(AF_INET6, &prefix, 64); |
519 | if (r < 0) | |
1633c457 | 520 | return log_link_warning_errno(link, r, "Can't allocate another prefix. Out of address space?: %m"); |
02e9e34b AR |
521 | } |
522 | ||
1419ff04 | 523 | return log_link_warning_errno(link, SYNTHETIC_ERRNO(ERANGE), "Couldn't find a suitable prefix. Ran out of address space."); |
02e9e34b AR |
524 | } |
525 | ||
d92681a6 YW |
526 | static int dhcp6_pd_prefix_distribute( |
527 | Link *dhcp6_link, | |
528 | const struct in6_addr *pd_prefix, | |
529 | uint8_t pd_prefix_len, | |
530 | uint32_t lifetime_preferred, | |
531 | uint32_t lifetime_valid, | |
532 | bool assign_preferred_subnet_id) { | |
1633c457 | 533 | |
1633c457 YW |
534 | Link *link; |
535 | int r; | |
103b81ee PF |
536 | |
537 | assert(dhcp6_link); | |
1633c457 | 538 | assert(dhcp6_link->manager); |
3b6a3bde | 539 | assert(pd_prefix); |
1633c457 | 540 | assert(pd_prefix_len <= 64); |
103b81ee | 541 | |
6eab614d | 542 | HASHMAP_FOREACH(link, dhcp6_link->manager->links_by_index) { |
d92681a6 | 543 | _cleanup_free_ char *buf = NULL; |
b8ce3b44 | 544 | struct in6_addr assigned_prefix; |
103b81ee | 545 | |
d92681a6 | 546 | if (!link_dhcp6_pd_is_enabled(link)) |
103b81ee PF |
547 | continue; |
548 | ||
d92681a6 | 549 | if (link == dhcp6_link && !link->network->dhcp6_pd_assign) |
103b81ee PF |
550 | continue; |
551 | ||
1633c457 YW |
552 | if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) |
553 | continue; | |
103b81ee | 554 | |
1633c457 YW |
555 | if (assign_preferred_subnet_id != link_has_preferred_subnet_id(link)) |
556 | continue; | |
557 | ||
3b6a3bde | 558 | r = dhcp6_pd_get_assigned_prefix(link, pd_prefix, pd_prefix_len, &assigned_prefix); |
1633c457 | 559 | if (r < 0) { |
3b6a3bde YW |
560 | r = dhcp6_get_preferred_delegated_prefix(link, pd_prefix, pd_prefix_len, &assigned_prefix); |
561 | if (r < 0) | |
1633c457 | 562 | continue; |
1633c457 YW |
563 | } |
564 | ||
d92681a6 YW |
565 | (void) in6_addr_prefix_to_string(&assigned_prefix, 64, &buf); |
566 | if (link == dhcp6_link) { | |
567 | r = dhcp6_pd_request_address(link, &assigned_prefix, lifetime_preferred, lifetime_valid); | |
568 | if (r < 0) | |
569 | return log_link_warning_errno(link, r, "Failed to assign addresses in prefix %s: %m", strna(buf)); | |
570 | ||
571 | log_link_debug(link, "Assigned addresses in prefix %s: %m", strna(buf)); | |
572 | } else { | |
573 | r = dhcp6_pd_assign_prefix(link, &assigned_prefix, lifetime_preferred, lifetime_valid); | |
574 | if (r < 0) { | |
575 | log_link_error_errno(link, r, "Failed to assign/update prefix %s: %m", strna(buf)); | |
576 | link_enter_failed(link); | |
577 | } else | |
578 | log_link_debug(link, "Assigned prefix %s", strna(buf)); | |
579 | } | |
1633c457 | 580 | } |
d92681a6 YW |
581 | |
582 | return 0; | |
103b81ee PF |
583 | } |
584 | ||
1633c457 | 585 | static int dhcp6_pd_prepare(Link *link) { |
1633c457 YW |
586 | if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) |
587 | return 0; | |
588 | ||
589 | if (!link_dhcp6_pd_is_enabled(link)) | |
590 | return 0; | |
591 | ||
3b6a3bde YW |
592 | link_mark_addresses(link, NETWORK_CONFIG_SOURCE_DHCP6PD, NULL); |
593 | link_mark_routes(link, NETWORK_CONFIG_SOURCE_DHCP6PD, NULL); | |
1633c457 | 594 | |
c62c4628 PF |
595 | return 0; |
596 | } | |
597 | ||
1633c457 | 598 | static int dhcp6_pd_finalize(Link *link) { |
76c3246d | 599 | int r; |
76c3246d | 600 | |
1633c457 YW |
601 | if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) |
602 | return 0; | |
76c3246d | 603 | |
1633c457 YW |
604 | if (!link_dhcp6_pd_is_enabled(link)) |
605 | return 0; | |
76c3246d | 606 | |
3b6a3bde YW |
607 | if (link->dhcp6_pd_messages == 0) { |
608 | link->dhcp6_pd_configured = false; | |
1633c457 | 609 | |
3b6a3bde YW |
610 | r = dhcp6_pd_remove(link, /* only_marked = */ true); |
611 | if (r < 0) | |
612 | return r; | |
613 | } | |
9efa8a3c | 614 | |
3b6a3bde | 615 | if (!link->dhcp6_pd_configured) |
1633c457 YW |
616 | link_set_state(link, LINK_STATE_CONFIGURING); |
617 | ||
76c5a0f2 | 618 | link_check_ready(link); |
1d596fde | 619 | return 0; |
76c3246d PF |
620 | } |
621 | ||
1633c457 YW |
622 | static void dhcp6_pd_prefix_lost(Link *dhcp6_link) { |
623 | Link *link; | |
494c868d PF |
624 | int r; |
625 | ||
1633c457 YW |
626 | assert(dhcp6_link); |
627 | assert(dhcp6_link->manager); | |
1046bf9b | 628 | |
6eab614d | 629 | HASHMAP_FOREACH(link, dhcp6_link->manager->links_by_index) { |
1633c457 YW |
630 | if (link == dhcp6_link) |
631 | continue; | |
4ff296b0 | 632 | |
3b6a3bde | 633 | r = dhcp6_pd_remove(link, /* only_marked = */ false); |
1633c457 YW |
634 | if (r < 0) |
635 | link_enter_failed(link); | |
636 | } | |
4b409e85 YW |
637 | |
638 | set_clear(dhcp6_link->dhcp6_pd_prefixes); | |
494c868d PF |
639 | } |
640 | ||
3b6a3bde | 641 | static int dhcp6_remove(Link *link, bool only_marked) { |
1633c457 YW |
642 | Address *address; |
643 | Route *route; | |
1633c457 | 644 | int k, r = 0; |
494c868d | 645 | |
1633c457 | 646 | assert(link); |
494c868d | 647 | |
3b6a3bde YW |
648 | if (!only_marked) |
649 | link->dhcp6_configured = false; | |
dd5ab7d9 | 650 | |
3b6a3bde YW |
651 | SET_FOREACH(route, link->routes) { |
652 | if (route->source != NETWORK_CONFIG_SOURCE_DHCP6) | |
653 | continue; | |
654 | if (only_marked && !route_is_marked(route)) | |
655 | continue; | |
494c868d | 656 | |
3b6a3bde YW |
657 | k = route_remove(route); |
658 | if (k < 0) | |
659 | r = k; | |
ca7c792b | 660 | |
3b6a3bde | 661 | route_cancel_request(route); |
494c868d PF |
662 | } |
663 | ||
3b6a3bde YW |
664 | SET_FOREACH(address, link->addresses) { |
665 | if (address->source != NETWORK_CONFIG_SOURCE_DHCP6) | |
666 | continue; | |
667 | if (only_marked && !address_is_marked(address)) | |
668 | continue; | |
120b5c0b | 669 | |
3b6a3bde | 670 | k = address_remove(address); |
1633c457 YW |
671 | if (k < 0) |
672 | r = k; | |
76c3246d | 673 | |
3b6a3bde | 674 | address_cancel_request(address); |
1633c457 | 675 | } |
76c3246d | 676 | |
1633c457 YW |
677 | return r; |
678 | } | |
76c3246d | 679 | |
3b6a3bde | 680 | static int dhcp6_check_ready(Link *link); |
76c3246d | 681 | |
3b6a3bde YW |
682 | static int dhcp6_address_ready_callback(Address *address) { |
683 | Address *a; | |
76c3246d | 684 | |
3b6a3bde YW |
685 | assert(address); |
686 | assert(address->link); | |
76c3246d | 687 | |
3b6a3bde YW |
688 | SET_FOREACH(a, address->link->addresses) |
689 | if (a->source == NETWORK_CONFIG_SOURCE_DHCP6) | |
690 | a->callback = NULL; | |
76c3246d | 691 | |
3b6a3bde YW |
692 | return dhcp6_check_ready(address->link); |
693 | } | |
694 | ||
695 | static int dhcp6_check_ready(Link *link) { | |
696 | bool has_ready = false; | |
697 | Address *address; | |
698 | int r; | |
02e9e34b | 699 | |
3b6a3bde | 700 | assert(link); |
02e9e34b | 701 | |
3b6a3bde YW |
702 | if (link->dhcp6_messages > 0) { |
703 | log_link_debug(link, "%s(): DHCPv6 addresses and routes are not set.", __func__); | |
704 | return 0; | |
76c3246d PF |
705 | } |
706 | ||
3b6a3bde YW |
707 | SET_FOREACH(address, link->addresses) { |
708 | if (address->source != NETWORK_CONFIG_SOURCE_DHCP6) | |
709 | continue; | |
710 | if (address_is_ready(address)) { | |
711 | has_ready = true; | |
712 | break; | |
713 | } | |
1633c457 | 714 | } |
02e9e34b | 715 | |
3b6a3bde YW |
716 | if (!has_ready) { |
717 | SET_FOREACH(address, link->addresses) | |
718 | if (address->source == NETWORK_CONFIG_SOURCE_DHCP6) | |
719 | address->callback = dhcp6_address_ready_callback; | |
720 | ||
721 | log_link_debug(link, "%s(): no DHCPv6 address is ready.", __func__); | |
722 | return 0; | |
723 | } | |
724 | ||
725 | link->dhcp6_configured = true; | |
726 | log_link_debug(link, "DHCPv6 addresses and routes set."); | |
727 | ||
728 | r = dhcp6_remove(link, /* only_marked = */ true); | |
729 | if (r < 0) | |
730 | return r; | |
731 | ||
732 | link_check_ready(link); | |
733 | return 0; | |
494c868d | 734 | } |
26db55f3 | 735 | |
5a07fa9d | 736 | static int dhcp6_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { |
494c868d | 737 | int r; |
73922903 | 738 | |
1046bf9b | 739 | assert(link); |
3b6a3bde | 740 | assert(link->dhcp6_messages > 0); |
2a877f45 | 741 | |
3b6a3bde | 742 | link->dhcp6_messages--; |
1046bf9b | 743 | |
5a07fa9d YW |
744 | r = route_configure_handler_internal(rtnl, m, link, "Failed to set unreachable route for DHCPv6 delegated subnet"); |
745 | if (r <= 0) | |
746 | return r; | |
2a877f45 | 747 | |
3b6a3bde | 748 | r = dhcp6_check_ready(link); |
76c5a0f2 | 749 | if (r < 0) |
3b6a3bde | 750 | link_enter_failed(link); |
76c5a0f2 | 751 | |
3b6a3bde | 752 | return 1; |
76c5a0f2 YW |
753 | } |
754 | ||
755 | static int dhcp6_request_unreachable_route(Link *link, const struct in6_addr *addr, uint8_t prefixlen) { | |
1633c457 YW |
756 | _cleanup_(route_freep) Route *route = NULL; |
757 | _cleanup_free_ char *buf = NULL; | |
3b6a3bde | 758 | Route *existing; |
120b5c0b | 759 | int r; |
76c3246d | 760 | |
1633c457 YW |
761 | assert(link); |
762 | assert(addr); | |
57445b53 | 763 | |
b8ce3b44 | 764 | (void) in6_addr_prefix_to_string(addr, prefixlen, &buf); |
494c868d | 765 | |
1633c457 | 766 | if (prefixlen == 64) { |
5380707a | 767 | log_link_debug(link, "Not adding a blocking route for DHCPv6 delegated subnet %s since distributed prefix is 64", |
1633c457 | 768 | strna(buf)); |
4b409e85 | 769 | return 0; |
1633c457 | 770 | } |
494c868d | 771 | |
1633c457 YW |
772 | r = route_new(&route); |
773 | if (r < 0) | |
774 | return log_oom(); | |
02e9e34b | 775 | |
3b6a3bde | 776 | route->source = NETWORK_CONFIG_SOURCE_DHCP6; |
1633c457 | 777 | route->family = AF_INET6; |
b8ce3b44 | 778 | route->dst.in6 = *addr; |
1633c457 | 779 | route->dst_prefixlen = prefixlen; |
e47bcb7d | 780 | route->table = link_get_dhcp6_route_table(link); |
1633c457 | 781 | route->type = RTN_UNREACHABLE; |
d9d6a10b | 782 | route->protocol = RTPROT_DHCP; |
02e9e34b | 783 | |
3b6a3bde YW |
784 | if (route_get(link->manager, NULL, route, &existing) < 0) |
785 | link->dhcp6_configured = false; | |
786 | else | |
787 | route_unmark(existing); | |
76c3246d | 788 | |
3b6a3bde YW |
789 | r = link_request_route(link, TAKE_PTR(route), true, &link->dhcp6_messages, |
790 | dhcp6_route_handler, NULL); | |
1633c457 | 791 | if (r < 0) |
76c5a0f2 | 792 | return log_link_error_errno(link, r, "Failed to request unreachable route for DHCPv6 delegated subnet %s: %m", |
5380707a | 793 | strna(buf)); |
2a877f45 | 794 | |
4b409e85 YW |
795 | return 0; |
796 | } | |
797 | ||
b8ce3b44 | 798 | static int dhcp6_pd_prefix_add(Link *link, const struct in6_addr *prefix, uint8_t prefixlen) { |
4b409e85 | 799 | _cleanup_free_ char *buf = NULL; |
3b6a3bde | 800 | struct in_addr_prefix *p; |
4b409e85 YW |
801 | int r; |
802 | ||
803 | assert(link); | |
804 | assert(prefix); | |
805 | ||
806 | p = new(struct in_addr_prefix, 1); | |
807 | if (!p) | |
808 | return log_oom(); | |
809 | ||
810 | *p = (struct in_addr_prefix) { | |
811 | .family = AF_INET6, | |
812 | .prefixlen = prefixlen, | |
b8ce3b44 | 813 | .address.in6 = *prefix, |
4b409e85 YW |
814 | }; |
815 | ||
b8ce3b44 | 816 | (void) in6_addr_prefix_to_string(prefix, prefixlen, &buf); |
4b409e85 YW |
817 | |
818 | log_link_full(link, | |
819 | set_contains(link->dhcp6_pd_prefixes, p) ? LOG_DEBUG : | |
820 | prefixlen > 64 || prefixlen < 48 ? LOG_WARNING : LOG_INFO, | |
87160186 | 821 | "DHCPv6: received PD Prefix %s%s", |
4b409e85 YW |
822 | strna(buf), |
823 | prefixlen > 64 ? " with prefix length > 64, ignoring." : | |
cead8ed6 | 824 | prefixlen < 48 ? " with prefix length < 48, looks unusual.": ""); |
4b409e85 YW |
825 | |
826 | /* Store PD prefix even if prefixlen > 64, not to make logged at warning level so frequently. */ | |
3b6a3bde | 827 | r = set_ensure_consume(&link->dhcp6_pd_prefixes, &in_addr_prefix_hash_ops_free, p); |
4b409e85 | 828 | if (r < 0) |
87160186 | 829 | return log_link_error_errno(link, r, "Failed to store DHCPv6 PD prefix %s: %m", strna(buf)); |
4b409e85 YW |
830 | |
831 | return prefixlen <= 64; | |
76c3246d PF |
832 | } |
833 | ||
1633c457 | 834 | static int dhcp6_pd_prefix_acquired(Link *dhcp6_link) { |
1633c457 YW |
835 | Link *link; |
836 | int r; | |
10752343 | 837 | |
1633c457 YW |
838 | assert(dhcp6_link); |
839 | assert(dhcp6_link->dhcp6_lease); | |
10752343 | 840 | |
6eab614d | 841 | HASHMAP_FOREACH(link, dhcp6_link->manager->links_by_index) { |
1633c457 YW |
842 | if (link == dhcp6_link) |
843 | continue; | |
10752343 | 844 | |
1633c457 YW |
845 | r = dhcp6_pd_prepare(link); |
846 | if (r < 0) | |
847 | link_enter_failed(link); | |
848 | } | |
10752343 | 849 | |
1633c457 YW |
850 | for (sd_dhcp6_lease_reset_pd_prefix_iter(dhcp6_link->dhcp6_lease);;) { |
851 | uint32_t lifetime_preferred, lifetime_valid; | |
b8ce3b44 | 852 | struct in6_addr pd_prefix; |
1633c457 | 853 | uint8_t pd_prefix_len; |
10752343 | 854 | |
b8ce3b44 | 855 | r = sd_dhcp6_lease_get_pd(dhcp6_link->dhcp6_lease, &pd_prefix, &pd_prefix_len, |
1633c457 YW |
856 | &lifetime_preferred, &lifetime_valid); |
857 | if (r < 0) | |
858 | break; | |
10752343 | 859 | |
4b409e85 | 860 | r = dhcp6_pd_prefix_add(dhcp6_link, &pd_prefix, pd_prefix_len); |
1633c457 YW |
861 | if (r < 0) |
862 | return r; | |
863 | if (r == 0) | |
10752343 PF |
864 | continue; |
865 | ||
76c5a0f2 | 866 | r = dhcp6_request_unreachable_route(dhcp6_link, &pd_prefix, pd_prefix_len); |
4b409e85 YW |
867 | if (r < 0) |
868 | return r; | |
869 | ||
1633c457 YW |
870 | /* We are doing prefix allocation in two steps: |
871 | * 1. all those links that have a preferred subnet id will be assigned their subnet | |
872 | * 2. all those links that remain will receive prefixes in sequential order. Prefixes | |
873 | * that were previously already allocated to another link will be skipped. | |
874 | * The assignment has to be split in two phases since subnet id | |
875 | * preferences should be honored. Meaning that any subnet id should be | |
876 | * handed out to the requesting link and not to some link that didn't | |
877 | * specify any preference. */ | |
10752343 | 878 | |
1633c457 | 879 | assert(pd_prefix_len <= 64); |
10752343 | 880 | |
d205851d YW |
881 | /* Mask prefix for safety. */ |
882 | r = in6_addr_mask(&pd_prefix, pd_prefix_len); | |
1633c457 YW |
883 | if (r < 0) |
884 | return log_link_error_errno(dhcp6_link, r, "Failed to mask DHCPv6 PD prefix: %m"); | |
10752343 | 885 | |
1633c457 YW |
886 | if (DEBUG_LOGGING) { |
887 | uint64_t n_prefixes = UINT64_C(1) << (64 - pd_prefix_len); | |
888 | _cleanup_free_ char *buf = NULL; | |
10752343 | 889 | |
d205851d | 890 | (void) in6_addr_prefix_to_string(&pd_prefix, pd_prefix_len, &buf); |
5380707a YW |
891 | log_link_debug(dhcp6_link, "Assigning up to %" PRIu64 " prefixes from %s", |
892 | n_prefixes, strna(buf)); | |
10752343 PF |
893 | } |
894 | ||
d92681a6 | 895 | r = dhcp6_pd_prefix_distribute(dhcp6_link, |
d205851d | 896 | &pd_prefix, |
d92681a6 YW |
897 | pd_prefix_len, |
898 | lifetime_preferred, | |
899 | lifetime_valid, | |
900 | true); | |
901 | if (r < 0) | |
902 | return r; | |
903 | ||
904 | r = dhcp6_pd_prefix_distribute(dhcp6_link, | |
d205851d | 905 | &pd_prefix, |
d92681a6 YW |
906 | pd_prefix_len, |
907 | lifetime_preferred, | |
908 | lifetime_valid, | |
909 | false); | |
910 | if (r < 0) | |
911 | return r; | |
1633c457 | 912 | } |
10752343 | 913 | |
6eab614d | 914 | HASHMAP_FOREACH(link, dhcp6_link->manager->links_by_index) { |
1633c457 | 915 | if (link == dhcp6_link) |
10752343 | 916 | continue; |
10752343 | 917 | |
1633c457 YW |
918 | r = dhcp6_pd_finalize(link); |
919 | if (r < 0) | |
920 | link_enter_failed(link); | |
10752343 PF |
921 | } |
922 | ||
923 | return 0; | |
924 | } | |
925 | ||
302a796f | 926 | static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { |
c62c4628 PF |
927 | int r; |
928 | ||
929 | assert(link); | |
3b6a3bde | 930 | assert(link->dhcp6_messages > 0); |
2a877f45 | 931 | |
3b6a3bde | 932 | link->dhcp6_messages--; |
c62c4628 | 933 | |
5a07fa9d YW |
934 | r = address_configure_handler_internal(rtnl, m, link, "Could not set DHCPv6 address"); |
935 | if (r <= 0) | |
936 | return r; | |
c62c4628 | 937 | |
3b6a3bde YW |
938 | r = dhcp6_check_ready(link); |
939 | if (r < 0) | |
940 | link_enter_failed(link); | |
6545067a | 941 | |
c62c4628 PF |
942 | return 1; |
943 | } | |
944 | ||
3b6a3bde | 945 | static void log_dhcp6_address(Link *link, const Address *address) { |
b432080d YW |
946 | _cleanup_free_ char *buffer = NULL; |
947 | bool by_ndisc = false; | |
948 | Address *existing; | |
3b6a3bde | 949 | int log_level; |
b432080d YW |
950 | |
951 | assert(link); | |
952 | assert(address); | |
b8ce3b44 | 953 | assert(address->family == AF_INET6); |
b432080d | 954 | |
3b6a3bde | 955 | (void) in6_addr_to_string(&address->in_addr.in6, &buffer); |
5291f26d | 956 | |
3b6a3bde | 957 | if (address_get(link, address, &existing) < 0) { |
b432080d YW |
958 | /* New address. */ |
959 | log_level = LOG_INFO; | |
960 | goto simple_log; | |
961 | } else | |
962 | log_level = LOG_DEBUG; | |
963 | ||
b432080d YW |
964 | if (address->prefixlen == existing->prefixlen) |
965 | /* Currently, only conflict in prefix length is reported. */ | |
966 | goto simple_log; | |
967 | ||
3b6a3bde YW |
968 | if (existing->source == NETWORK_CONFIG_SOURCE_NDISC) |
969 | by_ndisc = true; | |
b432080d | 970 | |
3b6a3bde YW |
971 | log_link_warning(link, "DHCPv6 address %s/%u (valid %s, preferred %s) conflicts the address %s/%u%s.", |
972 | strna(buffer), address->prefixlen, | |
0fd97a25 YW |
973 | FORMAT_LIFETIME(address->cinfo.ifa_valid), |
974 | FORMAT_LIFETIME(address->cinfo.ifa_prefered), | |
3b6a3bde YW |
975 | strna(buffer), existing->prefixlen, |
976 | by_ndisc ? " assigned by NDisc. Please try to use or update IPv6Token= setting " | |
b432080d | 977 | "to change the address generated by NDISC, or disable UseAutonomousPrefix=" : ""); |
3b6a3bde | 978 | return; |
b432080d YW |
979 | |
980 | simple_log: | |
3b6a3bde YW |
981 | log_link_full(link, log_level, "DHCPv6 address %s/%u (valid %s, preferred %s)", |
982 | strna(buffer), address->prefixlen, | |
0fd97a25 YW |
983 | FORMAT_LIFETIME(address->cinfo.ifa_valid), |
984 | FORMAT_LIFETIME(address->cinfo.ifa_prefered)); | |
76c5a0f2 YW |
985 | } |
986 | ||
987 | static int dhcp6_request_address( | |
1e7a0e21 | 988 | Link *link, |
1633c457 | 989 | const struct in6_addr *ip6_addr, |
1e7a0e21 LP |
990 | uint32_t lifetime_preferred, |
991 | uint32_t lifetime_valid) { | |
992 | ||
8e766630 | 993 | _cleanup_(address_freep) Address *addr = NULL; |
3b6a3bde | 994 | Address *existing; |
1e7a0e21 | 995 | int r; |
c62c4628 | 996 | |
f0213e37 | 997 | r = address_new(&addr); |
c62c4628 | 998 | if (r < 0) |
1633c457 | 999 | return log_oom(); |
c62c4628 | 1000 | |
3b6a3bde | 1001 | addr->source = NETWORK_CONFIG_SOURCE_DHCP6; |
c62c4628 | 1002 | addr->family = AF_INET6; |
57e44707 | 1003 | addr->in_addr.in6 = *ip6_addr; |
851c9f82 | 1004 | addr->flags = IFA_F_NOPREFIXROUTE; |
6d8f6b0b | 1005 | addr->prefixlen = 128; |
c62c4628 PF |
1006 | addr->cinfo.ifa_prefered = lifetime_preferred; |
1007 | addr->cinfo.ifa_valid = lifetime_valid; | |
1008 | ||
3b6a3bde | 1009 | log_dhcp6_address(link, addr); |
1633c457 | 1010 | |
3b6a3bde YW |
1011 | if (address_get(link, addr, &existing) < 0) |
1012 | link->dhcp6_configured = false; | |
1013 | else | |
1014 | address_unmark(existing); | |
1633c457 | 1015 | |
3b6a3bde YW |
1016 | r = link_request_address(link, TAKE_PTR(addr), true, &link->dhcp6_messages, |
1017 | dhcp6_address_handler, NULL); | |
1018 | if (r < 0) { | |
1019 | _cleanup_free_ char *buffer = NULL; | |
1633c457 | 1020 | |
3b6a3bde YW |
1021 | (void) in6_addr_to_string(ip6_addr, &buffer); |
1022 | return log_link_error_errno(link, r, "Failed to request DHCPv6 address %s/128: %m", strna(buffer)); | |
1023 | } | |
c62c4628 | 1024 | |
4ff296b0 | 1025 | return 0; |
c62c4628 PF |
1026 | } |
1027 | ||
1633c457 | 1028 | static int dhcp6_address_acquired(Link *link) { |
c62c4628 | 1029 | int r; |
1633c457 YW |
1030 | |
1031 | assert(link); | |
1536b7b2 | 1032 | assert(link->network); |
1633c457 YW |
1033 | assert(link->dhcp6_lease); |
1034 | ||
1536b7b2 YW |
1035 | if (!link->network->dhcp6_use_address) |
1036 | return 0; | |
1037 | ||
1633c457 YW |
1038 | for (sd_dhcp6_lease_reset_address_iter(link->dhcp6_lease);;) { |
1039 | uint32_t lifetime_preferred, lifetime_valid; | |
1040 | struct in6_addr ip6_addr; | |
1041 | ||
1042 | r = sd_dhcp6_lease_get_address(link->dhcp6_lease, &ip6_addr, &lifetime_preferred, &lifetime_valid); | |
1043 | if (r < 0) | |
1044 | break; | |
1045 | ||
76c5a0f2 | 1046 | r = dhcp6_request_address(link, &ip6_addr, lifetime_preferred, lifetime_valid); |
1633c457 YW |
1047 | if (r < 0) |
1048 | return r; | |
1049 | } | |
1050 | ||
38ba3da0 | 1051 | if (link->network->dhcp6_use_hostname) { |
f963f895 VM |
1052 | const char *dhcpname = NULL; |
1053 | _cleanup_free_ char *hostname = NULL; | |
38ba3da0 | 1054 | |
f963f895 VM |
1055 | (void) sd_dhcp6_lease_get_fqdn(link->dhcp6_lease, &dhcpname); |
1056 | ||
1057 | if (dhcpname) { | |
1058 | r = shorten_overlong(dhcpname, &hostname); | |
1059 | if (r < 0) | |
1060 | log_link_warning_errno(link, r, "Unable to shorten overlong DHCP hostname '%s', ignoring: %m", dhcpname); | |
1061 | if (r == 1) | |
1062 | log_link_notice(link, "Overlong DHCP hostname received, shortened from '%s' to '%s'", dhcpname, hostname); | |
1063 | } | |
1064 | if (hostname) { | |
1065 | r = manager_set_hostname(link->manager, hostname); | |
1066 | if (r < 0) | |
1067 | log_link_error_errno(link, r, "Failed to set transient hostname to '%s': %m", hostname); | |
1068 | } | |
1069 | } | |
1070 | ||
1633c457 YW |
1071 | return 0; |
1072 | } | |
1073 | ||
1074 | static int dhcp6_lease_ip_acquired(sd_dhcp6_client *client, Link *link) { | |
1075 | _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease_old = NULL; | |
c62c4628 | 1076 | sd_dhcp6_lease *lease; |
1633c457 | 1077 | int r; |
c62c4628 | 1078 | |
3b6a3bde YW |
1079 | link_mark_addresses(link, NETWORK_CONFIG_SOURCE_DHCP6, NULL); |
1080 | link_mark_routes(link, NETWORK_CONFIG_SOURCE_DHCP6, NULL); | |
2a877f45 | 1081 | |
c62c4628 PF |
1082 | r = sd_dhcp6_client_get_lease(client, &lease); |
1083 | if (r < 0) | |
1633c457 YW |
1084 | return log_link_error_errno(link, r, "Failed to get DHCPv6 lease: %m"); |
1085 | ||
1086 | lease_old = TAKE_PTR(link->dhcp6_lease); | |
1087 | link->dhcp6_lease = sd_dhcp6_lease_ref(lease); | |
c62c4628 | 1088 | |
1633c457 YW |
1089 | r = dhcp6_address_acquired(link); |
1090 | if (r < 0) | |
1091 | return r; | |
c62c4628 | 1092 | |
1633c457 YW |
1093 | if (dhcp6_lease_has_pd_prefix(lease)) { |
1094 | r = dhcp6_pd_prefix_acquired(link); | |
c62c4628 PF |
1095 | if (r < 0) |
1096 | return r; | |
1633c457 YW |
1097 | } else if (dhcp6_lease_has_pd_prefix(lease_old)) |
1098 | /* When we had PD prefixes but not now, we need to remove them. */ | |
1099 | dhcp6_pd_prefix_lost(link); | |
c62c4628 | 1100 | |
3b6a3bde YW |
1101 | if (link->dhcp6_messages == 0) { |
1102 | link->dhcp6_configured = true; | |
2a877f45 | 1103 | |
3b6a3bde YW |
1104 | r = dhcp6_remove(link, /* only_marked = */ true); |
1105 | if (r < 0) | |
1106 | return r; | |
1107 | } else | |
1108 | log_link_debug(link, "Setting DHCPv6 addresses and routes"); | |
1633c457 | 1109 | |
3b6a3bde | 1110 | if (!link->dhcp6_configured) |
1633c457 YW |
1111 | link_set_state(link, LINK_STATE_CONFIGURING); |
1112 | ||
76c5a0f2 | 1113 | link_check_ready(link); |
c62c4628 PF |
1114 | return 0; |
1115 | } | |
1116 | ||
1633c457 YW |
1117 | static int dhcp6_lease_information_acquired(sd_dhcp6_client *client, Link *link) { |
1118 | return 0; | |
1119 | } | |
1120 | ||
1121 | static int dhcp6_lease_lost(Link *link) { | |
c62c4628 | 1122 | int r; |
1633c457 YW |
1123 | |
1124 | assert(link); | |
1125 | assert(link->manager); | |
1126 | ||
1127 | log_link_info(link, "DHCPv6 lease lost"); | |
1128 | ||
1129 | if (dhcp6_lease_has_pd_prefix(link->dhcp6_lease)) | |
1130 | dhcp6_pd_prefix_lost(link); | |
1131 | ||
1132 | link->dhcp6_lease = sd_dhcp6_lease_unref(link->dhcp6_lease); | |
1133 | ||
3b6a3bde | 1134 | r = dhcp6_remove(link, /* only_marked = */ false); |
1633c457 YW |
1135 | if (r < 0) |
1136 | return r; | |
1137 | ||
1138 | return 0; | |
1139 | } | |
1140 | ||
1141 | static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) { | |
5c79bd79 | 1142 | Link *link = userdata; |
1633c457 | 1143 | int r; |
5c79bd79 PF |
1144 | |
1145 | assert(link); | |
1146 | assert(link->network); | |
5c79bd79 PF |
1147 | |
1148 | if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) | |
1149 | return; | |
1150 | ||
1633c457 | 1151 | switch (event) { |
10c9ce61 DH |
1152 | case SD_DHCP6_CLIENT_EVENT_STOP: |
1153 | case SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE: | |
1154 | case SD_DHCP6_CLIENT_EVENT_RETRANS_MAX: | |
1633c457 YW |
1155 | r = dhcp6_lease_lost(link); |
1156 | if (r < 0) | |
1157 | link_enter_failed(link); | |
c62c4628 PF |
1158 | break; |
1159 | ||
10c9ce61 | 1160 | case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE: |
1633c457 | 1161 | r = dhcp6_lease_ip_acquired(client, link); |
c62c4628 PF |
1162 | if (r < 0) { |
1163 | link_enter_failed(link); | |
1164 | return; | |
1165 | } | |
1166 | ||
4831981d | 1167 | _fallthrough_; |
10c9ce61 | 1168 | case SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST: |
c62c4628 | 1169 | r = dhcp6_lease_information_acquired(client, link); |
1633c457 | 1170 | if (r < 0) |
c62c4628 | 1171 | link_enter_failed(link); |
5c79bd79 PF |
1172 | break; |
1173 | ||
1174 | default: | |
1175 | if (event < 0) | |
e53fc357 | 1176 | log_link_warning_errno(link, event, "DHCPv6 error: %m"); |
5c79bd79 | 1177 | else |
e53fc357 | 1178 | log_link_warning(link, "DHCPv6 unknown event: %d", event); |
5c79bd79 PF |
1179 | return; |
1180 | } | |
1181 | } | |
1182 | ||
76c5a0f2 | 1183 | int dhcp6_request_information(Link *link, int ir) { |
125f20b4 | 1184 | int r, inf_req, pd; |
7a695d8e | 1185 | bool running; |
5c79bd79 | 1186 | |
7a695d8e TG |
1187 | assert(link); |
1188 | assert(link->dhcp6_client); | |
125f20b4 | 1189 | assert(link->network); |
94876904 | 1190 | assert(in6_addr_is_link_local(&link->ipv6ll_address)); |
85bd849f | 1191 | |
7a695d8e TG |
1192 | r = sd_dhcp6_client_is_running(link->dhcp6_client); |
1193 | if (r < 0) | |
1194 | return r; | |
5e871240 | 1195 | running = r; |
e6604041 | 1196 | |
125f20b4 PF |
1197 | r = sd_dhcp6_client_get_prefix_delegation(link->dhcp6_client, &pd); |
1198 | if (r < 0) | |
1199 | return r; | |
1200 | ||
1201 | if (pd && ir && link->network->dhcp6_force_pd_other_information) { | |
1202 | log_link_debug(link, "Enabling managed mode to request DHCPv6 PD with 'Other Information' set"); | |
1203 | ||
1633c457 | 1204 | r = sd_dhcp6_client_set_address_request(link->dhcp6_client, false); |
2a877f45 | 1205 | if (r < 0) |
125f20b4 PF |
1206 | return r; |
1207 | ||
1208 | ir = false; | |
1209 | } | |
1210 | ||
7a695d8e | 1211 | if (running) { |
720bec40 TY |
1212 | r = sd_dhcp6_client_get_information_request(link->dhcp6_client, &inf_req); |
1213 | if (r < 0) | |
1214 | return r; | |
1215 | ||
1216 | if (inf_req == ir) | |
1217 | return 0; | |
1218 | ||
7a695d8e TG |
1219 | r = sd_dhcp6_client_stop(link->dhcp6_client); |
1220 | if (r < 0) | |
1221 | return r; | |
720bec40 TY |
1222 | } else { |
1223 | r = sd_dhcp6_client_set_local_address(link->dhcp6_client, &link->ipv6ll_address); | |
1224 | if (r < 0) | |
1225 | return r; | |
7a695d8e | 1226 | } |
85bd849f | 1227 | |
720bec40 TY |
1228 | r = sd_dhcp6_client_set_information_request(link->dhcp6_client, ir); |
1229 | if (r < 0) | |
1230 | return r; | |
1231 | ||
1232 | r = sd_dhcp6_client_start(link->dhcp6_client); | |
7a695d8e TG |
1233 | if (r < 0) |
1234 | return r; | |
85bd849f | 1235 | |
7a695d8e TG |
1236 | return 0; |
1237 | } | |
18d29550 | 1238 | |
294f129b | 1239 | int dhcp6_start(Link *link) { |
ccffa166 YW |
1240 | int r; |
1241 | ||
294f129b YW |
1242 | assert(link); |
1243 | ||
1244 | if (!link->dhcp6_client) | |
1245 | return 0; | |
1246 | ||
1247 | if (!link_dhcp6_enabled(link)) | |
1248 | return 0; | |
1249 | ||
ccffa166 YW |
1250 | if (!link_has_carrier(link)) |
1251 | return 0; | |
1252 | ||
294f129b YW |
1253 | if (link->network->dhcp6_without_ra == DHCP6_CLIENT_START_MODE_NO) |
1254 | return 0; | |
1255 | ||
1256 | if (!in6_addr_is_link_local(&link->ipv6ll_address)) { | |
1257 | log_link_debug(link, "IPv6 link-local address is not set, delaying to start DHCPv6 client."); | |
1258 | return 0; | |
1259 | } | |
1260 | ||
1261 | if (sd_dhcp6_client_is_running(link->dhcp6_client) > 0) | |
1262 | return 0; | |
1263 | ||
ccffa166 YW |
1264 | r = dhcp6_request_information(link, link->network->dhcp6_without_ra == DHCP6_CLIENT_START_MODE_INFORMATION_REQUEST); |
1265 | if (r < 0) | |
1266 | return r; | |
294f129b | 1267 | |
ccffa166 | 1268 | return 1; |
294f129b YW |
1269 | } |
1270 | ||
1633c457 YW |
1271 | int dhcp6_request_prefix_delegation(Link *link) { |
1272 | Link *l; | |
1633c457 YW |
1273 | |
1274 | assert(link); | |
1275 | assert(link->manager); | |
1276 | ||
1277 | if (!link_dhcp6_pd_is_enabled(link)) | |
1278 | return 0; | |
1279 | ||
1280 | log_link_debug(link, "Requesting DHCPv6 prefixes to be delegated for new link"); | |
1281 | ||
6eab614d | 1282 | HASHMAP_FOREACH(l, link->manager->links_by_index) { |
1633c457 YW |
1283 | int r, enabled; |
1284 | ||
1285 | if (l == link) | |
1286 | continue; | |
1287 | ||
1288 | if (!l->dhcp6_client) | |
1289 | continue; | |
1290 | ||
1291 | r = sd_dhcp6_client_get_prefix_delegation(l->dhcp6_client, &enabled); | |
1292 | if (r < 0) { | |
1293 | log_link_warning_errno(l, r, "Cannot get prefix delegation when adding new link: %m"); | |
1294 | link_enter_failed(l); | |
1295 | continue; | |
1296 | } | |
1297 | ||
1298 | if (enabled == 0) { | |
1299 | r = sd_dhcp6_client_set_prefix_delegation(l->dhcp6_client, 1); | |
1300 | if (r < 0) { | |
1301 | log_link_warning_errno(l, r, "Cannot enable prefix delegation when adding new link: %m"); | |
1302 | link_enter_failed(l); | |
1303 | continue; | |
1304 | } | |
1305 | } | |
1306 | ||
1307 | r = sd_dhcp6_client_is_running(l->dhcp6_client); | |
1308 | if (r <= 0) | |
1309 | continue; | |
1310 | ||
1311 | if (enabled != 0) { | |
1312 | if (dhcp6_lease_has_pd_prefix(l->dhcp6_lease)) { | |
1313 | log_link_debug(l, "Requesting re-assignment of delegated prefixes after adding new link"); | |
1314 | r = dhcp6_pd_prefix_acquired(l); | |
1315 | if (r < 0) | |
1316 | link_enter_failed(l); | |
1317 | } | |
1318 | continue; | |
1319 | } | |
1320 | ||
1321 | r = sd_dhcp6_client_stop(l->dhcp6_client); | |
1322 | if (r < 0) { | |
1323 | log_link_warning_errno(l, r, "Cannot stop DHCPv6 prefix delegation client after adding new link: %m"); | |
1324 | link_enter_failed(l); | |
1325 | continue; | |
1326 | } | |
1327 | ||
1328 | r = sd_dhcp6_client_start(l->dhcp6_client); | |
1329 | if (r < 0) { | |
1330 | log_link_warning_errno(l, r, "Cannot restart DHCPv6 prefix delegation client after adding new link: %m"); | |
1331 | link_enter_failed(l); | |
1332 | continue; | |
1333 | } | |
1334 | ||
1335 | log_link_debug(l, "Restarted DHCPv6 client to acquire prefix delegations after adding new link"); | |
1336 | } | |
1337 | ||
1338 | /* dhcp6_pd_prefix_acquired() may make the link in failed state. */ | |
1339 | if (link->state == LINK_STATE_FAILED) | |
1340 | return -ENOANO; | |
1341 | ||
1342 | return 0; | |
1343 | } | |
1344 | ||
8006aa32 SA |
1345 | static int dhcp6_set_hostname(sd_dhcp6_client *client, Link *link) { |
1346 | _cleanup_free_ char *hostname = NULL; | |
1347 | const char *hn; | |
1348 | int r; | |
1349 | ||
1350 | assert(link); | |
1351 | ||
1352 | if (!link->network->dhcp_send_hostname) | |
1353 | hn = NULL; | |
1354 | else if (link->network->dhcp_hostname) | |
1355 | hn = link->network->dhcp_hostname; | |
1356 | else { | |
1357 | r = gethostname_strict(&hostname); | |
1358 | if (r < 0 && r != -ENXIO) /* ENXIO: no hostname set or hostname is "localhost" */ | |
87160186 | 1359 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to get hostname: %m"); |
8006aa32 SA |
1360 | |
1361 | hn = hostname; | |
1362 | } | |
1363 | ||
a8494759 YW |
1364 | r = sd_dhcp6_client_set_fqdn(client, hn); |
1365 | if (r == -EINVAL && hostname) | |
1366 | /* Ignore error when the machine's hostname is not suitable to send in DHCP packet. */ | |
87160186 | 1367 | log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set hostname from kernel hostname, ignoring: %m"); |
a8494759 | 1368 | else if (r < 0) |
87160186 | 1369 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set hostname: %m"); |
a8494759 YW |
1370 | |
1371 | return 0; | |
8006aa32 SA |
1372 | } |
1373 | ||
1633c457 YW |
1374 | static bool dhcp6_enable_prefix_delegation(Link *dhcp6_link) { |
1375 | Link *link; | |
1633c457 YW |
1376 | |
1377 | assert(dhcp6_link); | |
1378 | assert(dhcp6_link->manager); | |
1379 | ||
6eab614d | 1380 | HASHMAP_FOREACH(link, dhcp6_link->manager->links_by_index) { |
1633c457 YW |
1381 | if (link == dhcp6_link) |
1382 | continue; | |
1383 | ||
1384 | if (!link_dhcp6_pd_is_enabled(link)) | |
1385 | continue; | |
1386 | ||
1387 | return true; | |
1388 | } | |
1389 | ||
1390 | return false; | |
1391 | } | |
1392 | ||
eebba6dc YW |
1393 | static int dhcp6_set_identifier(Link *link, sd_dhcp6_client *client) { |
1394 | const DUID *duid; | |
1395 | int r; | |
1396 | ||
1397 | assert(link); | |
1398 | assert(link->network); | |
1399 | assert(client); | |
1400 | ||
ca2b7cd8 | 1401 | r = sd_dhcp6_client_set_mac(client, link->hw_addr.bytes, link->hw_addr.length, link->iftype); |
eebba6dc YW |
1402 | if (r < 0) |
1403 | return r; | |
1404 | ||
4e26a5ba YW |
1405 | if (link->network->dhcp6_iaid_set) { |
1406 | r = sd_dhcp6_client_set_iaid(client, link->network->dhcp6_iaid); | |
eebba6dc YW |
1407 | if (r < 0) |
1408 | return r; | |
1409 | } | |
1410 | ||
4e26a5ba | 1411 | duid = link_get_dhcp6_duid(link); |
eebba6dc YW |
1412 | if (duid->type == DUID_TYPE_LLT && duid->raw_data_len == 0) |
1413 | r = sd_dhcp6_client_set_duid_llt(client, duid->llt_time); | |
1414 | else | |
1415 | r = sd_dhcp6_client_set_duid(client, | |
1416 | duid->type, | |
1417 | duid->raw_data_len > 0 ? duid->raw_data : NULL, | |
1418 | duid->raw_data_len); | |
1419 | if (r < 0) | |
1420 | return r; | |
1421 | ||
1422 | return 0; | |
1423 | } | |
1424 | ||
ccffa166 | 1425 | static int dhcp6_configure(Link *link) { |
5bad7ebd | 1426 | _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL; |
b4ccc5de | 1427 | sd_dhcp6_option *vendor_option; |
e7d5fe17 | 1428 | sd_dhcp6_option *send_option; |
35f6a5cb | 1429 | void *request_options; |
fb5c8216 | 1430 | int r; |
7a695d8e TG |
1431 | |
1432 | assert(link); | |
fb5c8216 | 1433 | assert(link->network); |
7a695d8e | 1434 | |
62379e88 | 1435 | if (link->dhcp6_client) |
87160186 | 1436 | return log_link_debug_errno(link, SYNTHETIC_ERRNO(EBUSY), "DHCPv6 client is already configured."); |
62379e88 | 1437 | |
7a695d8e | 1438 | r = sd_dhcp6_client_new(&client); |
5bad7ebd | 1439 | if (r == -ENOMEM) |
bc8bd68d | 1440 | return log_oom_debug(); |
7a695d8e | 1441 | if (r < 0) |
87160186 | 1442 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to create DHCPv6 client: %m"); |
5c79bd79 | 1443 | |
4cf85000 | 1444 | r = sd_dhcp6_client_attach_event(client, link->manager->event, 0); |
5c79bd79 | 1445 | if (r < 0) |
87160186 | 1446 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to attach event: %m"); |
5c79bd79 | 1447 | |
eebba6dc | 1448 | r = dhcp6_set_identifier(link, client); |
e6604041 | 1449 | if (r < 0) |
87160186 | 1450 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set identifier: %m"); |
413708d1 | 1451 | |
90e74a66 | 1452 | ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp6_client_send_options) { |
e7d5fe17 AD |
1453 | r = sd_dhcp6_client_add_option(client, send_option); |
1454 | if (r == -EEXIST) | |
1455 | continue; | |
1456 | if (r < 0) | |
87160186 | 1457 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set option: %m"); |
e7d5fe17 AD |
1458 | } |
1459 | ||
8006aa32 | 1460 | r = dhcp6_set_hostname(client, link); |
8006aa32 | 1461 | if (r < 0) |
a8494759 | 1462 | return r; |
8006aa32 | 1463 | |
2f8e7633 | 1464 | r = sd_dhcp6_client_set_ifindex(client, link->ifindex); |
e6604041 | 1465 | if (r < 0) |
87160186 | 1466 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set ifindex: %m"); |
5c79bd79 | 1467 | |
3175a8c2 SS |
1468 | if (link->network->dhcp6_mudurl) { |
1469 | r = sd_dhcp6_client_set_request_mud_url(client, link->network->dhcp6_mudurl); | |
1470 | if (r < 0) | |
87160186 | 1471 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set MUD URL: %m"); |
3175a8c2 SS |
1472 | } |
1473 | ||
90e74a66 | 1474 | SET_FOREACH(request_options, link->network->dhcp6_request_options) { |
35f6a5cb SS |
1475 | uint32_t option = PTR_TO_UINT32(request_options); |
1476 | ||
1477 | r = sd_dhcp6_client_set_request_option(client, option); | |
1478 | if (r == -EEXIST) { | |
87160186 | 1479 | log_link_debug(link, "DHCPv6 CLIENT: Failed to set request flag for '%u' already exists, ignoring.", option); |
35f6a5cb SS |
1480 | continue; |
1481 | } | |
35f6a5cb | 1482 | if (r < 0) |
87160186 | 1483 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set request flag for '%u': %m", option); |
35f6a5cb SS |
1484 | } |
1485 | ||
f37f2a6b SS |
1486 | if (link->network->dhcp6_user_class) { |
1487 | r = sd_dhcp6_client_set_request_user_class(client, link->network->dhcp6_user_class); | |
1488 | if (r < 0) | |
87160186 | 1489 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set user class: %m"); |
f37f2a6b SS |
1490 | } |
1491 | ||
ed0d1b2e SS |
1492 | if (link->network->dhcp6_vendor_class) { |
1493 | r = sd_dhcp6_client_set_request_vendor_class(client, link->network->dhcp6_vendor_class); | |
1494 | if (r < 0) | |
87160186 | 1495 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set vendor class: %m"); |
ed0d1b2e SS |
1496 | } |
1497 | ||
90e74a66 | 1498 | ORDERED_HASHMAP_FOREACH(vendor_option, link->network->dhcp6_client_send_vendor_options) { |
b4ccc5de SS |
1499 | r = sd_dhcp6_client_add_vendor_option(client, vendor_option); |
1500 | if (r == -EEXIST) | |
1501 | continue; | |
1502 | if (r < 0) | |
87160186 | 1503 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set vendor option: %m"); |
b4ccc5de SS |
1504 | } |
1505 | ||
7a695d8e | 1506 | r = sd_dhcp6_client_set_callback(client, dhcp6_handler, link); |
e6604041 | 1507 | if (r < 0) |
87160186 | 1508 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set callback: %m"); |
5c79bd79 | 1509 | |
103b81ee PF |
1510 | if (dhcp6_enable_prefix_delegation(link)) { |
1511 | r = sd_dhcp6_client_set_prefix_delegation(client, true); | |
1512 | if (r < 0) | |
87160186 | 1513 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set prefix delegation: %m"); |
103b81ee PF |
1514 | } |
1515 | ||
2805536b SS |
1516 | if (link->network->dhcp6_pd_length > 0) { |
1517 | r = sd_dhcp6_client_set_prefix_delegation_hint(client, link->network->dhcp6_pd_length, &link->network->dhcp6_pd_address); | |
1518 | if (r < 0) | |
87160186 | 1519 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set prefix hint: %m"); |
2805536b SS |
1520 | } |
1521 | ||
5bad7ebd | 1522 | link->dhcp6_client = TAKE_PTR(client); |
e6604041 | 1523 | |
7a695d8e | 1524 | return 0; |
5c79bd79 | 1525 | } |
04ed9949 | 1526 | |
eebba6dc YW |
1527 | int dhcp6_update_mac(Link *link) { |
1528 | bool restart; | |
1529 | int r; | |
1530 | ||
1531 | assert(link); | |
1532 | ||
1533 | if (!link->dhcp6_client) | |
1534 | return 0; | |
1535 | ||
1536 | restart = sd_dhcp6_client_is_running(link->dhcp6_client) > 0; | |
1537 | ||
1538 | if (restart) { | |
1539 | r = sd_dhcp6_client_stop(link->dhcp6_client); | |
1540 | if (r < 0) | |
1541 | return r; | |
1542 | } | |
1543 | ||
1544 | r = dhcp6_set_identifier(link, link->dhcp6_client); | |
1545 | if (r < 0) | |
1546 | return r; | |
1547 | ||
1548 | if (restart) { | |
1549 | r = sd_dhcp6_client_start(link->dhcp6_client); | |
1550 | if (r < 0) | |
1551 | return log_link_warning_errno(link, r, "Could not restart DHCPv6 client: %m"); | |
1552 | } | |
1553 | ||
1554 | return 0; | |
1555 | } | |
1556 | ||
ccffa166 YW |
1557 | int request_process_dhcp6_client(Request *req) { |
1558 | Link *link; | |
1559 | int r; | |
1560 | ||
1561 | assert(req); | |
1562 | assert(req->link); | |
1563 | assert(req->type == REQUEST_TYPE_DHCP6_CLIENT); | |
1564 | ||
1565 | link = req->link; | |
1566 | ||
1567 | if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) | |
1568 | return 0; | |
1569 | ||
1570 | r = dhcp_configure_duid(link, link_get_dhcp6_duid(link)); | |
1571 | if (r <= 0) | |
1572 | return r; | |
1573 | ||
1574 | r = dhcp6_configure(link); | |
1575 | if (r < 0) | |
1576 | return log_link_warning_errno(link, r, "Failed to configure DHCPv6 client: %m"); | |
1577 | ||
1578 | r = ndisc_start(link); | |
1579 | if (r < 0) | |
1580 | return log_link_warning_errno(link, r, "Failed to start IPv6 Router Discovery: %m"); | |
1581 | ||
1582 | r = dhcp6_start(link); | |
1583 | if (r < 0) | |
1584 | return log_link_warning_errno(link, r, "Failed to start DHCPv6 client: %m"); | |
1585 | ||
1586 | log_link_debug(link, "DHCPv6 client is configured%s.", | |
1587 | r > 0 ? ", acquiring DHCPv6 lease" : ""); | |
1588 | ||
1589 | return 1; | |
1590 | } | |
1591 | ||
1592 | int link_request_dhcp6_client(Link *link) { | |
1593 | int r; | |
1594 | ||
1595 | assert(link); | |
1596 | ||
1597 | if (!link_dhcp6_enabled(link) && !link_ipv6_accept_ra_enabled(link)) | |
1598 | return 0; | |
1599 | ||
1600 | if (link->dhcp6_client) | |
1601 | return 0; | |
1602 | ||
1603 | r = link_queue_request(link, REQUEST_TYPE_DHCP6_CLIENT, NULL, false, NULL, NULL, NULL); | |
1604 | if (r < 0) | |
1605 | return log_link_warning_errno(link, r, "Failed to request configuring of the DHCPv6 client: %m"); | |
1606 | ||
1607 | log_link_debug(link, "Requested configuring of the DHCPv6 client."); | |
1608 | return 0; | |
1609 | } | |
1610 | ||
5460bde5 YW |
1611 | int link_serialize_dhcp6_client(Link *link, FILE *f) { |
1612 | _cleanup_free_ char *duid = NULL; | |
1613 | uint32_t iaid; | |
1614 | int r; | |
1615 | ||
1616 | assert(link); | |
1617 | ||
1618 | if (!link->dhcp6_client) | |
1619 | return 0; | |
1620 | ||
1621 | r = sd_dhcp6_client_get_iaid(link->dhcp6_client, &iaid); | |
1622 | if (r >= 0) | |
1623 | fprintf(f, "DHCP6_CLIENT_IAID=0x%x\n", iaid); | |
1624 | ||
1625 | r = sd_dhcp6_client_duid_as_string(link->dhcp6_client, &duid); | |
1626 | if (r >= 0) | |
1627 | fprintf(f, "DHCP6_CLIENT_DUID=%s\n", duid); | |
1628 | ||
1629 | return 0; | |
1630 | } | |
1631 | ||
c24dd739 YW |
1632 | int config_parse_dhcp6_pd_hint( |
1633 | const char* unit, | |
1634 | const char *filename, | |
1635 | unsigned line, | |
1636 | const char *section, | |
1637 | unsigned section_line, | |
1638 | const char *lvalue, | |
1639 | int ltype, | |
1640 | const char *rvalue, | |
1641 | void *data, | |
1642 | void *userdata) { | |
1643 | ||
1644 | Network *network = data; | |
275468c0 YW |
1645 | union in_addr_union u; |
1646 | unsigned char prefixlen; | |
c24dd739 YW |
1647 | int r; |
1648 | ||
1649 | assert(filename); | |
1650 | assert(lvalue); | |
1651 | assert(rvalue); | |
1652 | assert(data); | |
1653 | ||
275468c0 | 1654 | r = in_addr_prefix_from_string(rvalue, AF_INET6, &u, &prefixlen); |
c24dd739 | 1655 | if (r < 0) { |
275468c0 YW |
1656 | log_syntax(unit, LOG_WARNING, filename, line, r, |
1657 | "Failed to parse %s=%s, ignoring assignment.", lvalue, rvalue); | |
c24dd739 YW |
1658 | return 0; |
1659 | } | |
1660 | ||
275468c0 YW |
1661 | if (prefixlen < 1 || prefixlen > 128) { |
1662 | log_syntax(unit, LOG_WARNING, filename, line, 0, | |
1663 | "Invalid prefix length in %s=%s, ignoring assignment.", lvalue, rvalue); | |
c24dd739 YW |
1664 | return 0; |
1665 | } | |
1666 | ||
275468c0 YW |
1667 | network->dhcp6_pd_address = u.in6; |
1668 | network->dhcp6_pd_length = prefixlen; | |
1669 | ||
c24dd739 YW |
1670 | return 0; |
1671 | } | |
1672 | ||
99e015e2 YW |
1673 | DEFINE_CONFIG_PARSE_ENUM(config_parse_dhcp6_client_start_mode, dhcp6_client_start_mode, DHCP6ClientStartMode, |
1674 | "Failed to parse WithoutRA= setting"); | |
1675 | ||
1676 | static const char* const dhcp6_client_start_mode_table[_DHCP6_CLIENT_START_MODE_MAX] = { | |
1677 | [DHCP6_CLIENT_START_MODE_NO] = "no", | |
1678 | [DHCP6_CLIENT_START_MODE_INFORMATION_REQUEST] = "information-request", | |
1679 | [DHCP6_CLIENT_START_MODE_SOLICIT] = "solicit", | |
1680 | }; | |
1681 | ||
1682 | DEFINE_STRING_TABLE_LOOKUP(dhcp6_client_start_mode, DHCP6ClientStartMode); | |
1683 | ||
1684 | int config_parse_dhcp6_pd_subnet_id( | |
120b5c0b SS |
1685 | const char *unit, |
1686 | const char *filename, | |
1687 | unsigned line, | |
1688 | const char *section, | |
1689 | unsigned section_line, | |
1690 | const char *lvalue, | |
1691 | int ltype, | |
1692 | const char *rvalue, | |
1693 | void *data, | |
1694 | void *userdata) { | |
1695 | ||
99e015e2 YW |
1696 | int64_t *p = data; |
1697 | uint64_t t; | |
120b5c0b SS |
1698 | int r; |
1699 | ||
1700 | assert(filename); | |
1701 | assert(lvalue); | |
1702 | assert(rvalue); | |
1703 | assert(data); | |
1704 | ||
99e015e2 YW |
1705 | if (isempty(rvalue) || streq(rvalue, "auto")) { |
1706 | *p = -1; | |
120b5c0b SS |
1707 | return 0; |
1708 | } | |
1709 | ||
99e015e2 | 1710 | r = safe_atoux64(rvalue, &t); |
120b5c0b | 1711 | if (r < 0) { |
d96edb2c | 1712 | log_syntax(unit, LOG_WARNING, filename, line, r, |
99e015e2 YW |
1713 | "Failed to parse %s=, ignoring assignment: %s", |
1714 | lvalue, rvalue); | |
120b5c0b SS |
1715 | return 0; |
1716 | } | |
99e015e2 | 1717 | if (t > INT64_MAX) { |
d96edb2c | 1718 | log_syntax(unit, LOG_WARNING, filename, line, 0, |
99e015e2 YW |
1719 | "Invalid subnet id '%s', ignoring assignment.", |
1720 | rvalue); | |
120b5c0b SS |
1721 | return 0; |
1722 | } | |
1723 | ||
99e015e2 YW |
1724 | *p = (int64_t) t; |
1725 | ||
120b5c0b SS |
1726 | return 0; |
1727 | } |