chiark / gitweb /
10c5e5e87efd522f59d54a0aae98292c14273961
[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 <string.h>
22 #include <stdbool.h>
23 #include <netinet/in.h>
24
25 #include "socket-util.h"
26 #include "refcnt.h"
27 #include "async.h"
28
29 #include "dhcp6-internal.h"
30 #include "sd-icmp6-nd.h"
31
32 #define ICMP6_ROUTER_SOLICITATION_INTERVAL      4 * USEC_PER_SEC
33 #define ICMP6_MAX_ROUTER_SOLICITATIONS          3
34
35 enum icmp6_nd_state {
36         ICMP6_NEIGHBOR_DISCOVERY_IDLE           = 0,
37         ICMP6_ROUTER_SOLICITATION_SENT          = 10,
38         ICMP6_ROUTER_ADVERTISMENT_LISTEN        = 11,
39 };
40
41 struct sd_icmp6_nd {
42         RefCount n_ref;
43
44         enum icmp6_nd_state state;
45         sd_event *event;
46         int event_priority;
47         int index;
48         struct ether_addr mac_addr;
49         int fd;
50         sd_event_source *recv;
51         sd_event_source *timeout;
52         int nd_sent;
53         sd_icmp6_nd_callback_t callback;
54         void *userdata;
55 };
56
57 #define log_icmp6_nd(p, fmt, ...) log_meta(LOG_DEBUG, __FILE__, __LINE__, __func__, "ICMPv6 CLIENT: " fmt, ##__VA_ARGS__)
58
59 static void icmp6_nd_notify(sd_icmp6_nd *nd, int event)
60 {
61         if (nd->callback)
62                 nd->callback(nd, event, nd->userdata);
63 }
64
65 int sd_icmp6_nd_set_callback(sd_icmp6_nd *nd, sd_icmp6_nd_callback_t callback,
66                              void *userdata) {
67         assert(nd);
68
69         nd->callback = callback;
70         nd->userdata = userdata;
71
72         return 0;
73 }
74
75 int sd_icmp6_nd_set_index(sd_icmp6_nd *nd, int interface_index) {
76         assert(nd);
77         assert(interface_index >= -1);
78
79         nd->index = interface_index;
80
81         return 0;
82 }
83
84 int sd_icmp6_nd_set_mac(sd_icmp6_nd *nd, const struct ether_addr *mac_addr) {
85         assert(nd);
86
87         if (mac_addr)
88                 memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr));
89         else
90                 memset(&nd->mac_addr, 0x00, sizeof(nd->mac_addr));
91
92         return 0;
93
94 }
95
96 int sd_icmp6_nd_attach_event(sd_icmp6_nd *nd, sd_event *event, int priority) {
97         int r;
98
99         assert_return(nd, -EINVAL);
100         assert_return(!nd->event, -EBUSY);
101
102         if (event)
103                 nd->event = sd_event_ref(event);
104         else {
105                 r = sd_event_default(&nd->event);
106                 if (r < 0)
107                         return 0;
108         }
109
110         nd->event_priority = priority;
111
112         return 0;
113 }
114
115 int sd_icmp6_nd_detach_event(sd_icmp6_nd *nd) {
116         assert_return(nd, -EINVAL);
117
118         nd->event = sd_event_unref(nd->event);
119
120         return 0;
121 }
122
123 sd_event *sd_icmp6_nd_get_event(sd_icmp6_nd *nd) {
124         assert(nd);
125
126         return nd->event;
127 }
128
129 sd_icmp6_nd *sd_icmp6_nd_ref(sd_icmp6_nd *nd) {
130         assert (nd);
131
132         assert_se(REFCNT_INC(nd->n_ref) >= 2);
133
134         return nd;
135 }
136
137 static int icmp6_nd_init(sd_icmp6_nd *nd) {
138         assert(nd);
139
140         nd->recv = sd_event_source_unref(nd->recv);
141         nd->fd = asynchronous_close(nd->fd);
142         nd->timeout = sd_event_source_unref(nd->timeout);
143
144         return 0;
145 }
146
147 sd_icmp6_nd *sd_icmp6_nd_unref(sd_icmp6_nd *nd) {
148         if (nd && REFCNT_DEC(nd->n_ref) <= 0) {
149
150                 icmp6_nd_init(nd);
151                 sd_icmp6_nd_detach_event(nd);
152
153                 free(nd);
154         }
155
156         return NULL;
157 }
158
159 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_icmp6_nd*, sd_icmp6_nd_unref);
160 #define _cleanup_sd_icmp6_nd_free_ _cleanup_(sd_icmp6_nd_unrefp)
161
162 int sd_icmp6_nd_new(sd_icmp6_nd **ret) {
163         _cleanup_sd_icmp6_nd_free_ sd_icmp6_nd *nd = NULL;
164
165         assert(ret);
166
167         nd = new0(sd_icmp6_nd, 1);
168         if (!nd)
169                 return -ENOMEM;
170
171         nd->n_ref = REFCNT_INIT;
172
173         nd->index = -1;
174         nd->fd = -1;
175
176         *ret = nd;
177         nd = NULL;
178
179         return 0;
180 }
181
182 static int icmp6_router_advertisment_recv(sd_event_source *s, int fd,
183                                           uint32_t revents, void *userdata)
184 {
185         sd_icmp6_nd *nd = userdata;
186         ssize_t len;
187         struct nd_router_advert ra;
188         int event = ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE;
189
190         assert(s);
191         assert(nd);
192         assert(nd->event);
193
194         /* only interested in Managed/Other flag */
195         len = read(fd, &ra, sizeof(ra));
196         if ((size_t)len < sizeof(ra))
197                 return 0;
198
199         if (ra.nd_ra_type != ND_ROUTER_ADVERT)
200                 return 0;
201
202         if (ra.nd_ra_code != 0)
203                 return 0;
204
205         nd->timeout = sd_event_source_unref(nd->timeout);
206
207         nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
208
209         if (ra.nd_ra_flags_reserved & ND_RA_FLAG_OTHER )
210                 event = ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER;
211
212         if (ra.nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
213                 event = ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED;
214
215         log_icmp6_nd(nd, "Received Router Advertisment flags %s/%s",
216                      (ra.nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)? "MANAGED":
217                      "none",
218                      (ra.nd_ra_flags_reserved & ND_RA_FLAG_OTHER)? "OTHER":
219                      "none");
220
221         icmp6_nd_notify(nd, event);
222
223         return 0;
224 }
225
226 static int icmp6_router_solicitation_timeout(sd_event_source *s, uint64_t usec,
227                                              void *userdata)
228 {
229         sd_icmp6_nd *nd = userdata;
230         uint64_t time_now, next_timeout;
231         struct ether_addr unset = { };
232         struct ether_addr *addr = NULL;
233         int r;
234
235         assert(s);
236         assert(nd);
237         assert(nd->event);
238
239         nd->timeout = sd_event_source_unref(nd->timeout);
240
241         if (nd->nd_sent >= ICMP6_MAX_ROUTER_SOLICITATIONS) {
242                 icmp6_nd_notify(nd, ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT);
243                 nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
244         } else {
245                 if (memcmp(&nd->mac_addr, &unset, sizeof(struct ether_addr)))
246                         addr = &nd->mac_addr;
247
248                 r = dhcp_network_icmp6_send_router_solicitation(nd->fd, addr);
249                 if (r < 0)
250                         log_icmp6_nd(nd, "Error sending Router Solicitation");
251                 else {
252                         nd->state = ICMP6_ROUTER_SOLICITATION_SENT;
253                         log_icmp6_nd(nd, "Sent Router Solicitation");
254                 }
255
256                 nd->nd_sent++;
257
258                 r = sd_event_now(nd->event, CLOCK_MONOTONIC, &time_now);
259                 if (r < 0) {
260                         icmp6_nd_notify(nd, r);
261                         return 0;
262                 }
263
264                 next_timeout = time_now + ICMP6_ROUTER_SOLICITATION_INTERVAL;
265
266                 r = sd_event_add_time(nd->event, &nd->timeout, CLOCK_MONOTONIC,
267                                       next_timeout, 0,
268                                       icmp6_router_solicitation_timeout, nd);
269                 if (r < 0) {
270                         icmp6_nd_notify(nd, r);
271                         return 0;
272                 }
273
274                 r = sd_event_source_set_priority(nd->timeout,
275                                                  nd->event_priority);
276                 if (r < 0) {
277                         icmp6_nd_notify(nd, r);
278                         return 0;
279                 }
280         }
281
282         return 0;
283 }
284
285 int sd_icmp6_router_solicitation_start(sd_icmp6_nd *nd) {
286         int r;
287
288         assert(nd);
289         assert(nd->event);
290
291         if (nd->state != ICMP6_NEIGHBOR_DISCOVERY_IDLE)
292                 return -EINVAL;
293
294         if (nd->index < 1)
295                 return -EINVAL;
296
297         r = dhcp_network_icmp6_bind_router_solicitation(nd->index);
298         if (r < 0)
299                 return r;
300
301         nd->fd = r;
302
303         r = sd_event_add_io(nd->event, &nd->recv, nd->fd, EPOLLIN,
304                             icmp6_router_advertisment_recv, nd);
305         if (r < 0)
306                 goto error;
307
308         r = sd_event_source_set_priority(nd->recv, nd->event_priority);
309         if (r < 0)
310                 goto error;
311
312         r = sd_event_add_time(nd->event, &nd->timeout, CLOCK_MONOTONIC,
313                               0, 0, icmp6_router_solicitation_timeout, nd);
314         if (r < 0)
315                 goto error;
316
317         r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
318
319 error:
320         if (r < 0)
321                 icmp6_nd_init(nd);
322         else
323                 log_icmp6_nd(client, "Start Router Solicitation");
324
325         return r;
326 }