chiark / gitweb /
7be64244ad26a417cf027c1495b4a82e2ffa8ec2
[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         be32_t transaction_id;
50         int fd;
51         sd_event_source *receive_message;
52         usec_t retransmit_time;
53         uint8_t retransmit_count;
54         sd_event_source *timeout_resend;
55         sd_event_source *timeout_resend_expire;
56         sd_dhcp6_client_cb_t cb;
57         void *userdata;
58
59         struct duid_en {
60                 uint16_t type; /* DHCP6_DUID_EN */
61                 uint32_t pen;
62                 uint8_t id[8];
63         } _packed_ duid;
64 };
65
66 const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = {
67         [DHCP6_SOLICIT] = "SOLICIT",
68         [DHCP6_ADVERTISE] = "ADVERTISE",
69         [DHCP6_REQUEST] = "REQUEST",
70         [DHCP6_CONFIRM] = "CONFIRM",
71         [DHCP6_RENEW] = "RENEW",
72         [DHCP6_REBIND] = "REBIND",
73         [DHCP6_REPLY] = "REPLY",
74         [DHCP6_RELEASE] = "RELEASE",
75         [DHCP6_DECLINE] = "DECLINE",
76         [DHCP6_RECONFIGURE] = "RECONFIGURE",
77         [DHCP6_INFORMATION_REQUEST] = "INFORMATION-REQUEST",
78         [DHCP6_RELAY_FORW] = "RELAY-FORW",
79         [DHCP6_RELAY_REPL] = "RELAY-REPL",
80 };
81
82 DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);
83
84 int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
85                                  sd_dhcp6_client_cb_t cb, void *userdata)
86 {
87         assert_return(client, -EINVAL);
88
89         client->cb = cb;
90         client->userdata = userdata;
91
92         return 0;
93 }
94
95 int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index)
96 {
97         assert_return(client, -EINVAL);
98         assert_return(interface_index >= -1, -EINVAL);
99
100         client->index = interface_index;
101
102         return 0;
103 }
104
105 int sd_dhcp6_client_set_mac(sd_dhcp6_client *client,
106                             const struct ether_addr *mac_addr)
107 {
108         assert_return(client, -EINVAL);
109
110         if (mac_addr)
111                 memcpy(&client->mac_addr, mac_addr, sizeof(client->mac_addr));
112         else
113                 memset(&client->mac_addr, 0x00, sizeof(client->mac_addr));
114
115         return 0;
116 }
117
118 static sd_dhcp6_client *client_notify(sd_dhcp6_client *client, int event) {
119         if (client->cb) {
120                 client = sd_dhcp6_client_ref(client);
121                 client->cb(client, event, client->userdata);
122                 client = sd_dhcp6_client_unref(client);
123         }
124
125         return client;
126 }
127
128 static int client_initialize(sd_dhcp6_client *client)
129 {
130         assert_return(client, -EINVAL);
131
132         client->receive_message =
133                 sd_event_source_unref(client->receive_message);
134
135         if (client->fd > 0)
136                 client->fd = safe_close(client->fd);
137
138         client->transaction_id = random_u32() & 0x00ffffff;
139
140         client->ia_na.timeout_t1 =
141                 sd_event_source_unref(client->ia_na.timeout_t1);
142         client->ia_na.timeout_t2 =
143                 sd_event_source_unref(client->ia_na.timeout_t2);
144
145         client->retransmit_time = 0;
146         client->retransmit_count = 0;
147         client->timeout_resend = sd_event_source_unref(client->timeout_resend);
148         client->timeout_resend_expire =
149                 sd_event_source_unref(client->timeout_resend_expire);
150
151         client->state = DHCP6_STATE_STOPPED;
152
153         return 0;
154 }
155
156 static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) {
157         assert_return(client, NULL);
158
159         client = client_notify(client, error);
160         if (client)
161                 client_initialize(client);
162
163         return client;
164 }
165
166 static int client_send_message(sd_dhcp6_client *client) {
167         _cleanup_free_ DHCP6Message *message = NULL;
168         struct in6_addr all_servers =
169                 IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
170         size_t len, optlen = 512;
171         uint8_t *opt;
172         int r;
173
174         len = sizeof(DHCP6Message) + optlen;
175
176         message = malloc0(len);
177         if (!message)
178                 return -ENOMEM;
179
180         opt = (uint8_t *)(message + 1);
181
182         message->transaction_id = client->transaction_id;
183
184         switch(client->state) {
185         case DHCP6_STATE_SOLICITATION:
186                 message->type = DHCP6_SOLICIT;
187
188                 r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID,
189                                         sizeof(client->duid), &client->duid);
190                 if (r < 0)
191                         return r;
192
193                 r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
194                 if (r < 0)
195                         return r;
196
197                 break;
198
199         case DHCP6_STATE_STOPPED:
200         case DHCP6_STATE_RS:
201                 return -EINVAL;
202         }
203
204         r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
205                                           len - optlen);
206         if (r < 0)
207                 return r;
208
209         log_dhcp6_client(client, "Sent %s",
210                          dhcp6_message_type_to_string(message->type));
211
212         return 0;
213 }
214
215 static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
216                                         void *userdata) {
217         sd_dhcp6_client *client = userdata;
218
219         assert(s);
220         assert(client);
221         assert(client->event);
222
223         client_stop(client, DHCP6_EVENT_RESEND_EXPIRE);
224
225         return 0;
226 }
227
228 static usec_t client_timeout_compute_random(usec_t val) {
229         return val - val / 10 +
230                 (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
231 }
232
233 static int client_timeout_resend(sd_event_source *s, uint64_t usec,
234                                  void *userdata) {
235         int r = 0;
236         sd_dhcp6_client *client = userdata;
237         usec_t time_now, init_retransmit_time, max_retransmit_time;
238         usec_t max_retransmit_duration;
239         uint8_t max_retransmit_count;
240         char time_string[FORMAT_TIMESPAN_MAX];
241
242         assert(s);
243         assert(client);
244         assert(client->event);
245
246         client->timeout_resend = sd_event_source_unref(client->timeout_resend);
247
248         switch (client->state) {
249         case DHCP6_STATE_SOLICITATION:
250                 init_retransmit_time = DHCP6_SOL_TIMEOUT;
251                 max_retransmit_time = DHCP6_SOL_MAX_RT;
252                 max_retransmit_count = 0;
253                 max_retransmit_duration = 0;
254
255                 break;
256
257         case DHCP6_STATE_STOPPED:
258         case DHCP6_STATE_RS:
259                 return 0;
260         }
261
262         if (max_retransmit_count &&
263             client->retransmit_count >= max_retransmit_count) {
264                 client_stop(client, DHCP6_EVENT_RETRANS_MAX);
265                 return 0;
266         }
267
268         r = client_send_message(client);
269         if (r >= 0)
270                 client->retransmit_count++;
271
272
273         r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
274         if (r < 0)
275                 goto error;
276
277         if (!client->retransmit_time) {
278                 client->retransmit_time =
279                         client_timeout_compute_random(init_retransmit_time);
280
281                 if (client->state == DHCP6_STATE_SOLICITATION)
282                         client->retransmit_time += init_retransmit_time / 10;
283
284         } else {
285                 if (max_retransmit_time &&
286                     client->retransmit_time > max_retransmit_time / 2)
287                         client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
288                 else
289                         client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
290         }
291
292         log_dhcp6_client(client, "Next retransmission in %s",
293                          format_timespan(time_string, FORMAT_TIMESPAN_MAX,
294                                          client->retransmit_time, 0));
295
296         r = sd_event_add_time(client->event, &client->timeout_resend,
297                               CLOCK_MONOTONIC,
298                               time_now + client->retransmit_time,
299                               10 * USEC_PER_MSEC, client_timeout_resend,
300                               client);
301         if (r < 0)
302                 goto error;
303
304         r = sd_event_source_set_priority(client->timeout_resend,
305                                          client->event_priority);
306         if (r < 0)
307                 goto error;
308
309         if (max_retransmit_duration && !client->timeout_resend_expire) {
310
311                 log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
312                                  max_retransmit_duration / USEC_PER_SEC);
313
314                 r = sd_event_add_time(client->event,
315                                       &client->timeout_resend_expire,
316                                       CLOCK_MONOTONIC,
317                                       time_now + max_retransmit_duration,
318                                       USEC_PER_SEC,
319                                       client_timeout_resend_expire, client);
320                 if (r < 0)
321                         goto error;
322
323                 r = sd_event_source_set_priority(client->timeout_resend_expire,
324                                                  client->event_priority);
325                 if (r < 0)
326                         goto error;
327         }
328
329 error:
330         if (r < 0)
331                 client_stop(client, r);
332
333         return 0;
334 }
335
336 static int client_ensure_iaid(sd_dhcp6_client *client) {
337         const char *name = NULL;
338         uint64_t id;
339
340         assert(client);
341
342         if (client->ia_na.id)
343                 return 0;
344
345         if (detect_container(NULL) <= 0) {
346                 /* not in a container, udev will be around */
347                 _cleanup_udev_unref_ struct udev *udev;
348                 _cleanup_udev_device_unref_ struct udev_device *device;
349                 char ifindex_str[2 + DECIMAL_STR_MAX(int)];
350
351                 udev = udev_new();
352                 if (!udev)
353                         return -ENOMEM;
354
355                 sprintf(ifindex_str, "n%d", client->index);
356                 device = udev_device_new_from_device_id(udev, ifindex_str);
357                 if (!device)
358                         return -errno;
359
360                 if (udev_device_get_is_initialized(device) <= 0)
361                         /* not yet ready */
362                         return -EBUSY;
363
364                 name = net_get_name(device);
365         }
366
367         if (name)
368                 siphash24((uint8_t*)&id, name, strlen(name), HASH_KEY.bytes);
369         else
370                 /* fall back to mac address if no predictable name available */
371                 siphash24((uint8_t*)&id, &client->mac_addr, ETH_ALEN,
372                           HASH_KEY.bytes);
373
374         /* fold into 32 bits */
375         client->ia_na.id = (id & 0xffffffff) ^ (id >> 32);
376
377         return 0;
378 }
379
380 static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
381                                   void *userdata)
382 {
383         return 0;
384 }
385
386 static int client_start(sd_dhcp6_client *client)
387 {
388         int r;
389
390         assert_return(client, -EINVAL);
391         assert_return(client->event, -EINVAL);
392         assert_return(client->index > 0, -EINVAL);
393
394         r = client_ensure_iaid(client);
395         if (r < 0)
396                 return r;
397
398         r = dhcp6_network_bind_udp_socket(client->index, NULL);
399         if (r < 0)
400                 return r;
401
402         client->fd = r;
403
404         r = sd_event_add_io(client->event, &client->receive_message,
405                             client->fd, EPOLLIN, client_receive_message,
406                             client);
407         if (r < 0)
408                 return r;
409
410         r = sd_event_source_set_priority(client->receive_message,
411                                          client->event_priority);
412         if (r < 0)
413                 return r;
414
415         client->state = DHCP6_STATE_SOLICITATION;
416
417         r = sd_event_add_time(client->event, &client->timeout_resend,
418                               CLOCK_MONOTONIC, 0, 0, client_timeout_resend,
419                               client);
420         if (r < 0)
421                 return r;
422
423         r = sd_event_source_set_priority(client->timeout_resend,
424                                          client->event_priority);
425         if (r < 0)
426                 return r;
427
428         return 0;
429 }
430
431 int sd_dhcp6_client_stop(sd_dhcp6_client *client)
432 {
433         client_stop(client, DHCP6_EVENT_STOP);
434
435         return 0;
436 }
437
438 int sd_dhcp6_client_start(sd_dhcp6_client *client)
439 {
440         int r = 0;
441
442         assert_return(client, -EINVAL);
443         assert_return(client->event, -EINVAL);
444         assert_return(client->index > 0, -EINVAL);
445
446         r = client_initialize(client);
447         if (r < 0)
448                 return r;
449
450         return client_start(client);
451 }
452
453 int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
454                                  int priority)
455 {
456         int r;
457
458         assert_return(client, -EINVAL);
459         assert_return(!client->event, -EBUSY);
460
461         if (event)
462                 client->event = sd_event_ref(event);
463         else {
464                 r = sd_event_default(&client->event);
465                 if (r < 0)
466                         return 0;
467         }
468
469         client->event_priority = priority;
470
471         return 0;
472 }
473
474 int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
475         assert_return(client, -EINVAL);
476
477         client->event = sd_event_unref(client->event);
478
479         return 0;
480 }
481
482 sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
483         if (!client)
484                 return NULL;
485
486         return client->event;
487 }
488
489 sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
490         if (client)
491                 assert_se(REFCNT_INC(client->n_ref) >= 2);
492
493         return client;
494 }
495
496 sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
497         if (client && REFCNT_DEC(client->n_ref) <= 0) {
498                 client_initialize(client);
499
500                 sd_dhcp6_client_detach_event(client);
501
502                 free(client);
503
504                 return NULL;
505         }
506
507         return client;
508 }
509
510 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
511 #define _cleanup_dhcp6_client_free_ _cleanup_(sd_dhcp6_client_unrefp)
512
513 int sd_dhcp6_client_new(sd_dhcp6_client **ret)
514 {
515         _cleanup_dhcp6_client_free_ sd_dhcp6_client *client = NULL;
516         sd_id128_t machine_id;
517         int r;
518
519         assert_return(ret, -EINVAL);
520
521         client = new0(sd_dhcp6_client, 1);
522         if (!client)
523                 return -ENOMEM;
524
525         client->n_ref = REFCNT_INIT;
526
527         client->ia_na.type = DHCP6_OPTION_IA_NA;
528
529         client->index = -1;
530
531         /* initialize DUID */
532         client->duid.type = htobe16(DHCP6_DUID_EN);
533         client->duid.pen = htobe32(SYSTEMD_PEN);
534
535         r = sd_id128_get_machine(&machine_id);
536         if (r < 0)
537                 return r;
538
539         /* a bit of snake-oil perhaps, but no need to expose the machine-id
540            directly */
541         siphash24(client->duid.id, &machine_id, sizeof(machine_id),
542                   HASH_KEY.bytes);
543
544         *ret = client;
545         client = NULL;
546
547         return 0;
548 }