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 
ndisc_callback(sd_ndisc * ndisc,sd_ndisc_event_t event,sd_ndisc_router * rt)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 
sd_ndisc_set_callback(sd_ndisc * nd,sd_ndisc_callback_t callback,void * userdata)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 
sd_ndisc_set_ifindex(sd_ndisc * nd,int ifindex)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 
sd_ndisc_set_ifname(sd_ndisc * nd,const char * ifname)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 
sd_ndisc_get_ifname(sd_ndisc * nd,const char ** ret)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 
sd_ndisc_set_mac(sd_ndisc * nd,const struct ether_addr * mac_addr)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 
sd_ndisc_attach_event(sd_ndisc * nd,sd_event * event,int64_t priority)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 
sd_ndisc_detach_event(sd_ndisc * nd)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 
sd_ndisc_get_event(sd_ndisc * nd)132 sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
133         assert_return(nd, NULL);
134 
135         return nd->event;
136 }
137 
ndisc_reset(sd_ndisc * nd)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 
ndisc_free(sd_ndisc * nd)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 
sd_ndisc_new(sd_ndisc ** ret)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 = -1,
175         };
176 
177         *ret = TAKE_PTR(nd);
178 
179         return 0;
180 }
181 
ndisc_handle_datagram(sd_ndisc * nd,sd_ndisc_router * rt)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 
ndisc_recv(sd_event_source * s,int fd,uint32_t revents,void * userdata)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 = userdata;
204         ssize_t buflen;
205         int r;
206         _cleanup_free_ char *addr = NULL;
207 
208         assert(s);
209         assert(nd);
210         assert(nd->event);
211 
212         buflen = next_datagram_size_fd(fd);
213         if (buflen < 0) {
214                 if (ERRNO_IS_TRANSIENT(buflen) || ERRNO_IS_DISCONNECT(buflen))
215                         return 0;
216 
217                 log_ndisc_errno(nd, buflen, "Failed to determine datagram size to read, ignoring: %m");
218                 return 0;
219         }
220 
221         rt = ndisc_router_new(buflen);
222         if (!rt)
223                 return -ENOMEM;
224 
225         r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address, &rt->timestamp);
226         if (r < 0) {
227                 if (ERRNO_IS_TRANSIENT(r) || ERRNO_IS_DISCONNECT(r))
228                         return 0;
229 
230                 switch (r) {
231                 case -EADDRNOTAVAIL:
232                         (void) in_addr_to_string(AF_INET6, (const union in_addr_union*) &rt->address, &addr);
233                         log_ndisc(nd, "Received RA from non-link-local address %s. Ignoring", addr);
234                         break;
235 
236                 case -EMULTIHOP:
237                         log_ndisc(nd, "Received RA with invalid hop limit. Ignoring.");
238                         break;
239 
240                 case -EPFNOSUPPORT:
241                         log_ndisc(nd, "Received invalid source address from ICMPv6 socket. Ignoring.");
242                         break;
243 
244                 default:
245                         log_ndisc_errno(nd, r, "Unexpected error while reading from ICMPv6, ignoring: %m");
246                         break;
247                 }
248 
249                 return 0;
250         }
251 
252         (void) event_source_disable(nd->timeout_event_source);
253         (void) ndisc_handle_datagram(nd, rt);
254         return 0;
255 }
256 
ndisc_timeout_compute_random(usec_t val)257 static usec_t ndisc_timeout_compute_random(usec_t val) {
258         /* compute a time that is random within ±10% of the given value */
259         return val - val / 10 +
260                 (random_u64() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
261 }
262 
ndisc_timeout(sd_event_source * s,uint64_t usec,void * userdata)263 static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
264         sd_ndisc *nd = userdata;
265         usec_t time_now;
266         int r;
267 
268         assert(s);
269         assert(nd);
270         assert(nd->event);
271 
272         assert_se(sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now) >= 0);
273 
274         if (!nd->retransmit_time)
275                 nd->retransmit_time = ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL);
276         else {
277                 if (nd->retransmit_time > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 2)
278                         nd->retransmit_time = ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL);
279                 else
280                         nd->retransmit_time += ndisc_timeout_compute_random(nd->retransmit_time);
281         }
282 
283         r = event_reset_time(nd->event, &nd->timeout_event_source,
284                              CLOCK_BOOTTIME,
285                              time_now + nd->retransmit_time, 10 * USEC_PER_MSEC,
286                              ndisc_timeout, nd,
287                              nd->event_priority, "ndisc-timeout-no-ra", true);
288         if (r < 0)
289                 goto fail;
290 
291         r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
292         if (r < 0) {
293                 log_ndisc_errno(nd, r, "Error sending Router Solicitation: %m");
294                 goto fail;
295         }
296 
297         log_ndisc(nd, "Sent Router Solicitation, next solicitation in %s",
298                   FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC));
299 
300         return 0;
301 
302 fail:
303         (void) sd_ndisc_stop(nd);
304         return 0;
305 }
306 
ndisc_timeout_no_ra(sd_event_source * s,uint64_t usec,void * userdata)307 static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata) {
308         sd_ndisc *nd = userdata;
309 
310         assert(s);
311         assert(nd);
312 
313         log_ndisc(nd, "No RA received before link confirmation timeout");
314 
315         (void) event_source_disable(nd->timeout_no_ra);
316         ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
317 
318         return 0;
319 }
320 
sd_ndisc_stop(sd_ndisc * nd)321 int sd_ndisc_stop(sd_ndisc *nd) {
322         if (!nd)
323                 return 0;
324 
325         if (nd->fd < 0)
326                 return 0;
327 
328         log_ndisc(nd, "Stopping IPv6 Router Solicitation client");
329 
330         ndisc_reset(nd);
331         return 1;
332 }
333 
sd_ndisc_start(sd_ndisc * nd)334 int sd_ndisc_start(sd_ndisc *nd) {
335         int r;
336         usec_t time_now;
337 
338         assert_return(nd, -EINVAL);
339         assert_return(nd->event, -EINVAL);
340         assert_return(nd->ifindex > 0, -EINVAL);
341 
342         if (nd->fd >= 0)
343                 return 0;
344 
345         assert(!nd->recv_event_source);
346 
347         r = sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now);
348         if (r < 0)
349                 goto fail;
350 
351         nd->fd = icmp6_bind_router_solicitation(nd->ifindex);
352         if (nd->fd < 0)
353                 return nd->fd;
354 
355         r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd);
356         if (r < 0)
357                 goto fail;
358 
359         r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority);
360         if (r < 0)
361                 goto fail;
362 
363         (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
364 
365         r = event_reset_time(nd->event, &nd->timeout_event_source,
366                              CLOCK_BOOTTIME,
367                              time_now + USEC_PER_SEC / 2, 1 * USEC_PER_SEC, /* See RFC 8415 sec. 18.2.1 */
368                              ndisc_timeout, nd,
369                              nd->event_priority, "ndisc-timeout", true);
370         if (r < 0)
371                 goto fail;
372 
373         r = event_reset_time(nd->event, &nd->timeout_no_ra,
374                              CLOCK_BOOTTIME,
375                              time_now + NDISC_TIMEOUT_NO_RA_USEC, 10 * USEC_PER_MSEC,
376                              ndisc_timeout_no_ra, nd,
377                              nd->event_priority, "ndisc-timeout-no-ra", true);
378         if (r < 0)
379                 goto fail;
380 
381         log_ndisc(nd, "Started IPv6 Router Solicitation client");
382         return 1;
383 
384 fail:
385         ndisc_reset(nd);
386         return r;
387 }
388