]>
Commit | Line | Data |
---|---|---|
52ad194e | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
db2df898 | 2 | /*** |
b012e921 | 3 | Copyright © 2014 Intel Corporation. All rights reserved. |
db2df898 MP |
4 | ***/ |
5 | ||
6 | #include <netinet/icmp6.h> | |
7 | #include <netinet/in.h> | |
db2df898 MP |
8 | |
9 | #include "sd-ndisc.h" | |
10 | ||
11 | #include "alloc-util.h" | |
6e866b33 | 12 | #include "event-util.h" |
5a920b42 | 13 | #include "fd-util.h" |
db2df898 MP |
14 | #include "icmp6-util.h" |
15 | #include "in-addr-util.h" | |
bb4f798a | 16 | #include "memory-util.h" |
5a920b42 MP |
17 | #include "ndisc-internal.h" |
18 | #include "ndisc-router.h" | |
81c58355 | 19 | #include "random-util.h" |
db2df898 | 20 | #include "socket-util.h" |
6e866b33 | 21 | #include "string-table.h" |
4c89c718 | 22 | #include "string-util.h" |
db2df898 | 23 | |
81c58355 | 24 | #define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS) |
db2df898 | 25 | |
6e866b33 MB |
26 | static const char * const ndisc_event_table[_SD_NDISC_EVENT_MAX] = { |
27 | [SD_NDISC_EVENT_TIMEOUT] = "timeout", | |
28 | [SD_NDISC_EVENT_ROUTER] = "router", | |
29 | }; | |
30 | ||
31 | DEFINE_STRING_TABLE_LOOKUP(ndisc_event, sd_ndisc_event); | |
32 | ||
5a920b42 MP |
33 | static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) { |
34 | assert(ndisc); | |
6e866b33 | 35 | assert(event >= 0 && event < _SD_NDISC_EVENT_MAX); |
db2df898 | 36 | |
6e866b33 MB |
37 | if (!ndisc->callback) { |
38 | log_ndisc("Received '%s' event.", ndisc_event_to_string(event)); | |
5a920b42 | 39 | return; |
6e866b33 | 40 | } |
db2df898 | 41 | |
6e866b33 | 42 | log_ndisc("Invoking callback for '%s' event.", ndisc_event_to_string(event)); |
5a920b42 | 43 | ndisc->callback(ndisc, event, rt, ndisc->userdata); |
db2df898 MP |
44 | } |
45 | ||
5a920b42 MP |
46 | _public_ int sd_ndisc_set_callback( |
47 | sd_ndisc *nd, | |
48 | sd_ndisc_callback_t callback, | |
49 | void *userdata) { | |
db2df898 | 50 | |
5a920b42 | 51 | assert_return(nd, -EINVAL); |
db2df898 | 52 | |
db2df898 MP |
53 | nd->callback = callback; |
54 | nd->userdata = userdata; | |
55 | ||
56 | return 0; | |
57 | } | |
58 | ||
5a920b42 MP |
59 | _public_ int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) { |
60 | assert_return(nd, -EINVAL); | |
61 | assert_return(ifindex > 0, -EINVAL); | |
62 | assert_return(nd->fd < 0, -EBUSY); | |
db2df898 | 63 | |
5a920b42 | 64 | nd->ifindex = ifindex; |
db2df898 MP |
65 | return 0; |
66 | } | |
67 | ||
5a920b42 MP |
68 | _public_ int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) { |
69 | assert_return(nd, -EINVAL); | |
db2df898 MP |
70 | |
71 | if (mac_addr) | |
5a920b42 | 72 | nd->mac_addr = *mac_addr; |
db2df898 MP |
73 | else |
74 | zero(nd->mac_addr); | |
75 | ||
76 | return 0; | |
db2df898 MP |
77 | } |
78 | ||
5a920b42 | 79 | _public_ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) { |
db2df898 MP |
80 | int r; |
81 | ||
82 | assert_return(nd, -EINVAL); | |
5a920b42 | 83 | assert_return(nd->fd < 0, -EBUSY); |
db2df898 MP |
84 | assert_return(!nd->event, -EBUSY); |
85 | ||
86 | if (event) | |
87 | nd->event = sd_event_ref(event); | |
88 | else { | |
89 | r = sd_event_default(&nd->event); | |
90 | if (r < 0) | |
91 | return 0; | |
92 | } | |
93 | ||
94 | nd->event_priority = priority; | |
95 | ||
96 | return 0; | |
97 | } | |
98 | ||
5a920b42 MP |
99 | _public_ int sd_ndisc_detach_event(sd_ndisc *nd) { |
100 | ||
db2df898 | 101 | assert_return(nd, -EINVAL); |
5a920b42 | 102 | assert_return(nd->fd < 0, -EBUSY); |
db2df898 MP |
103 | |
104 | nd->event = sd_event_unref(nd->event); | |
db2df898 MP |
105 | return 0; |
106 | } | |
107 | ||
5a920b42 MP |
108 | _public_ sd_event *sd_ndisc_get_event(sd_ndisc *nd) { |
109 | assert_return(nd, NULL); | |
db2df898 MP |
110 | |
111 | return nd->event; | |
112 | } | |
113 | ||
6e866b33 | 114 | static void ndisc_reset(sd_ndisc *nd) { |
db2df898 MP |
115 | assert(nd); |
116 | ||
6e866b33 MB |
117 | (void) event_source_disable(nd->timeout_event_source); |
118 | (void) event_source_disable(nd->timeout_no_ra); | |
81c58355 | 119 | nd->retransmit_time = 0; |
5a920b42 MP |
120 | nd->recv_event_source = sd_event_source_unref(nd->recv_event_source); |
121 | nd->fd = safe_close(nd->fd); | |
db2df898 MP |
122 | } |
123 | ||
6e866b33 MB |
124 | static sd_ndisc *ndisc_free(sd_ndisc *nd) { |
125 | assert(nd); | |
db2df898 | 126 | |
6e866b33 MB |
127 | nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); |
128 | nd->timeout_no_ra = sd_event_source_unref(nd->timeout_no_ra); | |
db2df898 | 129 | |
5a920b42 | 130 | ndisc_reset(nd); |
db2df898 | 131 | sd_ndisc_detach_event(nd); |
8a584da2 | 132 | return mfree(nd); |
db2df898 MP |
133 | } |
134 | ||
6e866b33 MB |
135 | DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc, sd_ndisc, ndisc_free); |
136 | ||
5a920b42 | 137 | _public_ int sd_ndisc_new(sd_ndisc **ret) { |
4c89c718 | 138 | _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; |
db2df898 | 139 | |
5a920b42 | 140 | assert_return(ret, -EINVAL); |
db2df898 | 141 | |
6e866b33 | 142 | nd = new(sd_ndisc, 1); |
db2df898 MP |
143 | if (!nd) |
144 | return -ENOMEM; | |
145 | ||
6e866b33 MB |
146 | *nd = (sd_ndisc) { |
147 | .n_ref = 1, | |
148 | .fd = -1, | |
149 | }; | |
db2df898 | 150 | |
b012e921 | 151 | *ret = TAKE_PTR(nd); |
db2df898 MP |
152 | |
153 | return 0; | |
154 | } | |
155 | ||
5a920b42 | 156 | _public_ int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) { |
db2df898 MP |
157 | assert_return(nd, -EINVAL); |
158 | assert_return(mtu, -EINVAL); | |
159 | ||
160 | if (nd->mtu == 0) | |
5a920b42 | 161 | return -ENODATA; |
db2df898 MP |
162 | |
163 | *mtu = nd->mtu; | |
db2df898 MP |
164 | return 0; |
165 | } | |
166 | ||
5a920b42 MP |
167 | _public_ int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret) { |
168 | assert_return(nd, -EINVAL); | |
169 | assert_return(ret, -EINVAL); | |
db2df898 | 170 | |
5a920b42 MP |
171 | if (nd->hop_limit == 0) |
172 | return -ENODATA; | |
db2df898 | 173 | |
5a920b42 | 174 | *ret = nd->hop_limit; |
db2df898 MP |
175 | return 0; |
176 | } | |
177 | ||
5a920b42 | 178 | static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) { |
db2df898 MP |
179 | int r; |
180 | ||
181 | assert(nd); | |
5a920b42 | 182 | assert(rt); |
db2df898 | 183 | |
5a920b42 MP |
184 | r = ndisc_router_parse(rt); |
185 | if (r == -EBADMSG) /* Bad packet */ | |
db2df898 | 186 | return 0; |
db2df898 | 187 | if (r < 0) |
5a920b42 | 188 | return 0; |
db2df898 | 189 | |
5a920b42 MP |
190 | /* Update global variables we keep */ |
191 | if (rt->mtu > 0) | |
192 | nd->mtu = rt->mtu; | |
193 | if (rt->hop_limit > 0) | |
194 | nd->hop_limit = rt->hop_limit; | |
db2df898 | 195 | |
5a920b42 MP |
196 | log_ndisc("Received Router Advertisement: flags %s preference %s lifetime %" PRIu16 " sec", |
197 | rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none", | |
198 | rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium", | |
199 | rt->lifetime); | |
db2df898 | 200 | |
5a920b42 | 201 | ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt); |
db2df898 MP |
202 | return 0; |
203 | } | |
204 | ||
5a920b42 MP |
205 | static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { |
206 | _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL; | |
db2df898 | 207 | sd_ndisc *nd = userdata; |
81c58355 MB |
208 | ssize_t buflen; |
209 | int r; | |
210 | _cleanup_free_ char *addr = NULL; | |
db2df898 MP |
211 | |
212 | assert(s); | |
213 | assert(nd); | |
214 | assert(nd->event); | |
215 | ||
aa27b158 MP |
216 | buflen = next_datagram_size_fd(fd); |
217 | if (buflen < 0) | |
5a920b42 | 218 | return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m"); |
4c89c718 | 219 | |
5a920b42 MP |
220 | rt = ndisc_router_new(buflen); |
221 | if (!rt) | |
db2df898 MP |
222 | return -ENOMEM; |
223 | ||
81c58355 MB |
224 | r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address, |
225 | &rt->timestamp); | |
226 | if (r < 0) { | |
227 | switch (r) { | |
228 | case -EADDRNOTAVAIL: | |
229 | (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &rt->address, &addr); | |
230 | log_ndisc("Received RA from non-link-local address %s. Ignoring", addr); | |
231 | break; | |
232 | ||
233 | case -EMULTIHOP: | |
234 | log_ndisc("Received RA with invalid hop limit. Ignoring."); | |
235 | break; | |
236 | ||
237 | case -EPFNOSUPPORT: | |
6e866b33 MB |
238 | log_ndisc("Received invalid source address from ICMPv6 socket. Ignoring."); |
239 | break; | |
240 | ||
241 | case -EAGAIN: /* ignore spurious wakeups */ | |
242 | break; | |
243 | ||
244 | default: | |
245 | log_ndisc_errno(r, "Unexpected error while reading from ICMPv6, ignoring: %m"); | |
81c58355 | 246 | break; |
4c89c718 | 247 | } |
db2df898 | 248 | |
81c58355 | 249 | return 0; |
db2df898 MP |
250 | } |
251 | ||
6e866b33 | 252 | (void) event_source_disable(nd->timeout_event_source); |
db2df898 | 253 | |
5a920b42 | 254 | return ndisc_handle_datagram(nd, rt); |
db2df898 MP |
255 | } |
256 | ||
81c58355 MB |
257 | static usec_t ndisc_timeout_compute_random(usec_t val) { |
258 | /* compute a time that is random within ±10% of the given value */ | |
259 | return val - val / 10 + | |
260 | (random_u64() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC; | |
261 | } | |
262 | ||
5a920b42 | 263 | static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) { |
6e866b33 | 264 | char time_string[FORMAT_TIMESPAN_MAX]; |
db2df898 | 265 | sd_ndisc *nd = userdata; |
81c58355 | 266 | usec_t time_now; |
db2df898 MP |
267 | int r; |
268 | ||
269 | assert(s); | |
270 | assert(nd); | |
271 | assert(nd->event); | |
272 | ||
81c58355 MB |
273 | assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0); |
274 | ||
81c58355 MB |
275 | if (!nd->retransmit_time) |
276 | nd->retransmit_time = ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL); | |
277 | else { | |
278 | if (nd->retransmit_time > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 2) | |
279 | nd->retransmit_time = ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL); | |
280 | else | |
281 | nd->retransmit_time += ndisc_timeout_compute_random(nd->retransmit_time); | |
5a920b42 | 282 | } |
db2df898 | 283 | |
6e866b33 MB |
284 | r = event_reset_time(nd->event, &nd->timeout_event_source, |
285 | clock_boottime_or_monotonic(), | |
286 | time_now + nd->retransmit_time, 10 * USEC_PER_MSEC, | |
287 | ndisc_timeout, nd, | |
288 | nd->event_priority, "ndisc-timeout-no-ra", true); | |
81c58355 | 289 | if (r < 0) |
5a920b42 | 290 | goto fail; |
db2df898 | 291 | |
81c58355 | 292 | r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr); |
5a920b42 | 293 | if (r < 0) { |
81c58355 | 294 | log_ndisc_errno(r, "Error sending Router Solicitation: %m"); |
5a920b42 | 295 | goto fail; |
db2df898 MP |
296 | } |
297 | ||
81c58355 MB |
298 | log_ndisc("Sent Router Solicitation, next solicitation in %s", |
299 | format_timespan(time_string, FORMAT_TIMESPAN_MAX, | |
300 | nd->retransmit_time, USEC_PER_SEC)); | |
301 | ||
db2df898 | 302 | return 0; |
5a920b42 MP |
303 | |
304 | fail: | |
6e866b33 | 305 | (void) sd_ndisc_stop(nd); |
5a920b42 | 306 | return 0; |
db2df898 MP |
307 | } |
308 | ||
81c58355 MB |
309 | static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata) { |
310 | sd_ndisc *nd = userdata; | |
311 | ||
312 | assert(s); | |
313 | assert(nd); | |
314 | ||
315 | log_ndisc("No RA received before link confirmation timeout"); | |
316 | ||
6e866b33 | 317 | (void) event_source_disable(nd->timeout_no_ra); |
81c58355 MB |
318 | ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL); |
319 | ||
320 | return 0; | |
321 | } | |
322 | ||
5a920b42 | 323 | _public_ int sd_ndisc_stop(sd_ndisc *nd) { |
db2df898 | 324 | assert_return(nd, -EINVAL); |
db2df898 | 325 | |
5a920b42 MP |
326 | if (nd->fd < 0) |
327 | return 0; | |
db2df898 | 328 | |
5a920b42 | 329 | log_ndisc("Stopping IPv6 Router Solicitation client"); |
db2df898 | 330 | |
5a920b42 MP |
331 | ndisc_reset(nd); |
332 | return 1; | |
db2df898 MP |
333 | } |
334 | ||
5a920b42 | 335 | _public_ int sd_ndisc_start(sd_ndisc *nd) { |
db2df898 | 336 | int r; |
81c58355 | 337 | usec_t time_now; |
db2df898 | 338 | |
5a920b42 MP |
339 | assert_return(nd, -EINVAL); |
340 | assert_return(nd->event, -EINVAL); | |
341 | assert_return(nd->ifindex > 0, -EINVAL); | |
db2df898 | 342 | |
5a920b42 MP |
343 | if (nd->fd >= 0) |
344 | return 0; | |
db2df898 | 345 | |
5a920b42 | 346 | assert(!nd->recv_event_source); |
db2df898 | 347 | |
81c58355 MB |
348 | r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now); |
349 | if (r < 0) | |
350 | goto fail; | |
351 | ||
5a920b42 MP |
352 | nd->fd = icmp6_bind_router_solicitation(nd->ifindex); |
353 | if (nd->fd < 0) | |
354 | return nd->fd; | |
db2df898 | 355 | |
5a920b42 | 356 | r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd); |
db2df898 | 357 | if (r < 0) |
5a920b42 | 358 | goto fail; |
db2df898 | 359 | |
5a920b42 | 360 | r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority); |
db2df898 | 361 | if (r < 0) |
5a920b42 | 362 | goto fail; |
db2df898 | 363 | |
5a920b42 | 364 | (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message"); |
db2df898 | 365 | |
6e866b33 MB |
366 | r = event_reset_time(nd->event, &nd->timeout_event_source, |
367 | clock_boottime_or_monotonic(), | |
368 | 0, 0, | |
369 | ndisc_timeout, nd, | |
370 | nd->event_priority, "ndisc-timeout", true); | |
db2df898 | 371 | if (r < 0) |
5a920b42 | 372 | goto fail; |
db2df898 | 373 | |
6e866b33 MB |
374 | r = event_reset_time(nd->event, &nd->timeout_no_ra, |
375 | clock_boottime_or_monotonic(), | |
376 | time_now + NDISC_TIMEOUT_NO_RA_USEC, 10 * USEC_PER_MSEC, | |
377 | ndisc_timeout_no_ra, nd, | |
378 | nd->event_priority, "ndisc-timeout-no-ra", true); | |
db2df898 | 379 | if (r < 0) |
5a920b42 | 380 | goto fail; |
db2df898 | 381 | |
5a920b42 MP |
382 | log_ndisc("Started IPv6 Router Solicitation client"); |
383 | return 1; | |
db2df898 | 384 | |
5a920b42 MP |
385 | fail: |
386 | ndisc_reset(nd); | |
db2df898 MP |
387 | return r; |
388 | } |