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