chiark / gitweb /
cac431b0b0da6bbd59102c28d76c50990f9f84f6
[elogind.git] / src / libsystemd-network / sd-icmp6-nd.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/ip6.h>
22 #include <string.h>
23 #include <stdbool.h>
24 #include <netinet/in.h>
25 #include <sys/ioctl.h>
26
27 #include "socket-util.h"
28 #include "refcnt.h"
29 #include "async.h"
30
31 #include "dhcp6-internal.h"
32 #include "sd-icmp6-nd.h"
33
34 #define ICMP6_ROUTER_SOLICITATION_INTERVAL      4 * USEC_PER_SEC
35 #define ICMP6_MAX_ROUTER_SOLICITATIONS          3
36
37 enum icmp6_nd_state {
38         ICMP6_NEIGHBOR_DISCOVERY_IDLE           = 0,
39         ICMP6_ROUTER_SOLICITATION_SENT          = 10,
40         ICMP6_ROUTER_ADVERTISMENT_LISTEN        = 11,
41 };
42
43 #define IP6_MIN_MTU (unsigned)1280
44 #define ICMP6_ND_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr))
45 #define ICMP6_OPT_LEN_UNITS 8
46
47 struct sd_icmp6_nd {
48         RefCount n_ref;
49
50         enum icmp6_nd_state state;
51         sd_event *event;
52         int event_priority;
53         int index;
54         struct ether_addr mac_addr;
55         int fd;
56         sd_event_source *recv;
57         sd_event_source *timeout;
58         int nd_sent;
59         sd_icmp6_nd_callback_t callback;
60         void *userdata;
61 };
62
63 #define log_icmp6_nd(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "ICMPv6 CLIENT: " fmt, ##__VA_ARGS__)
64
65 static void icmp6_nd_notify(sd_icmp6_nd *nd, int event)
66 {
67         if (nd->callback)
68                 nd->callback(nd, event, nd->userdata);
69 }
70
71 int sd_icmp6_nd_set_callback(sd_icmp6_nd *nd, sd_icmp6_nd_callback_t callback,
72                              void *userdata) {
73         assert(nd);
74
75         nd->callback = callback;
76         nd->userdata = userdata;
77
78         return 0;
79 }
80
81 int sd_icmp6_nd_set_index(sd_icmp6_nd *nd, int interface_index) {
82         assert(nd);
83         assert(interface_index >= -1);
84
85         nd->index = interface_index;
86
87         return 0;
88 }
89
90 int sd_icmp6_nd_set_mac(sd_icmp6_nd *nd, const struct ether_addr *mac_addr) {
91         assert(nd);
92
93         if (mac_addr)
94                 memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr));
95         else
96                 zero(nd->mac_addr);
97
98         return 0;
99
100 }
101
102 int sd_icmp6_nd_attach_event(sd_icmp6_nd *nd, sd_event *event, int priority) {
103         int r;
104
105         assert_return(nd, -EINVAL);
106         assert_return(!nd->event, -EBUSY);
107
108         if (event)
109                 nd->event = sd_event_ref(event);
110         else {
111                 r = sd_event_default(&nd->event);
112                 if (r < 0)
113                         return 0;
114         }
115
116         nd->event_priority = priority;
117
118         return 0;
119 }
120
121 int sd_icmp6_nd_detach_event(sd_icmp6_nd *nd) {
122         assert_return(nd, -EINVAL);
123
124         nd->event = sd_event_unref(nd->event);
125
126         return 0;
127 }
128
129 sd_event *sd_icmp6_nd_get_event(sd_icmp6_nd *nd) {
130         assert(nd);
131
132         return nd->event;
133 }
134
135 sd_icmp6_nd *sd_icmp6_nd_ref(sd_icmp6_nd *nd) {
136         assert (nd);
137
138         assert_se(REFCNT_INC(nd->n_ref) >= 2);
139
140         return nd;
141 }
142
143 static int icmp6_nd_init(sd_icmp6_nd *nd) {
144         assert(nd);
145
146         nd->recv = sd_event_source_unref(nd->recv);
147         nd->fd = asynchronous_close(nd->fd);
148         nd->timeout = sd_event_source_unref(nd->timeout);
149
150         return 0;
151 }
152
153 sd_icmp6_nd *sd_icmp6_nd_unref(sd_icmp6_nd *nd) {
154         if (nd && REFCNT_DEC(nd->n_ref) == 0) {
155
156                 icmp6_nd_init(nd);
157                 sd_icmp6_nd_detach_event(nd);
158
159                 free(nd);
160         }
161
162         return NULL;
163 }
164
165 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_icmp6_nd*, sd_icmp6_nd_unref);
166 #define _cleanup_sd_icmp6_nd_free_ _cleanup_(sd_icmp6_nd_unrefp)
167
168 int sd_icmp6_nd_new(sd_icmp6_nd **ret) {
169         _cleanup_sd_icmp6_nd_free_ sd_icmp6_nd *nd = NULL;
170
171         assert(ret);
172
173         nd = new0(sd_icmp6_nd, 1);
174         if (!nd)
175                 return -ENOMEM;
176
177         nd->n_ref = REFCNT_INIT;
178
179         nd->index = -1;
180         nd->fd = -1;
181
182         *ret = nd;
183         nd = NULL;
184
185         return 0;
186 }
187
188 static int icmp6_ra_parse(sd_icmp6_nd *nd, struct nd_router_advert *ra,
189                           ssize_t len) {
190         void *opt;
191         struct nd_opt_hdr *opt_hdr;
192
193         assert_return(nd, -EINVAL);
194         assert_return(ra, -EINVAL);
195
196         len -= sizeof(*ra);
197         if (len < ICMP6_OPT_LEN_UNITS) {
198                 log_icmp6_nd(nd, "Router Advertisement below minimum length");
199
200                 return -ENOMSG;
201         }
202
203         opt = ra + 1;
204         opt_hdr = opt;
205
206         while (len != 0 && len >= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS) {
207
208                 if (opt_hdr->nd_opt_len == 0)
209                         return -ENOMSG;
210
211                 switch (opt_hdr->nd_opt_type) {
212
213                 }
214
215                 len -= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS;
216                 opt = (void *)((char *)opt +
217                         opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS);
218                 opt_hdr = opt;
219         }
220
221         if (len > 0)
222                 log_icmp6_nd(nd, "Router Advertisement contains %zd bytes of trailing garbage", len);
223
224         return 0;
225 }
226
227 static int icmp6_router_advertisment_recv(sd_event_source *s, int fd,
228                                           uint32_t revents, void *userdata)
229 {
230         sd_icmp6_nd *nd = userdata;
231         int r, buflen = 0;
232         ssize_t len;
233         _cleanup_free_ struct nd_router_advert *ra = NULL;
234         int event = ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE;
235
236         assert(s);
237         assert(nd);
238         assert(nd->event);
239
240         r = ioctl(fd, FIONREAD, &buflen);
241         if (r < 0 || buflen <= 0)
242                 buflen = ICMP6_ND_RECV_SIZE;
243
244         ra = malloc(buflen);
245         if (!ra)
246                 return -ENOMEM;
247
248         len = read(fd, ra, buflen);
249         if (len < 0) {
250                 log_icmp6_nd(nd, "Could not receive message from UDP socket: %m");
251                 return 0;
252         }
253
254         if (ra->nd_ra_type != ND_ROUTER_ADVERT)
255                 return 0;
256
257         if (ra->nd_ra_code != 0)
258                 return 0;
259
260         nd->timeout = sd_event_source_unref(nd->timeout);
261
262         nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
263
264         if (ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER )
265                 event = ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER;
266
267         if (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
268                 event = ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED;
269
270         log_icmp6_nd(nd, "Received Router Advertisement flags %s/%s",
271                      ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED? "MANAGED": "none",
272                      ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER? "OTHER": "none");
273
274         if (event != ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE) {
275                 r = icmp6_ra_parse(nd, ra, len);
276                 if (r < 0) {
277                         log_icmp6_nd(nd, "Could not parse Router Advertisement: %s",
278                                      strerror(-r));
279                         return 0;
280                 }
281         }
282
283         icmp6_nd_notify(nd, event);
284
285         return 0;
286 }
287
288 static int icmp6_router_solicitation_timeout(sd_event_source *s, uint64_t usec,
289                                              void *userdata)
290 {
291         sd_icmp6_nd *nd = userdata;
292         uint64_t time_now, next_timeout;
293         struct ether_addr unset = { };
294         struct ether_addr *addr = NULL;
295         int r;
296
297         assert(s);
298         assert(nd);
299         assert(nd->event);
300
301         nd->timeout = sd_event_source_unref(nd->timeout);
302
303         if (nd->nd_sent >= ICMP6_MAX_ROUTER_SOLICITATIONS) {
304                 icmp6_nd_notify(nd, ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT);
305                 nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
306         } else {
307                 if (memcmp(&nd->mac_addr, &unset, sizeof(struct ether_addr)))
308                         addr = &nd->mac_addr;
309
310                 r = dhcp_network_icmp6_send_router_solicitation(nd->fd, addr);
311                 if (r < 0)
312                         log_icmp6_nd(nd, "Error sending Router Solicitation");
313                 else {
314                         nd->state = ICMP6_ROUTER_SOLICITATION_SENT;
315                         log_icmp6_nd(nd, "Sent Router Solicitation");
316                 }
317
318                 nd->nd_sent++;
319
320                 r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
321                 if (r < 0) {
322                         icmp6_nd_notify(nd, r);
323                         return 0;
324                 }
325
326                 next_timeout = time_now + ICMP6_ROUTER_SOLICITATION_INTERVAL;
327
328                 r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
329                                       next_timeout, 0,
330                                       icmp6_router_solicitation_timeout, nd);
331                 if (r < 0) {
332                         icmp6_nd_notify(nd, r);
333                         return 0;
334                 }
335
336                 r = sd_event_source_set_priority(nd->timeout,
337                                                  nd->event_priority);
338                 if (r < 0) {
339                         icmp6_nd_notify(nd, r);
340                         return 0;
341                 }
342
343                 r = sd_event_source_set_description(nd->timeout, "icmp6-timeout");
344                 if (r < 0) {
345                         icmp6_nd_notify(nd, r);
346                         return 0;
347                 }
348         }
349
350         return 0;
351 }
352
353 int sd_icmp6_nd_stop(sd_icmp6_nd *nd) {
354         assert_return(nd, -EINVAL);
355         assert_return(nd->event, -EINVAL);
356
357         log_icmp6_nd(client, "Stop ICMPv6");
358
359         icmp6_nd_init(nd);
360
361         nd->state = ICMP6_NEIGHBOR_DISCOVERY_IDLE;
362
363         return 0;
364 }
365
366 int sd_icmp6_router_solicitation_start(sd_icmp6_nd *nd) {
367         int r;
368
369         assert(nd);
370         assert(nd->event);
371
372         if (nd->state != ICMP6_NEIGHBOR_DISCOVERY_IDLE)
373                 return -EINVAL;
374
375         if (nd->index < 1)
376                 return -EINVAL;
377
378         r = dhcp_network_icmp6_bind_router_solicitation(nd->index);
379         if (r < 0)
380                 return r;
381
382         nd->fd = r;
383
384         r = sd_event_add_io(nd->event, &nd->recv, nd->fd, EPOLLIN,
385                             icmp6_router_advertisment_recv, nd);
386         if (r < 0)
387                 goto error;
388
389         r = sd_event_source_set_priority(nd->recv, nd->event_priority);
390         if (r < 0)
391                 goto error;
392
393         r = sd_event_source_set_description(nd->recv, "icmp6-receive-message");
394         if (r < 0)
395                 goto error;
396
397         r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(),
398                               0, 0, icmp6_router_solicitation_timeout, nd);
399         if (r < 0)
400                 goto error;
401
402         r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
403         if (r < 0)
404                 goto error;
405
406         r = sd_event_source_set_description(nd->timeout, "icmp6-timeout");
407 error:
408         if (r < 0)
409                 icmp6_nd_init(nd);
410         else
411                 log_icmp6_nd(client, "Start Router Solicitation");
412
413         return r;
414 }