]>
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 | ||
f8da534e | 6 | #include "dhcp6-client-internal.h" |
d8ec95c7 | 7 | #include "dhcp6-lease-internal.h" |
d5ebcf65 YW |
8 | #include "hashmap.h" |
9 | #include "hostname-setup.h" | |
10 | #include "hostname-util.h" | |
11 | #include "networkd-address.h" | |
12 | #include "networkd-dhcp-prefix-delegation.h" | |
f8da534e | 13 | #include "networkd-dhcp6-bus.h" |
d5ebcf65 YW |
14 | #include "networkd-dhcp6.h" |
15 | #include "networkd-link.h" | |
16 | #include "networkd-manager.h" | |
d12fb2bc | 17 | #include "networkd-ntp.h" |
d5ebcf65 YW |
18 | #include "networkd-queue.h" |
19 | #include "networkd-route.h" | |
ef0a234a | 20 | #include "networkd-state-file.h" |
d5ebcf65 YW |
21 | #include "string-table.h" |
22 | #include "string-util.h" | |
9efa8a3c | 23 | |
d5ebcf65 YW |
24 | bool link_dhcp6_with_address_enabled(Link *link) { |
25 | if (!link_dhcp6_enabled(link)) | |
26 | return false; | |
1633c457 | 27 | |
d5ebcf65 | 28 | return link->network->dhcp6_use_address; |
76c3246d PF |
29 | } |
30 | ||
d5ebcf65 YW |
31 | static DHCP6ClientStartMode link_get_dhcp6_client_start_mode(Link *link) { |
32 | assert(link); | |
494c868d | 33 | |
d5ebcf65 YW |
34 | if (!link->network) |
35 | return DHCP6_CLIENT_START_MODE_NO; | |
1046bf9b | 36 | |
d5ebcf65 YW |
37 | /* When WithoutRA= is explicitly specified, then honor it. */ |
38 | if (link->network->dhcp6_client_start_mode >= 0) | |
39 | return link->network->dhcp6_client_start_mode; | |
4ff296b0 | 40 | |
0bcc6557 | 41 | /* When this interface itself is an uplink interface, then start dhcp6 client in solicit mode. */ |
a27588d4 | 42 | if (dhcp_pd_is_uplink(link, link, /* accept_auto = */ false)) |
d5ebcf65 | 43 | return DHCP6_CLIENT_START_MODE_SOLICIT; |
4b409e85 | 44 | |
d5ebcf65 YW |
45 | /* Otherwise, start dhcp6 client when RA is received. */ |
46 | return DHCP6_CLIENT_START_MODE_NO; | |
494c868d PF |
47 | } |
48 | ||
3b6a3bde | 49 | static int dhcp6_remove(Link *link, bool only_marked) { |
1633c457 YW |
50 | Address *address; |
51 | Route *route; | |
372acaad | 52 | int ret = 0; |
494c868d | 53 | |
1633c457 | 54 | assert(link); |
8d01e44c | 55 | assert(link->manager); |
494c868d | 56 | |
3b6a3bde YW |
57 | if (!only_marked) |
58 | link->dhcp6_configured = false; | |
dd5ab7d9 | 59 | |
8d01e44c | 60 | SET_FOREACH(route, link->manager->routes) { |
3b6a3bde YW |
61 | if (route->source != NETWORK_CONFIG_SOURCE_DHCP6) |
62 | continue; | |
63 | if (only_marked && !route_is_marked(route)) | |
64 | continue; | |
494c868d | 65 | |
3caed9ea | 66 | RET_GATHER(ret, route_remove_and_cancel(route, link->manager)); |
494c868d PF |
67 | } |
68 | ||
3b6a3bde YW |
69 | SET_FOREACH(address, link->addresses) { |
70 | if (address->source != NETWORK_CONFIG_SOURCE_DHCP6) | |
71 | continue; | |
72 | if (only_marked && !address_is_marked(address)) | |
73 | continue; | |
120b5c0b | 74 | |
f22b586a | 75 | RET_GATHER(ret, address_remove_and_cancel(address, link)); |
1633c457 | 76 | } |
76c3246d | 77 | |
372acaad | 78 | return ret; |
1633c457 | 79 | } |
76c3246d | 80 | |
3b6a3bde YW |
81 | static int dhcp6_address_ready_callback(Address *address) { |
82 | Address *a; | |
76c3246d | 83 | |
3b6a3bde YW |
84 | assert(address); |
85 | assert(address->link); | |
76c3246d | 86 | |
3b6a3bde YW |
87 | SET_FOREACH(a, address->link->addresses) |
88 | if (a->source == NETWORK_CONFIG_SOURCE_DHCP6) | |
89 | a->callback = NULL; | |
76c3246d | 90 | |
3b6a3bde YW |
91 | return dhcp6_check_ready(address->link); |
92 | } | |
93 | ||
d5ebcf65 | 94 | int dhcp6_check_ready(Link *link) { |
3b6a3bde | 95 | int r; |
02e9e34b | 96 | |
3b6a3bde | 97 | assert(link); |
fc4aa64c | 98 | assert(link->network); |
02e9e34b | 99 | |
3b6a3bde YW |
100 | if (link->dhcp6_messages > 0) { |
101 | log_link_debug(link, "%s(): DHCPv6 addresses and routes are not set.", __func__); | |
102 | return 0; | |
76c3246d PF |
103 | } |
104 | ||
fc4aa64c | 105 | if (link->network->dhcp6_use_address && |
43a75266 | 106 | sd_dhcp6_lease_has_address(link->dhcp6_lease) && |
fc4aa64c | 107 | !link_check_addresses_ready(link, NETWORK_CONFIG_SOURCE_DHCP6)) { |
f5e1781a | 108 | Address *address; |
02e9e34b | 109 | |
3b6a3bde YW |
110 | SET_FOREACH(address, link->addresses) |
111 | if (address->source == NETWORK_CONFIG_SOURCE_DHCP6) | |
112 | address->callback = dhcp6_address_ready_callback; | |
113 | ||
114 | log_link_debug(link, "%s(): no DHCPv6 address is ready.", __func__); | |
115 | return 0; | |
116 | } | |
117 | ||
118 | link->dhcp6_configured = true; | |
119 | log_link_debug(link, "DHCPv6 addresses and routes set."); | |
120 | ||
121 | r = dhcp6_remove(link, /* only_marked = */ true); | |
122 | if (r < 0) | |
123 | return r; | |
124 | ||
125 | link_check_ready(link); | |
126 | return 0; | |
494c868d | 127 | } |
26db55f3 | 128 | |
80d62d4f | 129 | static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Address *address) { |
c62c4628 PF |
130 | int r; |
131 | ||
132 | assert(link); | |
133 | ||
5a07fa9d YW |
134 | r = address_configure_handler_internal(rtnl, m, link, "Could not set DHCPv6 address"); |
135 | if (r <= 0) | |
136 | return r; | |
c62c4628 | 137 | |
3b6a3bde YW |
138 | r = dhcp6_check_ready(link); |
139 | if (r < 0) | |
140 | link_enter_failed(link); | |
6545067a | 141 | |
c62c4628 PF |
142 | return 1; |
143 | } | |
144 | ||
5d003031 | 145 | static int verify_dhcp6_address(Link *link, const Address *address) { |
b432080d YW |
146 | bool by_ndisc = false; |
147 | Address *existing; | |
3b6a3bde | 148 | int log_level; |
b432080d YW |
149 | |
150 | assert(link); | |
151 | assert(address); | |
b8ce3b44 | 152 | assert(address->family == AF_INET6); |
b432080d | 153 | |
84dbb3fd | 154 | const char *pretty = IN6_ADDR_TO_STRING(&address->in_addr.in6); |
5291f26d | 155 | |
b33dd04e | 156 | if (address_get_harder(link, address, &existing) < 0) { |
b432080d YW |
157 | /* New address. */ |
158 | log_level = LOG_INFO; | |
159 | goto simple_log; | |
160 | } else | |
161 | log_level = LOG_DEBUG; | |
162 | ||
77de62f9 | 163 | if (address_can_update(existing, address)) |
b432080d YW |
164 | goto simple_log; |
165 | ||
3b6a3bde YW |
166 | if (existing->source == NETWORK_CONFIG_SOURCE_NDISC) |
167 | by_ndisc = true; | |
b432080d | 168 | |
5d003031 | 169 | log_link_warning(link, "Ignoring DHCPv6 address %s/%u (valid %s, preferred %s) which conflicts with %s/%u%s.", |
84dbb3fd | 170 | pretty, address->prefixlen, |
16bc8635 YW |
171 | FORMAT_LIFETIME(address->lifetime_valid_usec), |
172 | FORMAT_LIFETIME(address->lifetime_preferred_usec), | |
84dbb3fd | 173 | pretty, existing->prefixlen, |
5d003031 YW |
174 | by_ndisc ? " assigned by NDisc" : ""); |
175 | if (by_ndisc) | |
176 | log_link_warning(link, "Hint: use IPv6Token= setting to change the address generated by NDisc or set UseAutonomousPrefix=no."); | |
177 | ||
178 | return -EEXIST; | |
b432080d YW |
179 | |
180 | simple_log: | |
3b6a3bde | 181 | log_link_full(link, log_level, "DHCPv6 address %s/%u (valid %s, preferred %s)", |
84dbb3fd | 182 | pretty, address->prefixlen, |
16bc8635 YW |
183 | FORMAT_LIFETIME(address->lifetime_valid_usec), |
184 | FORMAT_LIFETIME(address->lifetime_preferred_usec)); | |
5d003031 | 185 | return 0; |
76c5a0f2 YW |
186 | } |
187 | ||
188 | static int dhcp6_request_address( | |
1e7a0e21 | 189 | Link *link, |
c30f9aaf | 190 | const struct in6_addr *server_address, |
1633c457 | 191 | const struct in6_addr *ip6_addr, |
16bc8635 YW |
192 | usec_t lifetime_preferred_usec, |
193 | usec_t lifetime_valid_usec) { | |
1e7a0e21 | 194 | |
ebd96906 | 195 | _cleanup_(address_unrefp) Address *addr = NULL; |
3b6a3bde | 196 | Address *existing; |
1e7a0e21 | 197 | int r; |
c62c4628 | 198 | |
f0213e37 | 199 | r = address_new(&addr); |
c62c4628 | 200 | if (r < 0) |
1633c457 | 201 | return log_oom(); |
c62c4628 | 202 | |
3b6a3bde | 203 | addr->source = NETWORK_CONFIG_SOURCE_DHCP6; |
c30f9aaf | 204 | addr->provider.in6 = *server_address; |
c62c4628 | 205 | addr->family = AF_INET6; |
57e44707 | 206 | addr->in_addr.in6 = *ip6_addr; |
851c9f82 | 207 | addr->flags = IFA_F_NOPREFIXROUTE; |
6d8f6b0b | 208 | addr->prefixlen = 128; |
16bc8635 YW |
209 | addr->lifetime_preferred_usec = lifetime_preferred_usec; |
210 | addr->lifetime_valid_usec = lifetime_valid_usec; | |
c62c4628 | 211 | |
5d003031 YW |
212 | if (verify_dhcp6_address(link, addr) < 0) |
213 | return 0; | |
1633c457 | 214 | |
4b3590c3 TM |
215 | r = free_and_strdup_warn(&addr->netlabel, link->network->dhcp6_netlabel); |
216 | if (r < 0) | |
217 | return r; | |
218 | ||
3b6a3bde YW |
219 | if (address_get(link, addr, &existing) < 0) |
220 | link->dhcp6_configured = false; | |
221 | else | |
222 | address_unmark(existing); | |
1633c457 | 223 | |
f60e6558 | 224 | r = link_request_address(link, addr, &link->dhcp6_messages, |
3b6a3bde | 225 | dhcp6_address_handler, NULL); |
84dbb3fd ZJS |
226 | if (r < 0) |
227 | return log_link_error_errno(link, r, "Failed to request DHCPv6 address %s/128: %m", | |
228 | IN6_ADDR_TO_STRING(ip6_addr)); | |
4ff296b0 | 229 | return 0; |
c62c4628 PF |
230 | } |
231 | ||
1633c457 | 232 | static int dhcp6_address_acquired(Link *link) { |
c30f9aaf | 233 | struct in6_addr server_address; |
c62c4628 | 234 | int r; |
1633c457 YW |
235 | |
236 | assert(link); | |
1536b7b2 | 237 | assert(link->network); |
1633c457 YW |
238 | assert(link->dhcp6_lease); |
239 | ||
1536b7b2 YW |
240 | if (!link->network->dhcp6_use_address) |
241 | return 0; | |
242 | ||
c30f9aaf YW |
243 | r = sd_dhcp6_lease_get_server_address(link->dhcp6_lease, &server_address); |
244 | if (r < 0) | |
245 | return log_link_warning_errno(link, r, "Failed to get server address of DHCPv6 lease: %m"); | |
246 | ||
d8ec95c7 YW |
247 | FOREACH_DHCP6_ADDRESS(link->dhcp6_lease) { |
248 | usec_t lifetime_preferred_usec, lifetime_valid_usec; | |
1633c457 YW |
249 | struct in6_addr ip6_addr; |
250 | ||
d8ec95c7 | 251 | r = sd_dhcp6_lease_get_address(link->dhcp6_lease, &ip6_addr); |
1633c457 | 252 | if (r < 0) |
d8ec95c7 YW |
253 | return r; |
254 | ||
255 | r = sd_dhcp6_lease_get_address_lifetime_timestamp(link->dhcp6_lease, CLOCK_BOOTTIME, | |
256 | &lifetime_preferred_usec, &lifetime_valid_usec); | |
257 | if (r < 0) | |
258 | return r; | |
1633c457 | 259 | |
c30f9aaf | 260 | r = dhcp6_request_address(link, &server_address, &ip6_addr, |
d8ec95c7 YW |
261 | lifetime_preferred_usec, |
262 | lifetime_valid_usec); | |
1633c457 YW |
263 | if (r < 0) |
264 | return r; | |
265 | } | |
266 | ||
38ba3da0 | 267 | if (link->network->dhcp6_use_hostname) { |
f963f895 VM |
268 | const char *dhcpname = NULL; |
269 | _cleanup_free_ char *hostname = NULL; | |
38ba3da0 | 270 | |
f963f895 VM |
271 | (void) sd_dhcp6_lease_get_fqdn(link->dhcp6_lease, &dhcpname); |
272 | ||
273 | if (dhcpname) { | |
274 | r = shorten_overlong(dhcpname, &hostname); | |
275 | if (r < 0) | |
276 | log_link_warning_errno(link, r, "Unable to shorten overlong DHCP hostname '%s', ignoring: %m", dhcpname); | |
277 | if (r == 1) | |
278 | log_link_notice(link, "Overlong DHCP hostname received, shortened from '%s' to '%s'", dhcpname, hostname); | |
279 | } | |
280 | if (hostname) { | |
281 | r = manager_set_hostname(link->manager, hostname); | |
282 | if (r < 0) | |
283 | log_link_error_errno(link, r, "Failed to set transient hostname to '%s': %m", hostname); | |
284 | } | |
285 | } | |
286 | ||
1633c457 YW |
287 | return 0; |
288 | } | |
289 | ||
290 | static int dhcp6_lease_ip_acquired(sd_dhcp6_client *client, Link *link) { | |
291 | _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease_old = NULL; | |
c62c4628 | 292 | sd_dhcp6_lease *lease; |
1633c457 | 293 | int r; |
c62c4628 | 294 | |
2ccada8d | 295 | link_mark_addresses(link, NETWORK_CONFIG_SOURCE_DHCP6); |
8d01e44c | 296 | manager_mark_routes(link->manager, NULL, NETWORK_CONFIG_SOURCE_DHCP6); |
2a877f45 | 297 | |
c62c4628 PF |
298 | r = sd_dhcp6_client_get_lease(client, &lease); |
299 | if (r < 0) | |
1633c457 YW |
300 | return log_link_error_errno(link, r, "Failed to get DHCPv6 lease: %m"); |
301 | ||
302 | lease_old = TAKE_PTR(link->dhcp6_lease); | |
303 | link->dhcp6_lease = sd_dhcp6_lease_ref(lease); | |
c62c4628 | 304 | |
1633c457 YW |
305 | r = dhcp6_address_acquired(link); |
306 | if (r < 0) | |
307 | return r; | |
c62c4628 | 308 | |
fb70992d | 309 | if (sd_dhcp6_lease_has_pd_prefix(lease)) { |
1633c457 | 310 | r = dhcp6_pd_prefix_acquired(link); |
c62c4628 PF |
311 | if (r < 0) |
312 | return r; | |
fb70992d | 313 | } else if (sd_dhcp6_lease_has_pd_prefix(lease_old)) |
1633c457 | 314 | /* When we had PD prefixes but not now, we need to remove them. */ |
a27588d4 | 315 | dhcp_pd_prefix_lost(link); |
c62c4628 | 316 | |
3b6a3bde YW |
317 | if (link->dhcp6_messages == 0) { |
318 | link->dhcp6_configured = true; | |
2a877f45 | 319 | |
3b6a3bde YW |
320 | r = dhcp6_remove(link, /* only_marked = */ true); |
321 | if (r < 0) | |
322 | return r; | |
323 | } else | |
324 | log_link_debug(link, "Setting DHCPv6 addresses and routes"); | |
1633c457 | 325 | |
3b6a3bde | 326 | if (!link->dhcp6_configured) |
1633c457 YW |
327 | link_set_state(link, LINK_STATE_CONFIGURING); |
328 | ||
76c5a0f2 | 329 | link_check_ready(link); |
c62c4628 PF |
330 | return 0; |
331 | } | |
332 | ||
1633c457 | 333 | static int dhcp6_lease_information_acquired(sd_dhcp6_client *client, Link *link) { |
9709f9ed YW |
334 | sd_dhcp6_lease *lease; |
335 | int r; | |
336 | ||
337 | assert(client); | |
338 | assert(link); | |
339 | ||
340 | r = sd_dhcp6_client_get_lease(client, &lease); | |
341 | if (r < 0) | |
342 | return log_link_error_errno(link, r, "Failed to get DHCPv6 lease: %m"); | |
343 | ||
ef0a234a YW |
344 | unref_and_replace_full(link->dhcp6_lease, lease, sd_dhcp6_lease_ref, sd_dhcp6_lease_unref); |
345 | ||
346 | link_dirty(link); | |
347 | return 0; | |
1633c457 YW |
348 | } |
349 | ||
350 | static int dhcp6_lease_lost(Link *link) { | |
c62c4628 | 351 | int r; |
1633c457 YW |
352 | |
353 | assert(link); | |
354 | assert(link->manager); | |
355 | ||
d4dae4c2 YW |
356 | if (!link->dhcp6_lease) |
357 | return 0; | |
358 | ||
1633c457 YW |
359 | log_link_info(link, "DHCPv6 lease lost"); |
360 | ||
fb70992d | 361 | if (sd_dhcp6_lease_has_pd_prefix(link->dhcp6_lease)) |
a27588d4 | 362 | dhcp_pd_prefix_lost(link); |
1633c457 YW |
363 | |
364 | link->dhcp6_lease = sd_dhcp6_lease_unref(link->dhcp6_lease); | |
365 | ||
3b6a3bde | 366 | r = dhcp6_remove(link, /* only_marked = */ false); |
1633c457 YW |
367 | if (r < 0) |
368 | return r; | |
369 | ||
370 | return 0; | |
371 | } | |
372 | ||
373 | static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) { | |
99534007 | 374 | Link *link = ASSERT_PTR(userdata); |
814d8f96 | 375 | int r = 0; |
5c79bd79 | 376 | |
5c79bd79 | 377 | assert(link->network); |
5c79bd79 PF |
378 | |
379 | if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) | |
380 | return; | |
381 | ||
1633c457 | 382 | switch (event) { |
10c9ce61 DH |
383 | case SD_DHCP6_CLIENT_EVENT_STOP: |
384 | case SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE: | |
385 | case SD_DHCP6_CLIENT_EVENT_RETRANS_MAX: | |
1633c457 | 386 | r = dhcp6_lease_lost(link); |
c62c4628 PF |
387 | break; |
388 | ||
10c9ce61 | 389 | case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE: |
1633c457 | 390 | r = dhcp6_lease_ip_acquired(client, link); |
814d8f96 | 391 | break; |
c62c4628 | 392 | |
10c9ce61 | 393 | case SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST: |
c62c4628 | 394 | r = dhcp6_lease_information_acquired(client, link); |
5c79bd79 PF |
395 | break; |
396 | ||
397 | default: | |
398 | if (event < 0) | |
814d8f96 | 399 | log_link_warning_errno(link, event, "DHCPv6 error, ignoring: %m"); |
5c79bd79 | 400 | else |
e53fc357 | 401 | log_link_warning(link, "DHCPv6 unknown event: %d", event); |
5c79bd79 | 402 | } |
814d8f96 YW |
403 | if (r < 0) |
404 | link_enter_failed(link); | |
5c79bd79 PF |
405 | } |
406 | ||
fac19a21 YW |
407 | int dhcp6_start_on_ra(Link *link, bool information_request) { |
408 | int r; | |
5c79bd79 | 409 | |
7a695d8e TG |
410 | assert(link); |
411 | assert(link->dhcp6_client); | |
125f20b4 | 412 | assert(link->network); |
94876904 | 413 | assert(in6_addr_is_link_local(&link->ipv6ll_address)); |
85bd849f | 414 | |
0e45721e | 415 | if (link_get_dhcp6_client_start_mode(link) != DHCP6_CLIENT_START_MODE_NO) |
a6f44d61 | 416 | /* When WithoutRA= is specified, then the DHCPv6 client should be already running in |
fac19a21 YW |
417 | * the requested mode. Hence, ignore the requests by RA. */ |
418 | return 0; | |
419 | ||
7a695d8e TG |
420 | r = sd_dhcp6_client_is_running(link->dhcp6_client); |
421 | if (r < 0) | |
422 | return r; | |
e6604041 | 423 | |
fac19a21 YW |
424 | if (r > 0) { |
425 | int inf_req; | |
426 | ||
720bec40 TY |
427 | r = sd_dhcp6_client_get_information_request(link->dhcp6_client, &inf_req); |
428 | if (r < 0) | |
429 | return r; | |
430 | ||
fac19a21 | 431 | if (inf_req == information_request) |
8b1cfab9 | 432 | /* The client is already running in the requested mode. */ |
720bec40 TY |
433 | return 0; |
434 | ||
8b1cfab9 YW |
435 | if (!inf_req) { |
436 | log_link_debug(link, | |
437 | "The DHCPv6 client is already running in the managed mode, " | |
438 | "refusing to start the client in the information requesting mode."); | |
439 | return 0; | |
440 | } | |
441 | ||
442 | log_link_debug(link, | |
443 | "The DHCPv6 client is running in the information requesting mode. " | |
444 | "Restarting the client in the managed mode."); | |
445 | ||
7a695d8e TG |
446 | r = sd_dhcp6_client_stop(link->dhcp6_client); |
447 | if (r < 0) | |
448 | return r; | |
720bec40 TY |
449 | } else { |
450 | r = sd_dhcp6_client_set_local_address(link->dhcp6_client, &link->ipv6ll_address); | |
451 | if (r < 0) | |
452 | return r; | |
7a695d8e | 453 | } |
85bd849f | 454 | |
fac19a21 | 455 | r = sd_dhcp6_client_set_information_request(link->dhcp6_client, information_request); |
720bec40 TY |
456 | if (r < 0) |
457 | return r; | |
458 | ||
459 | r = sd_dhcp6_client_start(link->dhcp6_client); | |
7a695d8e TG |
460 | if (r < 0) |
461 | return r; | |
85bd849f | 462 | |
7a695d8e TG |
463 | return 0; |
464 | } | |
18d29550 | 465 | |
294f129b | 466 | int dhcp6_start(Link *link) { |
0e45721e | 467 | DHCP6ClientStartMode start_mode; |
ccffa166 YW |
468 | int r; |
469 | ||
294f129b | 470 | assert(link); |
fac19a21 | 471 | assert(link->network); |
294f129b YW |
472 | |
473 | if (!link->dhcp6_client) | |
474 | return 0; | |
475 | ||
476 | if (!link_dhcp6_enabled(link)) | |
477 | return 0; | |
478 | ||
ccffa166 YW |
479 | if (!link_has_carrier(link)) |
480 | return 0; | |
481 | ||
fac19a21 YW |
482 | if (sd_dhcp6_client_is_running(link->dhcp6_client) > 0) |
483 | return 0; | |
484 | ||
294f129b YW |
485 | if (!in6_addr_is_link_local(&link->ipv6ll_address)) { |
486 | log_link_debug(link, "IPv6 link-local address is not set, delaying to start DHCPv6 client."); | |
487 | return 0; | |
488 | } | |
489 | ||
fac19a21 YW |
490 | r = sd_dhcp6_client_set_local_address(link->dhcp6_client, &link->ipv6ll_address); |
491 | if (r < 0) | |
492 | return r; | |
294f129b | 493 | |
0e45721e YW |
494 | start_mode = link_get_dhcp6_client_start_mode(link); |
495 | if (start_mode == DHCP6_CLIENT_START_MODE_NO) | |
496 | return 0; | |
497 | ||
fac19a21 | 498 | r = sd_dhcp6_client_set_information_request(link->dhcp6_client, |
0e45721e | 499 | start_mode == DHCP6_CLIENT_START_MODE_INFORMATION_REQUEST); |
fac19a21 YW |
500 | if (r < 0) |
501 | return r; | |
502 | ||
503 | r = sd_dhcp6_client_start(link->dhcp6_client); | |
ccffa166 YW |
504 | if (r < 0) |
505 | return r; | |
294f129b | 506 | |
ccffa166 | 507 | return 1; |
294f129b YW |
508 | } |
509 | ||
8006aa32 SA |
510 | static int dhcp6_set_hostname(sd_dhcp6_client *client, Link *link) { |
511 | _cleanup_free_ char *hostname = NULL; | |
512 | const char *hn; | |
513 | int r; | |
514 | ||
515 | assert(link); | |
516 | ||
b90480c8 | 517 | if (!link->network->dhcp6_send_hostname) |
8006aa32 | 518 | hn = NULL; |
b90480c8 RP |
519 | else if (link->network->dhcp6_hostname) |
520 | hn = link->network->dhcp6_hostname; | |
8006aa32 SA |
521 | else { |
522 | r = gethostname_strict(&hostname); | |
523 | if (r < 0 && r != -ENXIO) /* ENXIO: no hostname set or hostname is "localhost" */ | |
87160186 | 524 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to get hostname: %m"); |
8006aa32 SA |
525 | |
526 | hn = hostname; | |
527 | } | |
528 | ||
a8494759 YW |
529 | r = sd_dhcp6_client_set_fqdn(client, hn); |
530 | if (r == -EINVAL && hostname) | |
531 | /* Ignore error when the machine's hostname is not suitable to send in DHCP packet. */ | |
87160186 | 532 | log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set hostname from kernel hostname, ignoring: %m"); |
a8494759 | 533 | else if (r < 0) |
87160186 | 534 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set hostname: %m"); |
a8494759 YW |
535 | |
536 | return 0; | |
8006aa32 SA |
537 | } |
538 | ||
eebba6dc YW |
539 | static int dhcp6_set_identifier(Link *link, sd_dhcp6_client *client) { |
540 | const DUID *duid; | |
541 | int r; | |
542 | ||
543 | assert(link); | |
544 | assert(link->network); | |
545 | assert(client); | |
546 | ||
ca2b7cd8 | 547 | r = sd_dhcp6_client_set_mac(client, link->hw_addr.bytes, link->hw_addr.length, link->iftype); |
eebba6dc YW |
548 | if (r < 0) |
549 | return r; | |
550 | ||
4e26a5ba YW |
551 | if (link->network->dhcp6_iaid_set) { |
552 | r = sd_dhcp6_client_set_iaid(client, link->network->dhcp6_iaid); | |
eebba6dc YW |
553 | if (r < 0) |
554 | return r; | |
555 | } | |
556 | ||
4e26a5ba | 557 | duid = link_get_dhcp6_duid(link); |
53488ea3 YW |
558 | |
559 | if (duid->raw_data_len == 0) | |
560 | switch (duid->type) { | |
561 | case DUID_TYPE_LLT: | |
562 | r = sd_dhcp6_client_set_duid_llt(client, duid->llt_time); | |
563 | break; | |
564 | case DUID_TYPE_LL: | |
565 | r = sd_dhcp6_client_set_duid_ll(client); | |
566 | break; | |
567 | case DUID_TYPE_EN: | |
568 | r = sd_dhcp6_client_set_duid_en(client); | |
569 | break; | |
570 | case DUID_TYPE_UUID: | |
571 | r = sd_dhcp6_client_set_duid_uuid(client); | |
572 | break; | |
573 | default: | |
574 | r = sd_dhcp6_client_set_duid_raw(client, duid->type, NULL, 0); | |
575 | } | |
eebba6dc | 576 | else |
53488ea3 | 577 | r = sd_dhcp6_client_set_duid_raw(client, duid->type, duid->raw_data, duid->raw_data_len); |
eebba6dc YW |
578 | if (r < 0) |
579 | return r; | |
580 | ||
581 | return 0; | |
582 | } | |
583 | ||
ccffa166 | 584 | static int dhcp6_configure(Link *link) { |
5bad7ebd | 585 | _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL; |
b4ccc5de | 586 | sd_dhcp6_option *vendor_option; |
e7d5fe17 | 587 | sd_dhcp6_option *send_option; |
35f6a5cb | 588 | void *request_options; |
fb5c8216 | 589 | int r; |
7a695d8e TG |
590 | |
591 | assert(link); | |
fb5c8216 | 592 | assert(link->network); |
7a695d8e | 593 | |
62379e88 | 594 | if (link->dhcp6_client) |
87160186 | 595 | return log_link_debug_errno(link, SYNTHETIC_ERRNO(EBUSY), "DHCPv6 client is already configured."); |
62379e88 | 596 | |
7a695d8e | 597 | r = sd_dhcp6_client_new(&client); |
5bad7ebd | 598 | if (r == -ENOMEM) |
bc8bd68d | 599 | return log_oom_debug(); |
7a695d8e | 600 | if (r < 0) |
87160186 | 601 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to create DHCPv6 client: %m"); |
5c79bd79 | 602 | |
4cf85000 | 603 | r = sd_dhcp6_client_attach_event(client, link->manager->event, 0); |
5c79bd79 | 604 | if (r < 0) |
87160186 | 605 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to attach event: %m"); |
5c79bd79 | 606 | |
3b1dbdf0 YW |
607 | r = sd_dhcp6_client_attach_device(client, link->dev); |
608 | if (r < 0) | |
609 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to attach device: %m"); | |
610 | ||
eebba6dc | 611 | r = dhcp6_set_identifier(link, client); |
e6604041 | 612 | if (r < 0) |
87160186 | 613 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set identifier: %m"); |
413708d1 | 614 | |
90e74a66 | 615 | ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp6_client_send_options) { |
e7d5fe17 AD |
616 | r = sd_dhcp6_client_add_option(client, send_option); |
617 | if (r == -EEXIST) | |
618 | continue; | |
619 | if (r < 0) | |
87160186 | 620 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set option: %m"); |
e7d5fe17 AD |
621 | } |
622 | ||
8006aa32 | 623 | r = dhcp6_set_hostname(client, link); |
8006aa32 | 624 | if (r < 0) |
a8494759 | 625 | return r; |
8006aa32 | 626 | |
2f8e7633 | 627 | r = sd_dhcp6_client_set_ifindex(client, link->ifindex); |
e6604041 | 628 | if (r < 0) |
87160186 | 629 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set ifindex: %m"); |
5c79bd79 | 630 | |
3175a8c2 SS |
631 | if (link->network->dhcp6_mudurl) { |
632 | r = sd_dhcp6_client_set_request_mud_url(client, link->network->dhcp6_mudurl); | |
633 | if (r < 0) | |
87160186 | 634 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set MUD URL: %m"); |
3175a8c2 SS |
635 | } |
636 | ||
9646ffe2 | 637 | if (link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP6)) { |
612caa26 YW |
638 | r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVER); |
639 | if (r < 0) | |
640 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request DNS servers: %m"); | |
641 | } | |
642 | ||
7a169cb4 | 643 | if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP6) > 0) { |
612caa26 YW |
644 | r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DOMAIN); |
645 | if (r < 0) | |
646 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request domains: %m"); | |
647 | } | |
648 | ||
a75feb55 RP |
649 | if (link->network->dhcp6_use_captive_portal > 0) { |
650 | r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_CAPTIVE_PORTAL); | |
651 | if (r < 0) | |
652 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request captive portal: %m"); | |
653 | } | |
654 | ||
d12fb2bc | 655 | if (link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP6)) { |
612caa26 YW |
656 | r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NTP_SERVER); |
657 | if (r < 0) | |
658 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request NTP servers: %m"); | |
659 | ||
660 | /* If the server does not provide NTP servers, then we fallback to use SNTP servers. */ | |
661 | r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_SNTP_SERVER); | |
662 | if (r < 0) | |
663 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request SNTP servers: %m"); | |
664 | } | |
665 | ||
90e74a66 | 666 | SET_FOREACH(request_options, link->network->dhcp6_request_options) { |
35f6a5cb SS |
667 | uint32_t option = PTR_TO_UINT32(request_options); |
668 | ||
669 | r = sd_dhcp6_client_set_request_option(client, option); | |
670 | if (r == -EEXIST) { | |
87160186 | 671 | log_link_debug(link, "DHCPv6 CLIENT: Failed to set request flag for '%u' already exists, ignoring.", option); |
35f6a5cb SS |
672 | continue; |
673 | } | |
35f6a5cb | 674 | if (r < 0) |
87160186 | 675 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set request flag for '%u': %m", option); |
35f6a5cb SS |
676 | } |
677 | ||
f37f2a6b SS |
678 | if (link->network->dhcp6_user_class) { |
679 | r = sd_dhcp6_client_set_request_user_class(client, link->network->dhcp6_user_class); | |
680 | if (r < 0) | |
87160186 | 681 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set user class: %m"); |
f37f2a6b SS |
682 | } |
683 | ||
ed0d1b2e SS |
684 | if (link->network->dhcp6_vendor_class) { |
685 | r = sd_dhcp6_client_set_request_vendor_class(client, link->network->dhcp6_vendor_class); | |
686 | if (r < 0) | |
87160186 | 687 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set vendor class: %m"); |
ed0d1b2e SS |
688 | } |
689 | ||
90e74a66 | 690 | ORDERED_HASHMAP_FOREACH(vendor_option, link->network->dhcp6_client_send_vendor_options) { |
b4ccc5de SS |
691 | r = sd_dhcp6_client_add_vendor_option(client, vendor_option); |
692 | if (r == -EEXIST) | |
693 | continue; | |
694 | if (r < 0) | |
87160186 | 695 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set vendor option: %m"); |
b4ccc5de SS |
696 | } |
697 | ||
7a695d8e | 698 | r = sd_dhcp6_client_set_callback(client, dhcp6_handler, link); |
e6604041 | 699 | if (r < 0) |
87160186 | 700 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set callback: %m"); |
5c79bd79 | 701 | |
f8da534e | 702 | r = dhcp6_client_set_state_callback(client, dhcp6_client_callback_bus, link); |
703 | if (r < 0) | |
704 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set state change callback: %m"); | |
705 | ||
0f5ef9b6 YW |
706 | r = sd_dhcp6_client_set_prefix_delegation(client, link->network->dhcp6_use_pd_prefix); |
707 | if (r < 0) | |
d5f8fd5b | 708 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to %s requesting prefixes to be delegated: %m", |
0f5ef9b6 | 709 | enable_disable(link->network->dhcp6_use_pd_prefix)); |
103b81ee | 710 | |
0bcc6557 | 711 | /* Even if UseAddress=no, we need to request IA_NA, as the dhcp6 client may be started in solicit mode. */ |
d5f8fd5b YW |
712 | r = sd_dhcp6_client_set_address_request(client, link->network->dhcp6_use_pd_prefix ? link->network->dhcp6_use_address : true); |
713 | if (r < 0) | |
714 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to %s requesting address: %m", | |
715 | enable_disable(link->network->dhcp6_use_address)); | |
716 | ||
df8bf726 YW |
717 | if (link->network->dhcp6_pd_prefix_length > 0) { |
718 | r = sd_dhcp6_client_set_prefix_delegation_hint(client, | |
719 | link->network->dhcp6_pd_prefix_length, | |
720 | &link->network->dhcp6_pd_prefix_hint); | |
2805536b | 721 | if (r < 0) |
df8bf726 | 722 | return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set prefix delegation hint: %m"); |
2805536b SS |
723 | } |
724 | ||
50ee1fec YW |
725 | r = sd_dhcp6_client_set_rapid_commit(client, link->network->dhcp6_use_rapid_commit); |
726 | if (r < 0) | |
727 | return log_link_debug_errno(link, r, | |
728 | "DHCPv6 CLIENT: Failed to %s rapid commit: %m", | |
729 | enable_disable(link->network->dhcp6_use_rapid_commit)); | |
730 | ||
b895aa5f | 731 | r = sd_dhcp6_client_set_send_release(client, link->network->dhcp6_send_release); |
732 | if (r < 0) | |
733 | return log_link_debug_errno(link, r, | |
734 | "DHCPv6 CLIENT: Failed to %s sending release message on stop: %m", | |
735 | enable_disable(link->network->dhcp6_send_release)); | |
736 | ||
5bad7ebd | 737 | link->dhcp6_client = TAKE_PTR(client); |
e6604041 | 738 | |
7a695d8e | 739 | return 0; |
5c79bd79 | 740 | } |
04ed9949 | 741 | |
eebba6dc YW |
742 | int dhcp6_update_mac(Link *link) { |
743 | bool restart; | |
744 | int r; | |
745 | ||
746 | assert(link); | |
747 | ||
748 | if (!link->dhcp6_client) | |
749 | return 0; | |
750 | ||
751 | restart = sd_dhcp6_client_is_running(link->dhcp6_client) > 0; | |
752 | ||
753 | if (restart) { | |
754 | r = sd_dhcp6_client_stop(link->dhcp6_client); | |
755 | if (r < 0) | |
756 | return r; | |
757 | } | |
758 | ||
759 | r = dhcp6_set_identifier(link, link->dhcp6_client); | |
760 | if (r < 0) | |
761 | return r; | |
762 | ||
763 | if (restart) { | |
764 | r = sd_dhcp6_client_start(link->dhcp6_client); | |
765 | if (r < 0) | |
766 | return log_link_warning_errno(link, r, "Could not restart DHCPv6 client: %m"); | |
767 | } | |
768 | ||
769 | return 0; | |
770 | } | |
771 | ||
09d09207 | 772 | static int dhcp6_process_request(Request *req, Link *link, void *userdata) { |
ccffa166 YW |
773 | int r; |
774 | ||
ff51134c | 775 | assert(link); |
ccffa166 | 776 | |
4b482e8b | 777 | if (!link_is_ready_to_configure(link, /* allow_unmanaged = */ false)) |
ccffa166 YW |
778 | return 0; |
779 | ||
780 | r = dhcp_configure_duid(link, link_get_dhcp6_duid(link)); | |
781 | if (r <= 0) | |
782 | return r; | |
783 | ||
784 | r = dhcp6_configure(link); | |
785 | if (r < 0) | |
786 | return log_link_warning_errno(link, r, "Failed to configure DHCPv6 client: %m"); | |
787 | ||
788 | r = ndisc_start(link); | |
789 | if (r < 0) | |
790 | return log_link_warning_errno(link, r, "Failed to start IPv6 Router Discovery: %m"); | |
791 | ||
792 | r = dhcp6_start(link); | |
793 | if (r < 0) | |
794 | return log_link_warning_errno(link, r, "Failed to start DHCPv6 client: %m"); | |
795 | ||
796 | log_link_debug(link, "DHCPv6 client is configured%s.", | |
797 | r > 0 ? ", acquiring DHCPv6 lease" : ""); | |
ccffa166 YW |
798 | return 1; |
799 | } | |
800 | ||
801 | int link_request_dhcp6_client(Link *link) { | |
802 | int r; | |
803 | ||
804 | assert(link); | |
805 | ||
52672db3 | 806 | if (!link_dhcp6_enabled(link) && !link_ndisc_enabled(link)) |
ccffa166 YW |
807 | return 0; |
808 | ||
809 | if (link->dhcp6_client) | |
810 | return 0; | |
811 | ||
09d09207 | 812 | r = link_queue_request(link, REQUEST_TYPE_DHCP6_CLIENT, dhcp6_process_request, NULL); |
ccffa166 YW |
813 | if (r < 0) |
814 | return log_link_warning_errno(link, r, "Failed to request configuring of the DHCPv6 client: %m"); | |
815 | ||
816 | log_link_debug(link, "Requested configuring of the DHCPv6 client."); | |
817 | return 0; | |
818 | } | |
819 | ||
5460bde5 YW |
820 | int link_serialize_dhcp6_client(Link *link, FILE *f) { |
821 | _cleanup_free_ char *duid = NULL; | |
822 | uint32_t iaid; | |
823 | int r; | |
824 | ||
825 | assert(link); | |
826 | ||
827 | if (!link->dhcp6_client) | |
828 | return 0; | |
829 | ||
830 | r = sd_dhcp6_client_get_iaid(link->dhcp6_client, &iaid); | |
831 | if (r >= 0) | |
832 | fprintf(f, "DHCP6_CLIENT_IAID=0x%x\n", iaid); | |
833 | ||
3a6c2227 | 834 | r = sd_dhcp6_client_get_duid_as_string(link->dhcp6_client, &duid); |
5460bde5 YW |
835 | if (r >= 0) |
836 | fprintf(f, "DHCP6_CLIENT_DUID=%s\n", duid); | |
837 | ||
838 | return 0; | |
839 | } | |
840 | ||
df8bf726 | 841 | int config_parse_dhcp6_pd_prefix_hint( |
c24dd739 YW |
842 | const char* unit, |
843 | const char *filename, | |
844 | unsigned line, | |
845 | const char *section, | |
846 | unsigned section_line, | |
847 | const char *lvalue, | |
848 | int ltype, | |
849 | const char *rvalue, | |
850 | void *data, | |
851 | void *userdata) { | |
852 | ||
99534007 | 853 | Network *network = ASSERT_PTR(userdata); |
275468c0 YW |
854 | union in_addr_union u; |
855 | unsigned char prefixlen; | |
c24dd739 YW |
856 | int r; |
857 | ||
858 | assert(filename); | |
859 | assert(lvalue); | |
860 | assert(rvalue); | |
c24dd739 | 861 | |
275468c0 | 862 | r = in_addr_prefix_from_string(rvalue, AF_INET6, &u, &prefixlen); |
c24dd739 | 863 | if (r < 0) { |
275468c0 YW |
864 | log_syntax(unit, LOG_WARNING, filename, line, r, |
865 | "Failed to parse %s=%s, ignoring assignment.", lvalue, rvalue); | |
c24dd739 YW |
866 | return 0; |
867 | } | |
868 | ||
275468c0 YW |
869 | if (prefixlen < 1 || prefixlen > 128) { |
870 | log_syntax(unit, LOG_WARNING, filename, line, 0, | |
871 | "Invalid prefix length in %s=%s, ignoring assignment.", lvalue, rvalue); | |
c24dd739 YW |
872 | return 0; |
873 | } | |
874 | ||
df8bf726 YW |
875 | network->dhcp6_pd_prefix_hint = u.in6; |
876 | network->dhcp6_pd_prefix_length = prefixlen; | |
275468c0 | 877 | |
c24dd739 YW |
878 | return 0; |
879 | } | |
880 | ||
99e015e2 YW |
881 | DEFINE_CONFIG_PARSE_ENUM(config_parse_dhcp6_client_start_mode, dhcp6_client_start_mode, DHCP6ClientStartMode, |
882 | "Failed to parse WithoutRA= setting"); | |
883 | ||
884 | static const char* const dhcp6_client_start_mode_table[_DHCP6_CLIENT_START_MODE_MAX] = { | |
885 | [DHCP6_CLIENT_START_MODE_NO] = "no", | |
886 | [DHCP6_CLIENT_START_MODE_INFORMATION_REQUEST] = "information-request", | |
887 | [DHCP6_CLIENT_START_MODE_SOLICIT] = "solicit", | |
888 | }; | |
889 | ||
890 | DEFINE_STRING_TABLE_LOOKUP(dhcp6_client_start_mode, DHCP6ClientStartMode); |