chiark / gitweb /
sd-dhcp6-client: return NULL from _unref() like the other sd-* libraries
[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 #include <sys/ioctl.h>
25
26 #include "udev.h"
27 #include "udev-util.h"
28 #include "virt.h"
29 #include "siphash24.h"
30 #include "util.h"
31 #include "refcnt.h"
32
33 #include "network-internal.h"
34 #include "sd-dhcp6-client.h"
35 #include "dhcp6-protocol.h"
36 #include "dhcp6-internal.h"
37 #include "dhcp6-lease-internal.h"
38
39 #define SYSTEMD_PEN 43793
40 #define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
41
42 struct sd_dhcp6_client {
43         RefCount n_ref;
44
45         enum DHCP6State state;
46         sd_event *event;
47         int event_priority;
48         int index;
49         struct ether_addr mac_addr;
50         DHCP6IA ia_na;
51         be32_t transaction_id;
52         struct sd_dhcp6_lease *lease;
53         int fd;
54         sd_event_source *receive_message;
55         usec_t retransmit_time;
56         uint8_t retransmit_count;
57         sd_event_source *timeout_resend;
58         sd_event_source *timeout_resend_expire;
59         sd_dhcp6_client_cb_t cb;
60         void *userdata;
61
62         struct duid_en {
63                 uint16_t type; /* DHCP6_DUID_EN */
64                 uint32_t pen;
65                 uint8_t id[8];
66         } _packed_ duid;
67 };
68
69 const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = {
70         [DHCP6_SOLICIT] = "SOLICIT",
71         [DHCP6_ADVERTISE] = "ADVERTISE",
72         [DHCP6_REQUEST] = "REQUEST",
73         [DHCP6_CONFIRM] = "CONFIRM",
74         [DHCP6_RENEW] = "RENEW",
75         [DHCP6_REBIND] = "REBIND",
76         [DHCP6_REPLY] = "REPLY",
77         [DHCP6_RELEASE] = "RELEASE",
78         [DHCP6_DECLINE] = "DECLINE",
79         [DHCP6_RECONFIGURE] = "RECONFIGURE",
80         [DHCP6_INFORMATION_REQUEST] = "INFORMATION-REQUEST",
81         [DHCP6_RELAY_FORW] = "RELAY-FORW",
82         [DHCP6_RELAY_REPL] = "RELAY-REPL",
83 };
84
85 DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);
86
87 const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {
88         [DHCP6_STATUS_SUCCESS] = "Success",
89         [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure",
90         [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available",
91         [DHCP6_STATUS_NO_BINDING] = "Binding unavailable",
92         [DHCP6_STATUS_NOT_ON_LINK] = "Not on link",
93         [DHCP6_STATUS_USE_MULTICAST] = "Use multicast",
94 };
95
96 DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int);
97
98 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
99 #define _cleanup_dhcp6_client_unref_ _cleanup_(sd_dhcp6_client_unrefp)
100
101 #define DHCP6_CLIENT_DONT_DESTROY(client) \
102         _cleanup_dhcp6_client_unref_ _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client)
103
104 static int client_start(sd_dhcp6_client *client, enum DHCP6State state);
105
106 int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
107                                  sd_dhcp6_client_cb_t cb, void *userdata)
108 {
109         assert_return(client, -EINVAL);
110
111         client->cb = cb;
112         client->userdata = userdata;
113
114         return 0;
115 }
116
117 int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index)
118 {
119         assert_return(client, -EINVAL);
120         assert_return(interface_index >= -1, -EINVAL);
121
122         client->index = interface_index;
123
124         return 0;
125 }
126
127 int sd_dhcp6_client_set_mac(sd_dhcp6_client *client,
128                             const struct ether_addr *mac_addr)
129 {
130         assert_return(client, -EINVAL);
131
132         if (mac_addr)
133                 memcpy(&client->mac_addr, mac_addr, sizeof(client->mac_addr));
134         else
135                 memset(&client->mac_addr, 0x00, sizeof(client->mac_addr));
136
137         return 0;
138 }
139
140 int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
141         assert_return(client, -EINVAL);
142         assert_return(ret, -EINVAL);
143
144         if (!client->lease)
145                 return -ENOMSG;
146
147         *ret = sd_dhcp6_lease_ref(client->lease);
148
149         return 0;
150 }
151
152 static void client_notify(sd_dhcp6_client *client, int event) {
153         if (client->cb)
154                 client->cb(client, event, client->userdata);
155 }
156
157 static int client_reset(sd_dhcp6_client *client) {
158         assert_return(client, -EINVAL);
159
160         client->receive_message =
161                 sd_event_source_unref(client->receive_message);
162
163         client->fd = safe_close(client->fd);
164
165         client->transaction_id = 0;
166
167         client->ia_na.timeout_t1 =
168                 sd_event_source_unref(client->ia_na.timeout_t1);
169         client->ia_na.timeout_t2 =
170                 sd_event_source_unref(client->ia_na.timeout_t2);
171
172         client->retransmit_time = 0;
173         client->retransmit_count = 0;
174         client->timeout_resend = sd_event_source_unref(client->timeout_resend);
175         client->timeout_resend_expire =
176                 sd_event_source_unref(client->timeout_resend_expire);
177
178         client->state = DHCP6_STATE_STOPPED;
179
180         return 0;
181 }
182
183 static void client_stop(sd_dhcp6_client *client, int error) {
184         DHCP6_CLIENT_DONT_DESTROY(client);
185
186         assert(client);
187
188         client_notify(client, error);
189
190         client_reset(client);
191 }
192
193 static int client_send_message(sd_dhcp6_client *client) {
194         _cleanup_free_ DHCP6Message *message = NULL;
195         struct in6_addr all_servers =
196                 IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
197         size_t len, optlen = 512;
198         uint8_t *opt;
199         int r;
200
201         len = sizeof(DHCP6Message) + optlen;
202
203         message = malloc0(len);
204         if (!message)
205                 return -ENOMEM;
206
207         opt = (uint8_t *)(message + 1);
208
209         message->transaction_id = client->transaction_id;
210
211         switch(client->state) {
212         case DHCP6_STATE_SOLICITATION:
213                 message->type = DHCP6_SOLICIT;
214
215                 r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
216                 if (r < 0)
217                         return r;
218
219                 break;
220
221         case DHCP6_STATE_REQUEST:
222                 message->type = DHCP6_REQUEST;
223
224                 r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_SERVERID,
225                                         client->lease->serverid_len,
226                                         client->lease->serverid);
227                 if (r < 0)
228                         return r;
229
230                 r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
231                 if (r < 0)
232                         return r;
233
234                 break;
235
236         case DHCP6_STATE_STOPPED:
237         case DHCP6_STATE_RS:
238         case DHCP6_STATE_BOUND:
239                 return -EINVAL;
240         }
241
242         r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID,
243                                 sizeof(client->duid), &client->duid);
244         if (r < 0)
245                 return r;
246
247         r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
248                                           len - optlen);
249         if (r < 0)
250                 return r;
251
252         log_dhcp6_client(client, "Sent %s",
253                          dhcp6_message_type_to_string(message->type));
254
255         return 0;
256 }
257
258 static int client_timeout_t2(sd_event_source *s, uint64_t usec,
259                              void *userdata) {
260         sd_dhcp6_client *client = userdata;
261
262         assert_return(s, -EINVAL);
263         assert_return(client, -EINVAL);
264         assert_return(client->lease, -EINVAL);
265
266         client->lease->ia.timeout_t2 =
267                 sd_event_source_unref(client->lease->ia.timeout_t2);
268
269         log_dhcp6_client(client, "Timeout T2");
270
271         return 0;
272 }
273
274 static int client_timeout_t1(sd_event_source *s, uint64_t usec,
275                              void *userdata) {
276         sd_dhcp6_client *client = userdata;
277
278         assert_return(s, -EINVAL);
279         assert_return(client, -EINVAL);
280         assert_return(client->lease, -EINVAL);
281
282         client->lease->ia.timeout_t1 =
283                 sd_event_source_unref(client->lease->ia.timeout_t1);
284
285         log_dhcp6_client(client, "Timeout T1");
286
287         return 0;
288 }
289
290 static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
291                                         void *userdata) {
292         sd_dhcp6_client *client = userdata;
293
294         assert(s);
295         assert(client);
296         assert(client->event);
297
298         client_stop(client, DHCP6_EVENT_RESEND_EXPIRE);
299
300         return 0;
301 }
302
303 static usec_t client_timeout_compute_random(usec_t val) {
304         return val - val / 10 +
305                 (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
306 }
307
308 static int client_timeout_resend(sd_event_source *s, uint64_t usec,
309                                  void *userdata) {
310         int r = 0;
311         sd_dhcp6_client *client = userdata;
312         usec_t time_now, init_retransmit_time, max_retransmit_time;
313         usec_t max_retransmit_duration;
314         uint8_t max_retransmit_count = 0;
315         char time_string[FORMAT_TIMESPAN_MAX];
316
317         assert(s);
318         assert(client);
319         assert(client->event);
320
321         client->timeout_resend = sd_event_source_unref(client->timeout_resend);
322
323         switch (client->state) {
324         case DHCP6_STATE_SOLICITATION:
325
326                 if (client->retransmit_count && client->lease) {
327                         client_start(client, DHCP6_STATE_REQUEST);
328                         return 0;
329                 }
330
331                 init_retransmit_time = DHCP6_SOL_TIMEOUT;
332                 max_retransmit_time = DHCP6_SOL_MAX_RT;
333                 max_retransmit_count = 0;
334                 max_retransmit_duration = 0;
335
336                 break;
337
338         case DHCP6_STATE_REQUEST:
339                 init_retransmit_time = DHCP6_REQ_TIMEOUT;
340                 max_retransmit_time = DHCP6_REQ_MAX_RT;
341                 max_retransmit_count = DHCP6_REQ_MAX_RC;
342                 max_retransmit_duration = 0;
343
344                 break;
345
346         case DHCP6_STATE_STOPPED:
347         case DHCP6_STATE_RS:
348         case DHCP6_STATE_BOUND:
349                 return 0;
350         }
351
352         if (max_retransmit_count &&
353             client->retransmit_count >= max_retransmit_count) {
354                 client_stop(client, DHCP6_EVENT_RETRANS_MAX);
355                 return 0;
356         }
357
358         r = client_send_message(client);
359         if (r >= 0)
360                 client->retransmit_count++;
361
362
363         r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
364         if (r < 0)
365                 goto error;
366
367         if (!client->retransmit_time) {
368                 client->retransmit_time =
369                         client_timeout_compute_random(init_retransmit_time);
370
371                 if (client->state == DHCP6_STATE_SOLICITATION)
372                         client->retransmit_time += init_retransmit_time / 10;
373
374         } else {
375                 if (max_retransmit_time &&
376                     client->retransmit_time > max_retransmit_time / 2)
377                         client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
378                 else
379                         client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
380         }
381
382         log_dhcp6_client(client, "Next retransmission in %s",
383                          format_timespan(time_string, FORMAT_TIMESPAN_MAX,
384                                          client->retransmit_time, 0));
385
386         r = sd_event_add_time(client->event, &client->timeout_resend,
387                               CLOCK_MONOTONIC,
388                               time_now + client->retransmit_time,
389                               10 * USEC_PER_MSEC, client_timeout_resend,
390                               client);
391         if (r < 0)
392                 goto error;
393
394         r = sd_event_source_set_priority(client->timeout_resend,
395                                          client->event_priority);
396         if (r < 0)
397                 goto error;
398
399         if (max_retransmit_duration && !client->timeout_resend_expire) {
400
401                 log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
402                                  max_retransmit_duration / USEC_PER_SEC);
403
404                 r = sd_event_add_time(client->event,
405                                       &client->timeout_resend_expire,
406                                       CLOCK_MONOTONIC,
407                                       time_now + max_retransmit_duration,
408                                       USEC_PER_SEC,
409                                       client_timeout_resend_expire, client);
410                 if (r < 0)
411                         goto error;
412
413                 r = sd_event_source_set_priority(client->timeout_resend_expire,
414                                                  client->event_priority);
415                 if (r < 0)
416                         goto error;
417         }
418
419 error:
420         if (r < 0)
421                 client_stop(client, r);
422
423         return 0;
424 }
425
426 static int client_ensure_iaid(sd_dhcp6_client *client) {
427         const char *name = NULL;
428         uint64_t id;
429
430         assert(client);
431
432         if (client->ia_na.id)
433                 return 0;
434
435         if (detect_container(NULL) <= 0) {
436                 /* not in a container, udev will be around */
437                 _cleanup_udev_unref_ struct udev *udev;
438                 _cleanup_udev_device_unref_ struct udev_device *device = NULL;
439                 char ifindex_str[2 + DECIMAL_STR_MAX(int)];
440
441                 udev = udev_new();
442                 if (!udev)
443                         return -ENOMEM;
444
445                 sprintf(ifindex_str, "n%d", client->index);
446                 device = udev_device_new_from_device_id(udev, ifindex_str);
447                 if (!device)
448                         return -errno;
449
450                 if (udev_device_get_is_initialized(device) <= 0)
451                         /* not yet ready */
452                         return -EBUSY;
453
454                 name = net_get_name(device);
455         }
456
457         if (name)
458                 siphash24((uint8_t*)&id, name, strlen(name), HASH_KEY.bytes);
459         else
460                 /* fall back to mac address if no predictable name available */
461                 siphash24((uint8_t*)&id, &client->mac_addr, ETH_ALEN,
462                           HASH_KEY.bytes);
463
464         /* fold into 32 bits */
465         client->ia_na.id = (id & 0xffffffff) ^ (id >> 32);
466
467         return 0;
468 }
469
470 static int client_parse_message(sd_dhcp6_client *client,
471                                 DHCP6Message *message, size_t len,
472                                 sd_dhcp6_lease *lease) {
473         int r;
474         uint8_t *optval, *option = (uint8_t *)(message + 1), *id = NULL;
475         uint16_t optcode, status;
476         size_t optlen, id_len;
477         bool clientid = false;
478         be32_t iaid_lease;
479
480         while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen,
481                                        &optval)) >= 0) {
482                 switch (optcode) {
483                 case DHCP6_OPTION_CLIENTID:
484                         if (clientid) {
485                                 log_dhcp6_client(client, "%s contains multiple clientids",
486                                                  dhcp6_message_type_to_string(message->type));
487                                 return -EINVAL;
488                         }
489
490                         if (optlen != sizeof(client->duid) ||
491                             memcmp(&client->duid, optval, optlen) != 0) {
492                                 log_dhcp6_client(client, "%s DUID does not match",
493                                                  dhcp6_message_type_to_string(message->type));
494
495                                 return -EINVAL;
496                         }
497                         clientid = true;
498
499                         break;
500
501                 case DHCP6_OPTION_SERVERID:
502                         r = dhcp6_lease_get_serverid(lease, &id, &id_len);
503                         if (r >= 0 && id) {
504                                 log_dhcp6_client(client, "%s contains multiple serverids",
505                                                  dhcp6_message_type_to_string(message->type));
506                                 return -EINVAL;
507                         }
508
509                         r = dhcp6_lease_set_serverid(lease, optval, optlen);
510                         if (r < 0)
511                                 return r;
512
513                         break;
514
515                 case DHCP6_OPTION_PREFERENCE:
516                         if (optlen != 1)
517                                 return -EINVAL;
518
519                         r = dhcp6_lease_set_preference(lease, *optval);
520                         if (r < 0)
521                                 return r;
522
523                         break;
524
525                 case DHCP6_OPTION_STATUS_CODE:
526                         if (optlen < 2)
527                                 return -EINVAL;
528
529                         status = optval[0] << 8 | optval[1];
530                         if (status) {
531                                 log_dhcp6_client(client, "%s Status %s",
532                                                  dhcp6_message_type_to_string(message->type),
533                                                  dhcp6_message_status_to_string(status));
534                                 return -EINVAL;
535                         }
536
537                         break;
538
539                 case DHCP6_OPTION_IA_NA:
540                         r = dhcp6_option_parse_ia(&optval, &optlen, optcode,
541                                                   &lease->ia);
542                         if (r < 0 && r != -ENOMSG)
543                                 return r;
544
545                         r = dhcp6_lease_get_iaid(lease, &iaid_lease);
546                         if (r < 0)
547                                 return r;
548
549                         if (client->ia_na.id != iaid_lease) {
550                                 log_dhcp6_client(client, "%s has wrong IAID",
551                                                  dhcp6_message_type_to_string(message->type));
552                                 return -EINVAL;
553                         }
554
555                         break;
556                 }
557         }
558
559         if ((r < 0 && r != -ENOMSG) || !clientid) {
560                 log_dhcp6_client(client, "%s has incomplete options",
561                                  dhcp6_message_type_to_string(message->type));
562                 return -EINVAL;
563         }
564
565         r = dhcp6_lease_get_serverid(lease, &id, &id_len);
566         if (r < 0)
567                 log_dhcp6_client(client, "%s has no server id",
568                                  dhcp6_message_type_to_string(message->type));
569
570         return r;
571 }
572
573 static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply,
574                                 size_t len)
575 {
576         int r;
577         _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
578
579         if (reply->type != DHCP6_REPLY)
580                 return -EINVAL;
581
582         r = dhcp6_lease_new(&lease);
583         if (r < 0)
584                 return -ENOMEM;
585
586         r = client_parse_message(client, reply, len, lease);
587         if (r < 0)
588                 return r;
589
590         dhcp6_lease_clear_timers(&client->lease->ia);
591
592         client->lease = sd_dhcp6_lease_unref(client->lease);
593         client->lease = lease;
594         lease = NULL;
595
596         return DHCP6_STATE_BOUND;
597 }
598
599 static int client_receive_advertise(sd_dhcp6_client *client,
600                                     DHCP6Message *advertise, size_t len) {
601         int r;
602         _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
603         uint8_t pref_advertise = 0, pref_lease = 0;
604
605         if (advertise->type != DHCP6_ADVERTISE)
606                 return -EINVAL;
607
608         r = dhcp6_lease_new(&lease);
609         if (r < 0)
610                 return r;
611
612         r = client_parse_message(client, advertise, len, lease);
613         if (r < 0)
614                 return r;
615
616         r = dhcp6_lease_get_preference(lease, &pref_advertise);
617         if (r < 0)
618                 return r;
619
620         r = dhcp6_lease_get_preference(client->lease, &pref_lease);
621         if (!client->lease || r < 0 || pref_advertise > pref_lease) {
622                 sd_dhcp6_lease_unref(client->lease);
623                 client->lease = lease;
624                 lease = NULL;
625                 r = 0;
626         }
627
628         if (pref_advertise == 255 || client->retransmit_count > 1)
629                 r = DHCP6_STATE_REQUEST;
630
631         return r;
632 }
633
634 static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
635                                   void *userdata) {
636         sd_dhcp6_client *client = userdata;
637         DHCP6_CLIENT_DONT_DESTROY(client);
638         _cleanup_free_ DHCP6Message *message;
639         int r, buflen, len;
640
641         assert(s);
642         assert(client);
643         assert(client->event);
644
645         r = ioctl(fd, FIONREAD, &buflen);
646         if (r < 0 || buflen <= 0)
647                 buflen = DHCP6_MIN_OPTIONS_SIZE;
648
649         message = malloc0(buflen);
650         if (!message)
651                 return -ENOMEM;
652
653         len = read(fd, message, buflen);
654         if ((size_t)len < sizeof(DHCP6Message)) {
655                 log_dhcp6_client(client, "could not receive message from UDP socket: %m");
656                 return 0;
657         }
658
659         switch(message->type) {
660         case DHCP6_SOLICIT:
661         case DHCP6_REQUEST:
662         case DHCP6_CONFIRM:
663         case DHCP6_RENEW:
664         case DHCP6_REBIND:
665         case DHCP6_RELEASE:
666         case DHCP6_DECLINE:
667         case DHCP6_INFORMATION_REQUEST:
668         case DHCP6_RELAY_FORW:
669         case DHCP6_RELAY_REPL:
670                 return 0;
671
672         case DHCP6_ADVERTISE:
673         case DHCP6_REPLY:
674         case DHCP6_RECONFIGURE:
675                 break;
676
677         default:
678                 log_dhcp6_client(client, "unknown message type %d",
679                                  message->type);
680                 return 0;
681         }
682
683         if (client->transaction_id != (message->transaction_id &
684                                        htobe32(0x00ffffff)))
685                 return 0;
686
687         switch (client->state) {
688         case DHCP6_STATE_SOLICITATION:
689                 r = client_receive_advertise(client, message, len);
690
691                 if (r == DHCP6_STATE_REQUEST)
692                         client_start(client, r);
693
694                 break;
695
696         case DHCP6_STATE_REQUEST:
697                 r = client_receive_reply(client, message, len);
698                 if (r < 0)
699                         return 0;
700
701                 if (r == DHCP6_STATE_BOUND) {
702
703                         r = client_start(client, DHCP6_STATE_BOUND);
704                         if (r < 0) {
705                                 client_stop(client, r);
706                                 return 0;
707                         }
708
709                         client_notify(client, DHCP6_EVENT_IP_ACQUIRE);
710                 }
711
712                 break;
713
714         case DHCP6_STATE_BOUND:
715
716                 break;
717
718         case DHCP6_STATE_STOPPED:
719         case DHCP6_STATE_RS:
720                 return 0;
721         }
722
723         if (r >= 0) {
724                 log_dhcp6_client(client, "Recv %s",
725                                  dhcp6_message_type_to_string(message->type));
726         }
727
728         return 0;
729 }
730
731 static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
732 {
733         int r;
734         usec_t timeout, time_now;
735         char time_string[FORMAT_TIMESPAN_MAX];
736
737         assert_return(client, -EINVAL);
738         assert_return(client->event, -EINVAL);
739         assert_return(client->index > 0, -EINVAL);
740         assert_return(client->state != state, -EINVAL);
741
742         client->timeout_resend_expire =
743                 sd_event_source_unref(client->timeout_resend_expire);
744         client->timeout_resend = sd_event_source_unref(client->timeout_resend);
745         client->retransmit_time = 0;
746         client->retransmit_count = 0;
747
748         switch (state) {
749         case DHCP6_STATE_STOPPED:
750         case DHCP6_STATE_RS:
751         case DHCP6_STATE_SOLICITATION:
752
753                 r = client_ensure_iaid(client);
754                 if (r < 0)
755                         return r;
756
757                 r = dhcp6_network_bind_udp_socket(client->index, NULL);
758                 if (r < 0)
759                         return r;
760
761                 client->fd = r;
762
763                 r = sd_event_add_io(client->event, &client->receive_message,
764                                     client->fd, EPOLLIN, client_receive_message,
765                                     client);
766                 if (r < 0)
767                         return r;
768
769                 r = sd_event_source_set_priority(client->receive_message,
770                                                  client->event_priority);
771                 if (r < 0)
772                         return r;
773
774                 client->state = DHCP6_STATE_SOLICITATION;
775
776                 break;
777
778         case DHCP6_STATE_REQUEST:
779
780                 client->state = state;
781
782                 break;
783
784         case DHCP6_STATE_BOUND:
785
786                 r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
787                 if (r < 0)
788                         return r;
789
790                 if (client->lease->ia.lifetime_t1 == 0xffffffff ||
791                     client->lease->ia.lifetime_t2 == 0xffffffff) {
792
793                         log_dhcp6_client(client, "infinite T1 0x%08x or T2 0x%08x",
794                                          be32toh(client->lease->ia.lifetime_t1),
795                                          be32toh(client->lease->ia.lifetime_t2));
796
797                         return 0;
798                 }
799
800                 timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t1) * USEC_PER_SEC);
801
802                 log_dhcp6_client(client, "T1 expires in %s",
803                                  format_timespan(time_string,
804                                                  FORMAT_TIMESPAN_MAX,
805                                                  timeout, 0));
806
807                 r = sd_event_add_time(client->event,
808                                       &client->lease->ia.timeout_t1,
809                                       CLOCK_MONOTONIC, time_now + timeout,
810                                       10 * USEC_PER_SEC, client_timeout_t1,
811                                       client);
812                 if (r < 0)
813                         return r;
814
815                 r = sd_event_source_set_priority(client->lease->ia.timeout_t1,
816                                                  client->event_priority);
817                 if (r < 0)
818                         return r;
819
820                 timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t2) * USEC_PER_SEC);
821
822                 log_dhcp6_client(client, "T2 expires in %s",
823                                  format_timespan(time_string,
824                                                  FORMAT_TIMESPAN_MAX,
825                                                  timeout, 0));
826
827                 r = sd_event_add_time(client->event,
828                                       &client->lease->ia.timeout_t2,
829                                       CLOCK_MONOTONIC, time_now + timeout,
830                                       10 * USEC_PER_SEC, client_timeout_t2,
831                                       client);
832                 if (r < 0)
833                         return r;
834
835                 r = sd_event_source_set_priority(client->lease->ia.timeout_t2,
836                                                  client->event_priority);
837                 if (r < 0)
838                         return r;
839
840                 return 0;
841         }
842
843         client->transaction_id = random_u32() & htobe32(0x00ffffff);
844
845         r = sd_event_add_time(client->event, &client->timeout_resend,
846                               CLOCK_MONOTONIC, 0, 0, client_timeout_resend,
847                               client);
848         if (r < 0)
849                 return r;
850
851         r = sd_event_source_set_priority(client->timeout_resend,
852                                          client->event_priority);
853         if (r < 0)
854                 return r;
855
856         return 0;
857 }
858
859 int sd_dhcp6_client_stop(sd_dhcp6_client *client)
860 {
861         client_stop(client, DHCP6_EVENT_STOP);
862
863         return 0;
864 }
865
866 int sd_dhcp6_client_start(sd_dhcp6_client *client)
867 {
868         int r = 0;
869
870         assert_return(client, -EINVAL);
871         assert_return(client->event, -EINVAL);
872         assert_return(client->index > 0, -EINVAL);
873
874         r = client_reset(client);
875         if (r < 0)
876                 return r;
877
878         return client_start(client, DHCP6_STATE_SOLICITATION);
879 }
880
881 int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
882                                  int priority)
883 {
884         int r;
885
886         assert_return(client, -EINVAL);
887         assert_return(!client->event, -EBUSY);
888
889         if (event)
890                 client->event = sd_event_ref(event);
891         else {
892                 r = sd_event_default(&client->event);
893                 if (r < 0)
894                         return 0;
895         }
896
897         client->event_priority = priority;
898
899         return 0;
900 }
901
902 int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
903         assert_return(client, -EINVAL);
904
905         client->event = sd_event_unref(client->event);
906
907         return 0;
908 }
909
910 sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
911         if (!client)
912                 return NULL;
913
914         return client->event;
915 }
916
917 sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
918         if (client)
919                 assert_se(REFCNT_INC(client->n_ref) >= 2);
920
921         return client;
922 }
923
924 sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
925         if (client && REFCNT_DEC(client->n_ref) <= 0) {
926                 client_reset(client);
927
928                 sd_dhcp6_client_detach_event(client);
929
930                 free(client);
931
932                 return NULL;
933         }
934
935         return client;
936 }
937
938 int sd_dhcp6_client_new(sd_dhcp6_client **ret)
939 {
940         _cleanup_dhcp6_client_unref_ sd_dhcp6_client *client = NULL;
941         sd_id128_t machine_id;
942         int r;
943
944         assert_return(ret, -EINVAL);
945
946         client = new0(sd_dhcp6_client, 1);
947         if (!client)
948                 return -ENOMEM;
949
950         client->n_ref = REFCNT_INIT;
951
952         client->ia_na.type = DHCP6_OPTION_IA_NA;
953
954         client->index = -1;
955
956         client->fd = -1;
957
958         /* initialize DUID */
959         client->duid.type = htobe16(DHCP6_DUID_EN);
960         client->duid.pen = htobe32(SYSTEMD_PEN);
961
962         r = sd_id128_get_machine(&machine_id);
963         if (r < 0)
964                 return r;
965
966         /* a bit of snake-oil perhaps, but no need to expose the machine-id
967            directly */
968         siphash24(client->duid.id, &machine_id, sizeof(machine_id),
969                   HASH_KEY.bytes);
970
971         *ret = client;
972         client = NULL;
973
974         return 0;
975 }