1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2014 Intel Corporation. All rights reserved.
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.
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.
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/>.
24 #include <sys/ioctl.h>
27 #include "udev-util.h"
29 #include "siphash24.h"
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"
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)
42 struct sd_dhcp6_client {
45 enum DHCP6State state;
49 struct ether_addr mac_addr;
51 be32_t transaction_id;
52 struct sd_dhcp6_lease *lease;
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;
63 uint16_t type; /* DHCP6_DUID_EN */
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",
85 DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);
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",
96 DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int);
98 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
99 #define _cleanup_dhcp6_client_unref_ _cleanup_(sd_dhcp6_client_unrefp)
101 #define DHCP6_CLIENT_DONT_DESTROY(client) \
102 _cleanup_dhcp6_client_unref_ _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client)
104 static int client_start(sd_dhcp6_client *client, enum DHCP6State state);
106 int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
107 sd_dhcp6_client_cb_t cb, void *userdata)
109 assert_return(client, -EINVAL);
112 client->userdata = userdata;
117 int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index)
119 assert_return(client, -EINVAL);
120 assert_return(interface_index >= -1, -EINVAL);
122 client->index = interface_index;
127 int sd_dhcp6_client_set_mac(sd_dhcp6_client *client,
128 const struct ether_addr *mac_addr)
130 assert_return(client, -EINVAL);
133 memcpy(&client->mac_addr, mac_addr, sizeof(client->mac_addr));
135 memset(&client->mac_addr, 0x00, sizeof(client->mac_addr));
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);
147 *ret = sd_dhcp6_lease_ref(client->lease);
152 static void client_notify(sd_dhcp6_client *client, int event) {
154 client->cb(client, event, client->userdata);
157 static int client_reset(sd_dhcp6_client *client) {
158 assert_return(client, -EINVAL);
160 client->receive_message =
161 sd_event_source_unref(client->receive_message);
163 client->fd = safe_close(client->fd);
165 client->transaction_id = 0;
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);
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);
178 client->state = DHCP6_STATE_STOPPED;
183 static void client_stop(sd_dhcp6_client *client, int error) {
184 DHCP6_CLIENT_DONT_DESTROY(client);
188 client_notify(client, error);
190 client_reset(client);
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;
201 len = sizeof(DHCP6Message) + optlen;
203 message = malloc0(len);
207 opt = (uint8_t *)(message + 1);
209 message->transaction_id = client->transaction_id;
211 switch(client->state) {
212 case DHCP6_STATE_SOLICITATION:
213 message->type = DHCP6_SOLICIT;
215 r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
221 case DHCP6_STATE_REQUEST:
222 message->type = DHCP6_REQUEST;
224 r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_SERVERID,
225 client->lease->serverid_len,
226 client->lease->serverid);
230 r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
236 case DHCP6_STATE_STOPPED:
238 case DHCP6_STATE_BOUND:
242 r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID,
243 sizeof(client->duid), &client->duid);
247 r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
252 log_dhcp6_client(client, "Sent %s",
253 dhcp6_message_type_to_string(message->type));
258 static int client_timeout_t2(sd_event_source *s, uint64_t usec,
260 sd_dhcp6_client *client = userdata;
262 assert_return(s, -EINVAL);
263 assert_return(client, -EINVAL);
264 assert_return(client->lease, -EINVAL);
266 client->lease->ia.timeout_t2 =
267 sd_event_source_unref(client->lease->ia.timeout_t2);
269 log_dhcp6_client(client, "Timeout T2");
274 static int client_timeout_t1(sd_event_source *s, uint64_t usec,
276 sd_dhcp6_client *client = userdata;
278 assert_return(s, -EINVAL);
279 assert_return(client, -EINVAL);
280 assert_return(client->lease, -EINVAL);
282 client->lease->ia.timeout_t1 =
283 sd_event_source_unref(client->lease->ia.timeout_t1);
285 log_dhcp6_client(client, "Timeout T1");
290 static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
292 sd_dhcp6_client *client = userdata;
296 assert(client->event);
298 client_stop(client, DHCP6_EVENT_RESEND_EXPIRE);
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;
308 static int client_timeout_resend(sd_event_source *s, uint64_t usec,
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];
319 assert(client->event);
321 client->timeout_resend = sd_event_source_unref(client->timeout_resend);
323 switch (client->state) {
324 case DHCP6_STATE_SOLICITATION:
326 if (client->retransmit_count && client->lease) {
327 client_start(client, DHCP6_STATE_REQUEST);
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;
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;
346 case DHCP6_STATE_STOPPED:
348 case DHCP6_STATE_BOUND:
352 if (max_retransmit_count &&
353 client->retransmit_count >= max_retransmit_count) {
354 client_stop(client, DHCP6_EVENT_RETRANS_MAX);
358 r = client_send_message(client);
360 client->retransmit_count++;
363 r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
367 if (!client->retransmit_time) {
368 client->retransmit_time =
369 client_timeout_compute_random(init_retransmit_time);
371 if (client->state == DHCP6_STATE_SOLICITATION)
372 client->retransmit_time += init_retransmit_time / 10;
375 if (max_retransmit_time &&
376 client->retransmit_time > max_retransmit_time / 2)
377 client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
379 client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
382 log_dhcp6_client(client, "Next retransmission in %s",
383 format_timespan(time_string, FORMAT_TIMESPAN_MAX,
384 client->retransmit_time, 0));
386 r = sd_event_add_time(client->event, &client->timeout_resend,
388 time_now + client->retransmit_time,
389 10 * USEC_PER_MSEC, client_timeout_resend,
394 r = sd_event_source_set_priority(client->timeout_resend,
395 client->event_priority);
399 if (max_retransmit_duration && !client->timeout_resend_expire) {
401 log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
402 max_retransmit_duration / USEC_PER_SEC);
404 r = sd_event_add_time(client->event,
405 &client->timeout_resend_expire,
407 time_now + max_retransmit_duration,
409 client_timeout_resend_expire, client);
413 r = sd_event_source_set_priority(client->timeout_resend_expire,
414 client->event_priority);
421 client_stop(client, r);
426 static int client_ensure_iaid(sd_dhcp6_client *client) {
427 const char *name = NULL;
432 if (client->ia_na.id)
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)];
445 sprintf(ifindex_str, "n%d", client->index);
446 device = udev_device_new_from_device_id(udev, ifindex_str);
450 if (udev_device_get_is_initialized(device) <= 0)
454 name = net_get_name(device);
458 siphash24((uint8_t*)&id, name, strlen(name), HASH_KEY.bytes);
460 /* fall back to mac address if no predictable name available */
461 siphash24((uint8_t*)&id, &client->mac_addr, ETH_ALEN,
464 /* fold into 32 bits */
465 client->ia_na.id = (id & 0xffffffff) ^ (id >> 32);
470 static int client_parse_message(sd_dhcp6_client *client,
471 DHCP6Message *message, size_t len,
472 sd_dhcp6_lease *lease) {
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;
480 while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen,
483 case DHCP6_OPTION_CLIENTID:
485 log_dhcp6_client(client, "%s contains multiple clientids",
486 dhcp6_message_type_to_string(message->type));
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));
501 case DHCP6_OPTION_SERVERID:
502 r = dhcp6_lease_get_serverid(lease, &id, &id_len);
504 log_dhcp6_client(client, "%s contains multiple serverids",
505 dhcp6_message_type_to_string(message->type));
509 r = dhcp6_lease_set_serverid(lease, optval, optlen);
515 case DHCP6_OPTION_PREFERENCE:
519 r = dhcp6_lease_set_preference(lease, *optval);
525 case DHCP6_OPTION_STATUS_CODE:
529 status = optval[0] << 8 | optval[1];
531 log_dhcp6_client(client, "%s Status %s",
532 dhcp6_message_type_to_string(message->type),
533 dhcp6_message_status_to_string(status));
539 case DHCP6_OPTION_IA_NA:
540 r = dhcp6_option_parse_ia(&optval, &optlen, optcode,
542 if (r < 0 && r != -ENOMSG)
545 r = dhcp6_lease_get_iaid(lease, &iaid_lease);
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));
559 if ((r < 0 && r != -ENOMSG) || !clientid) {
560 log_dhcp6_client(client, "%s has incomplete options",
561 dhcp6_message_type_to_string(message->type));
565 r = dhcp6_lease_get_serverid(lease, &id, &id_len);
567 log_dhcp6_client(client, "%s has no server id",
568 dhcp6_message_type_to_string(message->type));
573 static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply,
577 _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
579 if (reply->type != DHCP6_REPLY)
582 r = dhcp6_lease_new(&lease);
586 r = client_parse_message(client, reply, len, lease);
590 dhcp6_lease_clear_timers(&client->lease->ia);
592 client->lease = sd_dhcp6_lease_unref(client->lease);
593 client->lease = lease;
596 return DHCP6_STATE_BOUND;
599 static int client_receive_advertise(sd_dhcp6_client *client,
600 DHCP6Message *advertise, size_t len) {
602 _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
603 uint8_t pref_advertise = 0, pref_lease = 0;
605 if (advertise->type != DHCP6_ADVERTISE)
608 r = dhcp6_lease_new(&lease);
612 r = client_parse_message(client, advertise, len, lease);
616 r = dhcp6_lease_get_preference(lease, &pref_advertise);
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;
628 if (pref_advertise == 255 || client->retransmit_count > 1)
629 r = DHCP6_STATE_REQUEST;
634 static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
636 sd_dhcp6_client *client = userdata;
637 DHCP6_CLIENT_DONT_DESTROY(client);
638 _cleanup_free_ DHCP6Message *message;
643 assert(client->event);
645 r = ioctl(fd, FIONREAD, &buflen);
646 if (r < 0 || buflen <= 0)
647 buflen = DHCP6_MIN_OPTIONS_SIZE;
649 message = malloc0(buflen);
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");
659 switch(message->type) {
667 case DHCP6_INFORMATION_REQUEST:
668 case DHCP6_RELAY_FORW:
669 case DHCP6_RELAY_REPL:
672 case DHCP6_ADVERTISE:
674 case DHCP6_RECONFIGURE:
678 log_dhcp6_client(client, "unknown message type %d",
683 if (client->transaction_id != (message->transaction_id &
684 htobe32(0x00ffffff)))
687 switch (client->state) {
688 case DHCP6_STATE_SOLICITATION:
689 r = client_receive_advertise(client, message, len);
691 if (r == DHCP6_STATE_REQUEST)
692 client_start(client, r);
696 case DHCP6_STATE_REQUEST:
697 r = client_receive_reply(client, message, len);
701 if (r == DHCP6_STATE_BOUND) {
703 r = client_start(client, DHCP6_STATE_BOUND);
705 client_stop(client, r);
709 client_notify(client, DHCP6_EVENT_IP_ACQUIRE);
714 case DHCP6_STATE_BOUND:
718 case DHCP6_STATE_STOPPED:
724 log_dhcp6_client(client, "Recv %s",
725 dhcp6_message_type_to_string(message->type));
731 static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
734 usec_t timeout, time_now;
735 char time_string[FORMAT_TIMESPAN_MAX];
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);
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;
749 case DHCP6_STATE_STOPPED:
751 case DHCP6_STATE_SOLICITATION:
753 r = client_ensure_iaid(client);
757 r = dhcp6_network_bind_udp_socket(client->index, NULL);
763 r = sd_event_add_io(client->event, &client->receive_message,
764 client->fd, EPOLLIN, client_receive_message,
769 r = sd_event_source_set_priority(client->receive_message,
770 client->event_priority);
774 client->state = DHCP6_STATE_SOLICITATION;
778 case DHCP6_STATE_REQUEST:
780 client->state = state;
784 case DHCP6_STATE_BOUND:
786 r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
790 if (client->lease->ia.lifetime_t1 == 0xffffffff ||
791 client->lease->ia.lifetime_t2 == 0xffffffff) {
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));
800 timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t1) * USEC_PER_SEC);
802 log_dhcp6_client(client, "T1 expires in %s",
803 format_timespan(time_string,
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,
815 r = sd_event_source_set_priority(client->lease->ia.timeout_t1,
816 client->event_priority);
820 timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t2) * USEC_PER_SEC);
822 log_dhcp6_client(client, "T2 expires in %s",
823 format_timespan(time_string,
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,
835 r = sd_event_source_set_priority(client->lease->ia.timeout_t2,
836 client->event_priority);
843 client->transaction_id = random_u32() & htobe32(0x00ffffff);
845 r = sd_event_add_time(client->event, &client->timeout_resend,
846 CLOCK_MONOTONIC, 0, 0, client_timeout_resend,
851 r = sd_event_source_set_priority(client->timeout_resend,
852 client->event_priority);
859 int sd_dhcp6_client_stop(sd_dhcp6_client *client)
861 client_stop(client, DHCP6_EVENT_STOP);
866 int sd_dhcp6_client_start(sd_dhcp6_client *client)
870 assert_return(client, -EINVAL);
871 assert_return(client->event, -EINVAL);
872 assert_return(client->index > 0, -EINVAL);
874 r = client_reset(client);
878 return client_start(client, DHCP6_STATE_SOLICITATION);
881 int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
886 assert_return(client, -EINVAL);
887 assert_return(!client->event, -EBUSY);
890 client->event = sd_event_ref(event);
892 r = sd_event_default(&client->event);
897 client->event_priority = priority;
902 int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
903 assert_return(client, -EINVAL);
905 client->event = sd_event_unref(client->event);
910 sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
914 return client->event;
917 sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
919 assert_se(REFCNT_INC(client->n_ref) >= 2);
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);
928 sd_dhcp6_client_detach_event(client);
938 int sd_dhcp6_client_new(sd_dhcp6_client **ret)
940 _cleanup_dhcp6_client_unref_ sd_dhcp6_client *client = NULL;
941 sd_id128_t machine_id;
944 assert_return(ret, -EINVAL);
946 client = new0(sd_dhcp6_client, 1);
950 client->n_ref = REFCNT_INIT;
952 client->ia_na.type = DHCP6_OPTION_IA_NA;
958 /* initialize DUID */
959 client->duid.type = htobe16(DHCP6_DUID_EN);
960 client->duid.pen = htobe32(SYSTEMD_PEN);
962 r = sd_id128_get_machine(&machine_id);
966 /* a bit of snake-oil perhaps, but no need to expose the machine-id
968 siphash24(client->duid.id, &machine_id, sizeof(machine_id),