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