]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-ndisc.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / libsystemd-network / sd-ndisc.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
e3169126
PF
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>
07630cea 22#include <netinet/in.h>
e3169126 23
07630cea
LP
24#include "sd-ndisc.h"
25
b5efdb8a 26#include "alloc-util.h"
1e7a0e21 27#include "fd-util.h"
07630cea 28#include "icmp6-util.h"
9d96e6c3 29#include "in-addr-util.h"
1e7a0e21
LP
30#include "ndisc-internal.h"
31#include "ndisc-router.h"
1bd6f895 32#include "random-util.h"
940367a0 33#include "socket-util.h"
d7fa4380 34#include "string-util.h"
1e7a0e21 35#include "util.h"
e3169126 36
1bd6f895 37#define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS)
e3169126 38
1e7a0e21
LP
39static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) {
40 assert(ndisc);
e3169126 41
1e7a0e21 42 log_ndisc("Invoking callback for '%c'.", event);
09667885 43
1e7a0e21
LP
44 if (!ndisc->callback)
45 return;
9d96e6c3 46
1e7a0e21 47 ndisc->callback(ndisc, event, rt, ndisc->userdata);
5624c480
PF
48}
49
1e7a0e21 50_public_ int sd_ndisc_set_callback(
a1140666 51 sd_ndisc *nd,
a1140666
LP
52 sd_ndisc_callback_t callback,
53 void *userdata) {
54
55 assert_return(nd, -EINVAL);
e3169126
PF
56
57 nd->callback = callback;
58 nd->userdata = userdata;
59
60 return 0;
61}
62
1e7a0e21 63_public_ int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
2f8e7633
LP
64 assert_return(nd, -EINVAL);
65 assert_return(ifindex > 0, -EINVAL);
1e7a0e21 66 assert_return(nd->fd < 0, -EBUSY);
e3169126 67
2f8e7633 68 nd->ifindex = ifindex;
e3169126
PF
69 return 0;
70}
71
1e7a0e21 72_public_ int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
a1140666 73 assert_return(nd, -EINVAL);
e3169126
PF
74
75 if (mac_addr)
1e7a0e21 76 nd->mac_addr = *mac_addr;
e3169126 77 else
eccaf899 78 zero(nd->mac_addr);
e3169126
PF
79
80 return 0;
e3169126
PF
81}
82
1e7a0e21 83_public_ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
e3169126
PF
84 int r;
85
86 assert_return(nd, -EINVAL);
1e7a0e21 87 assert_return(nd->fd < 0, -EBUSY);
e3169126
PF
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
1e7a0e21
LP
103_public_ int sd_ndisc_detach_event(sd_ndisc *nd) {
104
e3169126 105 assert_return(nd, -EINVAL);
1e7a0e21 106 assert_return(nd->fd < 0, -EBUSY);
e3169126
PF
107
108 nd->event = sd_event_unref(nd->event);
e3169126
PF
109 return 0;
110}
111
1e7a0e21 112_public_ sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
a1140666 113 assert_return(nd, NULL);
e3169126
PF
114
115 return nd->event;
116}
117
1e7a0e21 118_public_ sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
e3169126 119
9c8e3101
LP
120 if (!nd)
121 return NULL;
122
123 assert(nd->n_ref > 0);
124 nd->n_ref++;
e3169126
PF
125
126 return nd;
127}
128
5c4c338a 129static int ndisc_reset(sd_ndisc *nd) {
e3169126
PF
130 assert(nd);
131
3e261cfd 132 nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
1bd6f895
PF
133 nd->timeout_no_ra = sd_event_source_unref(nd->timeout_no_ra);
134 nd->retransmit_time = 0;
1e7a0e21
LP
135 nd->recv_event_source = sd_event_source_unref(nd->recv_event_source);
136 nd->fd = safe_close(nd->fd);
e3169126
PF
137
138 return 0;
139}
140
1e7a0e21 141_public_ sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
e3169126 142
9c8e3101
LP
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;
e3169126 151
5c4c338a 152 ndisc_reset(nd);
4d7b83da 153 sd_ndisc_detach_event(nd);
6b430fdb 154 return mfree(nd);
e3169126
PF
155}
156
1e7a0e21 157_public_ int sd_ndisc_new(sd_ndisc **ret) {
4afd3348 158 _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
e3169126 159
a1140666 160 assert_return(ret, -EINVAL);
e3169126 161
4d7b83da 162 nd = new0(sd_ndisc, 1);
e3169126
PF
163 if (!nd)
164 return -ENOMEM;
165
9c8e3101 166 nd->n_ref = 1;
03de7ed9 167 nd->fd = -1;
e3169126
PF
168
169 *ret = nd;
170 nd = NULL;
171
172 return 0;
173}
174
1e7a0e21 175_public_ int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
d14b5bc6
PF
176 assert_return(nd, -EINVAL);
177 assert_return(mtu, -EINVAL);
178
179 if (nd->mtu == 0)
1e7a0e21 180 return -ENODATA;
d14b5bc6
PF
181
182 *mtu = nd->mtu;
d14b5bc6
PF
183 return 0;
184}
185
1e7a0e21
LP
186_public_ int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret) {
187 assert_return(nd, -EINVAL);
188 assert_return(ret, -EINVAL);
d77bde34 189
1e7a0e21
LP
190 if (nd->hop_limit == 0)
191 return -ENODATA;
d77bde34 192
1e7a0e21 193 *ret = nd->hop_limit;
d77bde34
PF
194 return 0;
195}
196
1e7a0e21 197static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
f6e0ce66 198 int r;
d77bde34 199
f6e0ce66 200 assert(nd);
1e7a0e21 201 assert(rt);
d77bde34 202
1e7a0e21
LP
203 r = ndisc_router_parse(rt);
204 if (r == -EBADMSG) /* Bad packet */
9d96e6c3 205 return 0;
f6e0ce66 206 if (r < 0)
1e7a0e21 207 return 0;
09667885 208
1e7a0e21
LP
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;
09667885 214
1e7a0e21
LP
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);
09667885 219
1e7a0e21 220 ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt);
09667885
PF
221 return 0;
222}
223
1e7a0e21
LP
224static 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;
4d7b83da 226 sd_ndisc *nd = userdata;
88d5a3db
PF
227 ssize_t buflen;
228 int r;
229 _cleanup_free_ char *addr = NULL;
e3169126
PF
230
231 assert(s);
232 assert(nd);
233 assert(nd->event);
234
4edc2c9b
LP
235 buflen = next_datagram_size_fd(fd);
236 if (buflen < 0)
1e7a0e21 237 return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m");
cddf4d81 238
1e7a0e21
LP
239 rt = ndisc_router_new(buflen);
240 if (!rt)
09667885
PF
241 return -ENOMEM;
242
88d5a3db
PF
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;
1e7a0e21
LP
259 }
260
88d5a3db 261 return 0;
d7fa4380 262 }
3ccd3163 263
3e261cfd 264 nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
09667885 265
1e7a0e21 266 return ndisc_handle_datagram(nd, rt);
e3169126
PF
267}
268
1bd6f895
PF
269static 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
1e7a0e21 275static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
4d7b83da 276 sd_ndisc *nd = userdata;
1bd6f895 277 usec_t time_now;
e3169126 278 int r;
1bd6f895 279 char time_string[FORMAT_TIMESPAN_MAX];
e3169126
PF
280
281 assert(s);
282 assert(nd);
283 assert(nd->event);
284
1e7a0e21 285 assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
e3169126 286
1bd6f895
PF
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);
1e7a0e21 296 }
9021bb9f 297
1bd6f895
PF
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
1e7a0e21
LP
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;
e3169126
PF
315 }
316
e82a19cb
PF
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
1bd6f895
PF
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
e3169126 327 return 0;
b9e7b1cf
LP
328
329fail:
330 sd_ndisc_stop(nd);
331 return 0;
e3169126
PF
332}
333
1bd6f895
PF
334static 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
1e7a0e21 348_public_ int sd_ndisc_stop(sd_ndisc *nd) {
836cf090 349 assert_return(nd, -EINVAL);
836cf090 350
1e7a0e21 351 if (nd->fd < 0)
c1c9b211
LP
352 return 0;
353
1e7a0e21 354 log_ndisc("Stopping IPv6 Router Solicitation client");
836cf090 355
5c4c338a 356 ndisc_reset(nd);
1e7a0e21 357 return 1;
836cf090
PF
358}
359
1e7a0e21 360_public_ int sd_ndisc_start(sd_ndisc *nd) {
e3169126 361 int r;
1bd6f895 362 usec_t time_now;
e3169126 363
a1140666
LP
364 assert_return(nd, -EINVAL);
365 assert_return(nd->event, -EINVAL);
366 assert_return(nd->ifindex > 0, -EINVAL);
e3169126 367
1e7a0e21
LP
368 if (nd->fd >= 0)
369 return 0;
e3169126 370
1e7a0e21
LP
371 assert(!nd->recv_event_source);
372 assert(!nd->timeout_event_source);
e3169126 373
1bd6f895
PF
374 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
375 if (r < 0)
376 goto fail;
377
1e7a0e21
LP
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);
e3169126 383 if (r < 0)
5c4c338a 384 goto fail;
e3169126 385
3e261cfd 386 r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority);
e3169126 387 if (r < 0)
5c4c338a 388 goto fail;
e3169126 389
3e261cfd 390 (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
9021bb9f 391
1e7a0e21 392 r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(), 0, 0, ndisc_timeout, nd);
e3169126 393 if (r < 0)
5c4c338a 394 goto fail;
e3169126 395
3e261cfd 396 r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority);
9021bb9f 397 if (r < 0)
5c4c338a 398 goto fail;
e3169126 399
3e261cfd 400 (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout");
5c4c338a 401
1bd6f895
PF
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
1e7a0e21
LP
415 log_ndisc("Started IPv6 Router Solicitation client");
416 return 1;
e3169126 417
5c4c338a
LP
418fail:
419 ndisc_reset(nd);
e3169126
PF
420 return r;
421}