]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-ndisc.c
icmp6-util: merge icmp6_bind_router_{solicitation,advertisement}() into icmp6_bind()
[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 17#include "ndisc-internal.h"
ca34b434 18#include "ndisc-router-internal.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
787e71e4
YW
45int sd_ndisc_is_running(sd_ndisc *nd) {
46 if (!nd)
47 return false;
48
49 return sd_event_source_get_enabled(nd->recv_event_source, NULL) > 0;
50}
51
17347053 52int sd_ndisc_set_callback(
a1140666 53 sd_ndisc *nd,
a1140666
LP
54 sd_ndisc_callback_t callback,
55 void *userdata) {
56
57 assert_return(nd, -EINVAL);
e3169126
PF
58
59 nd->callback = callback;
60 nd->userdata = userdata;
61
62 return 0;
63}
64
17347053 65int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
2f8e7633
LP
66 assert_return(nd, -EINVAL);
67 assert_return(ifindex > 0, -EINVAL);
787e71e4 68 assert_return(!sd_ndisc_is_running(nd), -EBUSY);
e3169126 69
2f8e7633 70 nd->ifindex = ifindex;
e3169126
PF
71 return 0;
72}
73
61a9fa8f
YW
74int sd_ndisc_set_ifname(sd_ndisc *nd, const char *ifname) {
75 assert_return(nd, -EINVAL);
76 assert_return(ifname, -EINVAL);
77
78 if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE))
79 return -EINVAL;
80
81 return free_and_strdup(&nd->ifname, ifname);
82}
83
5977b71f
YW
84int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret) {
85 int r;
86
87 assert_return(nd, -EINVAL);
61a9fa8f 88
5977b71f
YW
89 r = get_ifname(nd->ifindex, &nd->ifname);
90 if (r < 0)
91 return r;
92
93 if (ret)
94 *ret = nd->ifname;
95
96 return 0;
61a9fa8f
YW
97}
98
17347053 99int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
a1140666 100 assert_return(nd, -EINVAL);
e3169126
PF
101
102 if (mac_addr)
1e7a0e21 103 nd->mac_addr = *mac_addr;
e3169126 104 else
eccaf899 105 zero(nd->mac_addr);
e3169126
PF
106
107 return 0;
e3169126
PF
108}
109
17347053 110int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
e3169126
PF
111 int r;
112
113 assert_return(nd, -EINVAL);
787e71e4 114 assert_return(!sd_ndisc_is_running(nd), -EBUSY);
e3169126
PF
115 assert_return(!nd->event, -EBUSY);
116
117 if (event)
118 nd->event = sd_event_ref(event);
119 else {
120 r = sd_event_default(&nd->event);
121 if (r < 0)
122 return 0;
123 }
124
125 nd->event_priority = priority;
126
127 return 0;
128}
129
17347053 130int sd_ndisc_detach_event(sd_ndisc *nd) {
1e7a0e21 131
e3169126 132 assert_return(nd, -EINVAL);
787e71e4 133 assert_return(!sd_ndisc_is_running(nd), -EBUSY);
e3169126
PF
134
135 nd->event = sd_event_unref(nd->event);
e3169126
PF
136 return 0;
137}
138
17347053 139sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
a1140666 140 assert_return(nd, NULL);
e3169126
PF
141
142 return nd->event;
143}
144
165ad41b 145static void ndisc_reset(sd_ndisc *nd) {
e3169126
PF
146 assert(nd);
147
ff4b0321
YW
148 (void) event_source_disable(nd->timeout_event_source);
149 (void) event_source_disable(nd->timeout_no_ra);
1bd6f895 150 nd->retransmit_time = 0;
eb2f7502 151 nd->recv_event_source = sd_event_source_disable_unref(nd->recv_event_source);
1e7a0e21 152 nd->fd = safe_close(nd->fd);
e3169126
PF
153}
154
8301aa0b
YW
155static sd_ndisc *ndisc_free(sd_ndisc *nd) {
156 assert(nd);
e3169126 157
5c4c338a 158 ndisc_reset(nd);
eb2f7502
YW
159
160 sd_event_source_unref(nd->timeout_event_source);
161 sd_event_source_unref(nd->timeout_no_ra);
4d7b83da 162 sd_ndisc_detach_event(nd);
eb2f7502 163
61a9fa8f 164 free(nd->ifname);
6b430fdb 165 return mfree(nd);
e3169126
PF
166}
167
8301aa0b
YW
168DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc, sd_ndisc, ndisc_free);
169
17347053 170int sd_ndisc_new(sd_ndisc **ret) {
4afd3348 171 _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
e3169126 172
a1140666 173 assert_return(ret, -EINVAL);
e3169126 174
144faa8e 175 nd = new(sd_ndisc, 1);
e3169126
PF
176 if (!nd)
177 return -ENOMEM;
178
144faa8e
YW
179 *nd = (sd_ndisc) {
180 .n_ref = 1,
254d1313 181 .fd = -EBADF,
144faa8e 182 };
e3169126 183
1cc6c93a 184 *ret = TAKE_PTR(nd);
e3169126
PF
185
186 return 0;
187}
188
1e7a0e21 189static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
f6e0ce66 190 int r;
d77bde34 191
f6e0ce66 192 assert(nd);
1e7a0e21 193 assert(rt);
d77bde34 194
35388783 195 r = ndisc_router_parse(nd, rt);
f6e0ce66 196 if (r < 0)
ab8a8a4e 197 return r;
09667885 198
e7cb8047
YW
199 (void) event_source_disable(nd->timeout_event_source);
200
6197db53 201 log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %s",
1e7a0e21
LP
202 rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none",
203 rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium",
6197db53 204 FORMAT_TIMESPAN(rt->lifetime_usec, USEC_PER_SEC));
09667885 205
1e7a0e21 206 ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt);
09667885
PF
207 return 0;
208}
209
1e7a0e21
LP
210static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
211 _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
99534007 212 sd_ndisc *nd = ASSERT_PTR(userdata);
88d5a3db
PF
213 ssize_t buflen;
214 int r;
e3169126
PF
215
216 assert(s);
e3169126
PF
217 assert(nd->event);
218
4edc2c9b 219 buflen = next_datagram_size_fd(fd);
1f2db2e3
ZJS
220 if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen))
221 return 0;
35388783
YW
222 if (buflen < 0) {
223 log_ndisc_errno(nd, buflen, "Failed to determine datagram size to read, ignoring: %m");
224 return 0;
225 }
cddf4d81 226
1e7a0e21
LP
227 rt = ndisc_router_new(buflen);
228 if (!rt)
09667885
PF
229 return -ENOMEM;
230
0afa4d56 231 r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address, &rt->timestamp);
1f2db2e3
ZJS
232 if (ERRNO_IS_NEG_TRANSIENT(r) || ERRNO_IS_NEG_DISCONNECT(r))
233 return 0;
234 if (r < 0)
88d5a3db
PF
235 switch (r) {
236 case -EADDRNOTAVAIL:
4961f566 237 log_ndisc(nd, "Received RA from neither link-local nor null address. Ignoring.");
1f2db2e3 238 return 0;
88d5a3db
PF
239
240 case -EMULTIHOP:
35388783 241 log_ndisc(nd, "Received RA with invalid hop limit. Ignoring.");
1f2db2e3 242 return 0;
88d5a3db
PF
243
244 case -EPFNOSUPPORT:
35388783 245 log_ndisc(nd, "Received invalid source address from ICMPv6 socket. Ignoring.");
1f2db2e3 246 return 0;
437524f1 247
437524f1 248 default:
35388783 249 log_ndisc_errno(nd, r, "Unexpected error while reading from ICMPv6, ignoring: %m");
1f2db2e3 250 return 0;
1e7a0e21
LP
251 }
252
4961f566
YW
253 /* The function icmp6_receive() accepts the null source address, but RFC 4861 Section 6.1.2 states
254 * that hosts MUST discard messages with the null source address. */
189eedda
YW
255 if (in6_addr_is_null(&rt->address)) {
256 log_ndisc(nd, "Received an ICMPv6 packet from null address, ignoring.");
257 return 0;
258 }
4961f566 259
ab8a8a4e
YW
260 (void) ndisc_handle_datagram(nd, rt);
261 return 0;
e3169126
PF
262}
263
1bd6f895
PF
264static usec_t ndisc_timeout_compute_random(usec_t val) {
265 /* compute a time that is random within ±10% of the given value */
266 return val - val / 10 +
267 (random_u64() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
268}
269
1e7a0e21 270static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
99534007 271 sd_ndisc *nd = ASSERT_PTR(userdata);
1bd6f895 272 usec_t time_now;
e3169126
PF
273 int r;
274
275 assert(s);
e3169126
PF
276 assert(nd->event);
277
ba4e0427 278 assert_se(sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now) >= 0);
e3169126 279
1bd6f895
PF
280 if (!nd->retransmit_time)
281 nd->retransmit_time = ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL);
282 else {
283 if (nd->retransmit_time > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 2)
284 nd->retransmit_time = ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL);
285 else
286 nd->retransmit_time += ndisc_timeout_compute_random(nd->retransmit_time);
1e7a0e21 287 }
9021bb9f 288
ff4b0321 289 r = event_reset_time(nd->event, &nd->timeout_event_source,
ba4e0427 290 CLOCK_BOOTTIME,
ff4b0321
YW
291 time_now + nd->retransmit_time, 10 * USEC_PER_MSEC,
292 ndisc_timeout, nd,
293 nd->event_priority, "ndisc-timeout-no-ra", true);
1bd6f895
PF
294 if (r < 0)
295 goto fail;
296
e82a19cb 297 r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
852bf938
YW
298 if (r < 0)
299 log_ndisc_errno(nd, r, "Failed to send Router Solicitation, next solicitation in %s, ignoring: %m",
300 FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC));
301 else
302 log_ndisc(nd, "Sent Router Solicitation, next solicitation in %s",
303 FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC));
1bd6f895 304
e3169126 305 return 0;
b9e7b1cf
LP
306
307fail:
76f713df 308 (void) sd_ndisc_stop(nd);
b9e7b1cf 309 return 0;
e3169126
PF
310}
311
1bd6f895 312static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata) {
99534007 313 sd_ndisc *nd = ASSERT_PTR(userdata);
1bd6f895
PF
314
315 assert(s);
1bd6f895 316
35388783 317 log_ndisc(nd, "No RA received before link confirmation timeout");
1bd6f895 318
ff4b0321 319 (void) event_source_disable(nd->timeout_no_ra);
1bd6f895
PF
320 ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
321
322 return 0;
323}
324
17347053 325int sd_ndisc_stop(sd_ndisc *nd) {
c8bae363
YW
326 if (!nd)
327 return 0;
836cf090 328
787e71e4 329 if (!sd_ndisc_is_running(nd))
c1c9b211
LP
330 return 0;
331
35388783 332 log_ndisc(nd, "Stopping IPv6 Router Solicitation client");
836cf090 333
5c4c338a 334 ndisc_reset(nd);
1e7a0e21 335 return 1;
836cf090
PF
336}
337
17347053 338int sd_ndisc_start(sd_ndisc *nd) {
e3169126 339 int r;
1bd6f895 340 usec_t time_now;
e3169126 341
a1140666
LP
342 assert_return(nd, -EINVAL);
343 assert_return(nd->event, -EINVAL);
344 assert_return(nd->ifindex > 0, -EINVAL);
e3169126 345
787e71e4 346 if (sd_ndisc_is_running(nd))
1e7a0e21 347 return 0;
e3169126 348
1e7a0e21 349 assert(!nd->recv_event_source);
e3169126 350
ba4e0427 351 r = sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now);
1bd6f895
PF
352 if (r < 0)
353 goto fail;
354
37c011e7 355 nd->fd = icmp6_bind(nd->ifindex, /* is_router = */ false);
1e7a0e21
LP
356 if (nd->fd < 0)
357 return nd->fd;
358
359 r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd);
e3169126 360 if (r < 0)
5c4c338a 361 goto fail;
e3169126 362
3e261cfd 363 r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority);
e3169126 364 if (r < 0)
5c4c338a 365 goto fail;
e3169126 366
3e261cfd 367 (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
9021bb9f 368
ff4b0321 369 r = event_reset_time(nd->event, &nd->timeout_event_source,
ba4e0427 370 CLOCK_BOOTTIME,
9973e6c4 371 time_now + USEC_PER_SEC / 2, 1 * USEC_PER_SEC, /* See RFC 8415 sec. 18.2.1 */
ff4b0321
YW
372 ndisc_timeout, nd,
373 nd->event_priority, "ndisc-timeout", true);
e3169126 374 if (r < 0)
5c4c338a 375 goto fail;
e3169126 376
ff4b0321 377 r = event_reset_time(nd->event, &nd->timeout_no_ra,
ba4e0427 378 CLOCK_BOOTTIME,
ff4b0321
YW
379 time_now + NDISC_TIMEOUT_NO_RA_USEC, 10 * USEC_PER_MSEC,
380 ndisc_timeout_no_ra, nd,
381 nd->event_priority, "ndisc-timeout-no-ra", true);
9021bb9f 382 if (r < 0)
5c4c338a 383 goto fail;
e3169126 384
35388783 385 log_ndisc(nd, "Started IPv6 Router Solicitation client");
1e7a0e21 386 return 1;
e3169126 387
5c4c338a
LP
388fail:
389 ndisc_reset(nd);
e3169126
PF
390 return r;
391}