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