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