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