]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-ndisc.c
sd-radv: Send Router Advertisments
[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"
940367a0 31#include "socket-util.h"
d7fa4380 32#include "string-util.h"
1e7a0e21 33#include "util.h"
e3169126 34
9c2438b8 35#define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC)
1e7a0e21 36#define NDISC_MAX_ROUTER_SOLICITATIONS 3U
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);
1e7a0e21
LP
132 nd->recv_event_source = sd_event_source_unref(nd->recv_event_source);
133 nd->fd = safe_close(nd->fd);
7feeb899 134 nd->nd_sent = 0;
e3169126
PF
135
136 return 0;
137}
138
1e7a0e21 139_public_ sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
e3169126 140
9c8e3101
LP
141 if (!nd)
142 return NULL;
143
144 assert(nd->n_ref > 0);
145 nd->n_ref--;
146
147 if (nd->n_ref > 0)
148 return NULL;
e3169126 149
5c4c338a 150 ndisc_reset(nd);
4d7b83da 151 sd_ndisc_detach_event(nd);
6b430fdb 152 return mfree(nd);
e3169126
PF
153}
154
1e7a0e21 155_public_ int sd_ndisc_new(sd_ndisc **ret) {
4afd3348 156 _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
e3169126 157
a1140666 158 assert_return(ret, -EINVAL);
e3169126 159
4d7b83da 160 nd = new0(sd_ndisc, 1);
e3169126
PF
161 if (!nd)
162 return -ENOMEM;
163
9c8e3101 164 nd->n_ref = 1;
03de7ed9 165 nd->fd = -1;
e3169126
PF
166
167 *ret = nd;
168 nd = NULL;
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
1e7a0e21
LP
201 r = ndisc_router_parse(rt);
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
1e7a0e21
LP
213 log_ndisc("Received Router Advertisement: flags %s preference %s lifetime %" PRIu16 " sec",
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;
cddf4d81
TG
225 union {
226 struct cmsghdr cmsghdr;
1e7a0e21
LP
227 uint8_t buf[CMSG_SPACE(sizeof(int)) + /* ttl */
228 CMSG_SPACE(sizeof(struct timeval))];
cddf4d81
TG
229 } control = {};
230 struct iovec iov = {};
231 union sockaddr_union sa = {};
232 struct msghdr msg = {
233 .msg_name = &sa.sa,
234 .msg_namelen = sizeof(sa),
235 .msg_iov = &iov,
236 .msg_iovlen = 1,
237 .msg_control = &control,
238 .msg_controllen = sizeof(control),
239 };
240 struct cmsghdr *cmsg;
4edc2c9b 241 ssize_t len, buflen;
e3169126
PF
242
243 assert(s);
244 assert(nd);
245 assert(nd->event);
246
4edc2c9b
LP
247 buflen = next_datagram_size_fd(fd);
248 if (buflen < 0)
1e7a0e21 249 return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m");
cddf4d81 250
1e7a0e21
LP
251 rt = ndisc_router_new(buflen);
252 if (!rt)
09667885
PF
253 return -ENOMEM;
254
1e7a0e21
LP
255 iov.iov_base = NDISC_ROUTER_RAW(rt);
256 iov.iov_len = rt->raw_size;
cddf4d81 257
1e7a0e21 258 len = recvmsg(fd, &msg, MSG_DONTWAIT);
09667885 259 if (len < 0) {
0d43d2fc
TG
260 if (errno == EAGAIN || errno == EINTR)
261 return 0;
262
1e7a0e21 263 return log_ndisc_errno(errno, "Could not receive message from ICMPv6 socket: %m");
004845d1 264 }
1e7a0e21
LP
265
266 if ((size_t) len != rt->raw_size) {
267 log_ndisc("Packet size mismatch.");
268 return -EINVAL;
004845d1
LP
269 }
270
1e7a0e21
LP
271 if (msg.msg_namelen == sizeof(struct sockaddr_in6) &&
272 sa.in6.sin6_family == AF_INET6) {
273
274 if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr) <= 0) {
275 _cleanup_free_ char *addr = NULL;
276
277 (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr, &addr);
278 log_ndisc("Received RA from non-link-local address %s. Ignoring.", strna(addr));
279 return 0;
280 }
281
282 rt->address = sa.in6.sin6_addr;
283
284 } else if (msg.msg_namelen > 0) {
285 log_ndisc("Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t) msg.msg_namelen);
286 return -EINVAL;
287 }
288
289 /* namelen == 0 only happens when running the test-suite over a socketpair */
cddf4d81
TG
290
291 assert(!(msg.msg_flags & MSG_CTRUNC));
292 assert(!(msg.msg_flags & MSG_TRUNC));
293
294 CMSG_FOREACH(cmsg, &msg) {
295 if (cmsg->cmsg_level == SOL_IPV6 &&
296 cmsg->cmsg_type == IPV6_HOPLIMIT &&
297 cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
1e7a0e21 298 int hops = *(int*) CMSG_DATA(cmsg);
cddf4d81
TG
299
300 if (hops != 255) {
1e7a0e21 301 log_ndisc("Received RA with invalid hop limit %d. Ignoring.", hops);
cddf4d81
TG
302 return 0;
303 }
cddf4d81 304 }
d7fa4380 305
1e7a0e21
LP
306 if (cmsg->cmsg_level == SOL_SOCKET &&
307 cmsg->cmsg_type == SO_TIMESTAMP &&
308 cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
a48072b7 309 triple_timestamp_from_realtime(&rt->timestamp, timeval_load((struct timeval*) CMSG_DATA(cmsg)));
d7fa4380 310 }
3ccd3163 311
1e7a0e21
LP
312 if (!triple_timestamp_is_set(&rt->timestamp))
313 triple_timestamp_get(&rt->timestamp);
e3169126 314
3e261cfd 315 nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
09667885 316
1e7a0e21 317 return ndisc_handle_datagram(nd, rt);
e3169126
PF
318}
319
1e7a0e21 320static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
4d7b83da 321 sd_ndisc *nd = userdata;
9c4f6ccb 322 usec_t time_now, next_timeout;
e3169126
PF
323 int r;
324
325 assert(s);
326 assert(nd);
327 assert(nd->event);
328
46ec6687 329 if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) {
1e7a0e21
LP
330 nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
331 ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
332 return 0;
333 }
e3169126 334
1e7a0e21
LP
335 r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
336 if (r < 0) {
337 log_ndisc_errno(r, "Error sending Router Solicitation: %m");
338 goto fail;
339 }
e3169126 340
1e7a0e21
LP
341 log_ndisc("Sent Router Solicitation");
342 nd->nd_sent++;
e3169126 343
1e7a0e21
LP
344 assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
345 next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL;
e3169126 346
1e7a0e21
LP
347 r = sd_event_source_set_time(nd->timeout_event_source, next_timeout);
348 if (r < 0) {
349 log_ndisc_errno(r, "Error updating timer: %m");
350 goto fail;
351 }
9021bb9f 352
1e7a0e21
LP
353 r = sd_event_source_set_enabled(nd->timeout_event_source, SD_EVENT_ONESHOT);
354 if (r < 0) {
355 log_ndisc_errno(r, "Error reenabling timer: %m");
356 goto fail;
e3169126
PF
357 }
358
359 return 0;
b9e7b1cf
LP
360
361fail:
362 sd_ndisc_stop(nd);
363 return 0;
e3169126
PF
364}
365
1e7a0e21 366_public_ int sd_ndisc_stop(sd_ndisc *nd) {
836cf090 367 assert_return(nd, -EINVAL);
836cf090 368
1e7a0e21 369 if (nd->fd < 0)
c1c9b211
LP
370 return 0;
371
1e7a0e21 372 log_ndisc("Stopping IPv6 Router Solicitation client");
836cf090 373
5c4c338a 374 ndisc_reset(nd);
1e7a0e21 375 return 1;
836cf090
PF
376}
377
1e7a0e21 378_public_ int sd_ndisc_start(sd_ndisc *nd) {
e3169126
PF
379 int r;
380
a1140666
LP
381 assert_return(nd, -EINVAL);
382 assert_return(nd->event, -EINVAL);
383 assert_return(nd->ifindex > 0, -EINVAL);
e3169126 384
1e7a0e21
LP
385 if (nd->fd >= 0)
386 return 0;
e3169126 387
1e7a0e21
LP
388 assert(!nd->recv_event_source);
389 assert(!nd->timeout_event_source);
e3169126 390
1e7a0e21
LP
391 nd->fd = icmp6_bind_router_solicitation(nd->ifindex);
392 if (nd->fd < 0)
393 return nd->fd;
394
395 r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd);
e3169126 396 if (r < 0)
5c4c338a 397 goto fail;
e3169126 398
3e261cfd 399 r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority);
e3169126 400 if (r < 0)
5c4c338a 401 goto fail;
e3169126 402
3e261cfd 403 (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
9021bb9f 404
1e7a0e21 405 r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(), 0, 0, ndisc_timeout, nd);
e3169126 406 if (r < 0)
5c4c338a 407 goto fail;
e3169126 408
3e261cfd 409 r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority);
9021bb9f 410 if (r < 0)
5c4c338a 411 goto fail;
e3169126 412
3e261cfd 413 (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout");
5c4c338a 414
1e7a0e21
LP
415 log_ndisc("Started IPv6 Router Solicitation client");
416 return 1;
e3169126 417
5c4c338a
LP
418fail:
419 ndisc_reset(nd);
e3169126
PF
420 return r;
421}