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 static int client_start(sd_dhcp6_client *client, enum DHCP6State state);
100 int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
101 sd_dhcp6_client_cb_t cb, void *userdata)
103 assert_return(client, -EINVAL);
106 client->userdata = userdata;
111 int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index)
113 assert_return(client, -EINVAL);
114 assert_return(interface_index >= -1, -EINVAL);
116 client->index = interface_index;
121 int sd_dhcp6_client_set_mac(sd_dhcp6_client *client,
122 const struct ether_addr *mac_addr)
124 assert_return(client, -EINVAL);
127 memcpy(&client->mac_addr, mac_addr, sizeof(client->mac_addr));
129 memset(&client->mac_addr, 0x00, sizeof(client->mac_addr));
134 int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
135 assert_return(client, -EINVAL);
136 assert_return(ret, -EINVAL);
141 *ret = sd_dhcp6_lease_ref(client->lease);
146 static sd_dhcp6_client *client_notify(sd_dhcp6_client *client, int event) {
148 client = sd_dhcp6_client_ref(client);
149 client->cb(client, event, client->userdata);
150 client = sd_dhcp6_client_unref(client);
156 static int client_initialize(sd_dhcp6_client *client)
158 assert_return(client, -EINVAL);
160 client->receive_message =
161 sd_event_source_unref(client->receive_message);
164 client->fd = safe_close(client->fd);
166 client->transaction_id = 0;
168 client->ia_na.timeout_t1 =
169 sd_event_source_unref(client->ia_na.timeout_t1);
170 client->ia_na.timeout_t2 =
171 sd_event_source_unref(client->ia_na.timeout_t2);
173 client->retransmit_time = 0;
174 client->retransmit_count = 0;
175 client->timeout_resend = sd_event_source_unref(client->timeout_resend);
176 client->timeout_resend_expire =
177 sd_event_source_unref(client->timeout_resend_expire);
179 client->state = DHCP6_STATE_STOPPED;
184 static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) {
185 assert_return(client, NULL);
187 client = client_notify(client, error);
189 client_initialize(client);
194 static int client_send_message(sd_dhcp6_client *client) {
195 _cleanup_free_ DHCP6Message *message = NULL;
196 struct in6_addr all_servers =
197 IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
198 size_t len, optlen = 512;
202 len = sizeof(DHCP6Message) + optlen;
204 message = malloc0(len);
208 opt = (uint8_t *)(message + 1);
210 message->transaction_id = client->transaction_id;
212 switch(client->state) {
213 case DHCP6_STATE_SOLICITATION:
214 message->type = DHCP6_SOLICIT;
216 r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID,
217 sizeof(client->duid), &client->duid);
221 r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
227 case DHCP6_STATE_STOPPED:
232 r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
237 log_dhcp6_client(client, "Sent %s",
238 dhcp6_message_type_to_string(message->type));
243 static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
245 sd_dhcp6_client *client = userdata;
249 assert(client->event);
251 client_stop(client, DHCP6_EVENT_RESEND_EXPIRE);
256 static usec_t client_timeout_compute_random(usec_t val) {
257 return val - val / 10 +
258 (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
261 static int client_timeout_resend(sd_event_source *s, uint64_t usec,
264 sd_dhcp6_client *client = userdata;
265 usec_t time_now, init_retransmit_time, max_retransmit_time;
266 usec_t max_retransmit_duration;
267 uint8_t max_retransmit_count;
268 char time_string[FORMAT_TIMESPAN_MAX];
272 assert(client->event);
274 client->timeout_resend = sd_event_source_unref(client->timeout_resend);
276 switch (client->state) {
277 case DHCP6_STATE_SOLICITATION:
278 init_retransmit_time = DHCP6_SOL_TIMEOUT;
279 max_retransmit_time = DHCP6_SOL_MAX_RT;
280 max_retransmit_count = 0;
281 max_retransmit_duration = 0;
285 case DHCP6_STATE_STOPPED:
290 if (max_retransmit_count &&
291 client->retransmit_count >= max_retransmit_count) {
292 client_stop(client, DHCP6_EVENT_RETRANS_MAX);
296 r = client_send_message(client);
298 client->retransmit_count++;
301 r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
305 if (!client->retransmit_time) {
306 client->retransmit_time =
307 client_timeout_compute_random(init_retransmit_time);
309 if (client->state == DHCP6_STATE_SOLICITATION)
310 client->retransmit_time += init_retransmit_time / 10;
313 if (max_retransmit_time &&
314 client->retransmit_time > max_retransmit_time / 2)
315 client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
317 client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
320 log_dhcp6_client(client, "Next retransmission in %s",
321 format_timespan(time_string, FORMAT_TIMESPAN_MAX,
322 client->retransmit_time, 0));
324 r = sd_event_add_time(client->event, &client->timeout_resend,
326 time_now + client->retransmit_time,
327 10 * USEC_PER_MSEC, client_timeout_resend,
332 r = sd_event_source_set_priority(client->timeout_resend,
333 client->event_priority);
337 if (max_retransmit_duration && !client->timeout_resend_expire) {
339 log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
340 max_retransmit_duration / USEC_PER_SEC);
342 r = sd_event_add_time(client->event,
343 &client->timeout_resend_expire,
345 time_now + max_retransmit_duration,
347 client_timeout_resend_expire, client);
351 r = sd_event_source_set_priority(client->timeout_resend_expire,
352 client->event_priority);
359 client_stop(client, r);
364 static int client_ensure_iaid(sd_dhcp6_client *client) {
365 const char *name = NULL;
370 if (client->ia_na.id)
373 if (detect_container(NULL) <= 0) {
374 /* not in a container, udev will be around */
375 _cleanup_udev_unref_ struct udev *udev;
376 _cleanup_udev_device_unref_ struct udev_device *device;
377 char ifindex_str[2 + DECIMAL_STR_MAX(int)];
383 sprintf(ifindex_str, "n%d", client->index);
384 device = udev_device_new_from_device_id(udev, ifindex_str);
388 if (udev_device_get_is_initialized(device) <= 0)
392 name = net_get_name(device);
396 siphash24((uint8_t*)&id, name, strlen(name), HASH_KEY.bytes);
398 /* fall back to mac address if no predictable name available */
399 siphash24((uint8_t*)&id, &client->mac_addr, ETH_ALEN,
402 /* fold into 32 bits */
403 client->ia_na.id = (id & 0xffffffff) ^ (id >> 32);
408 static int client_parse_message(sd_dhcp6_client *client,
409 DHCP6Message *message, size_t len,
410 sd_dhcp6_lease *lease) {
412 uint8_t *optval, *option = (uint8_t *)(message + 1), *id = NULL;
413 uint16_t optcode, status;
414 size_t optlen, id_len;
415 bool clientid = false;
418 while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen,
421 case DHCP6_OPTION_CLIENTID:
423 log_dhcp6_client(client, "%s contains multiple clientids",
424 dhcp6_message_type_to_string(message->type));
428 if (optlen != sizeof(client->duid) ||
429 memcmp(&client->duid, optval, optlen) != 0) {
430 log_dhcp6_client(client, "%s DUID does not match",
431 dhcp6_message_type_to_string(message->type));
439 case DHCP6_OPTION_SERVERID:
440 r = dhcp6_lease_get_serverid(lease, &id, &id_len);
442 log_dhcp6_client(client, "%s contains multiple serverids",
443 dhcp6_message_type_to_string(message->type));
447 r = dhcp6_lease_set_serverid(lease, optval, optlen);
453 case DHCP6_OPTION_PREFERENCE:
457 r = dhcp6_lease_set_preference(lease, *optval);
463 case DHCP6_OPTION_STATUS_CODE:
467 status = optval[0] << 8 | optval[1];
469 log_dhcp6_client(client, "%s Status %s",
470 dhcp6_message_type_to_string(message->type),
471 dhcp6_message_status_to_string(status));
477 case DHCP6_OPTION_IA_NA:
478 r = dhcp6_option_parse_ia(&optval, &optlen, optcode,
480 if (r < 0 && r != -ENOMSG)
483 r = dhcp6_lease_get_iaid(lease, &iaid_lease);
487 if (client->ia_na.id != iaid_lease) {
488 log_dhcp6_client(client, "%s has wrong IAID",
489 dhcp6_message_type_to_string(message->type));
497 if ((r < 0 && r != -ENOMSG) || !clientid) {
498 log_dhcp6_client(client, "%s has incomplete options",
499 dhcp6_message_type_to_string(message->type));
503 r = dhcp6_lease_get_serverid(lease, &id, &id_len);
505 log_dhcp6_client(client, "%s has no server id",
506 dhcp6_message_type_to_string(message->type));
511 static int client_receive_advertise(sd_dhcp6_client *client,
512 DHCP6Message *advertise, size_t len) {
514 _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
515 uint8_t pref_advertise = 0, pref_lease = 0;
517 if (advertise->type != DHCP6_ADVERTISE)
520 r = dhcp6_lease_new(&lease);
524 r = client_parse_message(client, advertise, len, lease);
528 r = dhcp6_lease_get_preference(lease, &pref_advertise);
532 r = dhcp6_lease_get_preference(client->lease, &pref_lease);
533 if (!client->lease || r < 0 || pref_advertise > pref_lease) {
534 sd_dhcp6_lease_unref(client->lease);
535 client->lease = lease;
543 static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
545 sd_dhcp6_client *client = userdata;
546 _cleanup_free_ DHCP6Message *message;
551 assert(client->event);
553 r = ioctl(fd, FIONREAD, &buflen);
554 if (r < 0 || buflen <= 0)
555 buflen = DHCP6_MIN_OPTIONS_SIZE;
557 message = malloc0(buflen);
561 len = read(fd, message, buflen);
562 if ((size_t)len < sizeof(DHCP6Message)) {
563 log_dhcp6_client(client, "could not receive message from UDP socket: %s", strerror(errno));
567 switch(message->type) {
575 case DHCP6_INFORMATION_REQUEST:
576 case DHCP6_RELAY_FORW:
577 case DHCP6_RELAY_REPL:
580 case DHCP6_ADVERTISE:
582 case DHCP6_RECONFIGURE:
586 log_dhcp6_client(client, "unknown message type %d",
591 if (client->transaction_id != (message->transaction_id &
592 htobe32(0x00ffffff)))
595 switch (client->state) {
596 case DHCP6_STATE_SOLICITATION:
597 r = client_receive_advertise(client, message, len);
601 case DHCP6_STATE_STOPPED:
607 log_dhcp6_client(client, "Recv %s",
608 dhcp6_message_type_to_string(message->type));
614 static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
618 assert_return(client, -EINVAL);
619 assert_return(client->event, -EINVAL);
620 assert_return(client->index > 0, -EINVAL);
621 assert_return(client->state != state, -EINVAL);
623 client->timeout_resend_expire =
624 sd_event_source_unref(client->timeout_resend_expire);
625 client->timeout_resend = sd_event_source_unref(client->timeout_resend);
626 client->retransmit_time = 0;
627 client->retransmit_count = 0;
630 case DHCP6_STATE_STOPPED:
632 case DHCP6_STATE_SOLICITATION:
634 r = client_ensure_iaid(client);
638 r = dhcp6_network_bind_udp_socket(client->index, NULL);
644 r = sd_event_add_io(client->event, &client->receive_message,
645 client->fd, EPOLLIN, client_receive_message,
650 r = sd_event_source_set_priority(client->receive_message,
651 client->event_priority);
655 client->state = DHCP6_STATE_SOLICITATION;
660 client->transaction_id = random_u32() & htobe32(0x00ffffff);
662 r = sd_event_add_time(client->event, &client->timeout_resend,
663 CLOCK_MONOTONIC, 0, 0, client_timeout_resend,
668 r = sd_event_source_set_priority(client->timeout_resend,
669 client->event_priority);
676 int sd_dhcp6_client_stop(sd_dhcp6_client *client)
678 client_stop(client, DHCP6_EVENT_STOP);
683 int sd_dhcp6_client_start(sd_dhcp6_client *client)
687 assert_return(client, -EINVAL);
688 assert_return(client->event, -EINVAL);
689 assert_return(client->index > 0, -EINVAL);
691 r = client_initialize(client);
695 return client_start(client, DHCP6_STATE_SOLICITATION);
698 int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
703 assert_return(client, -EINVAL);
704 assert_return(!client->event, -EBUSY);
707 client->event = sd_event_ref(event);
709 r = sd_event_default(&client->event);
714 client->event_priority = priority;
719 int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
720 assert_return(client, -EINVAL);
722 client->event = sd_event_unref(client->event);
727 sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
731 return client->event;
734 sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
736 assert_se(REFCNT_INC(client->n_ref) >= 2);
741 sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
742 if (client && REFCNT_DEC(client->n_ref) <= 0) {
743 client_initialize(client);
745 sd_dhcp6_client_detach_event(client);
755 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
756 #define _cleanup_dhcp6_client_free_ _cleanup_(sd_dhcp6_client_unrefp)
758 int sd_dhcp6_client_new(sd_dhcp6_client **ret)
760 _cleanup_dhcp6_client_free_ sd_dhcp6_client *client = NULL;
761 sd_id128_t machine_id;
764 assert_return(ret, -EINVAL);
766 client = new0(sd_dhcp6_client, 1);
770 client->n_ref = REFCNT_INIT;
772 client->ia_na.type = DHCP6_OPTION_IA_NA;
776 /* initialize DUID */
777 client->duid.type = htobe16(DHCP6_DUID_EN);
778 client->duid.pen = htobe32(SYSTEMD_PEN);
780 r = sd_id128_get_machine(&machine_id);
784 /* a bit of snake-oil perhaps, but no need to expose the machine-id
786 siphash24(client->duid.id, &machine_id, sizeof(machine_id),