]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-dhcp6-client.c
networkd: Properly stop router solicitation and DHCPv6 client
[thirdparty/systemd.git] / src / libsystemd-network / sd-dhcp6-client.c
CommitLineData
139b011a
PF
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>
631bbe71 24#include <sys/ioctl.h>
139b011a 25
f12abb48
PF
26#include "udev.h"
27#include "udev-util.h"
28#include "virt.h"
a276e6d6 29#include "siphash24.h"
139b011a
PF
30#include "util.h"
31#include "refcnt.h"
32
f12abb48 33#include "network-internal.h"
139b011a
PF
34#include "sd-dhcp6-client.h"
35#include "dhcp6-protocol.h"
f12abb48 36#include "dhcp6-internal.h"
631bbe71 37#include "dhcp6-lease-internal.h"
139b011a 38
a276e6d6
TG
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
139b011a
PF
42struct 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;
f12abb48 50 DHCP6IA ia_na;
a9aff361 51 be32_t transaction_id;
631bbe71 52 struct sd_dhcp6_lease *lease;
a9aff361
PF
53 int fd;
54 sd_event_source *receive_message;
d1b0afe3
PF
55 usec_t retransmit_time;
56 uint8_t retransmit_count;
57 sd_event_source *timeout_resend;
58 sd_event_source *timeout_resend_expire;
139b011a
PF
59 sd_dhcp6_client_cb_t cb;
60 void *userdata;
a276e6d6
TG
61
62 struct duid_en {
63 uint16_t type; /* DHCP6_DUID_EN */
64 uint32_t pen;
65 uint8_t id[8];
66 } _packed_ duid;
139b011a
PF
67};
68
a9aff361
PF
69const 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
85DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);
86
631bbe71
PF
87const 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
96DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int);
97
3f0c075f
PF
98DEFINE_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
c3e2adea
PF
104static int client_start(sd_dhcp6_client *client, enum DHCP6State state);
105
139b011a
PF
106int 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
117int 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
127int 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
ea3b3a75
PF
140int 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
3f0c075f
PF
152static void client_notify(sd_dhcp6_client *client, int event) {
153 if (client->cb)
139b011a 154 client->cb(client, event, client->userdata);
139b011a
PF
155}
156
c806ffb9 157static int client_reset(sd_dhcp6_client *client) {
139b011a
PF
158 assert_return(client, -EINVAL);
159
a9aff361
PF
160 client->receive_message =
161 sd_event_source_unref(client->receive_message);
162
c806ffb9 163 client->fd = safe_close(client->fd);
a9aff361 164
c3e2adea 165 client->transaction_id = 0;
a9aff361 166
f12abb48
PF
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
d1b0afe3
PF
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
139b011a
PF
178 client->state = DHCP6_STATE_STOPPED;
179
180 return 0;
181}
182
3f0c075f
PF
183static void client_stop(sd_dhcp6_client *client, int error) {
184 DHCP6_CLIENT_DONT_DESTROY(client);
139b011a 185
3f0c075f 186 assert(client);
139b011a 187
3f0c075f
PF
188 client_notify(client, error);
189
190 client_reset(client);
139b011a
PF
191}
192
a9aff361
PF
193static 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
7246333c 215 r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
a9aff361
PF
216 if (r < 0)
217 return r;
218
7246333c
PF
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);
a9aff361
PF
231 if (r < 0)
232 return r;
233
234 break;
235
236 case DHCP6_STATE_STOPPED:
237 case DHCP6_STATE_RS:
a34b57c0 238 case DHCP6_STATE_BOUND:
a9aff361
PF
239 return -EINVAL;
240 }
241
7246333c
PF
242 r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID,
243 sizeof(client->duid), &client->duid);
244 if (r < 0)
245 return r;
246
a9aff361
PF
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
a34b57c0
PF
258static 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
274static 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
d1b0afe3
PF
290static 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
303static 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
308static 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;
513a6fa8 314 uint8_t max_retransmit_count = 0;
d1b0afe3
PF
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:
7246333c
PF
325
326 if (client->retransmit_count && client->lease) {
327 client_start(client, DHCP6_STATE_REQUEST);
328 return 0;
329 }
330
d1b0afe3
PF
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
7246333c
PF
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
d1b0afe3
PF
346 case DHCP6_STATE_STOPPED:
347 case DHCP6_STATE_RS:
a34b57c0 348 case DHCP6_STATE_BOUND:
d1b0afe3
PF
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
a9aff361
PF
358 r = client_send_message(client);
359 if (r >= 0)
360 client->retransmit_count++;
361
362
d1b0afe3
PF
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);
a9aff361
PF
370
371 if (client->state == DHCP6_STATE_SOLICITATION)
372 client->retransmit_time += init_retransmit_time / 10;
373
d1b0afe3
PF
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
419error:
420 if (r < 0)
421 client_stop(client, r);
422
423 return 0;
424}
425
f12abb48
PF
426static 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;
513a6fa8 438 _cleanup_udev_device_unref_ struct udev_device *device = NULL;
f12abb48
PF
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
631bbe71
PF
470static 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
a34b57c0
PF
573static 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
631bbe71
PF
599static 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
7246333c
PF
628 if (pref_advertise == 255 || client->retransmit_count > 1)
629 r = DHCP6_STATE_REQUEST;
630
631bbe71
PF
631 return r;
632}
633
a9aff361 634static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
631bbe71
PF
635 void *userdata) {
636 sd_dhcp6_client *client = userdata;
3f0c075f 637 DHCP6_CLIENT_DONT_DESTROY(client);
631bbe71
PF
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)) {
6ec60d20 655 log_dhcp6_client(client, "could not receive message from UDP socket: %m");
631bbe71
PF
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
7246333c
PF
691 if (r == DHCP6_STATE_REQUEST)
692 client_start(client, r);
693
631bbe71
PF
694 break;
695
7246333c 696 case DHCP6_STATE_REQUEST:
a34b57c0
PF
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
3f0c075f 709 client_notify(client, DHCP6_EVENT_IP_ACQUIRE);
a34b57c0
PF
710 }
711
712 break;
713
714 case DHCP6_STATE_BOUND:
715
716 break;
717
631bbe71
PF
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
a9aff361
PF
728 return 0;
729}
730
c3e2adea 731static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
f12abb48
PF
732{
733 int r;
a34b57c0
PF
734 usec_t timeout, time_now;
735 char time_string[FORMAT_TIMESPAN_MAX];
f12abb48
PF
736
737 assert_return(client, -EINVAL);
738 assert_return(client->event, -EINVAL);
739 assert_return(client->index > 0, -EINVAL);
c3e2adea 740 assert_return(client->state != state, -EINVAL);
f12abb48 741
c3e2adea
PF
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;
f12abb48 747
c3e2adea
PF
748 switch (state) {
749 case DHCP6_STATE_STOPPED:
750 case DHCP6_STATE_RS:
751 case DHCP6_STATE_SOLICITATION:
a9aff361 752
c3e2adea
PF
753 r = client_ensure_iaid(client);
754 if (r < 0)
755 return r;
a9aff361 756
c3e2adea
PF
757 r = dhcp6_network_bind_udp_socket(client->index, NULL);
758 if (r < 0)
759 return r;
a9aff361 760
c3e2adea
PF
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
7246333c
PF
776 break;
777
778 case DHCP6_STATE_REQUEST:
a34b57c0 779
7246333c
PF
780 client->state = state;
781
c3e2adea 782 break;
a34b57c0
PF
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;
c3e2adea 841 }
a9aff361 842
c3e2adea 843 client->transaction_id = random_u32() & htobe32(0x00ffffff);
d1b0afe3
PF
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
f12abb48
PF
856 return 0;
857}
858
139b011a
PF
859int sd_dhcp6_client_stop(sd_dhcp6_client *client)
860{
861 client_stop(client, DHCP6_EVENT_STOP);
862
863 return 0;
864}
865
866int 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
c806ffb9 874 r = client_reset(client);
f12abb48
PF
875 if (r < 0)
876 return r;
877
c3e2adea 878 return client_start(client, DHCP6_STATE_SOLICITATION);
139b011a
PF
879}
880
881int 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
902int 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
910sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
911 if (!client)
912 return NULL;
913
914 return client->event;
915}
916
917sd_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
924sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
925 if (client && REFCNT_DEC(client->n_ref) <= 0) {
c806ffb9 926 client_reset(client);
139b011a
PF
927
928 sd_dhcp6_client_detach_event(client);
929
930 free(client);
931
932 return NULL;
933 }
934
935 return client;
936}
937
139b011a
PF
938int sd_dhcp6_client_new(sd_dhcp6_client **ret)
939{
3f0c075f 940 _cleanup_dhcp6_client_unref_ sd_dhcp6_client *client = NULL;
a276e6d6
TG
941 sd_id128_t machine_id;
942 int r;
139b011a
PF
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
f12abb48
PF
952 client->ia_na.type = DHCP6_OPTION_IA_NA;
953
139b011a
PF
954 client->index = -1;
955
c806ffb9
ZJS
956 client->fd = -1;
957
a276e6d6
TG
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
139b011a
PF
971 *ret = client;
972 client = NULL;
973
974 return 0;
975}