]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/sd-ndisc.c
tree-wide: use mfree more
[thirdparty/systemd.git] / src / libsystemd-network / sd-ndisc.c
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>
21 #include <netinet/in.h>
22
23 #include "sd-ndisc.h"
24
25 #include "alloc-util.h"
26 #include "fd-util.h"
27 #include "icmp6-util.h"
28 #include "in-addr-util.h"
29 #include "ndisc-internal.h"
30 #include "ndisc-router.h"
31 #include "socket-util.h"
32 #include "string-util.h"
33 #include "util.h"
34
35 #define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC)
36 #define NDISC_MAX_ROUTER_SOLICITATIONS 3U
37
38 static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) {
39 assert(ndisc);
40
41 log_ndisc("Invoking callback for '%c'.", event);
42
43 if (!ndisc->callback)
44 return;
45
46 ndisc->callback(ndisc, event, rt, ndisc->userdata);
47 }
48
49 _public_ int sd_ndisc_set_callback(
50 sd_ndisc *nd,
51 sd_ndisc_callback_t callback,
52 void *userdata) {
53
54 assert_return(nd, -EINVAL);
55
56 nd->callback = callback;
57 nd->userdata = userdata;
58
59 return 0;
60 }
61
62 _public_ int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
63 assert_return(nd, -EINVAL);
64 assert_return(ifindex > 0, -EINVAL);
65 assert_return(nd->fd < 0, -EBUSY);
66
67 nd->ifindex = ifindex;
68 return 0;
69 }
70
71 _public_ int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
72 assert_return(nd, -EINVAL);
73
74 if (mac_addr)
75 nd->mac_addr = *mac_addr;
76 else
77 zero(nd->mac_addr);
78
79 return 0;
80 }
81
82 _public_ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
83 int r;
84
85 assert_return(nd, -EINVAL);
86 assert_return(nd->fd < 0, -EBUSY);
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
102 _public_ int sd_ndisc_detach_event(sd_ndisc *nd) {
103
104 assert_return(nd, -EINVAL);
105 assert_return(nd->fd < 0, -EBUSY);
106
107 nd->event = sd_event_unref(nd->event);
108 return 0;
109 }
110
111 _public_ sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
112 assert_return(nd, NULL);
113
114 return nd->event;
115 }
116
117 _public_ sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
118
119 if (!nd)
120 return NULL;
121
122 assert(nd->n_ref > 0);
123 nd->n_ref++;
124
125 return nd;
126 }
127
128 static int ndisc_reset(sd_ndisc *nd) {
129 assert(nd);
130
131 nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
132 nd->recv_event_source = sd_event_source_unref(nd->recv_event_source);
133 nd->fd = safe_close(nd->fd);
134
135 return 0;
136 }
137
138 _public_ sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
139
140 if (!nd)
141 return NULL;
142
143 assert(nd->n_ref > 0);
144 nd->n_ref--;
145
146 if (nd->n_ref > 0)
147 return NULL;
148
149 ndisc_reset(nd);
150 sd_ndisc_detach_event(nd);
151 return mfree(nd);
152 }
153
154 _public_ int sd_ndisc_new(sd_ndisc **ret) {
155 _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
156
157 assert_return(ret, -EINVAL);
158
159 nd = new0(sd_ndisc, 1);
160 if (!nd)
161 return -ENOMEM;
162
163 nd->n_ref = 1;
164 nd->fd = -1;
165
166 *ret = nd;
167 nd = NULL;
168
169 return 0;
170 }
171
172 _public_ int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
173 assert_return(nd, -EINVAL);
174 assert_return(mtu, -EINVAL);
175
176 if (nd->mtu == 0)
177 return -ENODATA;
178
179 *mtu = nd->mtu;
180 return 0;
181 }
182
183 _public_ int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret) {
184 assert_return(nd, -EINVAL);
185 assert_return(ret, -EINVAL);
186
187 if (nd->hop_limit == 0)
188 return -ENODATA;
189
190 *ret = nd->hop_limit;
191 return 0;
192 }
193
194 static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
195 int r;
196
197 assert(nd);
198 assert(rt);
199
200 r = ndisc_router_parse(rt);
201 if (r == -EBADMSG) /* Bad packet */
202 return 0;
203 if (r < 0)
204 return 0;
205
206 /* Update global variables we keep */
207 if (rt->mtu > 0)
208 nd->mtu = rt->mtu;
209 if (rt->hop_limit > 0)
210 nd->hop_limit = rt->hop_limit;
211
212 log_ndisc("Received Router Advertisement: flags %s preference %s lifetime %" PRIu16 " sec",
213 rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none",
214 rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium",
215 rt->lifetime);
216
217 ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt);
218 return 0;
219 }
220
221 static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
222 _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
223 sd_ndisc *nd = userdata;
224 union {
225 struct cmsghdr cmsghdr;
226 uint8_t buf[CMSG_SPACE(sizeof(int)) + /* ttl */
227 CMSG_SPACE(sizeof(struct timeval))];
228 } control = {};
229 struct iovec iov = {};
230 union sockaddr_union sa = {};
231 struct msghdr msg = {
232 .msg_name = &sa.sa,
233 .msg_namelen = sizeof(sa),
234 .msg_iov = &iov,
235 .msg_iovlen = 1,
236 .msg_control = &control,
237 .msg_controllen = sizeof(control),
238 };
239 struct cmsghdr *cmsg;
240 ssize_t len, buflen;
241
242 assert(s);
243 assert(nd);
244 assert(nd->event);
245
246 buflen = next_datagram_size_fd(fd);
247 if (buflen < 0)
248 return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m");
249
250 rt = ndisc_router_new(buflen);
251 if (!rt)
252 return -ENOMEM;
253
254 iov.iov_base = NDISC_ROUTER_RAW(rt);
255 iov.iov_len = rt->raw_size;
256
257 len = recvmsg(fd, &msg, MSG_DONTWAIT);
258 if (len < 0) {
259 if (errno == EAGAIN || errno == EINTR)
260 return 0;
261
262 return log_ndisc_errno(errno, "Could not receive message from ICMPv6 socket: %m");
263 }
264
265 if ((size_t) len != rt->raw_size) {
266 log_ndisc("Packet size mismatch.");
267 return -EINVAL;
268 }
269
270 if (msg.msg_namelen == sizeof(struct sockaddr_in6) &&
271 sa.in6.sin6_family == AF_INET6) {
272
273 if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr) <= 0) {
274 _cleanup_free_ char *addr = NULL;
275
276 (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr, &addr);
277 log_ndisc("Received RA from non-link-local address %s. Ignoring.", strna(addr));
278 return 0;
279 }
280
281 rt->address = sa.in6.sin6_addr;
282
283 } else if (msg.msg_namelen > 0) {
284 log_ndisc("Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t) msg.msg_namelen);
285 return -EINVAL;
286 }
287
288 /* namelen == 0 only happens when running the test-suite over a socketpair */
289
290 assert(!(msg.msg_flags & MSG_CTRUNC));
291 assert(!(msg.msg_flags & MSG_TRUNC));
292
293 CMSG_FOREACH(cmsg, &msg) {
294 if (cmsg->cmsg_level == SOL_IPV6 &&
295 cmsg->cmsg_type == IPV6_HOPLIMIT &&
296 cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
297 int hops = *(int*) CMSG_DATA(cmsg);
298
299 if (hops != 255) {
300 log_ndisc("Received RA with invalid hop limit %d. Ignoring.", hops);
301 return 0;
302 }
303 }
304
305 if (cmsg->cmsg_level == SOL_SOCKET &&
306 cmsg->cmsg_type == SO_TIMESTAMP &&
307 cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
308 triple_timestamp_from_realtime(&rt->timestamp, timeval_load((struct timeval*) CMSG_DATA(cmsg)));
309 }
310
311 if (!triple_timestamp_is_set(&rt->timestamp))
312 triple_timestamp_get(&rt->timestamp);
313
314 nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
315
316 return ndisc_handle_datagram(nd, rt);
317 }
318
319 static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
320 sd_ndisc *nd = userdata;
321 usec_t time_now, next_timeout;
322 int r;
323
324 assert(s);
325 assert(nd);
326 assert(nd->event);
327
328 if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) {
329 nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
330 ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
331 return 0;
332 }
333
334 r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
335 if (r < 0) {
336 log_ndisc_errno(r, "Error sending Router Solicitation: %m");
337 goto fail;
338 }
339
340 log_ndisc("Sent Router Solicitation");
341 nd->nd_sent++;
342
343 assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
344 next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL;
345
346 r = sd_event_source_set_time(nd->timeout_event_source, next_timeout);
347 if (r < 0) {
348 log_ndisc_errno(r, "Error updating timer: %m");
349 goto fail;
350 }
351
352 r = sd_event_source_set_enabled(nd->timeout_event_source, SD_EVENT_ONESHOT);
353 if (r < 0) {
354 log_ndisc_errno(r, "Error reenabling timer: %m");
355 goto fail;
356 }
357
358 return 0;
359
360 fail:
361 sd_ndisc_stop(nd);
362 return 0;
363 }
364
365 _public_ int sd_ndisc_stop(sd_ndisc *nd) {
366 assert_return(nd, -EINVAL);
367
368 if (nd->fd < 0)
369 return 0;
370
371 log_ndisc("Stopping IPv6 Router Solicitation client");
372
373 ndisc_reset(nd);
374 return 1;
375 }
376
377 _public_ int sd_ndisc_start(sd_ndisc *nd) {
378 int r;
379
380 assert_return(nd, -EINVAL);
381 assert_return(nd->event, -EINVAL);
382 assert_return(nd->ifindex > 0, -EINVAL);
383
384 if (nd->fd >= 0)
385 return 0;
386
387 assert(!nd->recv_event_source);
388 assert(!nd->timeout_event_source);
389
390 nd->fd = icmp6_bind_router_solicitation(nd->ifindex);
391 if (nd->fd < 0)
392 return nd->fd;
393
394 r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd);
395 if (r < 0)
396 goto fail;
397
398 r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority);
399 if (r < 0)
400 goto fail;
401
402 (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
403
404 r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(), 0, 0, ndisc_timeout, nd);
405 if (r < 0)
406 goto fail;
407
408 r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority);
409 if (r < 0)
410 goto fail;
411
412 (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout");
413
414 log_ndisc("Started IPv6 Router Solicitation client");
415 return 1;
416
417 fail:
418 ndisc_reset(nd);
419 return r;
420 }