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