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