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 int sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
99 sd_dhcp6_client_cb_t cb, void *userdata)
101 assert_return(client, -EINVAL);
104 client->userdata = userdata;
109 int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index)
111 assert_return(client, -EINVAL);
112 assert_return(interface_index >= -1, -EINVAL);
114 client->index = interface_index;
119 int sd_dhcp6_client_set_mac(sd_dhcp6_client *client,
120 const struct ether_addr *mac_addr)
122 assert_return(client, -EINVAL);
125 memcpy(&client->mac_addr, mac_addr, sizeof(client->mac_addr));
127 memset(&client->mac_addr, 0x00, sizeof(client->mac_addr));
132 static sd_dhcp6_client *client_notify(sd_dhcp6_client *client, int event) {
134 client = sd_dhcp6_client_ref(client);
135 client->cb(client, event, client->userdata);
136 client = sd_dhcp6_client_unref(client);
142 static int client_initialize(sd_dhcp6_client *client)
144 assert_return(client, -EINVAL);
146 client->receive_message =
147 sd_event_source_unref(client->receive_message);
150 client->fd = safe_close(client->fd);
152 client->transaction_id = random_u32() & 0x00ffffff;
154 client->ia_na.timeout_t1 =
155 sd_event_source_unref(client->ia_na.timeout_t1);
156 client->ia_na.timeout_t2 =
157 sd_event_source_unref(client->ia_na.timeout_t2);
159 client->retransmit_time = 0;
160 client->retransmit_count = 0;
161 client->timeout_resend = sd_event_source_unref(client->timeout_resend);
162 client->timeout_resend_expire =
163 sd_event_source_unref(client->timeout_resend_expire);
165 client->state = DHCP6_STATE_STOPPED;
170 static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) {
171 assert_return(client, NULL);
173 client = client_notify(client, error);
175 client_initialize(client);
180 static int client_send_message(sd_dhcp6_client *client) {
181 _cleanup_free_ DHCP6Message *message = NULL;
182 struct in6_addr all_servers =
183 IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
184 size_t len, optlen = 512;
188 len = sizeof(DHCP6Message) + optlen;
190 message = malloc0(len);
194 opt = (uint8_t *)(message + 1);
196 message->transaction_id = client->transaction_id;
198 switch(client->state) {
199 case DHCP6_STATE_SOLICITATION:
200 message->type = DHCP6_SOLICIT;
202 r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID,
203 sizeof(client->duid), &client->duid);
207 r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
213 case DHCP6_STATE_STOPPED:
218 r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
223 log_dhcp6_client(client, "Sent %s",
224 dhcp6_message_type_to_string(message->type));
229 static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
231 sd_dhcp6_client *client = userdata;
235 assert(client->event);
237 client_stop(client, DHCP6_EVENT_RESEND_EXPIRE);
242 static usec_t client_timeout_compute_random(usec_t val) {
243 return val - val / 10 +
244 (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
247 static int client_timeout_resend(sd_event_source *s, uint64_t usec,
250 sd_dhcp6_client *client = userdata;
251 usec_t time_now, init_retransmit_time, max_retransmit_time;
252 usec_t max_retransmit_duration;
253 uint8_t max_retransmit_count;
254 char time_string[FORMAT_TIMESPAN_MAX];
258 assert(client->event);
260 client->timeout_resend = sd_event_source_unref(client->timeout_resend);
262 switch (client->state) {
263 case DHCP6_STATE_SOLICITATION:
264 init_retransmit_time = DHCP6_SOL_TIMEOUT;
265 max_retransmit_time = DHCP6_SOL_MAX_RT;
266 max_retransmit_count = 0;
267 max_retransmit_duration = 0;
271 case DHCP6_STATE_STOPPED:
276 if (max_retransmit_count &&
277 client->retransmit_count >= max_retransmit_count) {
278 client_stop(client, DHCP6_EVENT_RETRANS_MAX);
282 r = client_send_message(client);
284 client->retransmit_count++;
287 r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
291 if (!client->retransmit_time) {
292 client->retransmit_time =
293 client_timeout_compute_random(init_retransmit_time);
295 if (client->state == DHCP6_STATE_SOLICITATION)
296 client->retransmit_time += init_retransmit_time / 10;
299 if (max_retransmit_time &&
300 client->retransmit_time > max_retransmit_time / 2)
301 client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
303 client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
306 log_dhcp6_client(client, "Next retransmission in %s",
307 format_timespan(time_string, FORMAT_TIMESPAN_MAX,
308 client->retransmit_time, 0));
310 r = sd_event_add_time(client->event, &client->timeout_resend,
312 time_now + client->retransmit_time,
313 10 * USEC_PER_MSEC, client_timeout_resend,
318 r = sd_event_source_set_priority(client->timeout_resend,
319 client->event_priority);
323 if (max_retransmit_duration && !client->timeout_resend_expire) {
325 log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
326 max_retransmit_duration / USEC_PER_SEC);
328 r = sd_event_add_time(client->event,
329 &client->timeout_resend_expire,
331 time_now + max_retransmit_duration,
333 client_timeout_resend_expire, client);
337 r = sd_event_source_set_priority(client->timeout_resend_expire,
338 client->event_priority);
345 client_stop(client, r);
350 static int client_ensure_iaid(sd_dhcp6_client *client) {
351 const char *name = NULL;
356 if (client->ia_na.id)
359 if (detect_container(NULL) <= 0) {
360 /* not in a container, udev will be around */
361 _cleanup_udev_unref_ struct udev *udev;
362 _cleanup_udev_device_unref_ struct udev_device *device;
363 char ifindex_str[2 + DECIMAL_STR_MAX(int)];
369 sprintf(ifindex_str, "n%d", client->index);
370 device = udev_device_new_from_device_id(udev, ifindex_str);
374 if (udev_device_get_is_initialized(device) <= 0)
378 name = net_get_name(device);
382 siphash24((uint8_t*)&id, name, strlen(name), HASH_KEY.bytes);
384 /* fall back to mac address if no predictable name available */
385 siphash24((uint8_t*)&id, &client->mac_addr, ETH_ALEN,
388 /* fold into 32 bits */
389 client->ia_na.id = (id & 0xffffffff) ^ (id >> 32);
394 static int client_parse_message(sd_dhcp6_client *client,
395 DHCP6Message *message, size_t len,
396 sd_dhcp6_lease *lease) {
398 uint8_t *optval, *option = (uint8_t *)(message + 1), *id = NULL;
399 uint16_t optcode, status;
400 size_t optlen, id_len;
401 bool clientid = false;
404 while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen,
407 case DHCP6_OPTION_CLIENTID:
409 log_dhcp6_client(client, "%s contains multiple clientids",
410 dhcp6_message_type_to_string(message->type));
414 if (optlen != sizeof(client->duid) ||
415 memcmp(&client->duid, optval, optlen) != 0) {
416 log_dhcp6_client(client, "%s DUID does not match",
417 dhcp6_message_type_to_string(message->type));
425 case DHCP6_OPTION_SERVERID:
426 r = dhcp6_lease_get_serverid(lease, &id, &id_len);
428 log_dhcp6_client(client, "%s contains multiple serverids",
429 dhcp6_message_type_to_string(message->type));
433 r = dhcp6_lease_set_serverid(lease, optval, optlen);
439 case DHCP6_OPTION_PREFERENCE:
443 r = dhcp6_lease_set_preference(lease, *optval);
449 case DHCP6_OPTION_STATUS_CODE:
453 status = optval[0] << 8 | optval[1];
455 log_dhcp6_client(client, "%s Status %s",
456 dhcp6_message_type_to_string(message->type),
457 dhcp6_message_status_to_string(status));
463 case DHCP6_OPTION_IA_NA:
464 r = dhcp6_option_parse_ia(&optval, &optlen, optcode,
466 if (r < 0 && r != -ENOMSG)
469 r = dhcp6_lease_get_iaid(lease, &iaid_lease);
473 if (client->ia_na.id != iaid_lease) {
474 log_dhcp6_client(client, "%s has wrong IAID",
475 dhcp6_message_type_to_string(message->type));
483 if ((r < 0 && r != -ENOMSG) || !clientid) {
484 log_dhcp6_client(client, "%s has incomplete options",
485 dhcp6_message_type_to_string(message->type));
489 r = dhcp6_lease_get_serverid(lease, &id, &id_len);
491 log_dhcp6_client(client, "%s has no server id",
492 dhcp6_message_type_to_string(message->type));
497 static int client_receive_advertise(sd_dhcp6_client *client,
498 DHCP6Message *advertise, size_t len) {
500 _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
501 uint8_t pref_advertise = 0, pref_lease = 0;
503 if (advertise->type != DHCP6_ADVERTISE)
506 r = dhcp6_lease_new(&lease);
510 r = client_parse_message(client, advertise, len, lease);
514 r = dhcp6_lease_get_preference(lease, &pref_advertise);
518 r = dhcp6_lease_get_preference(client->lease, &pref_lease);
519 if (!client->lease || r < 0 || pref_advertise > pref_lease) {
520 sd_dhcp6_lease_unref(client->lease);
521 client->lease = lease;
529 static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
531 sd_dhcp6_client *client = userdata;
532 _cleanup_free_ DHCP6Message *message;
537 assert(client->event);
539 r = ioctl(fd, FIONREAD, &buflen);
540 if (r < 0 || buflen <= 0)
541 buflen = DHCP6_MIN_OPTIONS_SIZE;
543 message = malloc0(buflen);
547 len = read(fd, message, buflen);
548 if ((size_t)len < sizeof(DHCP6Message)) {
549 log_dhcp6_client(client, "could not receive message from UDP socket: %s", strerror(errno));
553 switch(message->type) {
561 case DHCP6_INFORMATION_REQUEST:
562 case DHCP6_RELAY_FORW:
563 case DHCP6_RELAY_REPL:
566 case DHCP6_ADVERTISE:
568 case DHCP6_RECONFIGURE:
572 log_dhcp6_client(client, "unknown message type %d",
577 if (client->transaction_id != (message->transaction_id &
578 htobe32(0x00ffffff)))
581 switch (client->state) {
582 case DHCP6_STATE_SOLICITATION:
583 r = client_receive_advertise(client, message, len);
587 case DHCP6_STATE_STOPPED:
593 log_dhcp6_client(client, "Recv %s",
594 dhcp6_message_type_to_string(message->type));
600 static int client_start(sd_dhcp6_client *client)
604 assert_return(client, -EINVAL);
605 assert_return(client->event, -EINVAL);
606 assert_return(client->index > 0, -EINVAL);
608 r = client_ensure_iaid(client);
612 r = dhcp6_network_bind_udp_socket(client->index, NULL);
618 r = sd_event_add_io(client->event, &client->receive_message,
619 client->fd, EPOLLIN, client_receive_message,
624 r = sd_event_source_set_priority(client->receive_message,
625 client->event_priority);
629 client->state = DHCP6_STATE_SOLICITATION;
631 r = sd_event_add_time(client->event, &client->timeout_resend,
632 CLOCK_MONOTONIC, 0, 0, client_timeout_resend,
637 r = sd_event_source_set_priority(client->timeout_resend,
638 client->event_priority);
645 int sd_dhcp6_client_stop(sd_dhcp6_client *client)
647 client_stop(client, DHCP6_EVENT_STOP);
652 int sd_dhcp6_client_start(sd_dhcp6_client *client)
656 assert_return(client, -EINVAL);
657 assert_return(client->event, -EINVAL);
658 assert_return(client->index > 0, -EINVAL);
660 r = client_initialize(client);
664 return client_start(client);
667 int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
672 assert_return(client, -EINVAL);
673 assert_return(!client->event, -EBUSY);
676 client->event = sd_event_ref(event);
678 r = sd_event_default(&client->event);
683 client->event_priority = priority;
688 int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
689 assert_return(client, -EINVAL);
691 client->event = sd_event_unref(client->event);
696 sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
700 return client->event;
703 sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
705 assert_se(REFCNT_INC(client->n_ref) >= 2);
710 sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
711 if (client && REFCNT_DEC(client->n_ref) <= 0) {
712 client_initialize(client);
714 sd_dhcp6_client_detach_event(client);
724 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
725 #define _cleanup_dhcp6_client_free_ _cleanup_(sd_dhcp6_client_unrefp)
727 int sd_dhcp6_client_new(sd_dhcp6_client **ret)
729 _cleanup_dhcp6_client_free_ sd_dhcp6_client *client = NULL;
730 sd_id128_t machine_id;
733 assert_return(ret, -EINVAL);
735 client = new0(sd_dhcp6_client, 1);
739 client->n_ref = REFCNT_INIT;
741 client->ia_na.type = DHCP6_OPTION_IA_NA;
745 /* initialize DUID */
746 client->duid.type = htobe16(DHCP6_DUID_EN);
747 client->duid.pen = htobe32(SYSTEMD_PEN);
749 r = sd_id128_get_machine(&machine_id);
753 /* a bit of snake-oil perhaps, but no need to expose the machine-id
755 siphash24(client->duid.id, &machine_id, sizeof(machine_id),