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