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 13 : DEFINE_STRING_TABLE_LOOKUP(ndisc_event, sd_ndisc_event);
32 :
33 5 : static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) {
34 5 : assert(ndisc);
35 5 : assert(event >= 0 && event < _SD_NDISC_EVENT_MAX);
36 :
37 5 : if (!ndisc->callback) {
38 0 : log_ndisc("Received '%s' event.", ndisc_event_to_string(event));
39 0 : return;
40 : }
41 :
42 5 : log_ndisc("Invoking callback for '%s' event.", ndisc_event_to_string(event));
43 5 : ndisc->callback(ndisc, event, rt, ndisc->userdata);
44 : }
45 :
46 1 : _public_ int sd_ndisc_set_callback(
47 : sd_ndisc *nd,
48 : sd_ndisc_callback_t callback,
49 : void *userdata) {
50 :
51 1 : assert_return(nd, -EINVAL);
52 :
53 1 : nd->callback = callback;
54 1 : nd->userdata = userdata;
55 :
56 1 : return 0;
57 : }
58 :
59 2 : _public_ int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
60 2 : assert_return(nd, -EINVAL);
61 2 : assert_return(ifindex > 0, -EINVAL);
62 2 : assert_return(nd->fd < 0, -EBUSY);
63 :
64 2 : nd->ifindex = ifindex;
65 2 : return 0;
66 : }
67 :
68 2 : _public_ int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
69 2 : assert_return(nd, -EINVAL);
70 :
71 2 : if (mac_addr)
72 2 : nd->mac_addr = *mac_addr;
73 : else
74 0 : zero(nd->mac_addr);
75 :
76 2 : return 0;
77 : }
78 :
79 2 : _public_ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
80 : int r;
81 :
82 2 : assert_return(nd, -EINVAL);
83 2 : assert_return(nd->fd < 0, -EBUSY);
84 2 : assert_return(!nd->event, -EBUSY);
85 :
86 2 : if (event)
87 2 : 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 2 : nd->event_priority = priority;
95 :
96 2 : return 0;
97 : }
98 :
99 2 : _public_ int sd_ndisc_detach_event(sd_ndisc *nd) {
100 :
101 2 : assert_return(nd, -EINVAL);
102 2 : assert_return(nd->fd < 0, -EBUSY);
103 :
104 2 : nd->event = sd_event_unref(nd->event);
105 2 : 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 3 : static void ndisc_reset(sd_ndisc *nd) {
115 3 : assert(nd);
116 :
117 3 : (void) event_source_disable(nd->timeout_event_source);
118 3 : (void) event_source_disable(nd->timeout_no_ra);
119 3 : nd->retransmit_time = 0;
120 3 : nd->recv_event_source = sd_event_source_unref(nd->recv_event_source);
121 3 : nd->fd = safe_close(nd->fd);
122 3 : }
123 :
124 2 : static sd_ndisc *ndisc_free(sd_ndisc *nd) {
125 2 : assert(nd);
126 :
127 2 : nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
128 2 : nd->timeout_no_ra = sd_event_source_unref(nd->timeout_no_ra);
129 :
130 2 : ndisc_reset(nd);
131 2 : sd_ndisc_detach_event(nd);
132 2 : return mfree(nd);
133 : }
134 :
135 8 : DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc, sd_ndisc, ndisc_free);
136 :
137 2 : _public_ int sd_ndisc_new(sd_ndisc **ret) {
138 2 : _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
139 :
140 2 : assert_return(ret, -EINVAL);
141 :
142 2 : nd = new(sd_ndisc, 1);
143 2 : if (!nd)
144 0 : return -ENOMEM;
145 :
146 2 : *nd = (sd_ndisc) {
147 : .n_ref = 1,
148 : .fd = -1,
149 : };
150 :
151 2 : *ret = TAKE_PTR(nd);
152 :
153 2 : return 0;
154 : }
155 :
156 1 : _public_ int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
157 1 : assert_return(nd, -EINVAL);
158 1 : assert_return(mtu, -EINVAL);
159 :
160 1 : if (nd->mtu == 0)
161 1 : 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 5 : static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
179 : int r;
180 :
181 5 : assert(nd);
182 5 : assert(rt);
183 :
184 5 : r = ndisc_router_parse(rt);
185 5 : if (r == -EBADMSG) /* Bad packet */
186 0 : return 0;
187 5 : if (r < 0)
188 0 : return 0;
189 :
190 : /* Update global variables we keep */
191 5 : if (rt->mtu > 0)
192 0 : nd->mtu = rt->mtu;
193 5 : if (rt->hop_limit > 0)
194 5 : nd->hop_limit = rt->hop_limit;
195 :
196 5 : 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 5 : ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt);
202 5 : return 0;
203 : }
204 :
205 5 : static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
206 5 : _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
207 5 : sd_ndisc *nd = userdata;
208 : ssize_t buflen;
209 : int r;
210 5 : _cleanup_free_ char *addr = NULL;
211 :
212 5 : assert(s);
213 5 : assert(nd);
214 5 : assert(nd->event);
215 :
216 5 : buflen = next_datagram_size_fd(fd);
217 5 : if (buflen < 0)
218 0 : return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m");
219 :
220 5 : rt = ndisc_router_new(buflen);
221 5 : if (!rt)
222 0 : return -ENOMEM;
223 :
224 5 : r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address,
225 5 : &rt->timestamp);
226 5 : 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 5 : (void) event_source_disable(nd->timeout_event_source);
253 :
254 5 : return ndisc_handle_datagram(nd, rt);
255 : }
256 :
257 21 : static usec_t ndisc_timeout_compute_random(usec_t val) {
258 : /* compute a time that is random within ±10% of the given value */
259 63 : return val - val / 10 +
260 21 : (random_u64() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
261 : }
262 :
263 21 : static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
264 : char time_string[FORMAT_TIMESPAN_MAX];
265 21 : sd_ndisc *nd = userdata;
266 : usec_t time_now;
267 : int r;
268 :
269 21 : assert(s);
270 21 : assert(nd);
271 21 : assert(nd->event);
272 :
273 21 : assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
274 :
275 21 : if (!nd->retransmit_time)
276 2 : nd->retransmit_time = ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL);
277 : else {
278 19 : if (nd->retransmit_time > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 2)
279 10 : nd->retransmit_time = ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL);
280 : else
281 9 : nd->retransmit_time += ndisc_timeout_compute_random(nd->retransmit_time);
282 : }
283 :
284 21 : r = event_reset_time(nd->event, &nd->timeout_event_source,
285 : clock_boottime_or_monotonic(),
286 21 : time_now + nd->retransmit_time, 10 * USEC_PER_MSEC,
287 : ndisc_timeout, nd,
288 21 : nd->event_priority, "ndisc-timeout-no-ra", true);
289 21 : if (r < 0)
290 0 : goto fail;
291 :
292 21 : r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
293 21 : if (r < 0) {
294 0 : log_ndisc_errno(r, "Error sending Router Solicitation: %m");
295 0 : goto fail;
296 : }
297 :
298 21 : 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 21 : 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 2 : _public_ int sd_ndisc_stop(sd_ndisc *nd) {
324 2 : assert_return(nd, -EINVAL);
325 :
326 2 : if (nd->fd < 0)
327 1 : return 0;
328 :
329 1 : log_ndisc("Stopping IPv6 Router Solicitation client");
330 :
331 1 : ndisc_reset(nd);
332 1 : return 1;
333 : }
334 :
335 3 : _public_ int sd_ndisc_start(sd_ndisc *nd) {
336 : int r;
337 : usec_t time_now;
338 :
339 3 : assert_return(nd, -EINVAL);
340 3 : assert_return(nd->event, -EINVAL);
341 3 : assert_return(nd->ifindex > 0, -EINVAL);
342 :
343 3 : if (nd->fd >= 0)
344 0 : return 0;
345 :
346 3 : assert(!nd->recv_event_source);
347 :
348 3 : r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
349 3 : if (r < 0)
350 0 : goto fail;
351 :
352 3 : nd->fd = icmp6_bind_router_solicitation(nd->ifindex);
353 3 : if (nd->fd < 0)
354 0 : return nd->fd;
355 :
356 3 : r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd);
357 3 : if (r < 0)
358 0 : goto fail;
359 :
360 3 : r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority);
361 3 : if (r < 0)
362 0 : goto fail;
363 :
364 3 : (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
365 :
366 3 : r = event_reset_time(nd->event, &nd->timeout_event_source,
367 : clock_boottime_or_monotonic(),
368 : 0, 0,
369 : ndisc_timeout, nd,
370 3 : nd->event_priority, "ndisc-timeout", true);
371 3 : if (r < 0)
372 0 : goto fail;
373 :
374 3 : 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 3 : nd->event_priority, "ndisc-timeout-no-ra", true);
379 3 : if (r < 0)
380 0 : goto fail;
381 :
382 3 : log_ndisc("Started IPv6 Router Solicitation client");
383 3 : return 1;
384 :
385 0 : fail:
386 0 : ndisc_reset(nd);
387 0 : return r;
388 : }
|