]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | /*** | |
3 | Copyright © 2014 Intel Corporation. All rights reserved. | |
4 | ***/ | |
5 | ||
6 | #include <netinet/icmp6.h> | |
7 | #include <netinet/in.h> | |
8 | ||
9 | #include "sd-ndisc.h" | |
10 | ||
11 | #include "alloc-util.h" | |
12 | #include "ether-addr-util.h" | |
13 | #include "event-util.h" | |
14 | #include "fd-util.h" | |
15 | #include "icmp6-util.h" | |
16 | #include "in-addr-util.h" | |
17 | #include "memory-util.h" | |
18 | #include "ndisc-internal.h" | |
19 | #include "ndisc-neighbor-internal.h" | |
20 | #include "ndisc-redirect-internal.h" | |
21 | #include "ndisc-router-internal.h" | |
22 | #include "network-common.h" | |
23 | #include "random-util.h" | |
24 | #include "set.h" | |
25 | #include "socket-util.h" | |
26 | #include "string-table.h" | |
27 | #include "string-util.h" | |
28 | ||
29 | #define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS) | |
30 | ||
31 | static const char * const ndisc_event_table[_SD_NDISC_EVENT_MAX] = { | |
32 | [SD_NDISC_EVENT_TIMEOUT] = "timeout", | |
33 | [SD_NDISC_EVENT_ROUTER] = "router", | |
34 | [SD_NDISC_EVENT_NEIGHBOR] = "neighbor", | |
35 | [SD_NDISC_EVENT_REDIRECT] = "redirect", | |
36 | }; | |
37 | ||
38 | DEFINE_STRING_TABLE_LOOKUP(ndisc_event, sd_ndisc_event_t); | |
39 | ||
40 | static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event_t event, void *message) { | |
41 | assert(ndisc); | |
42 | assert(event >= 0 && event < _SD_NDISC_EVENT_MAX); | |
43 | ||
44 | if (!ndisc->callback) | |
45 | return (void) log_ndisc(ndisc, "Received '%s' event.", ndisc_event_to_string(event)); | |
46 | ||
47 | log_ndisc(ndisc, "Invoking callback for '%s' event.", ndisc_event_to_string(event)); | |
48 | ndisc->callback(ndisc, event, message, ndisc->userdata); | |
49 | } | |
50 | ||
51 | int sd_ndisc_is_running(sd_ndisc *nd) { | |
52 | if (!nd) | |
53 | return false; | |
54 | ||
55 | return sd_event_source_get_enabled(nd->recv_event_source, NULL) > 0; | |
56 | } | |
57 | ||
58 | int sd_ndisc_set_callback( | |
59 | sd_ndisc *nd, | |
60 | sd_ndisc_callback_t callback, | |
61 | void *userdata) { | |
62 | ||
63 | assert_return(nd, -EINVAL); | |
64 | ||
65 | nd->callback = callback; | |
66 | nd->userdata = userdata; | |
67 | ||
68 | return 0; | |
69 | } | |
70 | ||
71 | int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) { | |
72 | assert_return(nd, -EINVAL); | |
73 | assert_return(ifindex > 0, -EINVAL); | |
74 | assert_return(!sd_ndisc_is_running(nd), -EBUSY); | |
75 | ||
76 | nd->ifindex = ifindex; | |
77 | return 0; | |
78 | } | |
79 | ||
80 | int sd_ndisc_set_ifname(sd_ndisc *nd, const char *ifname) { | |
81 | assert_return(nd, -EINVAL); | |
82 | assert_return(ifname, -EINVAL); | |
83 | ||
84 | if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE)) | |
85 | return -EINVAL; | |
86 | ||
87 | return free_and_strdup(&nd->ifname, ifname); | |
88 | } | |
89 | ||
90 | int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret) { | |
91 | int r; | |
92 | ||
93 | assert_return(nd, -EINVAL); | |
94 | ||
95 | r = get_ifname(nd->ifindex, &nd->ifname); | |
96 | if (r < 0) | |
97 | return r; | |
98 | ||
99 | if (ret) | |
100 | *ret = nd->ifname; | |
101 | ||
102 | return 0; | |
103 | } | |
104 | ||
105 | int sd_ndisc_set_link_local_address(sd_ndisc *nd, const struct in6_addr *addr) { | |
106 | assert_return(nd, -EINVAL); | |
107 | assert_return(!addr || in6_addr_is_link_local(addr), -EINVAL); | |
108 | ||
109 | if (addr) | |
110 | nd->link_local_addr = *addr; | |
111 | else | |
112 | zero(nd->link_local_addr); | |
113 | ||
114 | return 0; | |
115 | } | |
116 | ||
117 | int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) { | |
118 | assert_return(nd, -EINVAL); | |
119 | ||
120 | if (mac_addr) | |
121 | nd->mac_addr = *mac_addr; | |
122 | else | |
123 | zero(nd->mac_addr); | |
124 | ||
125 | return 0; | |
126 | } | |
127 | ||
128 | int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) { | |
129 | int r; | |
130 | ||
131 | assert_return(nd, -EINVAL); | |
132 | assert_return(!sd_ndisc_is_running(nd), -EBUSY); | |
133 | assert_return(!nd->event, -EBUSY); | |
134 | ||
135 | if (event) | |
136 | nd->event = sd_event_ref(event); | |
137 | else { | |
138 | r = sd_event_default(&nd->event); | |
139 | if (r < 0) | |
140 | return 0; | |
141 | } | |
142 | ||
143 | nd->event_priority = priority; | |
144 | ||
145 | return 0; | |
146 | } | |
147 | ||
148 | int sd_ndisc_detach_event(sd_ndisc *nd) { | |
149 | ||
150 | assert_return(nd, -EINVAL); | |
151 | assert_return(!sd_ndisc_is_running(nd), -EBUSY); | |
152 | ||
153 | nd->event = sd_event_unref(nd->event); | |
154 | return 0; | |
155 | } | |
156 | ||
157 | sd_event *sd_ndisc_get_event(sd_ndisc *nd) { | |
158 | assert_return(nd, NULL); | |
159 | ||
160 | return nd->event; | |
161 | } | |
162 | ||
163 | static void ndisc_reset(sd_ndisc *nd) { | |
164 | assert(nd); | |
165 | ||
166 | (void) event_source_disable(nd->timeout_event_source); | |
167 | (void) event_source_disable(nd->timeout_no_ra); | |
168 | nd->retransmit_time = 0; | |
169 | nd->recv_event_source = sd_event_source_disable_unref(nd->recv_event_source); | |
170 | nd->fd = safe_close(nd->fd); | |
171 | } | |
172 | ||
173 | static sd_ndisc *ndisc_free(sd_ndisc *nd) { | |
174 | assert(nd); | |
175 | ||
176 | ndisc_reset(nd); | |
177 | ||
178 | sd_event_source_unref(nd->timeout_event_source); | |
179 | sd_event_source_unref(nd->timeout_no_ra); | |
180 | sd_ndisc_detach_event(nd); | |
181 | ||
182 | free(nd->ifname); | |
183 | return mfree(nd); | |
184 | } | |
185 | ||
186 | DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc, sd_ndisc, ndisc_free); | |
187 | ||
188 | int sd_ndisc_new(sd_ndisc **ret) { | |
189 | _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; | |
190 | ||
191 | assert_return(ret, -EINVAL); | |
192 | ||
193 | nd = new(sd_ndisc, 1); | |
194 | if (!nd) | |
195 | return -ENOMEM; | |
196 | ||
197 | *nd = (sd_ndisc) { | |
198 | .n_ref = 1, | |
199 | .fd = -EBADF, | |
200 | }; | |
201 | ||
202 | *ret = TAKE_PTR(nd); | |
203 | ||
204 | return 0; | |
205 | } | |
206 | ||
207 | static int ndisc_handle_router(sd_ndisc *nd, ICMP6Packet *packet) { | |
208 | _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL; | |
209 | int r; | |
210 | ||
211 | assert(nd); | |
212 | assert(packet); | |
213 | ||
214 | rt = ndisc_router_new(packet); | |
215 | if (!rt) | |
216 | return -ENOMEM; | |
217 | ||
218 | r = ndisc_router_parse(nd, rt); | |
219 | if (r < 0) | |
220 | return r; | |
221 | ||
222 | (void) event_source_disable(nd->timeout_event_source); | |
223 | (void) event_source_disable(nd->timeout_no_ra); | |
224 | ||
225 | if (DEBUG_LOGGING) { | |
226 | _cleanup_free_ char *s = NULL; | |
227 | struct in6_addr a; | |
228 | uint64_t flags; | |
229 | uint8_t pref; | |
230 | usec_t lifetime; | |
231 | ||
232 | r = sd_ndisc_router_get_sender_address(rt, &a); | |
233 | if (r < 0) | |
234 | return r; | |
235 | ||
236 | r = sd_ndisc_router_get_flags(rt, &flags); | |
237 | if (r < 0) | |
238 | return r; | |
239 | ||
240 | r = ndisc_router_flags_to_string(flags, &s); | |
241 | if (r < 0) | |
242 | return r; | |
243 | ||
244 | r = sd_ndisc_router_get_preference(rt, &pref); | |
245 | if (r < 0) | |
246 | return r; | |
247 | ||
248 | r = sd_ndisc_router_get_lifetime(rt, &lifetime); | |
249 | if (r < 0) | |
250 | return r; | |
251 | ||
252 | log_ndisc(nd, "Received Router Advertisement from %s: flags=0x%0*"PRIx64"(%s), preference=%s, lifetime=%s", | |
253 | IN6_ADDR_TO_STRING(&a), | |
254 | flags & UINT64_C(0x00ffffffffffff00) ? 14 : 2, flags, /* suppress too many zeros if no extension */ | |
255 | s ?: "none", | |
256 | ndisc_router_preference_to_string(pref), | |
257 | FORMAT_TIMESPAN(lifetime, USEC_PER_SEC)); | |
258 | } | |
259 | ||
260 | ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt); | |
261 | return 0; | |
262 | } | |
263 | ||
264 | static int ndisc_handle_neighbor(sd_ndisc *nd, ICMP6Packet *packet) { | |
265 | _cleanup_(sd_ndisc_neighbor_unrefp) sd_ndisc_neighbor *na = NULL; | |
266 | int r; | |
267 | ||
268 | assert(nd); | |
269 | assert(packet); | |
270 | ||
271 | na = ndisc_neighbor_new(packet); | |
272 | if (!na) | |
273 | return -ENOMEM; | |
274 | ||
275 | r = ndisc_neighbor_parse(nd, na); | |
276 | if (r < 0) | |
277 | return r; | |
278 | ||
279 | if (DEBUG_LOGGING) { | |
280 | struct in6_addr a; | |
281 | ||
282 | r = sd_ndisc_neighbor_get_sender_address(na, &a); | |
283 | if (r < 0) | |
284 | return r; | |
285 | ||
286 | log_ndisc(nd, "Received Neighbor Advertisement from %s: Router=%s, Solicited=%s, Override=%s", | |
287 | IN6_ADDR_TO_STRING(&a), | |
288 | yes_no(sd_ndisc_neighbor_is_router(na) > 0), | |
289 | yes_no(sd_ndisc_neighbor_is_solicited(na) > 0), | |
290 | yes_no(sd_ndisc_neighbor_is_override(na) > 0)); | |
291 | } | |
292 | ||
293 | ndisc_callback(nd, SD_NDISC_EVENT_NEIGHBOR, na); | |
294 | return 0; | |
295 | } | |
296 | ||
297 | static int ndisc_handle_redirect(sd_ndisc *nd, ICMP6Packet *packet) { | |
298 | _cleanup_(sd_ndisc_redirect_unrefp) sd_ndisc_redirect *rd = NULL; | |
299 | int r; | |
300 | ||
301 | assert(nd); | |
302 | assert(packet); | |
303 | ||
304 | rd = ndisc_redirect_new(packet); | |
305 | if (!rd) | |
306 | return -ENOMEM; | |
307 | ||
308 | r = ndisc_redirect_parse(nd, rd); | |
309 | if (r < 0) | |
310 | return r; | |
311 | ||
312 | if (DEBUG_LOGGING) { | |
313 | struct in6_addr sender, target, dest; | |
314 | ||
315 | r = sd_ndisc_redirect_get_sender_address(rd, &sender); | |
316 | if (r < 0) | |
317 | return r; | |
318 | ||
319 | r = sd_ndisc_redirect_get_target_address(rd, &target); | |
320 | if (r < 0) | |
321 | return r; | |
322 | ||
323 | r = sd_ndisc_redirect_get_destination_address(rd, &dest); | |
324 | if (r < 0) | |
325 | return r; | |
326 | ||
327 | log_ndisc(nd, "Received Redirect message from %s: Target=%s, Destination=%s", | |
328 | IN6_ADDR_TO_STRING(&sender), | |
329 | IN6_ADDR_TO_STRING(&target), | |
330 | IN6_ADDR_TO_STRING(&dest)); | |
331 | } | |
332 | ||
333 | ndisc_callback(nd, SD_NDISC_EVENT_REDIRECT, rd); | |
334 | return 0; | |
335 | } | |
336 | ||
337 | static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { | |
338 | _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; | |
339 | sd_ndisc *nd = ASSERT_PTR(userdata); | |
340 | int r; | |
341 | ||
342 | assert(s); | |
343 | assert(nd->event); | |
344 | ||
345 | r = icmp6_packet_receive(fd, &packet); | |
346 | if (r < 0) { | |
347 | log_ndisc_errno(nd, r, "Failed to receive ICMPv6 packet, ignoring: %m"); | |
348 | return 0; | |
349 | } | |
350 | ||
351 | /* The function icmp6_receive() accepts the null source address, but RFC 4861 Section 6.1.2 states | |
352 | * that hosts MUST discard messages with the null source address. */ | |
353 | if (in6_addr_is_null(&packet->sender_address)) { | |
354 | log_ndisc(nd, "Received an ICMPv6 packet from null address, ignoring."); | |
355 | return 0; | |
356 | } | |
357 | ||
358 | if (in6_addr_equal(&packet->sender_address, &nd->link_local_addr)) { | |
359 | log_ndisc(nd, "Received an ICMPv6 packet sent by the same interface, ignoring."); | |
360 | return 0; | |
361 | } | |
362 | ||
363 | r = icmp6_packet_get_type(packet); | |
364 | if (r < 0) { | |
365 | log_ndisc_errno(nd, r, "Received an invalid ICMPv6 packet, ignoring: %m"); | |
366 | return 0; | |
367 | } | |
368 | ||
369 | switch (r) { | |
370 | case ND_ROUTER_ADVERT: | |
371 | (void) ndisc_handle_router(nd, packet); | |
372 | break; | |
373 | ||
374 | case ND_NEIGHBOR_ADVERT: | |
375 | (void) ndisc_handle_neighbor(nd, packet); | |
376 | break; | |
377 | ||
378 | case ND_REDIRECT: | |
379 | (void) ndisc_handle_redirect(nd, packet); | |
380 | break; | |
381 | ||
382 | default: | |
383 | log_ndisc(nd, "Received an ICMPv6 packet with unexpected type %i, ignoring.", r); | |
384 | } | |
385 | ||
386 | return 0; | |
387 | } | |
388 | ||
389 | static int ndisc_send_router_solicitation(sd_ndisc *nd) { | |
390 | static const struct nd_router_solicit header = { | |
391 | .nd_rs_type = ND_ROUTER_SOLICIT, | |
392 | }; | |
393 | ||
394 | _cleanup_set_free_ Set *options = NULL; | |
395 | int r; | |
396 | ||
397 | assert(nd); | |
398 | ||
399 | if (!ether_addr_is_null(&nd->mac_addr)) { | |
400 | r = ndisc_option_set_link_layer_address(&options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, &nd->mac_addr); | |
401 | if (r < 0) | |
402 | return r; | |
403 | } | |
404 | ||
405 | return ndisc_send(nd->fd, &IN6_ADDR_ALL_ROUTERS_MULTICAST, &header.nd_rs_hdr, options, USEC_INFINITY); | |
406 | } | |
407 | ||
408 | static usec_t ndisc_timeout_compute_random(usec_t val) { | |
409 | /* compute a time that is random within ±10% of the given value */ | |
410 | return val - val / 10 + | |
411 | (random_u64() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC; | |
412 | } | |
413 | ||
414 | static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) { | |
415 | sd_ndisc *nd = ASSERT_PTR(userdata); | |
416 | usec_t time_now; | |
417 | int r; | |
418 | ||
419 | assert(s); | |
420 | assert(nd->event); | |
421 | ||
422 | assert_se(sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now) >= 0); | |
423 | ||
424 | if (!nd->retransmit_time) | |
425 | nd->retransmit_time = ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL); | |
426 | else { | |
427 | if (nd->retransmit_time > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 2) | |
428 | nd->retransmit_time = ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL); | |
429 | else | |
430 | nd->retransmit_time += ndisc_timeout_compute_random(nd->retransmit_time); | |
431 | } | |
432 | ||
433 | r = event_reset_time(nd->event, &nd->timeout_event_source, | |
434 | CLOCK_BOOTTIME, | |
435 | time_now + nd->retransmit_time, 10 * USEC_PER_MSEC, | |
436 | ndisc_timeout, nd, | |
437 | nd->event_priority, "ndisc-timeout-no-ra", true); | |
438 | if (r < 0) | |
439 | goto fail; | |
440 | ||
441 | r = ndisc_send_router_solicitation(nd); | |
442 | if (r < 0) | |
443 | log_ndisc_errno(nd, r, "Failed to send Router Solicitation, next solicitation in %s, ignoring: %m", | |
444 | FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC)); | |
445 | else | |
446 | log_ndisc(nd, "Sent Router Solicitation, next solicitation in %s", | |
447 | FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC)); | |
448 | ||
449 | return 0; | |
450 | ||
451 | fail: | |
452 | (void) sd_ndisc_stop(nd); | |
453 | return 0; | |
454 | } | |
455 | ||
456 | static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata) { | |
457 | sd_ndisc *nd = ASSERT_PTR(userdata); | |
458 | ||
459 | assert(s); | |
460 | ||
461 | log_ndisc(nd, "No RA received before link confirmation timeout"); | |
462 | ||
463 | (void) event_source_disable(nd->timeout_no_ra); | |
464 | ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL); | |
465 | ||
466 | return 0; | |
467 | } | |
468 | ||
469 | int sd_ndisc_stop(sd_ndisc *nd) { | |
470 | if (!nd) | |
471 | return 0; | |
472 | ||
473 | if (!sd_ndisc_is_running(nd)) | |
474 | return 0; | |
475 | ||
476 | log_ndisc(nd, "Stopping IPv6 Router Solicitation client"); | |
477 | ||
478 | ndisc_reset(nd); | |
479 | return 1; | |
480 | } | |
481 | ||
482 | static int ndisc_setup_recv_event(sd_ndisc *nd) { | |
483 | int r; | |
484 | ||
485 | assert(nd); | |
486 | assert(nd->event); | |
487 | assert(nd->ifindex > 0); | |
488 | ||
489 | _cleanup_close_ int fd = -EBADF; | |
490 | fd = icmp6_bind(nd->ifindex, /* is_router = */ false); | |
491 | if (fd < 0) | |
492 | return fd; | |
493 | ||
494 | _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; | |
495 | r = sd_event_add_io(nd->event, &s, fd, EPOLLIN, ndisc_recv, nd); | |
496 | if (r < 0) | |
497 | return r; | |
498 | ||
499 | r = sd_event_source_set_priority(s, nd->event_priority); | |
500 | if (r < 0) | |
501 | return r; | |
502 | ||
503 | (void) sd_event_source_set_description(s, "ndisc-receive-router-message"); | |
504 | ||
505 | nd->fd = TAKE_FD(fd); | |
506 | nd->recv_event_source = TAKE_PTR(s); | |
507 | return 1; | |
508 | } | |
509 | ||
510 | static int ndisc_setup_timer(sd_ndisc *nd) { | |
511 | int r; | |
512 | ||
513 | assert(nd); | |
514 | assert(nd->event); | |
515 | ||
516 | r = event_reset_time_relative(nd->event, &nd->timeout_event_source, | |
517 | CLOCK_BOOTTIME, | |
518 | USEC_PER_SEC / 2, 1 * USEC_PER_SEC, /* See RFC 8415 sec. 18.2.1 */ | |
519 | ndisc_timeout, nd, | |
520 | nd->event_priority, "ndisc-timeout", true); | |
521 | if (r < 0) | |
522 | return r; | |
523 | ||
524 | r = event_reset_time_relative(nd->event, &nd->timeout_no_ra, | |
525 | CLOCK_BOOTTIME, | |
526 | NDISC_TIMEOUT_NO_RA_USEC, 10 * USEC_PER_MSEC, | |
527 | ndisc_timeout_no_ra, nd, | |
528 | nd->event_priority, "ndisc-timeout-no-ra", true); | |
529 | if (r < 0) | |
530 | return r; | |
531 | ||
532 | return 0; | |
533 | } | |
534 | ||
535 | int sd_ndisc_start(sd_ndisc *nd) { | |
536 | int r; | |
537 | ||
538 | assert_return(nd, -EINVAL); | |
539 | assert_return(nd->event, -EINVAL); | |
540 | assert_return(nd->ifindex > 0, -EINVAL); | |
541 | ||
542 | if (sd_ndisc_is_running(nd)) | |
543 | return 0; | |
544 | ||
545 | r = ndisc_setup_recv_event(nd); | |
546 | if (r < 0) | |
547 | goto fail; | |
548 | ||
549 | r = ndisc_setup_timer(nd); | |
550 | if (r < 0) | |
551 | goto fail; | |
552 | ||
553 | log_ndisc(nd, "Started IPv6 Router Solicitation client"); | |
554 | return 1; | |
555 | ||
556 | fail: | |
557 | ndisc_reset(nd); | |
558 | return r; | |
559 | } |