]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
a13c50e7 | 2 | /*** |
810adae9 | 3 | Copyright © 2014 Intel Corporation. All rights reserved. |
a13c50e7 TG |
4 | ***/ |
5 | ||
23f53b99 | 6 | #include <arpa/inet.h> |
062c020f YW |
7 | #include <netinet/icmp6.h> |
8 | #include <linux/if.h> | |
f4ef1c19 | 9 | #include <linux/if_arp.h> |
a13c50e7 | 10 | |
a13c50e7 TG |
11 | #include "sd-ndisc.h" |
12 | ||
77302468 | 13 | #include "event-util.h" |
d909e4af | 14 | #include "missing_network.h" |
f09a4747 | 15 | #include "networkd-address-generation.h" |
093e3533 | 16 | #include "networkd-address.h" |
ca5ad760 | 17 | #include "networkd-dhcp6.h" |
73854ba1 | 18 | #include "networkd-manager.h" |
1e7a0e21 | 19 | #include "networkd-ndisc.h" |
76c5a0f2 | 20 | #include "networkd-queue.h" |
3b6a3bde | 21 | #include "networkd-route.h" |
3b5a4fc6 | 22 | #include "networkd-state-file.h" |
ac24e418 | 23 | #include "string-table.h" |
5f506a55 | 24 | #include "string-util.h" |
51517f9e | 25 | #include "strv.h" |
91750028 | 26 | #include "sysctl-util.h" |
1e7a0e21 LP |
27 | |
28 | #define NDISC_DNSSL_MAX 64U | |
29 | #define NDISC_RDNSS_MAX 64U | |
4df16cd0 | 30 | /* Not defined in the RFC, but let's set an upper limit to make not consume much memory. |
bf943a9d YW |
31 | * This should be safe as typically there should be at most 1 portal per network. */ |
32 | #define NDISC_CAPTIVE_PORTAL_MAX 64U | |
4df16cd0 YW |
33 | /* Neither defined in the RFC. Just for safety. Otherwise, malformed messages can make clients trigger OOM. |
34 | * Not sure if the threshold is high enough. Let's adjust later if not. */ | |
35 | #define NDISC_PREF64_MAX 64U | |
fe307276 | 36 | |
062c020f YW |
37 | bool link_ipv6_accept_ra_enabled(Link *link) { |
38 | assert(link); | |
39 | ||
40 | if (!socket_ipv6_is_supported()) | |
41 | return false; | |
42 | ||
43 | if (link->flags & IFF_LOOPBACK) | |
44 | return false; | |
45 | ||
f4ef1c19 YW |
46 | if (link->iftype == ARPHRD_CAN) |
47 | return false; | |
48 | ||
062c020f YW |
49 | if (!link->network) |
50 | return false; | |
51 | ||
bd7e0a3f | 52 | if (!link_may_have_ipv6ll(link, /* check_multicast = */ true)) |
062c020f YW |
53 | return false; |
54 | ||
3773eb54 YW |
55 | assert(link->network->ipv6_accept_ra >= 0); |
56 | return link->network->ipv6_accept_ra; | |
57 | } | |
58 | ||
59 | void network_adjust_ipv6_accept_ra(Network *network) { | |
60 | assert(network); | |
61 | ||
62 | if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) { | |
63 | if (network->ipv6_accept_ra > 0) | |
f81ac115 | 64 | log_warning("%s: IPv6AcceptRA= is enabled but IPv6 link-local addressing is disabled or not supported. " |
3773eb54 YW |
65 | "Disabling IPv6AcceptRA=.", network->filename); |
66 | network->ipv6_accept_ra = false; | |
67 | } | |
68 | ||
69 | if (network->ipv6_accept_ra < 0) | |
062c020f | 70 | /* default to accept RA if ip_forward is disabled and ignore RA if ip_forward is enabled */ |
3773eb54 | 71 | network->ipv6_accept_ra = !FLAGS_SET(network->ip_forward, ADDRESS_FAMILY_IPV6); |
de6b6ff8 | 72 | |
75d26411 YW |
73 | /* When RouterAllowList=, PrefixAllowList= or RouteAllowList= are specified, then |
74 | * RouterDenyList=, PrefixDenyList= or RouteDenyList= are ignored, respectively. */ | |
75 | if (!set_isempty(network->ndisc_allow_listed_router)) | |
76 | network->ndisc_deny_listed_router = set_free_free(network->ndisc_deny_listed_router); | |
de6b6ff8 SS |
77 | if (!set_isempty(network->ndisc_allow_listed_prefix)) |
78 | network->ndisc_deny_listed_prefix = set_free_free(network->ndisc_deny_listed_prefix); | |
79 | if (!set_isempty(network->ndisc_allow_listed_route_prefix)) | |
80 | network->ndisc_deny_listed_route_prefix = set_free_free(network->ndisc_deny_listed_route_prefix); | |
062c020f YW |
81 | } |
82 | ||
3b6a3bde | 83 | static int ndisc_check_ready(Link *link); |
50550722 | 84 | |
3b6a3bde YW |
85 | static int ndisc_address_ready_callback(Address *address) { |
86 | Address *a; | |
50550722 | 87 | |
3b6a3bde YW |
88 | assert(address); |
89 | assert(address->link); | |
50550722 | 90 | |
3b6a3bde YW |
91 | SET_FOREACH(a, address->link->addresses) |
92 | if (a->source == NETWORK_CONFIG_SOURCE_NDISC) | |
93 | a->callback = NULL; | |
50550722 | 94 | |
3b6a3bde YW |
95 | return ndisc_check_ready(address->link); |
96 | } | |
50550722 | 97 | |
3b6a3bde YW |
98 | static int ndisc_check_ready(Link *link) { |
99 | bool found = false, ready = false; | |
100 | Address *address; | |
50550722 | 101 | |
3b6a3bde | 102 | assert(link); |
50550722 | 103 | |
3b6a3bde YW |
104 | if (link->ndisc_messages > 0) { |
105 | log_link_debug(link, "%s(): SLAAC addresses and routes are not set.", __func__); | |
106 | return 0; | |
107 | } | |
50550722 | 108 | |
3b6a3bde YW |
109 | SET_FOREACH(address, link->addresses) { |
110 | if (address->source != NETWORK_CONFIG_SOURCE_NDISC) | |
111 | continue; | |
50550722 | 112 | |
3b6a3bde | 113 | found = true; |
50550722 | 114 | |
3b6a3bde YW |
115 | if (address_is_ready(address)) { |
116 | ready = true; | |
117 | break; | |
50550722 | 118 | } |
3b6a3bde | 119 | } |
50550722 | 120 | |
3b6a3bde YW |
121 | if (found && !ready) { |
122 | SET_FOREACH(address, link->addresses) | |
123 | if (address->source == NETWORK_CONFIG_SOURCE_NDISC) | |
124 | address->callback = ndisc_address_ready_callback; | |
50550722 | 125 | |
3b6a3bde YW |
126 | log_link_debug(link, "%s(): no SLAAC address is ready.", __func__); |
127 | return 0; | |
50550722 YW |
128 | } |
129 | ||
3b6a3bde YW |
130 | link->ndisc_configured = true; |
131 | log_link_debug(link, "SLAAC addresses and routes set."); | |
50550722 | 132 | |
3b6a3bde YW |
133 | link_check_ready(link); |
134 | return 0; | |
50550722 YW |
135 | } |
136 | ||
80d62d4f | 137 | static int ndisc_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Route *route) { |
3b015d40 TG |
138 | int r; |
139 | ||
140 | assert(link); | |
3b015d40 | 141 | |
2c0b49ba | 142 | r = route_configure_handler_internal(rtnl, m, link, route, "Could not set NDisc route"); |
5a07fa9d YW |
143 | if (r <= 0) |
144 | return r; | |
3b015d40 | 145 | |
3b6a3bde | 146 | r = ndisc_check_ready(link); |
50550722 | 147 | if (r < 0) |
3b6a3bde | 148 | link_enter_failed(link); |
76c5a0f2 | 149 | |
3b6a3bde | 150 | return 1; |
76c5a0f2 YW |
151 | } |
152 | ||
6f812d28 YW |
153 | static void ndisc_set_route_priority(Link *link, Route *route) { |
154 | assert(link); | |
155 | assert(route); | |
156 | ||
157 | if (route->priority_set) | |
158 | return; /* explicitly configured. */ | |
159 | ||
160 | switch (route->pref) { | |
161 | case SD_NDISC_PREFERENCE_LOW: | |
162 | route->priority = link->network->ipv6_accept_ra_route_metric_low; | |
163 | break; | |
164 | case SD_NDISC_PREFERENCE_MEDIUM: | |
165 | route->priority = link->network->ipv6_accept_ra_route_metric_medium; | |
166 | break; | |
167 | case SD_NDISC_PREFERENCE_HIGH: | |
168 | route->priority = link->network->ipv6_accept_ra_route_metric_high; | |
169 | break; | |
170 | default: | |
171 | assert_not_reached(); | |
172 | } | |
173 | } | |
174 | ||
e217da13 | 175 | static int ndisc_request_route(Route *route, Link *link, sd_ndisc_router *rt) { |
3b6a3bde | 176 | struct in6_addr router; |
f141b2c0 | 177 | uint8_t hop_limit = 0; |
fc4a7f13 | 178 | uint32_t mtu = 0; |
d9a95033 | 179 | bool is_new; |
76c5a0f2 YW |
180 | int r; |
181 | ||
182 | assert(route); | |
183 | assert(link); | |
8d01e44c | 184 | assert(link->manager); |
f95fb199 | 185 | assert(link->network); |
76c5a0f2 YW |
186 | assert(rt); |
187 | ||
3b6a3bde YW |
188 | r = sd_ndisc_router_get_address(rt, &router); |
189 | if (r < 0) | |
190 | return r; | |
191 | ||
fc4a7f13 SS |
192 | if (link->network->ipv6_accept_ra_use_mtu) { |
193 | r = sd_ndisc_router_get_mtu(rt, &mtu); | |
194 | if (r < 0 && r != -ENODATA) | |
34acdf90 | 195 | return log_link_warning_errno(link, r, "Failed to get MTU from RA: %m"); |
fc4a7f13 SS |
196 | } |
197 | ||
39713b07 | 198 | if (link->network->ipv6_accept_ra_use_hop_limit) { |
f141b2c0 SS |
199 | r = sd_ndisc_router_get_hop_limit(rt, &hop_limit); |
200 | if (r < 0 && r != -ENODATA) | |
34acdf90 | 201 | return log_link_warning_errno(link, r, "Failed to get hop limit from RA: %m"); |
93e583aa | 202 | } |
f141b2c0 | 203 | |
3b6a3bde YW |
204 | route->source = NETWORK_CONFIG_SOURCE_NDISC; |
205 | route->provider.in6 = router; | |
429dc05a YW |
206 | if (!route->table_set) |
207 | route->table = link_get_ipv6_accept_ra_route_table(link); | |
429dc05a YW |
208 | if (!route->protocol_set) |
209 | route->protocol = RTPROT_RA; | |
ebf4fa1e YW |
210 | r = route_metric_set(&route->metric, RTAX_MTU, mtu); |
211 | if (r < 0) | |
212 | return r; | |
213 | r = route_metric_set(&route->metric, RTAX_HOPLIMIT, hop_limit); | |
214 | if (r < 0) | |
215 | return r; | |
216 | r = route_metric_set(&route->metric, RTAX_QUICKACK, link->network->ipv6_accept_ra_quickack); | |
217 | if (r < 0) | |
218 | return r; | |
f141b2c0 | 219 | |
2f542fc3 YW |
220 | r = route_adjust_nexthops(route, link); |
221 | if (r < 0) | |
222 | return r; | |
223 | ||
972f1d17 YW |
224 | uint8_t pref, pref_original = route->pref; |
225 | FOREACH_ARGUMENT(pref, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH) { | |
226 | Route *existing; | |
227 | Request *req; | |
228 | ||
229 | /* If the preference is specified by the user config (that is, for semi-static routes), | |
230 | * rather than RA, then only search conflicting routes that have the same preference. */ | |
231 | if (route->pref_set && pref != pref_original) | |
232 | continue; | |
233 | ||
234 | route->pref = pref; | |
235 | ndisc_set_route_priority(link, route); | |
236 | ||
237 | /* Note, here do not call route_remove_and_cancel() with 'route' directly, otherwise | |
238 | * existing route(s) may be removed needlessly. */ | |
239 | ||
240 | if (route_get(link->manager, route, &existing) >= 0) { | |
241 | /* Found an existing route that may conflict with this route. */ | |
242 | if (!route_can_update(existing, route)) { | |
243 | log_link_debug(link, "Found an existing route that conflicts with new route based on a received RA, removing."); | |
244 | r = route_remove_and_cancel(existing, link->manager); | |
245 | if (r < 0) | |
246 | return r; | |
247 | } | |
248 | } | |
249 | ||
250 | if (route_get_request(link->manager, route, &req) >= 0) { | |
251 | existing = ASSERT_PTR(req->userdata); | |
252 | if (!route_can_update(existing, route)) { | |
253 | log_link_debug(link, "Found a pending route request that conflicts with new request based on a received RA, cancelling."); | |
254 | r = route_remove_and_cancel(existing, link->manager); | |
255 | if (r < 0) | |
256 | return r; | |
257 | } | |
258 | } | |
259 | } | |
260 | ||
261 | /* The preference (and priority) may be changed in the above loop. Restore it. */ | |
262 | route->pref = pref_original; | |
263 | ndisc_set_route_priority(link, route); | |
264 | ||
8d01e44c | 265 | is_new = route_get(link->manager, route, NULL) < 0; |
d9a95033 | 266 | |
5a18697d | 267 | r = link_request_route(link, route, &link->ndisc_messages, ndisc_route_handler); |
d9a95033 YW |
268 | if (r < 0) |
269 | return r; | |
270 | if (r > 0 && is_new) | |
3b6a3bde | 271 | link->ndisc_configured = false; |
50550722 | 272 | |
d9a95033 | 273 | return 0; |
50550722 YW |
274 | } |
275 | ||
479d3e19 YW |
276 | static int ndisc_remove_route(Route *route, Link *link) { |
277 | int r; | |
278 | ||
279 | assert(route); | |
280 | assert(link); | |
281 | assert(link->manager); | |
282 | ||
283 | ndisc_set_route_priority(link, route); | |
284 | ||
285 | if (!route->table_set) | |
286 | route->table = link_get_ipv6_accept_ra_route_table(link); | |
287 | ||
288 | r = route_adjust_nexthops(route, link); | |
289 | if (r < 0) | |
290 | return r; | |
291 | ||
292 | if (route->pref_set) { | |
293 | ndisc_set_route_priority(link, route); | |
294 | return route_remove_and_cancel(route, link->manager); | |
295 | } | |
296 | ||
297 | uint8_t pref; | |
298 | FOREACH_ARGUMENT(pref, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH) { | |
299 | route->pref = pref; | |
300 | ndisc_set_route_priority(link, route); | |
301 | r = route_remove_and_cancel(route, link->manager); | |
302 | if (r < 0) | |
303 | return r; | |
304 | } | |
305 | ||
306 | return 0; | |
307 | } | |
308 | ||
80d62d4f | 309 | static int ndisc_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Address *address) { |
73854ba1 YW |
310 | int r; |
311 | ||
312 | assert(link); | |
73854ba1 | 313 | |
5a07fa9d YW |
314 | r = address_configure_handler_internal(rtnl, m, link, "Could not set NDisc address"); |
315 | if (r <= 0) | |
316 | return r; | |
73854ba1 | 317 | |
3b6a3bde | 318 | r = ndisc_check_ready(link); |
50550722 | 319 | if (r < 0) |
3b6a3bde | 320 | link_enter_failed(link); |
69203fba | 321 | |
3b6a3bde | 322 | return 1; |
69203fba YW |
323 | } |
324 | ||
9e79ef91 | 325 | static int ndisc_request_address(Address *address, Link *link, sd_ndisc_router *rt) { |
3b6a3bde | 326 | struct in6_addr router; |
d9a95033 | 327 | bool is_new; |
76c5a0f2 YW |
328 | int r; |
329 | ||
330 | assert(address); | |
331 | assert(link); | |
332 | assert(rt); | |
333 | ||
3b6a3bde YW |
334 | r = sd_ndisc_router_get_address(rt, &router); |
335 | if (r < 0) | |
76c5a0f2 YW |
336 | return r; |
337 | ||
3b6a3bde YW |
338 | address->source = NETWORK_CONFIG_SOURCE_NDISC; |
339 | address->provider.in6 = router; | |
76c5a0f2 | 340 | |
4b3590c3 TM |
341 | r = free_and_strdup_warn(&address->netlabel, link->network->ndisc_netlabel); |
342 | if (r < 0) | |
343 | return r; | |
344 | ||
e720ad88 YW |
345 | Address *existing; |
346 | if (address_get_harder(link, address, &existing) < 0) | |
347 | is_new = true; | |
348 | else if (address_can_update(existing, address)) | |
349 | is_new = false; | |
350 | else if (existing->source == NETWORK_CONFIG_SOURCE_DHCP6) { | |
351 | /* SLAAC address is preferred over DHCPv6 address. */ | |
352 | log_link_debug(link, "Conflicting DHCPv6 address %s exists, removing.", | |
353 | IN_ADDR_PREFIX_TO_STRING(existing->family, &existing->in_addr, existing->prefixlen)); | |
354 | r = address_remove(existing, link); | |
355 | if (r < 0) | |
356 | return r; | |
357 | ||
358 | is_new = true; | |
359 | } else { | |
360 | /* Conflicting static address is configured?? */ | |
361 | log_link_debug(link, "Conflicting address %s exists, ignoring request.", | |
362 | IN_ADDR_PREFIX_TO_STRING(existing->family, &existing->in_addr, existing->prefixlen)); | |
363 | return 0; | |
364 | } | |
d9a95033 | 365 | |
f60e6558 | 366 | r = link_request_address(link, address, &link->ndisc_messages, |
d9a95033 YW |
367 | ndisc_address_handler, NULL); |
368 | if (r < 0) | |
369 | return r; | |
370 | if (r > 0 && is_new) | |
3b6a3bde | 371 | link->ndisc_configured = false; |
3b6a3bde | 372 | |
d9a95033 | 373 | return 0; |
76c5a0f2 YW |
374 | } |
375 | ||
479d3e19 YW |
376 | static int ndisc_router_drop_default(Link *link, sd_ndisc_router *rt) { |
377 | _cleanup_(route_unrefp) Route *route = NULL; | |
378 | struct in6_addr gateway; | |
379 | int r; | |
380 | ||
381 | assert(link); | |
382 | assert(link->network); | |
383 | assert(rt); | |
384 | ||
385 | r = sd_ndisc_router_get_address(rt, &gateway); | |
386 | if (r < 0) | |
387 | return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); | |
388 | ||
389 | r = route_new(&route); | |
390 | if (r < 0) | |
391 | return log_oom(); | |
392 | ||
393 | route->family = AF_INET6; | |
394 | route->nexthop.family = AF_INET6; | |
395 | route->nexthop.gw.in6 = gateway; | |
396 | ||
397 | r = ndisc_remove_route(route, link); | |
398 | if (r < 0) | |
399 | return log_link_warning_errno(link, r, "Failed to remove the default gateway configured by RA: %m"); | |
400 | ||
401 | Route *route_gw; | |
402 | HASHMAP_FOREACH(route_gw, link->network->routes_by_section) { | |
403 | _cleanup_(route_unrefp) Route *tmp = NULL; | |
404 | ||
405 | if (!route_gw->gateway_from_dhcp_or_ra) | |
406 | continue; | |
407 | ||
408 | if (route_gw->nexthop.family != AF_INET6) | |
409 | continue; | |
410 | ||
411 | r = route_dup(route_gw, NULL, &tmp); | |
412 | if (r < 0) | |
413 | return r; | |
414 | ||
415 | tmp->nexthop.gw.in6 = gateway; | |
416 | ||
417 | r = ndisc_remove_route(tmp, link); | |
418 | if (r < 0) | |
419 | return log_link_warning_errno(link, r, "Could not remove semi-static gateway: %m"); | |
420 | } | |
421 | ||
422 | return 0; | |
423 | } | |
424 | ||
d5017c84 | 425 | static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { |
6197db53 | 426 | usec_t lifetime_usec; |
b8ce3b44 | 427 | struct in6_addr gateway; |
1e7a0e21 | 428 | unsigned preference; |
13e8a49a | 429 | int r; |
3b015d40 | 430 | |
3b015d40 | 431 | assert(link); |
610c0db1 | 432 | assert(link->network); |
1e7a0e21 | 433 | assert(rt); |
3b015d40 | 434 | |
479d3e19 YW |
435 | /* If the router lifetime is zero, the router should not be used as the default gateway. */ |
436 | r = sd_ndisc_router_get_lifetime(rt, NULL); | |
437 | if (r < 0) | |
438 | return r; | |
439 | if (r == 0) | |
440 | return ndisc_router_drop_default(link, rt); | |
441 | ||
610c0db1 YW |
442 | if (!link->network->ipv6_accept_ra_use_gateway && |
443 | hashmap_isempty(link->network->routes_by_section)) | |
444 | return 0; | |
445 | ||
6197db53 | 446 | r = sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); |
d5017c84 | 447 | if (r < 0) |
2c5bca17 | 448 | return log_link_warning_errno(link, r, "Failed to get gateway lifetime from RA: %m"); |
d5017c84 | 449 | |
b8ce3b44 | 450 | r = sd_ndisc_router_get_address(rt, &gateway); |
d5017c84 | 451 | if (r < 0) |
2c5bca17 | 452 | return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m"); |
1e7a0e21 | 453 | |
5d003031 | 454 | if (link_get_ipv6_address(link, &gateway, 0, NULL) >= 0) { |
84dbb3fd | 455 | if (DEBUG_LOGGING) |
5eec0a08 | 456 | log_link_debug(link, "No NDisc route added, gateway %s matches local address", |
84dbb3fd | 457 | IN6_ADDR_TO_STRING(&gateway)); |
5eec0a08 | 458 | return 0; |
ce2ea782 | 459 | } |
6d7c7615 | 460 | |
1e7a0e21 | 461 | r = sd_ndisc_router_get_preference(rt, &preference); |
d5017c84 | 462 | if (r < 0) |
34acdf90 | 463 | return log_link_warning_errno(link, r, "Failed to get router preference from RA: %m"); |
1e7a0e21 | 464 | |
610c0db1 | 465 | if (link->network->ipv6_accept_ra_use_gateway) { |
74c301b9 | 466 | _cleanup_(route_unrefp) Route *route = NULL; |
1e7a0e21 | 467 | |
610c0db1 YW |
468 | r = route_new(&route); |
469 | if (r < 0) | |
470 | return log_oom(); | |
1e7a0e21 | 471 | |
610c0db1 YW |
472 | route->family = AF_INET6; |
473 | route->pref = preference; | |
054b8c28 YW |
474 | route->nexthop.family = AF_INET6; |
475 | route->nexthop.gw.in6 = gateway; | |
610c0db1 | 476 | route->lifetime_usec = lifetime_usec; |
610c0db1 | 477 | |
e217da13 | 478 | r = ndisc_request_route(route, link, rt); |
610c0db1 | 479 | if (r < 0) |
2c5bca17 | 480 | return log_link_warning_errno(link, r, "Could not request default route: %m"); |
610c0db1 | 481 | } |
d5017c84 | 482 | |
1985c54f | 483 | Route *route_gw; |
2a54a044 | 484 | HASHMAP_FOREACH(route_gw, link->network->routes_by_section) { |
74c301b9 | 485 | _cleanup_(route_unrefp) Route *route = NULL; |
610c0db1 | 486 | |
1a3a6309 | 487 | if (!route_gw->gateway_from_dhcp_or_ra) |
1985c54f YW |
488 | continue; |
489 | ||
054b8c28 | 490 | if (route_gw->nexthop.family != AF_INET6) |
1985c54f YW |
491 | continue; |
492 | ||
413ea20a | 493 | r = route_dup(route_gw, NULL, &route); |
76c5a0f2 YW |
494 | if (r < 0) |
495 | return r; | |
496 | ||
054b8c28 | 497 | route->nexthop.gw.in6 = gateway; |
76c5a0f2 YW |
498 | if (!route->pref_set) |
499 | route->pref = preference; | |
91fc5135 | 500 | route->lifetime_usec = lifetime_usec; |
76c5a0f2 | 501 | |
e217da13 | 502 | r = ndisc_request_route(route, link, rt); |
13e8a49a | 503 | if (r < 0) |
2c5bca17 | 504 | return log_link_warning_errno(link, r, "Could not request gateway: %m"); |
1985c54f YW |
505 | } |
506 | ||
0f9a2b80 YW |
507 | return 0; |
508 | } | |
91750028 | 509 | |
0f9a2b80 | 510 | static int ndisc_router_process_icmp6_ratelimit(Link *link, sd_ndisc_router *rt) { |
be89a76a | 511 | usec_t icmp6_ratelimit, msec; |
0f9a2b80 | 512 | int r; |
91750028 | 513 | |
0f9a2b80 YW |
514 | assert(link); |
515 | assert(link->network); | |
516 | assert(rt); | |
91750028 | 517 | |
0f9a2b80 YW |
518 | if (!link->network->ipv6_accept_ra_use_icmp6_ratelimit) |
519 | return 0; | |
520 | ||
ffef01ac YW |
521 | /* Ignore the icmp6 ratelimit field of the RA header if the lifetime is zero. */ |
522 | r = sd_ndisc_router_get_lifetime(rt, NULL); | |
523 | if (r <= 0) | |
524 | return r; | |
525 | ||
0f9a2b80 | 526 | r = sd_ndisc_router_get_icmp6_ratelimit(rt, &icmp6_ratelimit); |
b409ac6c YW |
527 | if (r < 0) |
528 | return log_link_warning_errno(link, r, "Failed to get ICMP6 ratelimit from RA: %m"); | |
91750028 | 529 | |
be89a76a | 530 | /* We do not allow 0 here. */ |
6197db53 | 531 | if (!timestamp_is_set(icmp6_ratelimit)) |
0f9a2b80 YW |
532 | return 0; |
533 | ||
be89a76a YW |
534 | msec = DIV_ROUND_UP(icmp6_ratelimit, USEC_PER_MSEC); |
535 | if (msec <= 0 || msec > INT_MAX) | |
536 | return 0; | |
537 | ||
6197db53 YW |
538 | /* Limit the maximal rates for sending ICMPv6 packets. 0 to disable any limiting, otherwise the |
539 | * minimal space between responses in milliseconds. Default: 1000. */ | |
be89a76a | 540 | r = sysctl_write_ip_property_int(AF_INET6, NULL, "icmp/ratelimit", (int) msec); |
0f9a2b80 YW |
541 | if (r < 0) |
542 | log_link_warning_errno(link, r, "Failed to apply ICMP6 ratelimit, ignoring: %m"); | |
543 | ||
d5017c84 | 544 | return 0; |
1e7a0e21 LP |
545 | } |
546 | ||
1452d497 YW |
547 | static int ndisc_router_process_reachable_time(Link *link, sd_ndisc_router *rt) { |
548 | usec_t reachable_time, msec; | |
549 | int r; | |
550 | ||
551 | assert(link); | |
552 | assert(link->network); | |
553 | assert(rt); | |
554 | ||
555 | if (!link->network->ipv6_accept_ra_use_reachable_time) | |
556 | return 0; | |
557 | ||
558 | /* Ignore the reachable time field of the RA header if the lifetime is zero. */ | |
559 | r = sd_ndisc_router_get_lifetime(rt, NULL); | |
560 | if (r <= 0) | |
561 | return r; | |
562 | ||
563 | r = sd_ndisc_router_get_reachable_time(rt, &reachable_time); | |
564 | if (r < 0) | |
565 | return log_link_warning_errno(link, r, "Failed to get reachable time from RA: %m"); | |
566 | ||
567 | /* 0 is the unspecified value and must not be set (see RFC4861, 6.3.4) */ | |
568 | if (!timestamp_is_set(reachable_time)) | |
569 | return 0; | |
570 | ||
571 | msec = DIV_ROUND_UP(reachable_time, USEC_PER_MSEC); | |
572 | if (msec <= 0 || msec > UINT32_MAX) { | |
573 | log_link_debug(link, "Failed to get reachable time from RA - out of range (%"PRIu64"), ignoring", msec); | |
574 | return 0; | |
575 | } | |
576 | ||
577 | /* Set the reachable time for Neighbor Solicitations. */ | |
578 | r = sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "base_reachable_time_ms", (uint32_t) msec); | |
579 | if (r < 0) | |
580 | log_link_warning_errno(link, r, "Failed to apply neighbor reachable time (%"PRIu64"), ignoring: %m", msec); | |
581 | ||
582 | return 0; | |
583 | } | |
584 | ||
d4c8de21 MM |
585 | static int ndisc_router_process_retransmission_time(Link *link, sd_ndisc_router *rt) { |
586 | usec_t retrans_time, msec; | |
587 | int r; | |
588 | ||
589 | assert(link); | |
590 | assert(link->network); | |
591 | assert(rt); | |
592 | ||
593 | if (!link->network->ipv6_accept_ra_use_retransmission_time) | |
594 | return 0; | |
595 | ||
ffef01ac YW |
596 | /* Ignore the retransmission time field of the RA header if the lifetime is zero. */ |
597 | r = sd_ndisc_router_get_lifetime(rt, NULL); | |
598 | if (r <= 0) | |
599 | return r; | |
600 | ||
d4c8de21 | 601 | r = sd_ndisc_router_get_retransmission_time(rt, &retrans_time); |
b409ac6c YW |
602 | if (r < 0) |
603 | return log_link_warning_errno(link, r, "Failed to get retransmission time from RA: %m"); | |
d4c8de21 MM |
604 | |
605 | /* 0 is the unspecified value and must not be set (see RFC4861, 6.3.4) */ | |
606 | if (!timestamp_is_set(retrans_time)) | |
607 | return 0; | |
608 | ||
609 | msec = DIV_ROUND_UP(retrans_time, USEC_PER_MSEC); | |
610 | if (msec <= 0 || msec > UINT32_MAX) { | |
611 | log_link_debug(link, "Failed to get retransmission time from RA - out of range (%"PRIu64"), ignoring", msec); | |
612 | return 0; | |
613 | } | |
614 | ||
6a8026e8 | 615 | /* Set the retransmission time for Neighbor Solicitations. */ |
d4c8de21 MM |
616 | r = sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "retrans_time_ms", (uint32_t) msec); |
617 | if (r < 0) | |
ffef01ac | 618 | log_link_warning_errno(link, r, "Failed to apply neighbor retransmission time (%"PRIu64"), ignoring: %m", msec); |
d4c8de21 MM |
619 | |
620 | return 0; | |
621 | } | |
622 | ||
b15ed2be MM |
623 | static int ndisc_router_process_hop_limit(Link *link, sd_ndisc_router *rt) { |
624 | uint8_t hop_limit; | |
625 | int r; | |
626 | ||
627 | assert(link); | |
628 | assert(link->network); | |
629 | assert(rt); | |
630 | ||
631 | if (!link->network->ipv6_accept_ra_use_hop_limit) | |
632 | return 0; | |
633 | ||
ffef01ac YW |
634 | /* Ignore the hop limit field of the RA header if the lifetime is zero. */ |
635 | r = sd_ndisc_router_get_lifetime(rt, NULL); | |
636 | if (r <= 0) | |
637 | return r; | |
638 | ||
b15ed2be MM |
639 | r = sd_ndisc_router_get_hop_limit(rt, &hop_limit); |
640 | if (r < 0) | |
641 | return log_link_warning_errno(link, r, "Failed to get hop limit from RA: %m"); | |
642 | ||
643 | /* 0 is the unspecified value and must not be set (see RFC4861, 6.3.4): | |
644 | * | |
645 | * A Router Advertisement field (e.g., Cur Hop Limit, Reachable Time, and Retrans Timer) may contain | |
646 | * a value denoting that it is unspecified. In such cases, the parameter should be ignored and the | |
647 | * host should continue using whatever value it is already using. In particular, a host MUST NOT | |
648 | * interpret the unspecified value as meaning change back to the default value that was in use before | |
649 | * the first Router Advertisement was received. | |
650 | * | |
651 | * If the received Cur Hop Limit value is non-zero, the host SHOULD set | |
652 | * its CurHopLimit variable to the received value.*/ | |
653 | if (hop_limit <= 0) | |
654 | return 0; | |
655 | ||
656 | r = sysctl_write_ip_property_uint32(AF_INET6, link->ifname, "hop_limit", (uint32_t) hop_limit); | |
657 | if (r < 0) | |
658 | log_link_warning_errno(link, r, "Failed to apply hop_limit (%u), ignoring: %m", hop_limit); | |
659 | ||
660 | return 0; | |
661 | } | |
662 | ||
d5017c84 | 663 | static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) { |
6197db53 | 664 | usec_t lifetime_valid_usec, lifetime_preferred_usec; |
d207581f | 665 | _cleanup_set_free_ Set *addresses = NULL; |
151b8ea3 | 666 | struct in6_addr prefix, *a; |
1e7a0e21 LP |
667 | unsigned prefixlen; |
668 | int r; | |
669 | ||
670 | assert(link); | |
fbdda4bb | 671 | assert(link->network); |
1e7a0e21 LP |
672 | assert(rt); |
673 | ||
fbdda4bb YW |
674 | if (!link->network->ipv6_accept_ra_use_autonomous_prefix) |
675 | return 0; | |
676 | ||
151b8ea3 YW |
677 | r = sd_ndisc_router_prefix_get_address(rt, &prefix); |
678 | if (r < 0) | |
2c5bca17 | 679 | return log_link_warning_errno(link, r, "Failed to get prefix address: %m"); |
151b8ea3 | 680 | |
1e7a0e21 | 681 | r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen); |
d5017c84 | 682 | if (r < 0) |
2c5bca17 | 683 | return log_link_warning_errno(link, r, "Failed to get prefix length: %m"); |
1e7a0e21 | 684 | |
868bd1aa | 685 | /* ndisc_generate_addresses() below requires the prefix length <= 64. */ |
151b8ea3 | 686 | if (prefixlen > 64) { |
c71384a9 ZJS |
687 | log_link_debug(link, "Prefix is longer than 64, ignoring autonomous prefix %s.", |
688 | IN6_ADDR_PREFIX_TO_STRING(&prefix, prefixlen)); | |
151b8ea3 YW |
689 | return 0; |
690 | } | |
691 | ||
6197db53 | 692 | r = sd_ndisc_router_prefix_get_valid_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_valid_usec); |
d5017c84 | 693 | if (r < 0) |
2c5bca17 | 694 | return log_link_warning_errno(link, r, "Failed to get prefix valid lifetime: %m"); |
1e7a0e21 | 695 | |
6197db53 | 696 | r = sd_ndisc_router_prefix_get_preferred_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_preferred_usec); |
d5017c84 | 697 | if (r < 0) |
2c5bca17 | 698 | return log_link_warning_errno(link, r, "Failed to get prefix preferred lifetime: %m"); |
3b015d40 | 699 | |
92bdc3ff | 700 | /* The preferred lifetime is never greater than the valid lifetime */ |
6197db53 | 701 | if (lifetime_preferred_usec > lifetime_valid_usec) |
d5017c84 | 702 | return 0; |
92bdc3ff | 703 | |
868bd1aa | 704 | r = ndisc_generate_addresses(link, &prefix, prefixlen, &addresses); |
5f506a55 | 705 | if (r < 0) |
2c5bca17 | 706 | return log_link_warning_errno(link, r, "Failed to generate SLAAC addresses: %m"); |
c24c83dc | 707 | |
90e74a66 | 708 | SET_FOREACH(a, addresses) { |
ebd96906 | 709 | _cleanup_(address_unrefp) Address *address = NULL; |
13e8a49a | 710 | |
76c5a0f2 YW |
711 | r = address_new(&address); |
712 | if (r < 0) | |
713 | return log_oom(); | |
714 | ||
715 | address->family = AF_INET6; | |
25db3aea | 716 | address->in_addr.in6 = *a; |
76c5a0f2 YW |
717 | address->prefixlen = prefixlen; |
718 | address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR; | |
16bc8635 YW |
719 | address->lifetime_valid_usec = lifetime_valid_usec; |
720 | address->lifetime_preferred_usec = lifetime_preferred_usec; | |
fe841414 | 721 | |
479d3e19 YW |
722 | /* draft-ietf-6man-slaac-renum-07 section 4.2 |
723 | * https://datatracker.ietf.org/doc/html/draft-ietf-6man-slaac-renum-07#section-4.2 | |
724 | * | |
725 | * If the advertised prefix is equal to the prefix of an address configured by stateless | |
726 | * autoconfiguration in the list, the valid lifetime and the preferred lifetime of the | |
727 | * address should be updated by processing the Valid Lifetime and the Preferred Lifetime | |
728 | * (respectively) in the received advertisement. */ | |
729 | if (lifetime_valid_usec == 0) { | |
730 | r = address_remove_and_cancel(address, link); | |
731 | if (r < 0) | |
732 | return log_link_warning_errno(link, r, "Could not remove SLAAC address: %m"); | |
733 | } else { | |
734 | r = ndisc_request_address(address, link, rt); | |
735 | if (r < 0) | |
736 | return log_link_warning_errno(link, r, "Could not request SLAAC address: %m"); | |
737 | } | |
3b015d40 | 738 | } |
d5017c84 YW |
739 | |
740 | return 0; | |
3b015d40 TG |
741 | } |
742 | ||
d5017c84 | 743 | static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { |
74c301b9 | 744 | _cleanup_(route_unrefp) Route *route = NULL; |
f44eebd1 | 745 | unsigned prefixlen, preference; |
6197db53 | 746 | usec_t lifetime_usec; |
167c7ae5 | 747 | struct in6_addr prefix; |
3b015d40 TG |
748 | int r; |
749 | ||
3b015d40 | 750 | assert(link); |
fbdda4bb | 751 | assert(link->network); |
1e7a0e21 | 752 | assert(rt); |
3b015d40 | 753 | |
fbdda4bb YW |
754 | if (!link->network->ipv6_accept_ra_use_onlink_prefix) |
755 | return 0; | |
756 | ||
6197db53 | 757 | r = sd_ndisc_router_prefix_get_valid_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); |
09845af5 | 758 | if (r < 0) |
2c5bca17 | 759 | return log_link_warning_errno(link, r, "Failed to get prefix lifetime: %m"); |
09845af5 | 760 | |
167c7ae5 YW |
761 | r = sd_ndisc_router_prefix_get_address(rt, &prefix); |
762 | if (r < 0) | |
2c5bca17 | 763 | return log_link_warning_errno(link, r, "Failed to get prefix address: %m"); |
167c7ae5 | 764 | |
1e7a0e21 | 765 | r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen); |
d5017c84 | 766 | if (r < 0) |
2c5bca17 | 767 | return log_link_warning_errno(link, r, "Failed to get prefix length: %m"); |
1e7a0e21 | 768 | |
f44eebd1 SS |
769 | /* Prefix Information option does not have preference, hence we use the 'main' preference here */ |
770 | r = sd_ndisc_router_get_preference(rt, &preference); | |
771 | if (r < 0) | |
a8b0b848 | 772 | return log_link_warning_errno(link, r, "Failed to get router preference from RA: %m"); |
f44eebd1 | 773 | |
3b015d40 | 774 | r = route_new(&route); |
d5017c84 | 775 | if (r < 0) |
13e8a49a | 776 | return log_oom(); |
3b015d40 | 777 | |
3b015d40 | 778 | route->family = AF_INET6; |
167c7ae5 | 779 | route->dst.in6 = prefix; |
3b015d40 | 780 | route->dst_prefixlen = prefixlen; |
f44eebd1 | 781 | route->pref = preference; |
6197db53 | 782 | route->lifetime_usec = lifetime_usec; |
3b015d40 | 783 | |
e217da13 | 784 | r = ndisc_request_route(route, link, rt); |
13e8a49a | 785 | if (r < 0) |
2c5bca17 | 786 | return log_link_warning_errno(link, r, "Could not request prefix route: %m"); |
d5017c84 YW |
787 | |
788 | return 0; | |
3b015d40 TG |
789 | } |
790 | ||
155d7a2c YW |
791 | static int ndisc_router_drop_onlink_prefix(Link *link, sd_ndisc_router *rt) { |
792 | _cleanup_(route_unrefp) Route *route = NULL; | |
479d3e19 | 793 | unsigned prefixlen; |
155d7a2c YW |
794 | struct in6_addr prefix; |
795 | usec_t lifetime_usec; | |
796 | int r; | |
797 | ||
798 | assert(link); | |
799 | assert(link->network); | |
800 | assert(rt); | |
801 | ||
802 | /* RFC 4861 section 6.3.4. | |
803 | * Note, however, that a Prefix Information option with the on-link flag set to zero conveys no | |
804 | * information concerning on-link determination and MUST NOT be interpreted to mean that addresses | |
805 | * covered by the prefix are off-link. The only way to cancel a previous on-link indication is to | |
806 | * advertise that prefix with the L-bit set and the Lifetime set to zero. */ | |
807 | ||
808 | if (!link->network->ipv6_accept_ra_use_onlink_prefix) | |
809 | return 0; | |
810 | ||
811 | r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_usec); | |
812 | if (r < 0) | |
813 | return log_link_warning_errno(link, r, "Failed to get prefix lifetime: %m"); | |
814 | ||
815 | if (lifetime_usec != 0) | |
816 | return 0; | |
817 | ||
818 | r = sd_ndisc_router_prefix_get_address(rt, &prefix); | |
819 | if (r < 0) | |
820 | return log_link_warning_errno(link, r, "Failed to get prefix address: %m"); | |
821 | ||
822 | r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen); | |
823 | if (r < 0) | |
824 | return log_link_warning_errno(link, r, "Failed to get prefix length: %m"); | |
825 | ||
155d7a2c YW |
826 | r = route_new(&route); |
827 | if (r < 0) | |
828 | return log_oom(); | |
829 | ||
830 | route->family = AF_INET6; | |
831 | route->dst.in6 = prefix; | |
832 | route->dst_prefixlen = prefixlen; | |
155d7a2c | 833 | |
479d3e19 | 834 | r = ndisc_remove_route(route, link); |
155d7a2c YW |
835 | if (r < 0) |
836 | return log_link_warning_errno(link, r, "Could not remove prefix route: %m"); | |
837 | ||
838 | return 0; | |
839 | } | |
840 | ||
fbdda4bb YW |
841 | static int ndisc_router_process_prefix(Link *link, sd_ndisc_router *rt) { |
842 | unsigned prefixlen; | |
843 | struct in6_addr a; | |
844 | uint8_t flags; | |
845 | int r; | |
846 | ||
847 | assert(link); | |
848 | assert(link->network); | |
849 | assert(rt); | |
850 | ||
851 | r = sd_ndisc_router_prefix_get_address(rt, &a); | |
852 | if (r < 0) | |
2c5bca17 | 853 | return log_link_warning_errno(link, r, "Failed to get prefix address: %m"); |
fbdda4bb | 854 | |
a115c60e YW |
855 | /* RFC 4861 Section 4.6.2: |
856 | * A router SHOULD NOT send a prefix option for the link-local prefix and a host SHOULD ignore such | |
857 | * a prefix option. */ | |
858 | if (in6_addr_is_link_local(&a)) { | |
479d3e19 | 859 | log_link_debug(link, "Received link-local prefix, ignoring prefix."); |
a115c60e YW |
860 | return 0; |
861 | } | |
862 | ||
fbdda4bb YW |
863 | r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen); |
864 | if (r < 0) | |
2c5bca17 | 865 | return log_link_warning_errno(link, r, "Failed to get prefix length: %m"); |
fbdda4bb YW |
866 | |
867 | if (in6_prefix_is_filtered(&a, prefixlen, link->network->ndisc_allow_listed_prefix, link->network->ndisc_deny_listed_prefix)) { | |
c71384a9 ZJS |
868 | if (DEBUG_LOGGING) |
869 | log_link_debug(link, "Prefix '%s' is %s, ignoring", | |
870 | !set_isempty(link->network->ndisc_allow_listed_prefix) ? "not in allow list" | |
871 | : "in deny list", | |
872 | IN6_ADDR_PREFIX_TO_STRING(&a, prefixlen)); | |
fbdda4bb YW |
873 | return 0; |
874 | } | |
875 | ||
876 | r = sd_ndisc_router_prefix_get_flags(rt, &flags); | |
877 | if (r < 0) | |
2c5bca17 | 878 | return log_link_warning_errno(link, r, "Failed to get RA prefix flags: %m"); |
fbdda4bb | 879 | |
155d7a2c | 880 | if (FLAGS_SET(flags, ND_OPT_PI_FLAG_ONLINK)) |
fbdda4bb | 881 | r = ndisc_router_process_onlink_prefix(link, rt); |
155d7a2c YW |
882 | else |
883 | r = ndisc_router_drop_onlink_prefix(link, rt); | |
884 | if (r < 0) | |
885 | return r; | |
fbdda4bb YW |
886 | |
887 | if (FLAGS_SET(flags, ND_OPT_PI_FLAG_AUTO)) { | |
888 | r = ndisc_router_process_autonomous_prefix(link, rt); | |
889 | if (r < 0) | |
890 | return r; | |
891 | } | |
892 | ||
893 | return 0; | |
894 | } | |
895 | ||
d5017c84 | 896 | static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { |
74c301b9 | 897 | _cleanup_(route_unrefp) Route *route = NULL; |
1e7a0e21 | 898 | unsigned preference, prefixlen; |
91fc5135 | 899 | struct in6_addr gateway, dst; |
6197db53 | 900 | usec_t lifetime_usec; |
7a695d8e | 901 | int r; |
a13c50e7 TG |
902 | |
903 | assert(link); | |
a13c50e7 | 904 | |
610c0db1 YW |
905 | if (!link->network->ipv6_accept_ra_use_route_prefix) |
906 | return 0; | |
907 | ||
6197db53 | 908 | r = sd_ndisc_router_route_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); |
d5017c84 | 909 | if (r < 0) |
2c5bca17 | 910 | return log_link_warning_errno(link, r, "Failed to get route lifetime from RA: %m"); |
d5017c84 | 911 | |
b8ce3b44 | 912 | r = sd_ndisc_router_route_get_address(rt, &dst); |
d5017c84 | 913 | if (r < 0) |
2c5bca17 | 914 | return log_link_warning_errno(link, r, "Failed to get route destination address: %m"); |
3b015d40 | 915 | |
c995fa02 YW |
916 | r = sd_ndisc_router_route_get_prefixlen(rt, &prefixlen); |
917 | if (r < 0) | |
2c5bca17 | 918 | return log_link_warning_errno(link, r, "Failed to get route prefix length: %m"); |
c995fa02 | 919 | |
80bfc3b9 YW |
920 | if (in6_addr_is_null(&dst) && prefixlen == 0) { |
921 | log_link_debug(link, "Route prefix is ::/0, ignoring"); | |
922 | return 0; | |
923 | } | |
924 | ||
c71384a9 ZJS |
925 | if (in6_prefix_is_filtered(&dst, prefixlen, |
926 | link->network->ndisc_allow_listed_route_prefix, | |
927 | link->network->ndisc_deny_listed_route_prefix)) { | |
16c89e64 | 928 | |
c71384a9 ZJS |
929 | if (DEBUG_LOGGING) |
930 | log_link_debug(link, "Route prefix %s is %s, ignoring", | |
931 | !set_isempty(link->network->ndisc_allow_listed_route_prefix) ? "not in allow list" | |
932 | : "in deny list", | |
933 | IN6_ADDR_PREFIX_TO_STRING(&dst, prefixlen)); | |
16c89e64 DP |
934 | return 0; |
935 | } | |
936 | ||
b8ce3b44 | 937 | r = sd_ndisc_router_get_address(rt, &gateway); |
19e334bd | 938 | if (r < 0) |
2c5bca17 | 939 | return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m"); |
19e334bd | 940 | |
5d003031 | 941 | if (link_get_ipv6_address(link, &gateway, 0, NULL) >= 0) { |
84dbb3fd ZJS |
942 | if (DEBUG_LOGGING) |
943 | log_link_debug(link, "Advertised route gateway %s is local to the link, ignoring route", | |
944 | IN6_ADDR_TO_STRING(&gateway)); | |
22101916 DP |
945 | return 0; |
946 | } | |
947 | ||
1e7a0e21 | 948 | r = sd_ndisc_router_route_get_preference(rt, &preference); |
4b68f708 | 949 | if (r == -EOPNOTSUPP) { |
3912d49d SS |
950 | log_link_debug_errno(link, r, "Received route prefix with unsupported preference, ignoring: %m"); |
951 | return 0; | |
952 | } | |
d5017c84 | 953 | if (r < 0) |
34acdf90 | 954 | return log_link_warning_errno(link, r, "Failed to get router preference from RA: %m"); |
1e7a0e21 | 955 | |
3b015d40 | 956 | r = route_new(&route); |
d5017c84 | 957 | if (r < 0) |
13e8a49a | 958 | return log_oom(); |
3b015d40 | 959 | |
3b015d40 | 960 | route->family = AF_INET6; |
1e7a0e21 | 961 | route->pref = preference; |
054b8c28 YW |
962 | route->nexthop.gw.in6 = gateway; |
963 | route->nexthop.family = AF_INET6; | |
b8ce3b44 | 964 | route->dst.in6 = dst; |
1e7a0e21 | 965 | route->dst_prefixlen = prefixlen; |
6197db53 | 966 | route->lifetime_usec = lifetime_usec; |
3b015d40 | 967 | |
e217da13 | 968 | r = ndisc_request_route(route, link, rt); |
13e8a49a | 969 | if (r < 0) |
2c5bca17 | 970 | return log_link_warning_errno(link, r, "Could not request additional route: %m"); |
d5017c84 YW |
971 | |
972 | return 0; | |
9d96e6c3 | 973 | } |
a13c50e7 | 974 | |
7a08d314 | 975 | static void ndisc_rdnss_hash_func(const NDiscRDNSS *x, struct siphash *state) { |
c01a5c05 | 976 | siphash24_compress_typesafe(x->address, state); |
1e7a0e21 LP |
977 | } |
978 | ||
7a08d314 | 979 | static int ndisc_rdnss_compare_func(const NDiscRDNSS *a, const NDiscRDNSS *b) { |
1e7a0e21 LP |
980 | return memcmp(&a->address, &b->address, sizeof(a->address)); |
981 | } | |
982 | ||
b0b97766 YW |
983 | DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( |
984 | ndisc_rdnss_hash_ops, | |
985 | NDiscRDNSS, | |
986 | ndisc_rdnss_hash_func, | |
987 | ndisc_rdnss_compare_func, | |
988 | free); | |
1e7a0e21 | 989 | |
d5017c84 | 990 | static int ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) { |
6197db53 | 991 | usec_t lifetime_usec; |
1e7a0e21 | 992 | const struct in6_addr *a; |
50550722 | 993 | struct in6_addr router; |
8aba7b83 | 994 | bool updated = false, logged_about_too_many = false; |
13e8a49a | 995 | int n, r; |
1e7a0e21 LP |
996 | |
997 | assert(link); | |
ad0b2df6 | 998 | assert(link->network); |
1e7a0e21 LP |
999 | assert(rt); |
1000 | ||
ad0b2df6 YW |
1001 | if (!link->network->ipv6_accept_ra_use_dns) |
1002 | return 0; | |
1003 | ||
50550722 YW |
1004 | r = sd_ndisc_router_get_address(rt, &router); |
1005 | if (r < 0) | |
2c5bca17 | 1006 | return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); |
50550722 | 1007 | |
6197db53 | 1008 | r = sd_ndisc_router_rdnss_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); |
d5017c84 | 1009 | if (r < 0) |
2c5bca17 | 1010 | return log_link_warning_errno(link, r, "Failed to get RDNSS lifetime: %m"); |
1e7a0e21 LP |
1011 | |
1012 | n = sd_ndisc_router_rdnss_get_addresses(rt, &a); | |
d5017c84 | 1013 | if (n < 0) |
2c5bca17 | 1014 | return log_link_warning_errno(link, n, "Failed to get RDNSS addresses: %m"); |
1e7a0e21 | 1015 | |
b0b97766 YW |
1016 | for (int j = 0; j < n; j++) { |
1017 | _cleanup_free_ NDiscRDNSS *x = NULL; | |
3b6a3bde | 1018 | NDiscRDNSS *rdnss, d = { |
b0b97766 YW |
1019 | .address = a[j], |
1020 | }; | |
1e7a0e21 | 1021 | |
af2aea8b YW |
1022 | if (lifetime_usec == 0) { |
1023 | /* The entry is outdated. */ | |
1024 | free(set_remove(link->ndisc_rdnss, &d)); | |
1025 | updated = true; | |
1026 | continue; | |
1027 | } | |
1028 | ||
b0b97766 YW |
1029 | rdnss = set_get(link->ndisc_rdnss, &d); |
1030 | if (rdnss) { | |
50550722 | 1031 | rdnss->router = router; |
03ccc4b4 | 1032 | rdnss->lifetime_usec = lifetime_usec; |
1e7a0e21 LP |
1033 | continue; |
1034 | } | |
1035 | ||
8aba7b83 YW |
1036 | if (set_size(link->ndisc_rdnss) >= NDISC_RDNSS_MAX) { |
1037 | if (!logged_about_too_many) | |
1038 | log_link_warning(link, "Too many RDNSS records per link. Only first %u records will be used.", NDISC_RDNSS_MAX); | |
1039 | logged_about_too_many = true; | |
1040 | continue; | |
1041 | } | |
1042 | ||
d5017c84 YW |
1043 | x = new(NDiscRDNSS, 1); |
1044 | if (!x) | |
1045 | return log_oom(); | |
1e7a0e21 | 1046 | |
d5017c84 | 1047 | *x = (NDiscRDNSS) { |
b0b97766 | 1048 | .address = a[j], |
50550722 | 1049 | .router = router, |
03ccc4b4 | 1050 | .lifetime_usec = lifetime_usec, |
d5017c84 | 1051 | }; |
1e7a0e21 | 1052 | |
35e601d4 | 1053 | r = set_ensure_consume(&link->ndisc_rdnss, &ndisc_rdnss_hash_ops, TAKE_PTR(x)); |
d5017c84 YW |
1054 | if (r < 0) |
1055 | return log_oom(); | |
1e7a0e21 | 1056 | assert(r > 0); |
9092113d YW |
1057 | |
1058 | updated = true; | |
1e7a0e21 | 1059 | } |
d5017c84 | 1060 | |
9092113d YW |
1061 | if (updated) |
1062 | link_dirty(link); | |
1063 | ||
d5017c84 | 1064 | return 0; |
1e7a0e21 LP |
1065 | } |
1066 | ||
7a08d314 | 1067 | static void ndisc_dnssl_hash_func(const NDiscDNSSL *x, struct siphash *state) { |
f281fc1e | 1068 | siphash24_compress_string(NDISC_DNSSL_DOMAIN(x), state); |
1e7a0e21 LP |
1069 | } |
1070 | ||
7a08d314 | 1071 | static int ndisc_dnssl_compare_func(const NDiscDNSSL *a, const NDiscDNSSL *b) { |
1e7a0e21 LP |
1072 | return strcmp(NDISC_DNSSL_DOMAIN(a), NDISC_DNSSL_DOMAIN(b)); |
1073 | } | |
1074 | ||
b0b97766 YW |
1075 | DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( |
1076 | ndisc_dnssl_hash_ops, | |
1077 | NDiscDNSSL, | |
1078 | ndisc_dnssl_hash_func, | |
1079 | ndisc_dnssl_compare_func, | |
1080 | free); | |
1e7a0e21 | 1081 | |
13e8a49a | 1082 | static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) { |
1e7a0e21 | 1083 | _cleanup_strv_free_ char **l = NULL; |
6197db53 | 1084 | usec_t lifetime_usec; |
50550722 | 1085 | struct in6_addr router; |
8aba7b83 | 1086 | bool updated = false, logged_about_too_many = false; |
1e7a0e21 LP |
1087 | int r; |
1088 | ||
1089 | assert(link); | |
ad0b2df6 | 1090 | assert(link->network); |
1e7a0e21 LP |
1091 | assert(rt); |
1092 | ||
ad0b2df6 YW |
1093 | if (link->network->ipv6_accept_ra_use_domains == DHCP_USE_DOMAINS_NO) |
1094 | return 0; | |
1095 | ||
50550722 YW |
1096 | r = sd_ndisc_router_get_address(rt, &router); |
1097 | if (r < 0) | |
2c5bca17 | 1098 | return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); |
50550722 | 1099 | |
6197db53 | 1100 | r = sd_ndisc_router_dnssl_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); |
13e8a49a | 1101 | if (r < 0) |
2c5bca17 | 1102 | return log_link_warning_errno(link, r, "Failed to get DNSSL lifetime: %m"); |
1e7a0e21 LP |
1103 | |
1104 | r = sd_ndisc_router_dnssl_get_domains(rt, &l); | |
13e8a49a | 1105 | if (r < 0) |
2c5bca17 | 1106 | return log_link_warning_errno(link, r, "Failed to get DNSSL addresses: %m"); |
1e7a0e21 | 1107 | |
b0b97766 YW |
1108 | STRV_FOREACH(j, l) { |
1109 | _cleanup_free_ NDiscDNSSL *s = NULL; | |
3b6a3bde | 1110 | NDiscDNSSL *dnssl; |
1e7a0e21 | 1111 | |
b0b97766 YW |
1112 | s = malloc0(ALIGN(sizeof(NDiscDNSSL)) + strlen(*j) + 1); |
1113 | if (!s) | |
1114 | return log_oom(); | |
1e7a0e21 | 1115 | |
b0b97766 | 1116 | strcpy(NDISC_DNSSL_DOMAIN(s), *j); |
1e7a0e21 | 1117 | |
af2aea8b YW |
1118 | if (lifetime_usec == 0) { |
1119 | /* The entry is outdated. */ | |
1120 | free(set_remove(link->ndisc_dnssl, s)); | |
1121 | updated = true; | |
1122 | continue; | |
1123 | } | |
1124 | ||
b0b97766 YW |
1125 | dnssl = set_get(link->ndisc_dnssl, s); |
1126 | if (dnssl) { | |
50550722 | 1127 | dnssl->router = router; |
03ccc4b4 | 1128 | dnssl->lifetime_usec = lifetime_usec; |
1e7a0e21 LP |
1129 | continue; |
1130 | } | |
1131 | ||
8aba7b83 YW |
1132 | if (set_size(link->ndisc_dnssl) >= NDISC_DNSSL_MAX) { |
1133 | if (!logged_about_too_many) | |
1134 | log_link_warning(link, "Too many DNSSL records per link. Only first %u records will be used.", NDISC_DNSSL_MAX); | |
1135 | logged_about_too_many = true; | |
1136 | continue; | |
1137 | } | |
1138 | ||
50550722 | 1139 | s->router = router; |
03ccc4b4 | 1140 | s->lifetime_usec = lifetime_usec; |
1e7a0e21 | 1141 | |
35e601d4 | 1142 | r = set_ensure_consume(&link->ndisc_dnssl, &ndisc_dnssl_hash_ops, TAKE_PTR(s)); |
13e8a49a YW |
1143 | if (r < 0) |
1144 | return log_oom(); | |
1e7a0e21 | 1145 | assert(r > 0); |
9092113d YW |
1146 | |
1147 | updated = true; | |
1e7a0e21 | 1148 | } |
13e8a49a | 1149 | |
9092113d YW |
1150 | if (updated) |
1151 | link_dirty(link); | |
1152 | ||
13e8a49a | 1153 | return 0; |
1e7a0e21 LP |
1154 | } |
1155 | ||
64de00c4 YW |
1156 | static NDiscCaptivePortal* ndisc_captive_portal_free(NDiscCaptivePortal *x) { |
1157 | if (!x) | |
1158 | return NULL; | |
1159 | ||
1160 | free(x->captive_portal); | |
1161 | return mfree(x); | |
1162 | } | |
1163 | ||
1164 | DEFINE_TRIVIAL_CLEANUP_FUNC(NDiscCaptivePortal*, ndisc_captive_portal_free); | |
1165 | ||
1166 | static void ndisc_captive_portal_hash_func(const NDiscCaptivePortal *x, struct siphash *state) { | |
1167 | assert(x); | |
1168 | siphash24_compress_string(x->captive_portal, state); | |
1169 | } | |
1170 | ||
1171 | static int ndisc_captive_portal_compare_func(const NDiscCaptivePortal *a, const NDiscCaptivePortal *b) { | |
1172 | assert(a); | |
1173 | assert(b); | |
1174 | return strcmp_ptr(a->captive_portal, b->captive_portal); | |
1175 | } | |
1176 | ||
1177 | DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( | |
1178 | ndisc_captive_portal_hash_ops, | |
1179 | NDiscCaptivePortal, | |
1180 | ndisc_captive_portal_hash_func, | |
1181 | ndisc_captive_portal_compare_func, | |
535134bc | 1182 | ndisc_captive_portal_free); |
64de00c4 YW |
1183 | |
1184 | static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt) { | |
1185 | _cleanup_(ndisc_captive_portal_freep) NDiscCaptivePortal *new_entry = NULL; | |
1186 | _cleanup_free_ char *captive_portal = NULL; | |
6197db53 | 1187 | usec_t lifetime_usec; |
64de00c4 YW |
1188 | NDiscCaptivePortal *exist; |
1189 | struct in6_addr router; | |
64de00c4 YW |
1190 | const char *uri; |
1191 | size_t len; | |
1192 | int r; | |
1193 | ||
1194 | assert(link); | |
1195 | assert(link->network); | |
1196 | assert(rt); | |
1197 | ||
1198 | if (!link->network->ipv6_accept_ra_use_captive_portal) | |
1199 | return 0; | |
1200 | ||
1201 | r = sd_ndisc_router_get_address(rt, &router); | |
1202 | if (r < 0) | |
1203 | return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); | |
1204 | ||
218f3738 YW |
1205 | /* RFC 4861 section 4.2. states that the lifetime in the message header should be used only for the |
1206 | * default gateway, but the captive portal option does not have a lifetime field, hence, we use the | |
1207 | * main lifetime for the portal. */ | |
6197db53 | 1208 | r = sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); |
64de00c4 YW |
1209 | if (r < 0) |
1210 | return log_link_warning_errno(link, r, "Failed to get lifetime of RA message: %m"); | |
1211 | ||
64de00c4 YW |
1212 | r = sd_ndisc_router_captive_portal_get_uri(rt, &uri, &len); |
1213 | if (r < 0) | |
1214 | return log_link_warning_errno(link, r, "Failed to get captive portal from RA: %m"); | |
1215 | ||
1216 | if (len == 0) | |
6df82d12 | 1217 | return log_link_warning_errno(link, SYNTHETIC_ERRNO(EBADMSG), "Received empty captive portal, ignoring."); |
64de00c4 YW |
1218 | |
1219 | r = make_cstring(uri, len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &captive_portal); | |
1220 | if (r < 0) | |
1221 | return log_link_warning_errno(link, r, "Failed to convert captive portal URI: %m"); | |
1222 | ||
1223 | if (!in_charset(captive_portal, URI_VALID)) | |
6df82d12 | 1224 | return log_link_warning_errno(link, SYNTHETIC_ERRNO(EBADMSG), "Received invalid captive portal, ignoring."); |
64de00c4 | 1225 | |
218f3738 YW |
1226 | if (lifetime_usec == 0) { |
1227 | /* Drop the portal with zero lifetime. */ | |
1228 | ndisc_captive_portal_free(set_remove(link->ndisc_captive_portals, | |
1229 | &(NDiscCaptivePortal) { | |
1230 | .captive_portal = captive_portal, | |
1231 | })); | |
1232 | return 0; | |
1233 | } | |
1234 | ||
1235 | exist = set_get(link->ndisc_captive_portals, | |
1236 | &(NDiscCaptivePortal) { | |
1237 | .captive_portal = captive_portal, | |
1238 | }); | |
64de00c4 YW |
1239 | if (exist) { |
1240 | /* update existing entry */ | |
1241 | exist->router = router; | |
1242 | exist->lifetime_usec = lifetime_usec; | |
6df82d12 | 1243 | return 1; |
64de00c4 YW |
1244 | } |
1245 | ||
bf943a9d YW |
1246 | if (set_size(link->ndisc_captive_portals) >= NDISC_CAPTIVE_PORTAL_MAX) { |
1247 | NDiscCaptivePortal *c, *target = NULL; | |
1248 | ||
1249 | /* Find the portal who has the minimal lifetime and drop it to store new one. */ | |
1250 | SET_FOREACH(c, link->ndisc_captive_portals) | |
1251 | if (!target || c->lifetime_usec < target->lifetime_usec) | |
1252 | target = c; | |
1253 | ||
1254 | assert(target); | |
1255 | assert(set_remove(link->ndisc_captive_portals, target) == target); | |
1256 | ndisc_captive_portal_free(target); | |
1257 | } | |
1258 | ||
64de00c4 YW |
1259 | new_entry = new(NDiscCaptivePortal, 1); |
1260 | if (!new_entry) | |
1261 | return log_oom(); | |
1262 | ||
1263 | *new_entry = (NDiscCaptivePortal) { | |
1264 | .router = router, | |
1265 | .lifetime_usec = lifetime_usec, | |
1266 | .captive_portal = TAKE_PTR(captive_portal), | |
1267 | }; | |
1268 | ||
1269 | r = set_ensure_put(&link->ndisc_captive_portals, &ndisc_captive_portal_hash_ops, new_entry); | |
1270 | if (r < 0) | |
1271 | return log_oom(); | |
1272 | assert(r > 0); | |
1273 | TAKE_PTR(new_entry); | |
1274 | ||
1275 | link_dirty(link); | |
6df82d12 | 1276 | return 1; |
64de00c4 YW |
1277 | } |
1278 | ||
6e8f5e4c SS |
1279 | static void ndisc_pref64_hash_func(const NDiscPREF64 *x, struct siphash *state) { |
1280 | assert(x); | |
1281 | ||
c01a5c05 YW |
1282 | siphash24_compress_typesafe(x->prefix_len, state); |
1283 | siphash24_compress_typesafe(x->prefix, state); | |
6e8f5e4c SS |
1284 | } |
1285 | ||
1286 | static int ndisc_pref64_compare_func(const NDiscPREF64 *a, const NDiscPREF64 *b) { | |
1287 | int r; | |
1288 | ||
1289 | assert(a); | |
1290 | assert(b); | |
1291 | ||
1292 | r = CMP(a->prefix_len, b->prefix_len); | |
1293 | if (r != 0) | |
1294 | return r; | |
1295 | ||
1296 | return memcmp(&a->prefix, &b->prefix, sizeof(a->prefix)); | |
1297 | } | |
1298 | ||
1299 | DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( | |
1300 | ndisc_pref64_hash_ops, | |
1301 | NDiscPREF64, | |
1302 | ndisc_pref64_hash_func, | |
1303 | ndisc_pref64_compare_func, | |
1304 | mfree); | |
1305 | ||
1306 | static int ndisc_router_process_pref64(Link *link, sd_ndisc_router *rt) { | |
1307 | _cleanup_free_ NDiscPREF64 *new_entry = NULL; | |
6197db53 | 1308 | usec_t lifetime_usec; |
6e8f5e4c | 1309 | struct in6_addr a, router; |
6e8f5e4c SS |
1310 | unsigned prefix_len; |
1311 | NDiscPREF64 *exist; | |
1312 | int r; | |
1313 | ||
1314 | assert(link); | |
1315 | assert(link->network); | |
1316 | assert(rt); | |
1317 | ||
1318 | if (!link->network->ipv6_accept_ra_use_pref64) | |
1319 | return 0; | |
1320 | ||
1321 | r = sd_ndisc_router_get_address(rt, &router); | |
1322 | if (r < 0) | |
1323 | return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); | |
1324 | ||
1325 | r = sd_ndisc_router_prefix64_get_prefix(rt, &a); | |
1326 | if (r < 0) | |
1327 | return log_link_warning_errno(link, r, "Failed to get pref64 prefix: %m"); | |
1328 | ||
1329 | r = sd_ndisc_router_prefix64_get_prefixlen(rt, &prefix_len); | |
1330 | if (r < 0) | |
1331 | return log_link_warning_errno(link, r, "Failed to get pref64 prefix length: %m"); | |
1332 | ||
6197db53 | 1333 | r = sd_ndisc_router_prefix64_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); |
6e8f5e4c SS |
1334 | if (r < 0) |
1335 | return log_link_warning_errno(link, r, "Failed to get pref64 prefix lifetime: %m"); | |
1336 | ||
6e8f5e4c SS |
1337 | if (lifetime_usec == 0) { |
1338 | free(set_remove(link->ndisc_pref64, | |
1339 | &(NDiscPREF64) { | |
1340 | .prefix = a, | |
1341 | .prefix_len = prefix_len | |
1342 | })); | |
1343 | return 0; | |
1344 | } | |
1345 | ||
1346 | exist = set_get(link->ndisc_pref64, | |
1347 | &(NDiscPREF64) { | |
1348 | .prefix = a, | |
1349 | .prefix_len = prefix_len | |
1350 | }); | |
1351 | if (exist) { | |
1352 | /* update existing entry */ | |
1353 | exist->router = router; | |
1354 | exist->lifetime_usec = lifetime_usec; | |
1355 | return 0; | |
1356 | } | |
1357 | ||
4df16cd0 YW |
1358 | if (set_size(link->ndisc_pref64) >= NDISC_PREF64_MAX) { |
1359 | log_link_debug(link, "Too many PREF64 records received. Only first %u records will be used.", NDISC_PREF64_MAX); | |
1360 | return 0; | |
1361 | } | |
1362 | ||
6e8f5e4c SS |
1363 | new_entry = new(NDiscPREF64, 1); |
1364 | if (!new_entry) | |
1365 | return log_oom(); | |
1366 | ||
1367 | *new_entry = (NDiscPREF64) { | |
1368 | .router = router, | |
1369 | .lifetime_usec = lifetime_usec, | |
1370 | .prefix = a, | |
1371 | .prefix_len = prefix_len, | |
1372 | }; | |
1373 | ||
1374 | r = set_ensure_put(&link->ndisc_pref64, &ndisc_pref64_hash_ops, new_entry); | |
1375 | if (r < 0) | |
1376 | return log_oom(); | |
1377 | ||
1378 | assert(r > 0); | |
1379 | TAKE_PTR(new_entry); | |
1380 | ||
1381 | return 0; | |
1382 | } | |
1383 | ||
e8c9b5b0 | 1384 | static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) { |
6df82d12 | 1385 | size_t n_captive_portal = 0; |
fbdda4bb YW |
1386 | int r; |
1387 | ||
1e7a0e21 | 1388 | assert(link); |
55d3fdcf | 1389 | assert(link->network); |
1e7a0e21 LP |
1390 | assert(rt); |
1391 | ||
fbdda4bb | 1392 | for (r = sd_ndisc_router_option_rewind(rt); ; r = sd_ndisc_router_option_next(rt)) { |
1e7a0e21 LP |
1393 | uint8_t type; |
1394 | ||
e8c9b5b0 | 1395 | if (r < 0) |
2c5bca17 | 1396 | return log_link_warning_errno(link, r, "Failed to iterate through options: %m"); |
1e7a0e21 | 1397 | if (r == 0) /* EOF */ |
13e8a49a | 1398 | return 0; |
1e7a0e21 LP |
1399 | |
1400 | r = sd_ndisc_router_option_get_type(rt, &type); | |
e8c9b5b0 | 1401 | if (r < 0) |
2c5bca17 | 1402 | return log_link_warning_errno(link, r, "Failed to get RA option type: %m"); |
1e7a0e21 LP |
1403 | |
1404 | switch (type) { | |
fbdda4bb YW |
1405 | case SD_NDISC_OPTION_PREFIX_INFORMATION: |
1406 | r = ndisc_router_process_prefix(link, rt); | |
1e7a0e21 | 1407 | break; |
1e7a0e21 LP |
1408 | |
1409 | case SD_NDISC_OPTION_ROUTE_INFORMATION: | |
13e8a49a | 1410 | r = ndisc_router_process_route(link, rt); |
1e7a0e21 LP |
1411 | break; |
1412 | ||
1413 | case SD_NDISC_OPTION_RDNSS: | |
ad0b2df6 | 1414 | r = ndisc_router_process_rdnss(link, rt); |
1e7a0e21 LP |
1415 | break; |
1416 | ||
1417 | case SD_NDISC_OPTION_DNSSL: | |
ad0b2df6 | 1418 | r = ndisc_router_process_dnssl(link, rt); |
1e7a0e21 | 1419 | break; |
9747955d | 1420 | case SD_NDISC_OPTION_CAPTIVE_PORTAL: |
6df82d12 YW |
1421 | if (n_captive_portal > 0) { |
1422 | if (n_captive_portal == 1) | |
1423 | log_link_notice(link, "Received RA with multiple captive portals, only using the first one."); | |
1424 | ||
1425 | n_captive_portal++; | |
1426 | continue; | |
1427 | } | |
9747955d | 1428 | r = ndisc_router_process_captive_portal(link, rt); |
6df82d12 YW |
1429 | if (r > 0) |
1430 | n_captive_portal++; | |
9747955d | 1431 | break; |
6e8f5e4c SS |
1432 | case SD_NDISC_OPTION_PREF64: |
1433 | r = ndisc_router_process_pref64(link, rt); | |
1434 | break; | |
1e7a0e21 | 1435 | } |
5908d864 YW |
1436 | if (r < 0 && r != -EBADMSG) |
1437 | return r; | |
1e7a0e21 LP |
1438 | } |
1439 | } | |
1440 | ||
2b4fca55 | 1441 | static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { |
94e6d37c YW |
1442 | bool updated = false; |
1443 | NDiscDNSSL *dnssl; | |
1444 | NDiscRDNSS *rdnss; | |
64de00c4 | 1445 | NDiscCaptivePortal *cp; |
fabea9c0 | 1446 | NDiscPREF64 *p64; |
94e6d37c YW |
1447 | Address *address; |
1448 | Route *route; | |
372acaad | 1449 | int r, ret = 0; |
94e6d37c YW |
1450 | |
1451 | assert(link); | |
8d01e44c | 1452 | assert(link->manager); |
94e6d37c YW |
1453 | |
1454 | /* If an address or friends is already assigned, but not valid anymore, then refuse to update it, | |
1455 | * and let's immediately remove it. | |
1456 | * See RFC4862, section 5.5.3.e. But the following logic is deviated from RFC4862 by honoring all | |
1457 | * valid lifetimes to improve the reaction of SLAAC to renumbering events. | |
1458 | * See draft-ietf-6man-slaac-renum-02, section 4.2. */ | |
1459 | ||
8d01e44c | 1460 | SET_FOREACH(route, link->manager->routes) { |
94e6d37c YW |
1461 | if (route->source != NETWORK_CONFIG_SOURCE_NDISC) |
1462 | continue; | |
1463 | ||
8d01e44c YW |
1464 | if (route->nexthop.ifindex != link->ifindex) |
1465 | continue; | |
1466 | ||
94e6d37c YW |
1467 | if (route->lifetime_usec >= timestamp_usec) |
1468 | continue; /* the route is still valid */ | |
1469 | ||
3caed9ea | 1470 | r = route_remove_and_cancel(route, link->manager); |
372acaad YW |
1471 | if (r < 0) |
1472 | RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove outdated SLAAC route, ignoring: %m")); | |
94e6d37c YW |
1473 | } |
1474 | ||
1475 | SET_FOREACH(address, link->addresses) { | |
1476 | if (address->source != NETWORK_CONFIG_SOURCE_NDISC) | |
1477 | continue; | |
1478 | ||
1479 | if (address->lifetime_valid_usec >= timestamp_usec) | |
1480 | continue; /* the address is still valid */ | |
1481 | ||
f22b586a | 1482 | r = address_remove_and_cancel(address, link); |
372acaad YW |
1483 | if (r < 0) |
1484 | RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove outdated SLAAC address, ignoring: %m")); | |
94e6d37c YW |
1485 | } |
1486 | ||
1487 | SET_FOREACH(rdnss, link->ndisc_rdnss) { | |
1488 | if (rdnss->lifetime_usec >= timestamp_usec) | |
1489 | continue; /* the DNS server is still valid */ | |
1490 | ||
1491 | free(set_remove(link->ndisc_rdnss, rdnss)); | |
1492 | updated = true; | |
1493 | } | |
1494 | ||
1495 | SET_FOREACH(dnssl, link->ndisc_dnssl) { | |
1496 | if (dnssl->lifetime_usec >= timestamp_usec) | |
1497 | continue; /* the DNS domain is still valid */ | |
1498 | ||
1499 | free(set_remove(link->ndisc_dnssl, dnssl)); | |
1500 | updated = true; | |
1501 | } | |
1502 | ||
64de00c4 YW |
1503 | SET_FOREACH(cp, link->ndisc_captive_portals) { |
1504 | if (cp->lifetime_usec >= timestamp_usec) | |
1505 | continue; /* the captive portal is still valid */ | |
1506 | ||
75a91226 | 1507 | ndisc_captive_portal_free(set_remove(link->ndisc_captive_portals, cp)); |
64de00c4 YW |
1508 | updated = true; |
1509 | } | |
1510 | ||
fabea9c0 YW |
1511 | SET_FOREACH(p64, link->ndisc_pref64) { |
1512 | if (p64->lifetime_usec >= timestamp_usec) | |
1513 | continue; /* the pref64 prefix is still valid */ | |
1514 | ||
1515 | free(set_remove(link->ndisc_pref64, p64)); | |
1516 | /* The pref64 prefix is not exported through the state file, hence it is not necessary to set | |
1517 | * the 'updated' flag. */ | |
1518 | } | |
1519 | ||
94e6d37c YW |
1520 | if (updated) |
1521 | link_dirty(link); | |
1522 | ||
372acaad | 1523 | return ret; |
94e6d37c YW |
1524 | } |
1525 | ||
77302468 YW |
1526 | static int ndisc_setup_expire(Link *link); |
1527 | ||
1528 | static int ndisc_expire_handler(sd_event_source *s, uint64_t usec, void *userdata) { | |
1529 | Link *link = ASSERT_PTR(userdata); | |
1530 | usec_t now_usec; | |
1531 | ||
1532 | assert(link->manager); | |
1533 | ||
1534 | assert_se(sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec) >= 0); | |
1535 | ||
2b4fca55 | 1536 | (void) ndisc_drop_outdated(link, now_usec); |
77302468 YW |
1537 | (void) ndisc_setup_expire(link); |
1538 | return 0; | |
1539 | } | |
1540 | ||
1541 | static int ndisc_setup_expire(Link *link) { | |
1542 | usec_t lifetime_usec = USEC_INFINITY; | |
64de00c4 | 1543 | NDiscCaptivePortal *cp; |
77302468 YW |
1544 | NDiscDNSSL *dnssl; |
1545 | NDiscRDNSS *rdnss; | |
fabea9c0 | 1546 | NDiscPREF64 *p64; |
77302468 YW |
1547 | Address *address; |
1548 | Route *route; | |
1549 | int r; | |
1550 | ||
1551 | assert(link); | |
1552 | assert(link->manager); | |
1553 | ||
8d01e44c | 1554 | SET_FOREACH(route, link->manager->routes) { |
77302468 YW |
1555 | if (route->source != NETWORK_CONFIG_SOURCE_NDISC) |
1556 | continue; | |
1557 | ||
8d01e44c YW |
1558 | if (route->nexthop.ifindex != link->ifindex) |
1559 | continue; | |
1560 | ||
77302468 YW |
1561 | if (!route_exists(route)) |
1562 | continue; | |
1563 | ||
1564 | lifetime_usec = MIN(lifetime_usec, route->lifetime_usec); | |
1565 | } | |
1566 | ||
1567 | SET_FOREACH(address, link->addresses) { | |
1568 | if (address->source != NETWORK_CONFIG_SOURCE_NDISC) | |
1569 | continue; | |
1570 | ||
1571 | if (!address_exists(address)) | |
1572 | continue; | |
1573 | ||
1574 | lifetime_usec = MIN(lifetime_usec, address->lifetime_valid_usec); | |
1575 | } | |
1576 | ||
1577 | SET_FOREACH(rdnss, link->ndisc_rdnss) | |
1578 | lifetime_usec = MIN(lifetime_usec, rdnss->lifetime_usec); | |
1579 | ||
1580 | SET_FOREACH(dnssl, link->ndisc_dnssl) | |
1581 | lifetime_usec = MIN(lifetime_usec, dnssl->lifetime_usec); | |
1582 | ||
64de00c4 YW |
1583 | SET_FOREACH(cp, link->ndisc_captive_portals) |
1584 | lifetime_usec = MIN(lifetime_usec, cp->lifetime_usec); | |
1585 | ||
fabea9c0 YW |
1586 | SET_FOREACH(p64, link->ndisc_pref64) |
1587 | lifetime_usec = MIN(lifetime_usec, p64->lifetime_usec); | |
1588 | ||
77302468 YW |
1589 | if (lifetime_usec == USEC_INFINITY) |
1590 | return 0; | |
1591 | ||
1592 | r = event_reset_time(link->manager->event, &link->ndisc_expire, CLOCK_BOOTTIME, | |
1593 | lifetime_usec, 0, ndisc_expire_handler, link, 0, "ndisc-expiration", true); | |
1594 | if (r < 0) | |
1595 | return log_link_warning_errno(link, r, "Failed to update expiration timer for ndisc: %m"); | |
1596 | ||
1597 | return 0; | |
1598 | } | |
1599 | ||
928112a4 YW |
1600 | static int ndisc_start_dhcp6_client(Link *link, sd_ndisc_router *rt) { |
1601 | int r; | |
1602 | ||
1603 | assert(link); | |
1604 | assert(link->network); | |
1605 | ||
ffef01ac YW |
1606 | /* Do not start DHCPv6 client if the router lifetime is zero, as the message sent as a signal of |
1607 | * that the router is e.g. shutting down, revoked, etc,. */ | |
1608 | r = sd_ndisc_router_get_lifetime(rt, NULL); | |
1609 | if (r <= 0) | |
1610 | return r; | |
1611 | ||
928112a4 YW |
1612 | switch (link->network->ipv6_accept_ra_start_dhcp6_client) { |
1613 | case IPV6_ACCEPT_RA_START_DHCP6_CLIENT_NO: | |
1614 | return 0; | |
1615 | ||
1616 | case IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES: { | |
1617 | uint64_t flags; | |
1618 | ||
1619 | r = sd_ndisc_router_get_flags(rt, &flags); | |
1620 | if (r < 0) | |
1621 | return log_link_warning_errno(link, r, "Failed to get RA flags: %m"); | |
1622 | ||
1623 | if ((flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)) == 0) | |
1624 | return 0; | |
1625 | ||
1626 | /* (re)start DHCPv6 client in stateful or stateless mode according to RA flags. | |
0bcc6557 AH |
1627 | * Note, if both "managed" and "other configuration" bits are set, then ignore |
1628 | * "other configuration" bit. See RFC 4861. */ | |
fac19a21 | 1629 | r = dhcp6_start_on_ra(link, !(flags & ND_RA_FLAG_MANAGED)); |
928112a4 YW |
1630 | break; |
1631 | } | |
1632 | case IPV6_ACCEPT_RA_START_DHCP6_CLIENT_ALWAYS: | |
0bcc6557 | 1633 | /* When IPv6AcceptRA.DHCPv6Client=always, start dhcp6 client in solicit mode |
928112a4 | 1634 | * even if the router flags have neither M nor O flags. */ |
fac19a21 | 1635 | r = dhcp6_start_on_ra(link, /* information_request = */ false); |
928112a4 YW |
1636 | break; |
1637 | ||
1638 | default: | |
1639 | assert_not_reached(); | |
1640 | } | |
1641 | ||
1642 | if (r < 0) | |
2c5bca17 | 1643 | return log_link_warning_errno(link, r, "Could not acquire DHCPv6 lease on NDisc request: %m"); |
928112a4 YW |
1644 | |
1645 | log_link_debug(link, "Acquiring DHCPv6 lease on NDisc request"); | |
1646 | return 0; | |
1647 | } | |
1648 | ||
d5017c84 | 1649 | static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { |
b8ce3b44 | 1650 | struct in6_addr router; |
94e6d37c | 1651 | usec_t timestamp_usec; |
86e2be7b | 1652 | int r; |
1e7a0e21 LP |
1653 | |
1654 | assert(link); | |
1655 | assert(link->network); | |
1656 | assert(link->manager); | |
1657 | assert(rt); | |
1658 | ||
b8ce3b44 | 1659 | r = sd_ndisc_router_get_address(rt, &router); |
5908d864 YW |
1660 | if (r == -ENODATA) { |
1661 | log_link_debug(link, "Received RA without router address, ignoring."); | |
1662 | return 0; | |
1663 | } | |
75d26411 | 1664 | if (r < 0) |
2c5bca17 | 1665 | return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); |
75d26411 | 1666 | |
c995fa02 | 1667 | if (in6_prefix_is_filtered(&router, 128, link->network->ndisc_allow_listed_router, link->network->ndisc_deny_listed_router)) { |
75d26411 | 1668 | if (DEBUG_LOGGING) { |
75d26411 | 1669 | if (!set_isempty(link->network->ndisc_allow_listed_router)) |
84dbb3fd | 1670 | log_link_debug(link, "Router %s is not in allow list, ignoring.", IN6_ADDR_TO_STRING(&router)); |
75d26411 | 1671 | else |
84dbb3fd | 1672 | log_link_debug(link, "Router %s is in deny list, ignoring.", IN6_ADDR_TO_STRING(&router)); |
75d26411 YW |
1673 | } |
1674 | return 0; | |
1675 | } | |
1676 | ||
94e6d37c | 1677 | r = sd_ndisc_router_get_timestamp(rt, CLOCK_BOOTTIME, ×tamp_usec); |
5908d864 YW |
1678 | if (r == -ENODATA) { |
1679 | log_link_debug(link, "Received RA without timestamp, ignoring."); | |
1680 | return 0; | |
1681 | } | |
94e6d37c YW |
1682 | if (r < 0) |
1683 | return r; | |
1684 | ||
2b4fca55 | 1685 | r = ndisc_drop_outdated(link, timestamp_usec); |
94e6d37c | 1686 | if (r < 0) |
2b4fca55 | 1687 | return r; |
94e6d37c | 1688 | |
928112a4 YW |
1689 | r = ndisc_start_dhcp6_client(link, rt); |
1690 | if (r < 0) | |
1691 | return r; | |
1e7a0e21 | 1692 | |
13e8a49a YW |
1693 | r = ndisc_router_process_default(link, rt); |
1694 | if (r < 0) | |
1695 | return r; | |
fbdda4bb | 1696 | |
0f9a2b80 YW |
1697 | r = ndisc_router_process_icmp6_ratelimit(link, rt); |
1698 | if (r < 0) | |
1699 | return r; | |
1700 | ||
1452d497 YW |
1701 | r = ndisc_router_process_reachable_time(link, rt); |
1702 | if (r < 0) | |
1703 | return r; | |
1704 | ||
d4c8de21 MM |
1705 | r = ndisc_router_process_retransmission_time(link, rt); |
1706 | if (r < 0) | |
1707 | return r; | |
1708 | ||
b15ed2be MM |
1709 | r = ndisc_router_process_hop_limit(link, rt); |
1710 | if (r < 0) | |
1711 | return r; | |
1712 | ||
13e8a49a YW |
1713 | r = ndisc_router_process_options(link, rt); |
1714 | if (r < 0) | |
1715 | return r; | |
d5017c84 | 1716 | |
77302468 YW |
1717 | r = ndisc_setup_expire(link); |
1718 | if (r < 0) | |
1719 | return r; | |
1720 | ||
2ccada8d | 1721 | if (link->ndisc_messages == 0) |
3b6a3bde | 1722 | link->ndisc_configured = true; |
2ccada8d | 1723 | else |
3b6a3bde | 1724 | log_link_debug(link, "Setting SLAAC addresses and router."); |
69203fba | 1725 | |
3b6a3bde | 1726 | if (!link->ndisc_configured) |
69203fba YW |
1727 | link_set_state(link, LINK_STATE_CONFIGURING); |
1728 | ||
76c5a0f2 | 1729 | link_check_ready(link); |
69203fba | 1730 | return 0; |
1e7a0e21 LP |
1731 | } |
1732 | ||
2324fd3a | 1733 | static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router *rt, void *userdata) { |
99534007 | 1734 | Link *link = ASSERT_PTR(userdata); |
13e8a49a | 1735 | int r; |
a13c50e7 | 1736 | |
9d96e6c3 TG |
1737 | if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) |
1738 | return; | |
a13c50e7 | 1739 | |
9d96e6c3 | 1740 | switch (event) { |
1e7a0e21 LP |
1741 | |
1742 | case SD_NDISC_EVENT_ROUTER: | |
13e8a49a | 1743 | r = ndisc_router_handler(link, rt); |
5908d864 | 1744 | if (r < 0 && r != -EBADMSG) { |
13e8a49a YW |
1745 | link_enter_failed(link); |
1746 | return; | |
1747 | } | |
1e7a0e21 LP |
1748 | break; |
1749 | ||
9d96e6c3 | 1750 | case SD_NDISC_EVENT_TIMEOUT: |
a8c10331 | 1751 | log_link_debug(link, "NDisc handler get timeout event"); |
3b6a3bde YW |
1752 | if (link->ndisc_messages == 0) { |
1753 | link->ndisc_configured = true; | |
3336e946 YW |
1754 | link_check_ready(link); |
1755 | } | |
9d96e6c3 TG |
1756 | break; |
1757 | default: | |
04499a70 | 1758 | assert_not_reached(); |
a13c50e7 TG |
1759 | } |
1760 | } | |
1761 | ||
ba4c7184 | 1762 | static int ndisc_configure(Link *link) { |
a13c50e7 TG |
1763 | int r; |
1764 | ||
1e7a0e21 LP |
1765 | assert(link); |
1766 | ||
2ffd6d73 YW |
1767 | if (!link_ipv6_accept_ra_enabled(link)) |
1768 | return 0; | |
a13c50e7 | 1769 | |
5c078687 | 1770 | if (link->ndisc) |
bc9e40c9 | 1771 | return -EBUSY; /* Already configured. */ |
2ffd6d73 | 1772 | |
5c078687 YW |
1773 | r = sd_ndisc_new(&link->ndisc); |
1774 | if (r < 0) | |
1775 | return r; | |
1776 | ||
1777 | r = sd_ndisc_attach_event(link->ndisc, link->manager->event, 0); | |
1778 | if (r < 0) | |
1779 | return r; | |
a13c50e7 | 1780 | |
3be64aa4 YW |
1781 | if (link->hw_addr.length == ETH_ALEN) { |
1782 | r = sd_ndisc_set_mac(link->ndisc, &link->hw_addr.ether); | |
1783 | if (r < 0) | |
1784 | return r; | |
1785 | } | |
a13c50e7 | 1786 | |
1e7a0e21 | 1787 | r = sd_ndisc_set_ifindex(link->ndisc, link->ifindex); |
a13c50e7 TG |
1788 | if (r < 0) |
1789 | return r; | |
1790 | ||
1e7a0e21 | 1791 | r = sd_ndisc_set_callback(link->ndisc, ndisc_handler, link); |
a13c50e7 TG |
1792 | if (r < 0) |
1793 | return r; | |
1794 | ||
1e7a0e21 LP |
1795 | return 0; |
1796 | } | |
1797 | ||
294f129b | 1798 | int ndisc_start(Link *link) { |
ba4c7184 YW |
1799 | int r; |
1800 | ||
294f129b YW |
1801 | assert(link); |
1802 | ||
1803 | if (!link->ndisc || !link->dhcp6_client) | |
1804 | return 0; | |
1805 | ||
ccffa166 YW |
1806 | if (!link_has_carrier(link)) |
1807 | return 0; | |
1808 | ||
3b6a3bde YW |
1809 | if (in6_addr_is_null(&link->ipv6ll_address)) |
1810 | return 0; | |
1811 | ||
294f129b YW |
1812 | log_link_debug(link, "Discovering IPv6 routers"); |
1813 | ||
ba4c7184 YW |
1814 | r = sd_ndisc_start(link->ndisc); |
1815 | if (r < 0) | |
1816 | return r; | |
1817 | ||
1818 | return 1; | |
1819 | } | |
1820 | ||
09d09207 | 1821 | static int ndisc_process_request(Request *req, Link *link, void *userdata) { |
ba4c7184 YW |
1822 | int r; |
1823 | ||
ff51134c | 1824 | assert(link); |
ba4c7184 | 1825 | |
4b482e8b | 1826 | if (!link_is_ready_to_configure(link, /* allow_unmanaged = */ false)) |
ba4c7184 YW |
1827 | return 0; |
1828 | ||
ba4c7184 YW |
1829 | r = ndisc_configure(link); |
1830 | if (r < 0) | |
1831 | return log_link_warning_errno(link, r, "Failed to configure IPv6 Router Discovery: %m"); | |
1832 | ||
1833 | r = ndisc_start(link); | |
1834 | if (r < 0) | |
1835 | return log_link_warning_errno(link, r, "Failed to start IPv6 Router Discovery: %m"); | |
1836 | ||
1837 | log_link_debug(link, "IPv6 Router Discovery is configured%s.", | |
1838 | r > 0 ? " and started" : ""); | |
ba4c7184 YW |
1839 | return 1; |
1840 | } | |
1841 | ||
1842 | int link_request_ndisc(Link *link) { | |
1843 | int r; | |
1844 | ||
1845 | assert(link); | |
1846 | ||
1847 | if (!link_ipv6_accept_ra_enabled(link)) | |
1848 | return 0; | |
1849 | ||
1850 | if (link->ndisc) | |
1851 | return 0; | |
1852 | ||
09d09207 | 1853 | r = link_queue_request(link, REQUEST_TYPE_NDISC, ndisc_process_request, NULL); |
ba4c7184 YW |
1854 | if (r < 0) |
1855 | return log_link_warning_errno(link, r, "Failed to request configuring of the IPv6 Router Discovery: %m"); | |
1856 | ||
1857 | log_link_debug(link, "Requested configuring of the IPv6 Router Discovery."); | |
1858 | return 0; | |
294f129b YW |
1859 | } |
1860 | ||
77302468 YW |
1861 | int ndisc_stop(Link *link) { |
1862 | assert(link); | |
1863 | ||
1864 | link->ndisc_expire = sd_event_source_disable_unref(link->ndisc_expire); | |
1865 | ||
1866 | return sd_ndisc_stop(link->ndisc); | |
1867 | } | |
1868 | ||
1869 | ||
c69305ff LP |
1870 | void ndisc_flush(Link *link) { |
1871 | assert(link); | |
1872 | ||
a86763c7 YW |
1873 | /* Remove all addresses, routes, RDNSS, DNSSL, and Captive Portal entries, without exception. */ |
1874 | (void) ndisc_drop_outdated(link, /* timestamp_usec = */ USEC_INFINITY); | |
c69305ff | 1875 | |
b0b97766 YW |
1876 | link->ndisc_rdnss = set_free(link->ndisc_rdnss); |
1877 | link->ndisc_dnssl = set_free(link->ndisc_dnssl); | |
64de00c4 | 1878 | link->ndisc_captive_portals = set_free(link->ndisc_captive_portals); |
6e8f5e4c | 1879 | link->ndisc_pref64 = set_free(link->ndisc_pref64); |
c69305ff | 1880 | } |
e520ce64 | 1881 | |
ac24e418 SS |
1882 | static const char* const ipv6_accept_ra_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = { |
1883 | [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_NO] = "no", | |
1884 | [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_ALWAYS] = "always", | |
1885 | [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES] = "yes", | |
1886 | }; | |
1887 | ||
3b6a3bde YW |
1888 | DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(ipv6_accept_ra_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES); |
1889 | ||
1890 | DEFINE_CONFIG_PARSE_ENUM(config_parse_ipv6_accept_ra_use_domains, dhcp_use_domains, DHCPUseDomains, | |
1891 | "Failed to parse UseDomains= setting"); | |
1892 | DEFINE_CONFIG_PARSE_ENUM(config_parse_ipv6_accept_ra_start_dhcp6_client, ipv6_accept_ra_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, | |
1893 | "Failed to parse DHCPv6Client= setting"); |