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