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