]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
a13c50e7 | 2 | /*** |
810adae9 | 3 | Copyright © 2014 Intel Corporation. All rights reserved. |
a13c50e7 TG |
4 | ***/ |
5 | ||
9d96e6c3 | 6 | #include <netinet/icmp6.h> |
23f53b99 | 7 | #include <arpa/inet.h> |
a13c50e7 | 8 | |
a13c50e7 TG |
9 | #include "sd-ndisc.h" |
10 | ||
d909e4af | 11 | #include "missing_network.h" |
ca5ad760 | 12 | #include "networkd-dhcp6.h" |
73854ba1 | 13 | #include "networkd-manager.h" |
1e7a0e21 | 14 | #include "networkd-ndisc.h" |
23f53b99 | 15 | #include "networkd-route.h" |
51517f9e | 16 | #include "strv.h" |
1e7a0e21 LP |
17 | |
18 | #define NDISC_DNSSL_MAX 64U | |
19 | #define NDISC_RDNSS_MAX 64U | |
6554550f | 20 | #define NDISC_PREFIX_LFT_MIN 7200U |
fe307276 | 21 | |
73854ba1 | 22 | static int ndisc_netlink_route_message_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { |
3b015d40 TG |
23 | int r; |
24 | ||
25 | assert(link); | |
26 | assert(link->ndisc_messages > 0); | |
27 | ||
313cefa1 | 28 | link->ndisc_messages--; |
3b015d40 | 29 | |
4ff296b0 YW |
30 | if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) |
31 | return 1; | |
32 | ||
3b015d40 | 33 | r = sd_netlink_message_get_errno(m); |
4ff296b0 | 34 | if (r < 0 && r != -EEXIST) { |
5ecb131d | 35 | log_link_message_error_errno(link, m, r, "Could not set NDisc route or address"); |
4ff296b0 YW |
36 | link_enter_failed(link); |
37 | return 1; | |
38 | } | |
3b015d40 TG |
39 | |
40 | if (link->ndisc_messages == 0) { | |
41 | link->ndisc_configured = true; | |
4ff296b0 YW |
42 | r = link_request_set_routes(link); |
43 | if (r < 0) { | |
44 | link_enter_failed(link); | |
45 | return 1; | |
46 | } | |
3b015d40 TG |
47 | link_check_ready(link); |
48 | } | |
49 | ||
50 | return 1; | |
51 | } | |
52 | ||
73854ba1 YW |
53 | static int ndisc_netlink_address_message_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { |
54 | int r; | |
55 | ||
56 | assert(link); | |
57 | assert(link->ndisc_messages > 0); | |
58 | ||
59 | link->ndisc_messages--; | |
60 | ||
4ff296b0 YW |
61 | if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) |
62 | return 1; | |
63 | ||
73854ba1 | 64 | r = sd_netlink_message_get_errno(m); |
4ff296b0 | 65 | if (r < 0 && r != -EEXIST) { |
5ecb131d | 66 | log_link_message_error_errno(link, m, r, "Could not set NDisc route or address"); |
4ff296b0 YW |
67 | link_enter_failed(link); |
68 | return 1; | |
69 | } else if (r >= 0) | |
70 | (void) manager_rtnl_process_address(rtnl, m, link->manager); | |
73854ba1 YW |
71 | |
72 | if (link->ndisc_messages == 0) { | |
73 | link->ndisc_configured = true; | |
4ff296b0 YW |
74 | r = link_request_set_routes(link); |
75 | if (r < 0) { | |
76 | link_enter_failed(link); | |
77 | return 1; | |
78 | } | |
73854ba1 YW |
79 | link_check_ready(link); |
80 | } | |
81 | ||
82 | return 1; | |
83 | } | |
84 | ||
d5017c84 | 85 | static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { |
8e766630 | 86 | _cleanup_(route_freep) Route *route = NULL; |
ce2ea782 | 87 | union in_addr_union gateway; |
1e7a0e21 LP |
88 | uint16_t lifetime; |
89 | unsigned preference; | |
d6fceaf1 | 90 | uint32_t mtu; |
3b015d40 TG |
91 | usec_t time_now; |
92 | int r; | |
6d7c7615 PF |
93 | Address *address; |
94 | Iterator i; | |
3b015d40 | 95 | |
3b015d40 | 96 | assert(link); |
1e7a0e21 | 97 | assert(rt); |
3b015d40 | 98 | |
1e7a0e21 | 99 | r = sd_ndisc_router_get_lifetime(rt, &lifetime); |
d5017c84 YW |
100 | if (r < 0) |
101 | return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m"); | |
102 | ||
1e7a0e21 | 103 | if (lifetime == 0) /* not a default router */ |
d5017c84 | 104 | return 0; |
1e7a0e21 | 105 | |
ce2ea782 | 106 | r = sd_ndisc_router_get_address(rt, &gateway.in6); |
d5017c84 YW |
107 | if (r < 0) |
108 | return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m"); | |
1e7a0e21 | 109 | |
ce2ea782 YW |
110 | SET_FOREACH(address, link->addresses, i) { |
111 | if (address->family != AF_INET6) | |
112 | continue; | |
113 | if (in_addr_equal(AF_INET6, &gateway, &address->in_addr)) { | |
57e44707 | 114 | _cleanup_free_ char *buffer = NULL; |
6d7c7615 | 115 | |
57e44707 | 116 | (void) in_addr_to_string(AF_INET6, &address->in_addr, &buffer); |
6d7c7615 | 117 | log_link_debug(link, "No NDisc route added, gateway %s matches local address", |
57e44707 | 118 | strnull(buffer)); |
d5017c84 | 119 | return 0; |
6d7c7615 | 120 | } |
ce2ea782 | 121 | } |
6d7c7615 | 122 | |
ce2ea782 YW |
123 | SET_FOREACH(address, link->addresses_foreign, i) { |
124 | if (address->family != AF_INET6) | |
125 | continue; | |
126 | if (in_addr_equal(AF_INET6, &gateway, &address->in_addr)) { | |
57e44707 | 127 | _cleanup_free_ char *buffer = NULL; |
6d7c7615 | 128 | |
57e44707 | 129 | (void) in_addr_to_string(AF_INET6, &address->in_addr, &buffer); |
6d7c7615 | 130 | log_link_debug(link, "No NDisc route added, gateway %s matches local address", |
57e44707 | 131 | strnull(buffer)); |
d5017c84 | 132 | return 0; |
6d7c7615 | 133 | } |
ce2ea782 | 134 | } |
6d7c7615 | 135 | |
1e7a0e21 | 136 | r = sd_ndisc_router_get_preference(rt, &preference); |
d5017c84 YW |
137 | if (r < 0) |
138 | return log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m"); | |
1e7a0e21 LP |
139 | |
140 | r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now); | |
d5017c84 YW |
141 | if (r < 0) |
142 | return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m"); | |
1e7a0e21 | 143 | |
d6fceaf1 | 144 | r = sd_ndisc_router_get_mtu(rt, &mtu); |
29b5ad08 JT |
145 | if (r == -ENODATA) |
146 | mtu = 0; | |
d5017c84 YW |
147 | else if (r < 0) |
148 | return log_link_warning_errno(link, r, "Failed to get default router MTU from RA: %m"); | |
d6fceaf1 | 149 | |
1e7a0e21 | 150 | r = route_new(&route); |
d5017c84 YW |
151 | if (r < 0) |
152 | return log_link_error_errno(link, r, "Could not allocate route: %m"); | |
1e7a0e21 LP |
153 | |
154 | route->family = AF_INET6; | |
bdb9f580 | 155 | route->table = link_get_ipv6_accept_ra_route_table(link); |
91b8fd3c | 156 | route->priority = link->network->dhcp_route_metric; |
1e7a0e21 LP |
157 | route->protocol = RTPROT_RA; |
158 | route->pref = preference; | |
ce2ea782 | 159 | route->gw = gateway; |
1e7a0e21 | 160 | route->lifetime = time_now + lifetime * USEC_PER_SEC; |
d6fceaf1 | 161 | route->mtu = mtu; |
1e7a0e21 | 162 | |
73854ba1 | 163 | r = route_configure(route, link, ndisc_netlink_route_message_handler); |
1e7a0e21 LP |
164 | if (r < 0) { |
165 | log_link_warning_errno(link, r, "Could not set default route: %m"); | |
166 | link_enter_failed(link); | |
d5017c84 | 167 | return r; |
1e7a0e21 | 168 | } |
c4423317 YW |
169 | if (r > 0) |
170 | link->ndisc_messages++; | |
d5017c84 YW |
171 | |
172 | return 0; | |
1e7a0e21 LP |
173 | } |
174 | ||
d5017c84 | 175 | static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) { |
8e766630 | 176 | _cleanup_(address_freep) Address *address = NULL; |
6554550f HW |
177 | Address *existing_address; |
178 | uint32_t lifetime_valid, lifetime_preferred, lifetime_remaining; | |
179 | usec_t time_now; | |
1e7a0e21 LP |
180 | unsigned prefixlen; |
181 | int r; | |
182 | ||
183 | assert(link); | |
184 | assert(rt); | |
185 | ||
6554550f | 186 | r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now); |
d5017c84 YW |
187 | if (r < 0) |
188 | return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m"); | |
6554550f | 189 | |
1e7a0e21 | 190 | r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen); |
d5017c84 YW |
191 | if (r < 0) |
192 | return log_link_error_errno(link, r, "Failed to get prefix length: %m"); | |
1e7a0e21 LP |
193 | |
194 | r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid); | |
d5017c84 YW |
195 | if (r < 0) |
196 | return log_link_error_errno(link, r, "Failed to get prefix valid lifetime: %m"); | |
1e7a0e21 LP |
197 | |
198 | r = sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred); | |
d5017c84 YW |
199 | if (r < 0) |
200 | return log_link_error_errno(link, r, "Failed to get prefix preferred lifetime: %m"); | |
3b015d40 | 201 | |
92bdc3ff SS |
202 | /* The preferred lifetime is never greater than the valid lifetime */ |
203 | if (lifetime_preferred > lifetime_valid) | |
d5017c84 | 204 | return 0; |
92bdc3ff | 205 | |
3b015d40 | 206 | r = address_new(&address); |
d5017c84 YW |
207 | if (r < 0) |
208 | return log_link_error_errno(link, r, "Could not allocate address: %m"); | |
3b015d40 | 209 | |
3b015d40 | 210 | address->family = AF_INET6; |
1e7a0e21 | 211 | r = sd_ndisc_router_prefix_get_address(rt, &address->in_addr.in6); |
d5017c84 YW |
212 | if (r < 0) |
213 | return log_link_error_errno(link, r, "Failed to get prefix address: %m"); | |
1e7a0e21 | 214 | |
3b015d40 | 215 | if (in_addr_is_null(AF_INET6, (const union in_addr_union *) &link->network->ipv6_token) == 0) |
fb84d896 | 216 | memcpy(((char *)&address->in_addr.in6) + 8, ((char *)&link->network->ipv6_token) + 8, 8); |
3b015d40 | 217 | else { |
fe307276 | 218 | /* see RFC4291 section 2.5.1 */ |
3a437557 NM |
219 | address->in_addr.in6.s6_addr[8] = link->mac.ether_addr_octet[0]; |
220 | address->in_addr.in6.s6_addr[8] ^= 1 << 1; | |
221 | address->in_addr.in6.s6_addr[9] = link->mac.ether_addr_octet[1]; | |
222 | address->in_addr.in6.s6_addr[10] = link->mac.ether_addr_octet[2]; | |
223 | address->in_addr.in6.s6_addr[11] = 0xff; | |
224 | address->in_addr.in6.s6_addr[12] = 0xfe; | |
225 | address->in_addr.in6.s6_addr[13] = link->mac.ether_addr_octet[3]; | |
226 | address->in_addr.in6.s6_addr[14] = link->mac.ether_addr_octet[4]; | |
227 | address->in_addr.in6.s6_addr[15] = link->mac.ether_addr_octet[5]; | |
3b015d40 TG |
228 | } |
229 | address->prefixlen = prefixlen; | |
f217be19 | 230 | address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR; |
3b015d40 | 231 | address->cinfo.ifa_prefered = lifetime_preferred; |
6554550f HW |
232 | |
233 | /* see RFC4862 section 5.5.3.e */ | |
234 | r = address_get(link, address->family, &address->in_addr, address->prefixlen, &existing_address); | |
235 | if (r > 0) { | |
236 | lifetime_remaining = existing_address->cinfo.tstamp / 100 + existing_address->cinfo.ifa_valid - time_now / USEC_PER_SEC; | |
237 | if (lifetime_valid > NDISC_PREFIX_LFT_MIN || lifetime_valid > lifetime_remaining) | |
238 | address->cinfo.ifa_valid = lifetime_valid; | |
239 | else if (lifetime_remaining <= NDISC_PREFIX_LFT_MIN) | |
240 | address->cinfo.ifa_valid = lifetime_remaining; | |
241 | else | |
242 | address->cinfo.ifa_valid = NDISC_PREFIX_LFT_MIN; | |
243 | } else if (lifetime_valid > 0) | |
244 | address->cinfo.ifa_valid = lifetime_valid; | |
245 | else | |
d5017c84 | 246 | return 0; /* see RFC4862 section 5.5.3.d */ |
6554550f HW |
247 | |
248 | if (address->cinfo.ifa_valid == 0) | |
d5017c84 | 249 | return 0; |
3b015d40 | 250 | |
73854ba1 | 251 | r = address_configure(address, link, ndisc_netlink_address_message_handler, true); |
3b015d40 TG |
252 | if (r < 0) { |
253 | log_link_warning_errno(link, r, "Could not set SLAAC address: %m"); | |
254 | link_enter_failed(link); | |
d5017c84 | 255 | return r; |
3b015d40 | 256 | } |
54a1a535 YW |
257 | if (r > 0) |
258 | link->ndisc_messages++; | |
d5017c84 YW |
259 | |
260 | return 0; | |
3b015d40 TG |
261 | } |
262 | ||
d5017c84 | 263 | static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { |
8e766630 | 264 | _cleanup_(route_freep) Route *route = NULL; |
3b015d40 | 265 | usec_t time_now; |
1e7a0e21 LP |
266 | uint32_t lifetime; |
267 | unsigned prefixlen; | |
3b015d40 TG |
268 | int r; |
269 | ||
3b015d40 | 270 | assert(link); |
1e7a0e21 | 271 | assert(rt); |
3b015d40 | 272 | |
1e7a0e21 | 273 | r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now); |
d5017c84 YW |
274 | if (r < 0) |
275 | return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m"); | |
1e7a0e21 LP |
276 | |
277 | r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen); | |
d5017c84 YW |
278 | if (r < 0) |
279 | return log_link_error_errno(link, r, "Failed to get prefix length: %m"); | |
1e7a0e21 LP |
280 | |
281 | r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime); | |
d5017c84 YW |
282 | if (r < 0) |
283 | return log_link_error_errno(link, r, "Failed to get prefix lifetime: %m"); | |
3b015d40 TG |
284 | |
285 | r = route_new(&route); | |
d5017c84 YW |
286 | if (r < 0) |
287 | return log_link_error_errno(link, r, "Could not allocate route: %m"); | |
3b015d40 | 288 | |
3b015d40 | 289 | route->family = AF_INET6; |
bdb9f580 | 290 | route->table = link_get_ipv6_accept_ra_route_table(link); |
91b8fd3c | 291 | route->priority = link->network->dhcp_route_metric; |
3b015d40 TG |
292 | route->protocol = RTPROT_RA; |
293 | route->flags = RTM_F_PREFIX; | |
3b015d40 TG |
294 | route->dst_prefixlen = prefixlen; |
295 | route->lifetime = time_now + lifetime * USEC_PER_SEC; | |
296 | ||
1e7a0e21 | 297 | r = sd_ndisc_router_prefix_get_address(rt, &route->dst.in6); |
d5017c84 YW |
298 | if (r < 0) |
299 | return log_link_error_errno(link, r, "Failed to get prefix address: %m"); | |
1e7a0e21 | 300 | |
73854ba1 | 301 | r = route_configure(route, link, ndisc_netlink_route_message_handler); |
3b015d40 TG |
302 | if (r < 0) { |
303 | log_link_warning_errno(link, r, "Could not set prefix route: %m"); | |
304 | link_enter_failed(link); | |
d5017c84 | 305 | return r; |
3b015d40 | 306 | } |
c4423317 YW |
307 | if (r > 0) |
308 | link->ndisc_messages++; | |
d5017c84 YW |
309 | |
310 | return 0; | |
3b015d40 TG |
311 | } |
312 | ||
d5017c84 | 313 | static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { |
8e766630 | 314 | _cleanup_(route_freep) Route *route = NULL; |
1e7a0e21 LP |
315 | struct in6_addr gateway; |
316 | uint32_t lifetime; | |
317 | unsigned preference, prefixlen; | |
fe307276 | 318 | usec_t time_now; |
7a695d8e | 319 | int r; |
a13c50e7 TG |
320 | |
321 | assert(link); | |
a13c50e7 | 322 | |
1e7a0e21 | 323 | r = sd_ndisc_router_route_get_lifetime(rt, &lifetime); |
d5017c84 YW |
324 | if (r < 0) |
325 | return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m"); | |
326 | ||
1e7a0e21 | 327 | if (lifetime == 0) |
d5017c84 | 328 | return 0; |
a13c50e7 | 329 | |
1e7a0e21 | 330 | r = sd_ndisc_router_get_address(rt, &gateway); |
d5017c84 YW |
331 | if (r < 0) |
332 | return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m"); | |
3b015d40 | 333 | |
1e7a0e21 | 334 | r = sd_ndisc_router_route_get_prefixlen(rt, &prefixlen); |
d5017c84 YW |
335 | if (r < 0) |
336 | return log_link_warning_errno(link, r, "Failed to get route prefix length: %m"); | |
1e7a0e21 LP |
337 | |
338 | r = sd_ndisc_router_route_get_preference(rt, &preference); | |
d5017c84 YW |
339 | if (r < 0) |
340 | return log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m"); | |
1e7a0e21 LP |
341 | |
342 | r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now); | |
d5017c84 YW |
343 | if (r < 0) |
344 | return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m"); | |
3b015d40 TG |
345 | |
346 | r = route_new(&route); | |
d5017c84 YW |
347 | if (r < 0) |
348 | return log_link_error_errno(link, r, "Could not allocate route: %m"); | |
3b015d40 | 349 | |
3b015d40 | 350 | route->family = AF_INET6; |
bdb9f580 | 351 | route->table = link_get_ipv6_accept_ra_route_table(link); |
3b015d40 | 352 | route->protocol = RTPROT_RA; |
1e7a0e21 LP |
353 | route->pref = preference; |
354 | route->gw.in6 = gateway; | |
355 | route->dst_prefixlen = prefixlen; | |
3b015d40 TG |
356 | route->lifetime = time_now + lifetime * USEC_PER_SEC; |
357 | ||
1e7a0e21 | 358 | r = sd_ndisc_router_route_get_address(rt, &route->dst.in6); |
d5017c84 YW |
359 | if (r < 0) |
360 | return log_link_error_errno(link, r, "Failed to get route address: %m"); | |
1e7a0e21 | 361 | |
73854ba1 | 362 | r = route_configure(route, link, ndisc_netlink_route_message_handler); |
3b015d40 | 363 | if (r < 0) { |
1e7a0e21 | 364 | log_link_warning_errno(link, r, "Could not set additional route: %m"); |
3b015d40 | 365 | link_enter_failed(link); |
d5017c84 | 366 | return r; |
3b015d40 | 367 | } |
c4423317 YW |
368 | if (r > 0) |
369 | link->ndisc_messages++; | |
d5017c84 YW |
370 | |
371 | return 0; | |
9d96e6c3 | 372 | } |
a13c50e7 | 373 | |
7a08d314 | 374 | static void ndisc_rdnss_hash_func(const NDiscRDNSS *x, struct siphash *state) { |
1e7a0e21 LP |
375 | siphash24_compress(&x->address, sizeof(x->address), state); |
376 | } | |
377 | ||
7a08d314 | 378 | static int ndisc_rdnss_compare_func(const NDiscRDNSS *a, const NDiscRDNSS *b) { |
1e7a0e21 LP |
379 | return memcmp(&a->address, &b->address, sizeof(a->address)); |
380 | } | |
381 | ||
7a08d314 | 382 | DEFINE_PRIVATE_HASH_OPS(ndisc_rdnss_hash_ops, NDiscRDNSS, ndisc_rdnss_hash_func, ndisc_rdnss_compare_func); |
1e7a0e21 | 383 | |
d5017c84 | 384 | static int ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) { |
1e7a0e21 LP |
385 | uint32_t lifetime; |
386 | const struct in6_addr *a; | |
387 | usec_t time_now; | |
388 | int i, n, r; | |
389 | ||
390 | assert(link); | |
391 | assert(rt); | |
392 | ||
393 | r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now); | |
d5017c84 YW |
394 | if (r < 0) |
395 | return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m"); | |
1e7a0e21 LP |
396 | |
397 | r = sd_ndisc_router_rdnss_get_lifetime(rt, &lifetime); | |
d5017c84 YW |
398 | if (r < 0) |
399 | return log_link_warning_errno(link, r, "Failed to get RDNSS lifetime: %m"); | |
1e7a0e21 LP |
400 | |
401 | n = sd_ndisc_router_rdnss_get_addresses(rt, &a); | |
d5017c84 YW |
402 | if (n < 0) |
403 | return log_link_warning_errno(link, n, "Failed to get RDNSS addresses: %m"); | |
1e7a0e21 LP |
404 | |
405 | for (i = 0; i < n; i++) { | |
d5017c84 | 406 | _cleanup_free_ NDiscRDNSS *x = NULL; |
1e7a0e21 | 407 | NDiscRDNSS d = { |
d5017c84 YW |
408 | .address = a[i], |
409 | }, *y; | |
1e7a0e21 LP |
410 | |
411 | if (lifetime == 0) { | |
412 | (void) set_remove(link->ndisc_rdnss, &d); | |
413 | link_dirty(link); | |
414 | continue; | |
415 | } | |
416 | ||
d5017c84 YW |
417 | y = set_get(link->ndisc_rdnss, &d); |
418 | if (y) { | |
419 | y->valid_until = time_now + lifetime * USEC_PER_SEC; | |
1e7a0e21 LP |
420 | continue; |
421 | } | |
422 | ||
423 | ndisc_vacuum(link); | |
424 | ||
425 | if (set_size(link->ndisc_rdnss) >= NDISC_RDNSS_MAX) { | |
426 | log_link_warning(link, "Too many RDNSS records per link, ignoring."); | |
427 | continue; | |
428 | } | |
429 | ||
430 | r = set_ensure_allocated(&link->ndisc_rdnss, &ndisc_rdnss_hash_ops); | |
d5017c84 YW |
431 | if (r < 0) |
432 | return log_oom(); | |
1e7a0e21 | 433 | |
d5017c84 YW |
434 | x = new(NDiscRDNSS, 1); |
435 | if (!x) | |
436 | return log_oom(); | |
1e7a0e21 | 437 | |
d5017c84 YW |
438 | *x = (NDiscRDNSS) { |
439 | .address = a[i], | |
440 | .valid_until = time_now + lifetime * USEC_PER_SEC, | |
441 | }; | |
1e7a0e21 LP |
442 | |
443 | r = set_put(link->ndisc_rdnss, x); | |
d5017c84 YW |
444 | if (r < 0) |
445 | return log_oom(); | |
446 | ||
447 | TAKE_PTR(x); | |
1e7a0e21 LP |
448 | |
449 | assert(r > 0); | |
450 | link_dirty(link); | |
451 | } | |
d5017c84 YW |
452 | |
453 | return 0; | |
1e7a0e21 LP |
454 | } |
455 | ||
7a08d314 | 456 | static void ndisc_dnssl_hash_func(const NDiscDNSSL *x, struct siphash *state) { |
1e7a0e21 LP |
457 | siphash24_compress(NDISC_DNSSL_DOMAIN(x), strlen(NDISC_DNSSL_DOMAIN(x)), state); |
458 | } | |
459 | ||
7a08d314 | 460 | static int ndisc_dnssl_compare_func(const NDiscDNSSL *a, const NDiscDNSSL *b) { |
1e7a0e21 LP |
461 | return strcmp(NDISC_DNSSL_DOMAIN(a), NDISC_DNSSL_DOMAIN(b)); |
462 | } | |
463 | ||
7a08d314 | 464 | DEFINE_PRIVATE_HASH_OPS(ndisc_dnssl_hash_ops, NDiscDNSSL, ndisc_dnssl_hash_func, ndisc_dnssl_compare_func); |
1e7a0e21 LP |
465 | |
466 | static void ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) { | |
467 | _cleanup_strv_free_ char **l = NULL; | |
468 | uint32_t lifetime; | |
469 | usec_t time_now; | |
470 | char **i; | |
471 | int r; | |
472 | ||
473 | assert(link); | |
474 | assert(rt); | |
475 | ||
476 | r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now); | |
477 | if (r < 0) { | |
478 | log_link_warning_errno(link, r, "Failed to get RA timestamp: %m"); | |
479 | return; | |
480 | } | |
481 | ||
482 | r = sd_ndisc_router_dnssl_get_lifetime(rt, &lifetime); | |
483 | if (r < 0) { | |
484 | log_link_warning_errno(link, r, "Failed to get RDNSS lifetime: %m"); | |
485 | return; | |
486 | } | |
487 | ||
488 | r = sd_ndisc_router_dnssl_get_domains(rt, &l); | |
489 | if (r < 0) { | |
490 | log_link_warning_errno(link, r, "Failed to get RDNSS addresses: %m"); | |
491 | return; | |
492 | } | |
493 | ||
494 | STRV_FOREACH(i, l) { | |
a34349e7 | 495 | _cleanup_free_ NDiscDNSSL *s; |
1e7a0e21 LP |
496 | NDiscDNSSL *x; |
497 | ||
a34349e7 DM |
498 | s = malloc0(ALIGN(sizeof(NDiscDNSSL)) + strlen(*i) + 1); |
499 | if (!s) { | |
500 | log_oom(); | |
501 | return; | |
502 | } | |
503 | ||
504 | strcpy(NDISC_DNSSL_DOMAIN(s), *i); | |
1e7a0e21 LP |
505 | |
506 | if (lifetime == 0) { | |
a34349e7 | 507 | (void) set_remove(link->ndisc_dnssl, s); |
1e7a0e21 LP |
508 | link_dirty(link); |
509 | continue; | |
510 | } | |
511 | ||
a34349e7 | 512 | x = set_get(link->ndisc_dnssl, s); |
1e7a0e21 LP |
513 | if (x) { |
514 | x->valid_until = time_now + lifetime * USEC_PER_SEC; | |
515 | continue; | |
516 | } | |
517 | ||
518 | ndisc_vacuum(link); | |
519 | ||
520 | if (set_size(link->ndisc_dnssl) >= NDISC_DNSSL_MAX) { | |
521 | log_link_warning(link, "Too many DNSSL records per link, ignoring."); | |
522 | continue; | |
523 | } | |
524 | ||
525 | r = set_ensure_allocated(&link->ndisc_dnssl, &ndisc_dnssl_hash_ops); | |
526 | if (r < 0) { | |
527 | log_oom(); | |
528 | return; | |
529 | } | |
530 | ||
a34349e7 | 531 | s->valid_until = time_now + lifetime * USEC_PER_SEC; |
1e7a0e21 | 532 | |
a34349e7 | 533 | r = set_put(link->ndisc_dnssl, s); |
1e7a0e21 | 534 | if (r < 0) { |
1e7a0e21 LP |
535 | log_oom(); |
536 | return; | |
537 | } | |
538 | ||
a34349e7 | 539 | s = NULL; |
1e7a0e21 LP |
540 | assert(r > 0); |
541 | link_dirty(link); | |
542 | } | |
543 | } | |
544 | ||
e8c9b5b0 | 545 | static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) { |
1e7a0e21 LP |
546 | int r; |
547 | ||
548 | assert(link); | |
55d3fdcf | 549 | assert(link->network); |
1e7a0e21 LP |
550 | assert(rt); |
551 | ||
552 | r = sd_ndisc_router_option_rewind(rt); | |
553 | for (;;) { | |
554 | uint8_t type; | |
555 | ||
e8c9b5b0 YW |
556 | if (r < 0) |
557 | return log_link_warning_errno(link, r, "Failed to iterate through options: %m"); | |
1e7a0e21 LP |
558 | if (r == 0) /* EOF */ |
559 | break; | |
560 | ||
561 | r = sd_ndisc_router_option_get_type(rt, &type); | |
e8c9b5b0 YW |
562 | if (r < 0) |
563 | return log_link_warning_errno(link, r, "Failed to get RA option type: %m"); | |
1e7a0e21 LP |
564 | |
565 | switch (type) { | |
566 | ||
567 | case SD_NDISC_OPTION_PREFIX_INFORMATION: { | |
55d3fdcf | 568 | union in_addr_union a; |
1e7a0e21 LP |
569 | uint8_t flags; |
570 | ||
55d3fdcf YW |
571 | r = sd_ndisc_router_prefix_get_address(rt, &a.in6); |
572 | if (r < 0) | |
573 | return log_link_error_errno(link, r, "Failed to get prefix address: %m"); | |
574 | ||
575 | if (set_contains(link->network->ndisc_black_listed_prefix, &a.in6)) { | |
576 | if (DEBUG_LOGGING) { | |
577 | _cleanup_free_ char *b = NULL; | |
578 | ||
579 | (void) in_addr_to_string(AF_INET6, &a, &b); | |
580 | log_link_debug(link, "Prefix '%s' is black listed, ignoring", strna(b)); | |
581 | } | |
582 | ||
583 | break; | |
584 | } | |
585 | ||
1e7a0e21 | 586 | r = sd_ndisc_router_prefix_get_flags(rt, &flags); |
e8c9b5b0 YW |
587 | if (r < 0) |
588 | return log_link_warning_errno(link, r, "Failed to get RA prefix flags: %m"); | |
1e7a0e21 | 589 | |
87d8a4de YW |
590 | if (link->network->ipv6_accept_ra_use_onlink_prefix && |
591 | FLAGS_SET(flags, ND_OPT_PI_FLAG_ONLINK)) | |
592 | (void) ndisc_router_process_onlink_prefix(link, rt); | |
062c2eea | 593 | |
87d8a4de YW |
594 | if (link->network->ipv6_accept_ra_use_autonomous_prefix && |
595 | FLAGS_SET(flags, ND_OPT_PI_FLAG_AUTO)) | |
596 | (void) ndisc_router_process_autonomous_prefix(link, rt); | |
1e7a0e21 LP |
597 | |
598 | break; | |
599 | } | |
600 | ||
601 | case SD_NDISC_OPTION_ROUTE_INFORMATION: | |
d5017c84 | 602 | (void) ndisc_router_process_route(link, rt); |
1e7a0e21 LP |
603 | break; |
604 | ||
605 | case SD_NDISC_OPTION_RDNSS: | |
fe0252e5 | 606 | if (link->network->ipv6_accept_ra_use_dns) |
d5017c84 | 607 | (void) ndisc_router_process_rdnss(link, rt); |
1e7a0e21 LP |
608 | break; |
609 | ||
610 | case SD_NDISC_OPTION_DNSSL: | |
fe0252e5 | 611 | if (link->network->ipv6_accept_ra_use_dns) |
d5017c84 | 612 | (void) ndisc_router_process_dnssl(link, rt); |
1e7a0e21 LP |
613 | break; |
614 | } | |
615 | ||
616 | r = sd_ndisc_router_option_next(rt); | |
617 | } | |
e8c9b5b0 YW |
618 | |
619 | return 0; | |
1e7a0e21 LP |
620 | } |
621 | ||
d5017c84 | 622 | static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { |
1e7a0e21 | 623 | uint64_t flags; |
86e2be7b | 624 | int r; |
1e7a0e21 LP |
625 | |
626 | assert(link); | |
627 | assert(link->network); | |
628 | assert(link->manager); | |
629 | assert(rt); | |
630 | ||
631 | r = sd_ndisc_router_get_flags(rt, &flags); | |
d5017c84 YW |
632 | if (r < 0) |
633 | return log_link_warning_errno(link, r, "Failed to get RA flags: %m"); | |
1e7a0e21 LP |
634 | |
635 | if (flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)) { | |
636 | /* (re)start DHCPv6 client in stateful or stateless mode according to RA flags */ | |
637 | r = dhcp6_request_address(link, !(flags & ND_RA_FLAG_MANAGED)); | |
638 | if (r < 0 && r != -EBUSY) | |
639 | log_link_warning_errno(link, r, "Could not acquire DHCPv6 lease on NDisc request: %m"); | |
d5017c84 | 640 | else { |
1e7a0e21 | 641 | log_link_debug(link, "Acquiring DHCPv6 lease on NDisc request"); |
d5017c84 YW |
642 | r = 0; |
643 | } | |
1e7a0e21 LP |
644 | } |
645 | ||
55d3fdcf YW |
646 | (void) ndisc_router_process_default(link, rt); |
647 | (void) ndisc_router_process_options(link, rt); | |
d5017c84 YW |
648 | |
649 | return r; | |
1e7a0e21 LP |
650 | } |
651 | ||
652 | static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata) { | |
9d96e6c3 | 653 | Link *link = userdata; |
a13c50e7 | 654 | |
9d96e6c3 | 655 | assert(link); |
a13c50e7 | 656 | |
9d96e6c3 TG |
657 | if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) |
658 | return; | |
a13c50e7 | 659 | |
9d96e6c3 | 660 | switch (event) { |
1e7a0e21 LP |
661 | |
662 | case SD_NDISC_EVENT_ROUTER: | |
d5017c84 | 663 | (void) ndisc_router_handler(link, rt); |
1e7a0e21 LP |
664 | break; |
665 | ||
9d96e6c3 | 666 | case SD_NDISC_EVENT_TIMEOUT: |
962b0647 TG |
667 | link->ndisc_configured = true; |
668 | link_check_ready(link); | |
669 | ||
9d96e6c3 TG |
670 | break; |
671 | default: | |
672 | log_link_warning(link, "IPv6 Neighbor Discovery unknown event: %d", event); | |
a13c50e7 TG |
673 | } |
674 | } | |
675 | ||
676 | int ndisc_configure(Link *link) { | |
677 | int r; | |
678 | ||
1e7a0e21 LP |
679 | assert(link); |
680 | ||
681 | r = sd_ndisc_new(&link->ndisc); | |
682 | if (r < 0) | |
683 | return r; | |
a13c50e7 | 684 | |
1e7a0e21 | 685 | r = sd_ndisc_attach_event(link->ndisc, NULL, 0); |
a13c50e7 TG |
686 | if (r < 0) |
687 | return r; | |
688 | ||
1e7a0e21 | 689 | r = sd_ndisc_set_mac(link->ndisc, &link->mac); |
a13c50e7 TG |
690 | if (r < 0) |
691 | return r; | |
692 | ||
1e7a0e21 | 693 | r = sd_ndisc_set_ifindex(link->ndisc, link->ifindex); |
a13c50e7 TG |
694 | if (r < 0) |
695 | return r; | |
696 | ||
1e7a0e21 | 697 | r = sd_ndisc_set_callback(link->ndisc, ndisc_handler, link); |
a13c50e7 TG |
698 | if (r < 0) |
699 | return r; | |
700 | ||
1e7a0e21 LP |
701 | return 0; |
702 | } | |
703 | ||
704 | void ndisc_vacuum(Link *link) { | |
705 | NDiscRDNSS *r; | |
706 | NDiscDNSSL *d; | |
707 | Iterator i; | |
708 | usec_t time_now; | |
709 | ||
710 | assert(link); | |
711 | ||
712 | /* Removes all RDNSS and DNSSL entries whose validity time has passed */ | |
713 | ||
714 | time_now = now(clock_boottime_or_monotonic()); | |
715 | ||
716 | SET_FOREACH(r, link->ndisc_rdnss, i) | |
717 | if (r->valid_until < time_now) { | |
02affb4e | 718 | free(set_remove(link->ndisc_rdnss, r)); |
1e7a0e21 LP |
719 | link_dirty(link); |
720 | } | |
a13c50e7 | 721 | |
1e7a0e21 LP |
722 | SET_FOREACH(d, link->ndisc_dnssl, i) |
723 | if (d->valid_until < time_now) { | |
02affb4e | 724 | free(set_remove(link->ndisc_dnssl, d)); |
1e7a0e21 LP |
725 | link_dirty(link); |
726 | } | |
a13c50e7 | 727 | } |
c69305ff LP |
728 | |
729 | void ndisc_flush(Link *link) { | |
730 | assert(link); | |
731 | ||
732 | /* Removes all RDNSS and DNSSL entries, without exception */ | |
733 | ||
734 | link->ndisc_rdnss = set_free_free(link->ndisc_rdnss); | |
735 | link->ndisc_dnssl = set_free_free(link->ndisc_dnssl); | |
736 | } | |
e520ce64 SS |
737 | |
738 | int config_parse_ndisc_black_listed_prefix( | |
739 | const char *unit, | |
740 | const char *filename, | |
741 | unsigned line, | |
742 | const char *section, | |
743 | unsigned section_line, | |
744 | const char *lvalue, | |
745 | int ltype, | |
746 | const char *rvalue, | |
747 | void *data, | |
748 | void *userdata) { | |
749 | ||
750 | Network *network = data; | |
751 | const char *p; | |
752 | int r; | |
753 | ||
754 | assert(filename); | |
755 | assert(lvalue); | |
756 | assert(rvalue); | |
757 | assert(data); | |
758 | ||
759 | if (isempty(rvalue)) { | |
760 | network->ndisc_black_listed_prefix = set_free_free(network->ndisc_black_listed_prefix); | |
761 | return 0; | |
762 | } | |
763 | ||
764 | for (p = rvalue;;) { | |
765 | _cleanup_free_ char *n = NULL; | |
766 | _cleanup_free_ struct in6_addr *a = NULL; | |
767 | union in_addr_union ip; | |
768 | ||
769 | r = extract_first_word(&p, &n, NULL, 0); | |
770 | if (r < 0) { | |
771 | log_syntax(unit, LOG_ERR, filename, line, r, | |
772 | "Failed to parse NDISC black listed prefix, ignoring assignment: %s", | |
773 | rvalue); | |
774 | return 0; | |
775 | } | |
776 | if (r == 0) | |
777 | return 0; | |
778 | ||
779 | r = in_addr_from_string(AF_INET6, n, &ip); | |
780 | if (r < 0) { | |
781 | log_syntax(unit, LOG_ERR, filename, line, r, | |
782 | "NDISC black listed prefix is invalid, ignoring assignment: %s", n); | |
783 | continue; | |
784 | } | |
785 | ||
e4443f9b YW |
786 | if (set_contains(network->ndisc_black_listed_prefix, &ip.in6)) |
787 | continue; | |
788 | ||
e520ce64 SS |
789 | r = set_ensure_allocated(&network->ndisc_black_listed_prefix, &in6_addr_hash_ops); |
790 | if (r < 0) | |
791 | return log_oom(); | |
792 | ||
793 | a = newdup(struct in6_addr, &ip.in6, 1); | |
794 | if (!a) | |
795 | return log_oom(); | |
796 | ||
797 | r = set_put(network->ndisc_black_listed_prefix, a); | |
798 | if (r < 0) { | |
e4443f9b YW |
799 | log_syntax(unit, LOG_ERR, filename, line, r, |
800 | "Failed to store NDISC black listed prefix '%s', ignoring assignment: %m", n); | |
e520ce64 SS |
801 | continue; |
802 | } | |
803 | ||
804 | TAKE_PTR(a); | |
805 | } | |
806 | ||
807 | return 0; | |
808 | } |