]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-ndisc.c
sd-ndisc: Implement Router Solicitation backoff method
[thirdparty/systemd.git] / src / libsystemd-network / sd-ndisc.c
CommitLineData
e3169126
PF
1/***
2 This file is part of systemd.
3
4 Copyright (C) 2014 Intel Corporation. All rights reserved.
5
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18***/
19
20#include <netinet/icmp6.h>
07630cea 21#include <netinet/in.h>
e3169126 22
07630cea
LP
23#include "sd-ndisc.h"
24
b5efdb8a 25#include "alloc-util.h"
1e7a0e21 26#include "fd-util.h"
07630cea 27#include "icmp6-util.h"
9d96e6c3 28#include "in-addr-util.h"
1e7a0e21
LP
29#include "ndisc-internal.h"
30#include "ndisc-router.h"
1bd6f895 31#include "random-util.h"
940367a0 32#include "socket-util.h"
d7fa4380 33#include "string-util.h"
1e7a0e21 34#include "util.h"
e3169126 35
1bd6f895 36#define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS)
e3169126 37
1e7a0e21
LP
38static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) {
39 assert(ndisc);
e3169126 40
1e7a0e21 41 log_ndisc("Invoking callback for '%c'.", event);
09667885 42
1e7a0e21
LP
43 if (!ndisc->callback)
44 return;
9d96e6c3 45
1e7a0e21 46 ndisc->callback(ndisc, event, rt, ndisc->userdata);
5624c480
PF
47}
48
1e7a0e21 49_public_ int sd_ndisc_set_callback(
a1140666 50 sd_ndisc *nd,
a1140666
LP
51 sd_ndisc_callback_t callback,
52 void *userdata) {
53
54 assert_return(nd, -EINVAL);
e3169126
PF
55
56 nd->callback = callback;
57 nd->userdata = userdata;
58
59 return 0;
60}
61
1e7a0e21 62_public_ int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
2f8e7633
LP
63 assert_return(nd, -EINVAL);
64 assert_return(ifindex > 0, -EINVAL);
1e7a0e21 65 assert_return(nd->fd < 0, -EBUSY);
e3169126 66
2f8e7633 67 nd->ifindex = ifindex;
e3169126
PF
68 return 0;
69}
70
1e7a0e21 71_public_ int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
a1140666 72 assert_return(nd, -EINVAL);
e3169126
PF
73
74 if (mac_addr)
1e7a0e21 75 nd->mac_addr = *mac_addr;
e3169126 76 else
eccaf899 77 zero(nd->mac_addr);
e3169126
PF
78
79 return 0;
e3169126
PF
80}
81
1e7a0e21 82_public_ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
e3169126
PF
83 int r;
84
85 assert_return(nd, -EINVAL);
1e7a0e21 86 assert_return(nd->fd < 0, -EBUSY);
e3169126
PF
87 assert_return(!nd->event, -EBUSY);
88
89 if (event)
90 nd->event = sd_event_ref(event);
91 else {
92 r = sd_event_default(&nd->event);
93 if (r < 0)
94 return 0;
95 }
96
97 nd->event_priority = priority;
98
99 return 0;
100}
101
1e7a0e21
LP
102_public_ int sd_ndisc_detach_event(sd_ndisc *nd) {
103
e3169126 104 assert_return(nd, -EINVAL);
1e7a0e21 105 assert_return(nd->fd < 0, -EBUSY);
e3169126
PF
106
107 nd->event = sd_event_unref(nd->event);
e3169126
PF
108 return 0;
109}
110
1e7a0e21 111_public_ sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
a1140666 112 assert_return(nd, NULL);
e3169126
PF
113
114 return nd->event;
115}
116
1e7a0e21 117_public_ sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
e3169126 118
9c8e3101
LP
119 if (!nd)
120 return NULL;
121
122 assert(nd->n_ref > 0);
123 nd->n_ref++;
e3169126
PF
124
125 return nd;
126}
127
5c4c338a 128static int ndisc_reset(sd_ndisc *nd) {
e3169126
PF
129 assert(nd);
130
3e261cfd 131 nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
1bd6f895
PF
132 nd->timeout_no_ra = sd_event_source_unref(nd->timeout_no_ra);
133 nd->retransmit_time = 0;
1e7a0e21
LP
134 nd->recv_event_source = sd_event_source_unref(nd->recv_event_source);
135 nd->fd = safe_close(nd->fd);
e3169126
PF
136
137 return 0;
138}
139
1e7a0e21 140_public_ sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
e3169126 141
9c8e3101
LP
142 if (!nd)
143 return NULL;
144
145 assert(nd->n_ref > 0);
146 nd->n_ref--;
147
148 if (nd->n_ref > 0)
149 return NULL;
e3169126 150
5c4c338a 151 ndisc_reset(nd);
4d7b83da 152 sd_ndisc_detach_event(nd);
6b430fdb 153 return mfree(nd);
e3169126
PF
154}
155
1e7a0e21 156_public_ int sd_ndisc_new(sd_ndisc **ret) {
4afd3348 157 _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
e3169126 158
a1140666 159 assert_return(ret, -EINVAL);
e3169126 160
4d7b83da 161 nd = new0(sd_ndisc, 1);
e3169126
PF
162 if (!nd)
163 return -ENOMEM;
164
9c8e3101 165 nd->n_ref = 1;
03de7ed9 166 nd->fd = -1;
e3169126
PF
167
168 *ret = nd;
169 nd = NULL;
170
171 return 0;
172}
173
1e7a0e21 174_public_ int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
d14b5bc6
PF
175 assert_return(nd, -EINVAL);
176 assert_return(mtu, -EINVAL);
177
178 if (nd->mtu == 0)
1e7a0e21 179 return -ENODATA;
d14b5bc6
PF
180
181 *mtu = nd->mtu;
d14b5bc6
PF
182 return 0;
183}
184
1e7a0e21
LP
185_public_ int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret) {
186 assert_return(nd, -EINVAL);
187 assert_return(ret, -EINVAL);
d77bde34 188
1e7a0e21
LP
189 if (nd->hop_limit == 0)
190 return -ENODATA;
d77bde34 191
1e7a0e21 192 *ret = nd->hop_limit;
d77bde34
PF
193 return 0;
194}
195
1e7a0e21 196static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
f6e0ce66 197 int r;
d77bde34 198
f6e0ce66 199 assert(nd);
1e7a0e21 200 assert(rt);
d77bde34 201
1e7a0e21
LP
202 r = ndisc_router_parse(rt);
203 if (r == -EBADMSG) /* Bad packet */
9d96e6c3 204 return 0;
f6e0ce66 205 if (r < 0)
1e7a0e21 206 return 0;
09667885 207
1e7a0e21
LP
208 /* Update global variables we keep */
209 if (rt->mtu > 0)
210 nd->mtu = rt->mtu;
211 if (rt->hop_limit > 0)
212 nd->hop_limit = rt->hop_limit;
09667885 213
1e7a0e21
LP
214 log_ndisc("Received Router Advertisement: flags %s preference %s lifetime %" PRIu16 " sec",
215 rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none",
216 rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium",
217 rt->lifetime);
09667885 218
1e7a0e21 219 ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt);
09667885
PF
220 return 0;
221}
222
1e7a0e21
LP
223static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
224 _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
4d7b83da 225 sd_ndisc *nd = userdata;
88d5a3db
PF
226 ssize_t buflen;
227 int r;
228 _cleanup_free_ char *addr = NULL;
e3169126
PF
229
230 assert(s);
231 assert(nd);
232 assert(nd->event);
233
4edc2c9b
LP
234 buflen = next_datagram_size_fd(fd);
235 if (buflen < 0)
1e7a0e21 236 return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m");
cddf4d81 237
1e7a0e21
LP
238 rt = ndisc_router_new(buflen);
239 if (!rt)
09667885
PF
240 return -ENOMEM;
241
88d5a3db
PF
242 r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address,
243 &rt->timestamp);
244 if (r < 0) {
245 switch (r) {
246 case -EADDRNOTAVAIL:
247 (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &rt->address, &addr);
248 log_ndisc("Received RA from non-link-local address %s. Ignoring", addr);
249 break;
250
251 case -EMULTIHOP:
252 log_ndisc("Received RA with invalid hop limit. Ignoring.");
253 break;
254
255 case -EPFNOSUPPORT:
256 log_ndisc("Received invalid source address from ICMPv6 socket.");
257 break;
1e7a0e21
LP
258 }
259
88d5a3db 260 return 0;
d7fa4380 261 }
3ccd3163 262
3e261cfd 263 nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
09667885 264
1e7a0e21 265 return ndisc_handle_datagram(nd, rt);
e3169126
PF
266}
267
1bd6f895
PF
268static usec_t ndisc_timeout_compute_random(usec_t val) {
269 /* compute a time that is random within ±10% of the given value */
270 return val - val / 10 +
271 (random_u64() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
272}
273
1e7a0e21 274static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
4d7b83da 275 sd_ndisc *nd = userdata;
1bd6f895 276 usec_t time_now;
e3169126 277 int r;
1bd6f895 278 char time_string[FORMAT_TIMESPAN_MAX];
e3169126
PF
279
280 assert(s);
281 assert(nd);
282 assert(nd->event);
283
1e7a0e21
LP
284 r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
285 if (r < 0) {
286 log_ndisc_errno(r, "Error sending Router Solicitation: %m");
287 goto fail;
288 }
e3169126 289
1e7a0e21 290 assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
e3169126 291
1bd6f895
PF
292 nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
293
294 if (!nd->retransmit_time)
295 nd->retransmit_time = ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL);
296 else {
297 if (nd->retransmit_time > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 2)
298 nd->retransmit_time = ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL);
299 else
300 nd->retransmit_time += ndisc_timeout_compute_random(nd->retransmit_time);
1e7a0e21 301 }
9021bb9f 302
1bd6f895
PF
303 r = sd_event_add_time(nd->event, &nd->timeout_event_source,
304 clock_boottime_or_monotonic(),
305 time_now + nd->retransmit_time,
306 10 * USEC_PER_MSEC, ndisc_timeout, nd);
307 if (r < 0)
308 goto fail;
309
310 r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority);
311 if (r < 0)
312 goto fail;
313
314 (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout-no-ra");
315
1e7a0e21
LP
316 r = sd_event_source_set_enabled(nd->timeout_event_source, SD_EVENT_ONESHOT);
317 if (r < 0) {
318 log_ndisc_errno(r, "Error reenabling timer: %m");
319 goto fail;
e3169126
PF
320 }
321
1bd6f895
PF
322 log_ndisc("Sent Router Solicitation, next solicitation in %s",
323 format_timespan(time_string, FORMAT_TIMESPAN_MAX,
324 nd->retransmit_time, USEC_PER_SEC));
325
e3169126 326 return 0;
b9e7b1cf
LP
327
328fail:
329 sd_ndisc_stop(nd);
330 return 0;
e3169126
PF
331}
332
1bd6f895
PF
333static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata) {
334 sd_ndisc *nd = userdata;
335
336 assert(s);
337 assert(nd);
338
339 log_ndisc("No RA received before link confirmation timeout");
340
341 nd->timeout_no_ra = sd_event_source_unref(nd->timeout_no_ra);
342 ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
343
344 return 0;
345}
346
1e7a0e21 347_public_ int sd_ndisc_stop(sd_ndisc *nd) {
836cf090 348 assert_return(nd, -EINVAL);
836cf090 349
1e7a0e21 350 if (nd->fd < 0)
c1c9b211
LP
351 return 0;
352
1e7a0e21 353 log_ndisc("Stopping IPv6 Router Solicitation client");
836cf090 354
5c4c338a 355 ndisc_reset(nd);
1e7a0e21 356 return 1;
836cf090
PF
357}
358
1e7a0e21 359_public_ int sd_ndisc_start(sd_ndisc *nd) {
e3169126 360 int r;
1bd6f895 361 usec_t time_now;
e3169126 362
a1140666
LP
363 assert_return(nd, -EINVAL);
364 assert_return(nd->event, -EINVAL);
365 assert_return(nd->ifindex > 0, -EINVAL);
e3169126 366
1e7a0e21
LP
367 if (nd->fd >= 0)
368 return 0;
e3169126 369
1e7a0e21
LP
370 assert(!nd->recv_event_source);
371 assert(!nd->timeout_event_source);
e3169126 372
1bd6f895
PF
373 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
374 if (r < 0)
375 goto fail;
376
1e7a0e21
LP
377 nd->fd = icmp6_bind_router_solicitation(nd->ifindex);
378 if (nd->fd < 0)
379 return nd->fd;
380
381 r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd);
e3169126 382 if (r < 0)
5c4c338a 383 goto fail;
e3169126 384
3e261cfd 385 r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority);
e3169126 386 if (r < 0)
5c4c338a 387 goto fail;
e3169126 388
3e261cfd 389 (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
9021bb9f 390
1e7a0e21 391 r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(), 0, 0, ndisc_timeout, nd);
e3169126 392 if (r < 0)
5c4c338a 393 goto fail;
e3169126 394
3e261cfd 395 r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority);
9021bb9f 396 if (r < 0)
5c4c338a 397 goto fail;
e3169126 398
3e261cfd 399 (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout");
5c4c338a 400
1bd6f895
PF
401 r = sd_event_add_time(nd->event, &nd->timeout_no_ra,
402 clock_boottime_or_monotonic(),
403 time_now + NDISC_TIMEOUT_NO_RA_USEC,
404 10 * USEC_PER_MSEC, ndisc_timeout_no_ra, nd);
405 if (r < 0)
406 goto fail;
407
408 r = sd_event_source_set_priority(nd->timeout_no_ra, nd->event_priority);
409 if (r < 0)
410 goto fail;
411
412 (void) sd_event_source_set_description(nd->timeout_no_ra, "ndisc-timeout-no-ra");
413
1e7a0e21
LP
414 log_ndisc("Started IPv6 Router Solicitation client");
415 return 1;
e3169126 416
5c4c338a
LP
417fail:
418 ndisc_reset(nd);
e3169126
PF
419 return r;
420}