]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/sd-ndisc.c
tree-wide: use -EBADF for fd initialization
[thirdparty/systemd.git] / src / libsystemd-network / sd-ndisc.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /***
3 Copyright © 2014 Intel Corporation. All rights reserved.
4 ***/
5
6 #include <netinet/icmp6.h>
7 #include <netinet/in.h>
8
9 #include "sd-ndisc.h"
10
11 #include "alloc-util.h"
12 #include "event-util.h"
13 #include "fd-util.h"
14 #include "icmp6-util.h"
15 #include "in-addr-util.h"
16 #include "memory-util.h"
17 #include "ndisc-internal.h"
18 #include "ndisc-router.h"
19 #include "network-common.h"
20 #include "random-util.h"
21 #include "socket-util.h"
22 #include "string-table.h"
23 #include "string-util.h"
24
25 #define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS)
26
27 static const char * const ndisc_event_table[_SD_NDISC_EVENT_MAX] = {
28 [SD_NDISC_EVENT_TIMEOUT] = "timeout",
29 [SD_NDISC_EVENT_ROUTER] = "router",
30 };
31
32 DEFINE_STRING_TABLE_LOOKUP(ndisc_event, sd_ndisc_event_t);
33
34 static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event_t event, sd_ndisc_router *rt) {
35 assert(ndisc);
36 assert(event >= 0 && event < _SD_NDISC_EVENT_MAX);
37
38 if (!ndisc->callback)
39 return (void) log_ndisc(ndisc, "Received '%s' event.", ndisc_event_to_string(event));
40
41 log_ndisc(ndisc, "Invoking callback for '%s' event.", ndisc_event_to_string(event));
42 ndisc->callback(ndisc, event, rt, ndisc->userdata);
43 }
44
45 int sd_ndisc_set_callback(
46 sd_ndisc *nd,
47 sd_ndisc_callback_t callback,
48 void *userdata) {
49
50 assert_return(nd, -EINVAL);
51
52 nd->callback = callback;
53 nd->userdata = userdata;
54
55 return 0;
56 }
57
58 int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
59 assert_return(nd, -EINVAL);
60 assert_return(ifindex > 0, -EINVAL);
61 assert_return(nd->fd < 0, -EBUSY);
62
63 nd->ifindex = ifindex;
64 return 0;
65 }
66
67 int 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
77 int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret) {
78 int r;
79
80 assert_return(nd, -EINVAL);
81
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;
90 }
91
92 int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
93 assert_return(nd, -EINVAL);
94
95 if (mac_addr)
96 nd->mac_addr = *mac_addr;
97 else
98 zero(nd->mac_addr);
99
100 return 0;
101 }
102
103 int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
104 int r;
105
106 assert_return(nd, -EINVAL);
107 assert_return(nd->fd < 0, -EBUSY);
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
123 int sd_ndisc_detach_event(sd_ndisc *nd) {
124
125 assert_return(nd, -EINVAL);
126 assert_return(nd->fd < 0, -EBUSY);
127
128 nd->event = sd_event_unref(nd->event);
129 return 0;
130 }
131
132 sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
133 assert_return(nd, NULL);
134
135 return nd->event;
136 }
137
138 static void ndisc_reset(sd_ndisc *nd) {
139 assert(nd);
140
141 (void) event_source_disable(nd->timeout_event_source);
142 (void) event_source_disable(nd->timeout_no_ra);
143 nd->retransmit_time = 0;
144 nd->recv_event_source = sd_event_source_disable_unref(nd->recv_event_source);
145 nd->fd = safe_close(nd->fd);
146 }
147
148 static sd_ndisc *ndisc_free(sd_ndisc *nd) {
149 assert(nd);
150
151 ndisc_reset(nd);
152
153 sd_event_source_unref(nd->timeout_event_source);
154 sd_event_source_unref(nd->timeout_no_ra);
155 sd_ndisc_detach_event(nd);
156
157 free(nd->ifname);
158 return mfree(nd);
159 }
160
161 DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc, sd_ndisc, ndisc_free);
162
163 int sd_ndisc_new(sd_ndisc **ret) {
164 _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
165
166 assert_return(ret, -EINVAL);
167
168 nd = new(sd_ndisc, 1);
169 if (!nd)
170 return -ENOMEM;
171
172 *nd = (sd_ndisc) {
173 .n_ref = 1,
174 .fd = -EBADF,
175 };
176
177 *ret = TAKE_PTR(nd);
178
179 return 0;
180 }
181
182 static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
183 int r;
184
185 assert(nd);
186 assert(rt);
187
188 r = ndisc_router_parse(nd, rt);
189 if (r < 0)
190 return r;
191
192 log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %" PRIu16 " sec",
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);
196
197 ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt);
198 return 0;
199 }
200
201 static 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;
203 sd_ndisc *nd = ASSERT_PTR(userdata);
204 ssize_t buflen;
205 int r;
206
207 assert(s);
208 assert(nd->event);
209
210 buflen = next_datagram_size_fd(fd);
211 if (buflen < 0) {
212 if (ERRNO_IS_TRANSIENT(buflen) || ERRNO_IS_DISCONNECT(buflen))
213 return 0;
214
215 log_ndisc_errno(nd, buflen, "Failed to determine datagram size to read, ignoring: %m");
216 return 0;
217 }
218
219 rt = ndisc_router_new(buflen);
220 if (!rt)
221 return -ENOMEM;
222
223 r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address, &rt->timestamp);
224 if (r < 0) {
225 if (ERRNO_IS_TRANSIENT(r) || ERRNO_IS_DISCONNECT(r))
226 return 0;
227
228 switch (r) {
229 case -EADDRNOTAVAIL:
230 log_ndisc(nd, "Received RA from non-link-local address %s. Ignoring.",
231 IN6_ADDR_TO_STRING(&rt->address));
232 break;
233
234 case -EMULTIHOP:
235 log_ndisc(nd, "Received RA with invalid hop limit. Ignoring.");
236 break;
237
238 case -EPFNOSUPPORT:
239 log_ndisc(nd, "Received invalid source address from ICMPv6 socket. Ignoring.");
240 break;
241
242 default:
243 log_ndisc_errno(nd, r, "Unexpected error while reading from ICMPv6, ignoring: %m");
244 break;
245 }
246
247 return 0;
248 }
249
250 (void) event_source_disable(nd->timeout_event_source);
251 (void) ndisc_handle_datagram(nd, rt);
252 return 0;
253 }
254
255 static usec_t ndisc_timeout_compute_random(usec_t val) {
256 /* compute a time that is random within ±10% of the given value */
257 return val - val / 10 +
258 (random_u64() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
259 }
260
261 static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
262 sd_ndisc *nd = ASSERT_PTR(userdata);
263 usec_t time_now;
264 int r;
265
266 assert(s);
267 assert(nd->event);
268
269 assert_se(sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now) >= 0);
270
271 if (!nd->retransmit_time)
272 nd->retransmit_time = ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL);
273 else {
274 if (nd->retransmit_time > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 2)
275 nd->retransmit_time = ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL);
276 else
277 nd->retransmit_time += ndisc_timeout_compute_random(nd->retransmit_time);
278 }
279
280 r = event_reset_time(nd->event, &nd->timeout_event_source,
281 CLOCK_BOOTTIME,
282 time_now + nd->retransmit_time, 10 * USEC_PER_MSEC,
283 ndisc_timeout, nd,
284 nd->event_priority, "ndisc-timeout-no-ra", true);
285 if (r < 0)
286 goto fail;
287
288 r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
289 if (r < 0)
290 log_ndisc_errno(nd, r, "Failed to send Router Solicitation, next solicitation in %s, ignoring: %m",
291 FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC));
292 else
293 log_ndisc(nd, "Sent Router Solicitation, next solicitation in %s",
294 FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC));
295
296 return 0;
297
298 fail:
299 (void) sd_ndisc_stop(nd);
300 return 0;
301 }
302
303 static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata) {
304 sd_ndisc *nd = ASSERT_PTR(userdata);
305
306 assert(s);
307
308 log_ndisc(nd, "No RA received before link confirmation timeout");
309
310 (void) event_source_disable(nd->timeout_no_ra);
311 ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
312
313 return 0;
314 }
315
316 int sd_ndisc_stop(sd_ndisc *nd) {
317 if (!nd)
318 return 0;
319
320 if (nd->fd < 0)
321 return 0;
322
323 log_ndisc(nd, "Stopping IPv6 Router Solicitation client");
324
325 ndisc_reset(nd);
326 return 1;
327 }
328
329 int sd_ndisc_start(sd_ndisc *nd) {
330 int r;
331 usec_t time_now;
332
333 assert_return(nd, -EINVAL);
334 assert_return(nd->event, -EINVAL);
335 assert_return(nd->ifindex > 0, -EINVAL);
336
337 if (nd->fd >= 0)
338 return 0;
339
340 assert(!nd->recv_event_source);
341
342 r = sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now);
343 if (r < 0)
344 goto fail;
345
346 nd->fd = icmp6_bind_router_solicitation(nd->ifindex);
347 if (nd->fd < 0)
348 return nd->fd;
349
350 r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd);
351 if (r < 0)
352 goto fail;
353
354 r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority);
355 if (r < 0)
356 goto fail;
357
358 (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
359
360 r = event_reset_time(nd->event, &nd->timeout_event_source,
361 CLOCK_BOOTTIME,
362 time_now + USEC_PER_SEC / 2, 1 * USEC_PER_SEC, /* See RFC 8415 sec. 18.2.1 */
363 ndisc_timeout, nd,
364 nd->event_priority, "ndisc-timeout", true);
365 if (r < 0)
366 goto fail;
367
368 r = event_reset_time(nd->event, &nd->timeout_no_ra,
369 CLOCK_BOOTTIME,
370 time_now + NDISC_TIMEOUT_NO_RA_USEC, 10 * USEC_PER_MSEC,
371 ndisc_timeout_no_ra, nd,
372 nd->event_priority, "ndisc-timeout-no-ra", true);
373 if (r < 0)
374 goto fail;
375
376 log_ndisc(nd, "Started IPv6 Router Solicitation client");
377 return 1;
378
379 fail:
380 ndisc_reset(nd);
381 return r;
382 }