chiark / gitweb /
f820a9c8f04540be3e913d5edc7a81887b9cf9bf
[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
175         *ret = nd;
176         nd = NULL;
177
178         return 0;
179 }
180
181 static int icmp6_router_advertisment_recv(sd_event_source *s, int fd,
182                                           uint32_t revents, void *userdata)
183 {
184         sd_icmp6_nd *nd = userdata;
185         ssize_t len;
186         struct nd_router_advert ra;
187         int event = ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE;
188
189         assert(s);
190         assert(nd);
191         assert(nd->event);
192
193         /* only interested in Managed/Other flag */
194         len = read(fd, &ra, sizeof(ra));
195         if ((size_t)len < sizeof(ra))
196                 return 0;
197
198         if (ra.nd_ra_type != ND_ROUTER_ADVERT)
199                 return 0;
200
201         if (ra.nd_ra_code != 0)
202                 return 0;
203
204         nd->timeout = sd_event_source_unref(nd->timeout);
205
206         nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
207
208         if (ra.nd_ra_flags_reserved & ND_RA_FLAG_OTHER )
209                 event = ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER;
210
211         if (ra.nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
212                 event = ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED;
213
214         log_icmp6_nd(nd, "Received Router Advertisment flags %s/%s",
215                      (ra.nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)? "MANAGED":
216                      "none",
217                      (ra.nd_ra_flags_reserved & ND_RA_FLAG_OTHER)? "OTHER":
218                      "none");
219
220         icmp6_nd_notify(nd, event);
221
222         return 0;
223 }
224
225 static int icmp6_router_solicitation_timeout(sd_event_source *s, uint64_t usec,
226                                              void *userdata)
227 {
228         sd_icmp6_nd *nd = userdata;
229         uint64_t time_now, next_timeout;
230         struct ether_addr unset = { };
231         struct ether_addr *addr = NULL;
232         int r;
233
234         assert(s);
235         assert(nd);
236         assert(nd->event);
237
238         nd->timeout = sd_event_source_unref(nd->timeout);
239
240         if (nd->nd_sent >= ICMP6_MAX_ROUTER_SOLICITATIONS) {
241                 icmp6_nd_notify(nd, ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT);
242                 nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN;
243         } else {
244                 if (memcmp(&nd->mac_addr, &unset, sizeof(struct ether_addr)))
245                         addr = &nd->mac_addr;
246
247                 r = dhcp_network_icmp6_send_router_solicitation(nd->fd, addr);
248                 if (r < 0)
249                         log_icmp6_nd(nd, "Error sending Router Solicitation");
250                 else {
251                         nd->state = ICMP6_ROUTER_SOLICITATION_SENT;
252                         log_icmp6_nd(nd, "Sent Router Solicitation");
253                 }
254
255                 nd->nd_sent++;
256
257                 r = sd_event_now(nd->event, CLOCK_MONOTONIC, &time_now);
258                 if (r < 0) {
259                         icmp6_nd_notify(nd, r);
260                         return 0;
261                 }
262
263                 next_timeout = time_now + ICMP6_ROUTER_SOLICITATION_INTERVAL;
264
265                 r = sd_event_add_time(nd->event, &nd->timeout, CLOCK_MONOTONIC,
266                                       next_timeout, 0,
267                                       icmp6_router_solicitation_timeout, nd);
268                 if (r < 0) {
269                         icmp6_nd_notify(nd, r);
270                         return 0;
271                 }
272
273                 r = sd_event_source_set_priority(nd->timeout,
274                                                  nd->event_priority);
275                 if (r < 0) {
276                         icmp6_nd_notify(nd, r);
277                         return 0;
278                 }
279         }
280
281         return 0;
282 }
283
284 int sd_icmp6_router_solicitation_start(sd_icmp6_nd *nd) {
285         int r;
286
287         assert(nd);
288         assert(nd->event);
289
290         if (nd->state != ICMP6_NEIGHBOR_DISCOVERY_IDLE)
291                 return -EINVAL;
292
293         if (nd->index < 1)
294                 return -EINVAL;
295
296         r = dhcp_network_icmp6_bind_router_solicitation(nd->index);
297         if (r < 0)
298                 return r;
299
300         nd->fd = r;
301
302         r = sd_event_add_io(nd->event, &nd->recv, nd->fd, EPOLLIN,
303                             icmp6_router_advertisment_recv, nd);
304         if (r < 0)
305                 goto error;
306
307         r = sd_event_source_set_priority(nd->recv, nd->event_priority);
308         if (r < 0)
309                 goto error;
310
311         r = sd_event_add_time(nd->event, &nd->timeout, CLOCK_MONOTONIC,
312                               0, 0, icmp6_router_solicitation_timeout, nd);
313         if (r < 0)
314                 goto error;
315
316         r = sd_event_source_set_priority(nd->timeout, nd->event_priority);
317
318 error:
319         if (r < 0)
320                 icmp6_nd_init(nd);
321         else
322                 log_icmp6_nd(client, "Start Router Solicitation");
323
324         return r;
325 }