chiark / gitweb /
d98ae0294a4c0d7e6547cf50eb100d21eb703822
[elogind.git] / src / libsystemd-network / sd-dhcp6-client.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright (C) 2014 Intel Corporation. All rights reserved.
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <string.h>
24
25 #include "udev.h"
26 #include "udev-util.h"
27 #include "virt.h"
28 #include "siphash24.h"
29 #include "util.h"
30 #include "refcnt.h"
31
32 #include "network-internal.h"
33 #include "sd-dhcp6-client.h"
34 #include "dhcp6-protocol.h"
35 #include "dhcp6-internal.h"
36
37 #define SYSTEMD_PEN 43793
38 #define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
39
40 struct sd_dhcp6_client {
41         RefCount n_ref;
42
43         enum DHCP6State state;
44         sd_event *event;
45         int event_priority;
46         int index;
47         struct ether_addr mac_addr;
48         DHCP6IA ia_na;
49         usec_t retransmit_time;
50         uint8_t retransmit_count;
51         sd_event_source *timeout_resend;
52         sd_event_source *timeout_resend_expire;
53         sd_dhcp6_client_cb_t cb;
54         void *userdata;
55
56         struct duid_en {
57                 uint16_t type; /* DHCP6_DUID_EN */
58                 uint32_t pen;
59                 uint8_t id[8];
60         } _packed_ duid;
61 };
62
63 int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
64                                  sd_dhcp6_client_cb_t cb, void *userdata)
65 {
66         assert_return(client, -EINVAL);
67
68         client->cb = cb;
69         client->userdata = userdata;
70
71         return 0;
72 }
73
74 int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index)
75 {
76         assert_return(client, -EINVAL);
77         assert_return(interface_index >= -1, -EINVAL);
78
79         client->index = interface_index;
80
81         return 0;
82 }
83
84 int sd_dhcp6_client_set_mac(sd_dhcp6_client *client,
85                             const struct ether_addr *mac_addr)
86 {
87         assert_return(client, -EINVAL);
88
89         if (mac_addr)
90                 memcpy(&client->mac_addr, mac_addr, sizeof(client->mac_addr));
91         else
92                 memset(&client->mac_addr, 0x00, sizeof(client->mac_addr));
93
94         return 0;
95 }
96
97 static sd_dhcp6_client *client_notify(sd_dhcp6_client *client, int event) {
98         if (client->cb) {
99                 client = sd_dhcp6_client_ref(client);
100                 client->cb(client, event, client->userdata);
101                 client = sd_dhcp6_client_unref(client);
102         }
103
104         return client;
105 }
106
107 static int client_initialize(sd_dhcp6_client *client)
108 {
109         assert_return(client, -EINVAL);
110
111         client->ia_na.timeout_t1 =
112                 sd_event_source_unref(client->ia_na.timeout_t1);
113         client->ia_na.timeout_t2 =
114                 sd_event_source_unref(client->ia_na.timeout_t2);
115
116         client->retransmit_time = 0;
117         client->retransmit_count = 0;
118         client->timeout_resend = sd_event_source_unref(client->timeout_resend);
119         client->timeout_resend_expire =
120                 sd_event_source_unref(client->timeout_resend_expire);
121
122         client->state = DHCP6_STATE_STOPPED;
123
124         return 0;
125 }
126
127 static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) {
128         assert_return(client, NULL);
129
130         client = client_notify(client, error);
131         if (client)
132                 client_initialize(client);
133
134         return client;
135 }
136
137 static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
138                                         void *userdata) {
139         sd_dhcp6_client *client = userdata;
140
141         assert(s);
142         assert(client);
143         assert(client->event);
144
145         client_stop(client, DHCP6_EVENT_RESEND_EXPIRE);
146
147         return 0;
148 }
149
150 static usec_t client_timeout_compute_random(usec_t val) {
151         return val - val / 10 +
152                 (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
153 }
154
155 static int client_timeout_resend(sd_event_source *s, uint64_t usec,
156                                  void *userdata) {
157         int r = 0;
158         sd_dhcp6_client *client = userdata;
159         usec_t time_now, init_retransmit_time, max_retransmit_time;
160         usec_t max_retransmit_duration;
161         uint8_t max_retransmit_count;
162         char time_string[FORMAT_TIMESPAN_MAX];
163
164         assert(s);
165         assert(client);
166         assert(client->event);
167
168         client->timeout_resend = sd_event_source_unref(client->timeout_resend);
169
170         switch (client->state) {
171         case DHCP6_STATE_SOLICITATION:
172                 init_retransmit_time = DHCP6_SOL_TIMEOUT;
173                 max_retransmit_time = DHCP6_SOL_MAX_RT;
174                 max_retransmit_count = 0;
175                 max_retransmit_duration = 0;
176
177                 break;
178
179         case DHCP6_STATE_STOPPED:
180         case DHCP6_STATE_RS:
181                 return 0;
182         }
183
184         if (max_retransmit_count &&
185             client->retransmit_count >= max_retransmit_count) {
186                 client_stop(client, DHCP6_EVENT_RETRANS_MAX);
187                 return 0;
188         }
189
190         r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
191         if (r < 0)
192                 goto error;
193
194         if (!client->retransmit_time) {
195                 client->retransmit_time =
196                         client_timeout_compute_random(init_retransmit_time);
197         } else {
198                 if (max_retransmit_time &&
199                     client->retransmit_time > max_retransmit_time / 2)
200                         client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
201                 else
202                         client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
203         }
204
205         log_dhcp6_client(client, "Next retransmission in %s",
206                          format_timespan(time_string, FORMAT_TIMESPAN_MAX,
207                                          client->retransmit_time, 0));
208
209         r = sd_event_add_time(client->event, &client->timeout_resend,
210                               CLOCK_MONOTONIC,
211                               time_now + client->retransmit_time,
212                               10 * USEC_PER_MSEC, client_timeout_resend,
213                               client);
214         if (r < 0)
215                 goto error;
216
217         r = sd_event_source_set_priority(client->timeout_resend,
218                                          client->event_priority);
219         if (r < 0)
220                 goto error;
221
222         if (max_retransmit_duration && !client->timeout_resend_expire) {
223
224                 log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
225                                  max_retransmit_duration / USEC_PER_SEC);
226
227                 r = sd_event_add_time(client->event,
228                                       &client->timeout_resend_expire,
229                                       CLOCK_MONOTONIC,
230                                       time_now + max_retransmit_duration,
231                                       USEC_PER_SEC,
232                                       client_timeout_resend_expire, client);
233                 if (r < 0)
234                         goto error;
235
236                 r = sd_event_source_set_priority(client->timeout_resend_expire,
237                                                  client->event_priority);
238                 if (r < 0)
239                         goto error;
240         }
241
242 error:
243         if (r < 0)
244                 client_stop(client, r);
245
246         return 0;
247 }
248
249 static int client_ensure_iaid(sd_dhcp6_client *client) {
250         const char *name = NULL;
251         uint64_t id;
252
253         assert(client);
254
255         if (client->ia_na.id)
256                 return 0;
257
258         if (detect_container(NULL) <= 0) {
259                 /* not in a container, udev will be around */
260                 _cleanup_udev_unref_ struct udev *udev;
261                 _cleanup_udev_device_unref_ struct udev_device *device;
262                 char ifindex_str[2 + DECIMAL_STR_MAX(int)];
263
264                 udev = udev_new();
265                 if (!udev)
266                         return -ENOMEM;
267
268                 sprintf(ifindex_str, "n%d", client->index);
269                 device = udev_device_new_from_device_id(udev, ifindex_str);
270                 if (!device)
271                         return -errno;
272
273                 if (udev_device_get_is_initialized(device) <= 0)
274                         /* not yet ready */
275                         return -EBUSY;
276
277                 name = net_get_name(device);
278         }
279
280         if (name)
281                 siphash24((uint8_t*)&id, name, strlen(name), HASH_KEY.bytes);
282         else
283                 /* fall back to mac address if no predictable name available */
284                 siphash24((uint8_t*)&id, &client->mac_addr, ETH_ALEN,
285                           HASH_KEY.bytes);
286
287         /* fold into 32 bits */
288         client->ia_na.id = (id & 0xffffffff) ^ (id >> 32);
289
290         return 0;
291 }
292
293 static int client_start(sd_dhcp6_client *client)
294 {
295         int r;
296
297         assert_return(client, -EINVAL);
298         assert_return(client->event, -EINVAL);
299         assert_return(client->index > 0, -EINVAL);
300
301         r = client_ensure_iaid(client);
302         if (r < 0)
303                 return r;
304
305         client->state = DHCP6_STATE_SOLICITATION;
306
307         r = sd_event_add_time(client->event, &client->timeout_resend,
308                               CLOCK_MONOTONIC, 0, 0, client_timeout_resend,
309                               client);
310         if (r < 0)
311                 return r;
312
313         r = sd_event_source_set_priority(client->timeout_resend,
314                                          client->event_priority);
315         if (r < 0)
316                 return r;
317
318         return 0;
319 }
320
321 int sd_dhcp6_client_stop(sd_dhcp6_client *client)
322 {
323         client_stop(client, DHCP6_EVENT_STOP);
324
325         return 0;
326 }
327
328 int sd_dhcp6_client_start(sd_dhcp6_client *client)
329 {
330         int r = 0;
331
332         assert_return(client, -EINVAL);
333         assert_return(client->event, -EINVAL);
334         assert_return(client->index > 0, -EINVAL);
335
336         r = client_initialize(client);
337         if (r < 0)
338                 return r;
339
340         return client_start(client);
341 }
342
343 int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
344                                  int priority)
345 {
346         int r;
347
348         assert_return(client, -EINVAL);
349         assert_return(!client->event, -EBUSY);
350
351         if (event)
352                 client->event = sd_event_ref(event);
353         else {
354                 r = sd_event_default(&client->event);
355                 if (r < 0)
356                         return 0;
357         }
358
359         client->event_priority = priority;
360
361         return 0;
362 }
363
364 int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
365         assert_return(client, -EINVAL);
366
367         client->event = sd_event_unref(client->event);
368
369         return 0;
370 }
371
372 sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
373         if (!client)
374                 return NULL;
375
376         return client->event;
377 }
378
379 sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
380         if (client)
381                 assert_se(REFCNT_INC(client->n_ref) >= 2);
382
383         return client;
384 }
385
386 sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
387         if (client && REFCNT_DEC(client->n_ref) <= 0) {
388                 client_initialize(client);
389
390                 sd_dhcp6_client_detach_event(client);
391
392                 free(client);
393
394                 return NULL;
395         }
396
397         return client;
398 }
399
400 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
401 #define _cleanup_dhcp6_client_free_ _cleanup_(sd_dhcp6_client_unrefp)
402
403 int sd_dhcp6_client_new(sd_dhcp6_client **ret)
404 {
405         _cleanup_dhcp6_client_free_ sd_dhcp6_client *client = NULL;
406         sd_id128_t machine_id;
407         int r;
408
409         assert_return(ret, -EINVAL);
410
411         client = new0(sd_dhcp6_client, 1);
412         if (!client)
413                 return -ENOMEM;
414
415         client->n_ref = REFCNT_INIT;
416
417         client->ia_na.type = DHCP6_OPTION_IA_NA;
418
419         client->index = -1;
420
421         /* initialize DUID */
422         client->duid.type = htobe16(DHCP6_DUID_EN);
423         client->duid.pen = htobe32(SYSTEMD_PEN);
424
425         r = sd_id128_get_machine(&machine_id);
426         if (r < 0)
427                 return r;
428
429         /* a bit of snake-oil perhaps, but no need to expose the machine-id
430            directly */
431         siphash24(client->duid.id, &machine_id, sizeof(machine_id),
432                   HASH_KEY.bytes);
433
434         *ret = client;
435         client = NULL;
436
437         return 0;
438 }