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