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 int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
133 assert_return(client, -EINVAL);
134 assert_return(ret, -EINVAL);
139 *ret = sd_dhcp6_lease_ref(client->lease);
144 static sd_dhcp6_client *client_notify(sd_dhcp6_client *client, int event) {
146 client = sd_dhcp6_client_ref(client);
147 client->cb(client, event, client->userdata);
148 client = sd_dhcp6_client_unref(client);
154 static int client_initialize(sd_dhcp6_client *client)
156 assert_return(client, -EINVAL);
158 client->receive_message =
159 sd_event_source_unref(client->receive_message);
162 client->fd = safe_close(client->fd);
164 client->transaction_id = random_u32() & 0x00ffffff;
166 client->ia_na.timeout_t1 =
167 sd_event_source_unref(client->ia_na.timeout_t1);
168 client->ia_na.timeout_t2 =
169 sd_event_source_unref(client->ia_na.timeout_t2);
171 client->retransmit_time = 0;
172 client->retransmit_count = 0;
173 client->timeout_resend = sd_event_source_unref(client->timeout_resend);
174 client->timeout_resend_expire =
175 sd_event_source_unref(client->timeout_resend_expire);
177 client->state = DHCP6_STATE_STOPPED;
182 static sd_dhcp6_client *client_stop(sd_dhcp6_client *client, int error) {
183 assert_return(client, NULL);
185 client = client_notify(client, error);
187 client_initialize(client);
192 static int client_send_message(sd_dhcp6_client *client) {
193 _cleanup_free_ DHCP6Message *message = NULL;
194 struct in6_addr all_servers =
195 IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
196 size_t len, optlen = 512;
200 len = sizeof(DHCP6Message) + optlen;
202 message = malloc0(len);
206 opt = (uint8_t *)(message + 1);
208 message->transaction_id = client->transaction_id;
210 switch(client->state) {
211 case DHCP6_STATE_SOLICITATION:
212 message->type = DHCP6_SOLICIT;
214 r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID,
215 sizeof(client->duid), &client->duid);
219 r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
225 case DHCP6_STATE_STOPPED:
230 r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
235 log_dhcp6_client(client, "Sent %s",
236 dhcp6_message_type_to_string(message->type));
241 static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
243 sd_dhcp6_client *client = userdata;
247 assert(client->event);
249 client_stop(client, DHCP6_EVENT_RESEND_EXPIRE);
254 static usec_t client_timeout_compute_random(usec_t val) {
255 return val - val / 10 +
256 (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
259 static int client_timeout_resend(sd_event_source *s, uint64_t usec,
262 sd_dhcp6_client *client = userdata;
263 usec_t time_now, init_retransmit_time, max_retransmit_time;
264 usec_t max_retransmit_duration;
265 uint8_t max_retransmit_count;
266 char time_string[FORMAT_TIMESPAN_MAX];
270 assert(client->event);
272 client->timeout_resend = sd_event_source_unref(client->timeout_resend);
274 switch (client->state) {
275 case DHCP6_STATE_SOLICITATION:
276 init_retransmit_time = DHCP6_SOL_TIMEOUT;
277 max_retransmit_time = DHCP6_SOL_MAX_RT;
278 max_retransmit_count = 0;
279 max_retransmit_duration = 0;
283 case DHCP6_STATE_STOPPED:
288 if (max_retransmit_count &&
289 client->retransmit_count >= max_retransmit_count) {
290 client_stop(client, DHCP6_EVENT_RETRANS_MAX);
294 r = client_send_message(client);
296 client->retransmit_count++;
299 r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now);
303 if (!client->retransmit_time) {
304 client->retransmit_time =
305 client_timeout_compute_random(init_retransmit_time);
307 if (client->state == DHCP6_STATE_SOLICITATION)
308 client->retransmit_time += init_retransmit_time / 10;
311 if (max_retransmit_time &&
312 client->retransmit_time > max_retransmit_time / 2)
313 client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
315 client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
318 log_dhcp6_client(client, "Next retransmission in %s",
319 format_timespan(time_string, FORMAT_TIMESPAN_MAX,
320 client->retransmit_time, 0));
322 r = sd_event_add_time(client->event, &client->timeout_resend,
324 time_now + client->retransmit_time,
325 10 * USEC_PER_MSEC, client_timeout_resend,
330 r = sd_event_source_set_priority(client->timeout_resend,
331 client->event_priority);
335 if (max_retransmit_duration && !client->timeout_resend_expire) {
337 log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
338 max_retransmit_duration / USEC_PER_SEC);
340 r = sd_event_add_time(client->event,
341 &client->timeout_resend_expire,
343 time_now + max_retransmit_duration,
345 client_timeout_resend_expire, client);
349 r = sd_event_source_set_priority(client->timeout_resend_expire,
350 client->event_priority);
357 client_stop(client, r);
362 static int client_ensure_iaid(sd_dhcp6_client *client) {
363 const char *name = NULL;
368 if (client->ia_na.id)
371 if (detect_container(NULL) <= 0) {
372 /* not in a container, udev will be around */
373 _cleanup_udev_unref_ struct udev *udev;
374 _cleanup_udev_device_unref_ struct udev_device *device;
375 char ifindex_str[2 + DECIMAL_STR_MAX(int)];
381 sprintf(ifindex_str, "n%d", client->index);
382 device = udev_device_new_from_device_id(udev, ifindex_str);
386 if (udev_device_get_is_initialized(device) <= 0)
390 name = net_get_name(device);
394 siphash24((uint8_t*)&id, name, strlen(name), HASH_KEY.bytes);
396 /* fall back to mac address if no predictable name available */
397 siphash24((uint8_t*)&id, &client->mac_addr, ETH_ALEN,
400 /* fold into 32 bits */
401 client->ia_na.id = (id & 0xffffffff) ^ (id >> 32);
406 static int client_parse_message(sd_dhcp6_client *client,
407 DHCP6Message *message, size_t len,
408 sd_dhcp6_lease *lease) {
410 uint8_t *optval, *option = (uint8_t *)(message + 1), *id = NULL;
411 uint16_t optcode, status;
412 size_t optlen, id_len;
413 bool clientid = false;
416 while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen,
419 case DHCP6_OPTION_CLIENTID:
421 log_dhcp6_client(client, "%s contains multiple clientids",
422 dhcp6_message_type_to_string(message->type));
426 if (optlen != sizeof(client->duid) ||
427 memcmp(&client->duid, optval, optlen) != 0) {
428 log_dhcp6_client(client, "%s DUID does not match",
429 dhcp6_message_type_to_string(message->type));
437 case DHCP6_OPTION_SERVERID:
438 r = dhcp6_lease_get_serverid(lease, &id, &id_len);
440 log_dhcp6_client(client, "%s contains multiple serverids",
441 dhcp6_message_type_to_string(message->type));
445 r = dhcp6_lease_set_serverid(lease, optval, optlen);
451 case DHCP6_OPTION_PREFERENCE:
455 r = dhcp6_lease_set_preference(lease, *optval);
461 case DHCP6_OPTION_STATUS_CODE:
465 status = optval[0] << 8 | optval[1];
467 log_dhcp6_client(client, "%s Status %s",
468 dhcp6_message_type_to_string(message->type),
469 dhcp6_message_status_to_string(status));
475 case DHCP6_OPTION_IA_NA:
476 r = dhcp6_option_parse_ia(&optval, &optlen, optcode,
478 if (r < 0 && r != -ENOMSG)
481 r = dhcp6_lease_get_iaid(lease, &iaid_lease);
485 if (client->ia_na.id != iaid_lease) {
486 log_dhcp6_client(client, "%s has wrong IAID",
487 dhcp6_message_type_to_string(message->type));
495 if ((r < 0 && r != -ENOMSG) || !clientid) {
496 log_dhcp6_client(client, "%s has incomplete options",
497 dhcp6_message_type_to_string(message->type));
501 r = dhcp6_lease_get_serverid(lease, &id, &id_len);
503 log_dhcp6_client(client, "%s has no server id",
504 dhcp6_message_type_to_string(message->type));
509 static int client_receive_advertise(sd_dhcp6_client *client,
510 DHCP6Message *advertise, size_t len) {
512 _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
513 uint8_t pref_advertise = 0, pref_lease = 0;
515 if (advertise->type != DHCP6_ADVERTISE)
518 r = dhcp6_lease_new(&lease);
522 r = client_parse_message(client, advertise, len, lease);
526 r = dhcp6_lease_get_preference(lease, &pref_advertise);
530 r = dhcp6_lease_get_preference(client->lease, &pref_lease);
531 if (!client->lease || r < 0 || pref_advertise > pref_lease) {
532 sd_dhcp6_lease_unref(client->lease);
533 client->lease = lease;
541 static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
543 sd_dhcp6_client *client = userdata;
544 _cleanup_free_ DHCP6Message *message;
549 assert(client->event);
551 r = ioctl(fd, FIONREAD, &buflen);
552 if (r < 0 || buflen <= 0)
553 buflen = DHCP6_MIN_OPTIONS_SIZE;
555 message = malloc0(buflen);
559 len = read(fd, message, buflen);
560 if ((size_t)len < sizeof(DHCP6Message)) {
561 log_dhcp6_client(client, "could not receive message from UDP socket: %s", strerror(errno));
565 switch(message->type) {
573 case DHCP6_INFORMATION_REQUEST:
574 case DHCP6_RELAY_FORW:
575 case DHCP6_RELAY_REPL:
578 case DHCP6_ADVERTISE:
580 case DHCP6_RECONFIGURE:
584 log_dhcp6_client(client, "unknown message type %d",
589 if (client->transaction_id != (message->transaction_id &
590 htobe32(0x00ffffff)))
593 switch (client->state) {
594 case DHCP6_STATE_SOLICITATION:
595 r = client_receive_advertise(client, message, len);
599 case DHCP6_STATE_STOPPED:
605 log_dhcp6_client(client, "Recv %s",
606 dhcp6_message_type_to_string(message->type));
612 static int client_start(sd_dhcp6_client *client)
616 assert_return(client, -EINVAL);
617 assert_return(client->event, -EINVAL);
618 assert_return(client->index > 0, -EINVAL);
620 r = client_ensure_iaid(client);
624 r = dhcp6_network_bind_udp_socket(client->index, NULL);
630 r = sd_event_add_io(client->event, &client->receive_message,
631 client->fd, EPOLLIN, client_receive_message,
636 r = sd_event_source_set_priority(client->receive_message,
637 client->event_priority);
641 client->state = DHCP6_STATE_SOLICITATION;
643 r = sd_event_add_time(client->event, &client->timeout_resend,
644 CLOCK_MONOTONIC, 0, 0, client_timeout_resend,
649 r = sd_event_source_set_priority(client->timeout_resend,
650 client->event_priority);
657 int sd_dhcp6_client_stop(sd_dhcp6_client *client)
659 client_stop(client, DHCP6_EVENT_STOP);
664 int sd_dhcp6_client_start(sd_dhcp6_client *client)
668 assert_return(client, -EINVAL);
669 assert_return(client->event, -EINVAL);
670 assert_return(client->index > 0, -EINVAL);
672 r = client_initialize(client);
676 return client_start(client);
679 int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
684 assert_return(client, -EINVAL);
685 assert_return(!client->event, -EBUSY);
688 client->event = sd_event_ref(event);
690 r = sd_event_default(&client->event);
695 client->event_priority = priority;
700 int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
701 assert_return(client, -EINVAL);
703 client->event = sd_event_unref(client->event);
708 sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
712 return client->event;
715 sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
717 assert_se(REFCNT_INC(client->n_ref) >= 2);
722 sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
723 if (client && REFCNT_DEC(client->n_ref) <= 0) {
724 client_initialize(client);
726 sd_dhcp6_client_detach_event(client);
736 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
737 #define _cleanup_dhcp6_client_free_ _cleanup_(sd_dhcp6_client_unrefp)
739 int sd_dhcp6_client_new(sd_dhcp6_client **ret)
741 _cleanup_dhcp6_client_free_ sd_dhcp6_client *client = NULL;
742 sd_id128_t machine_id;
745 assert_return(ret, -EINVAL);
747 client = new0(sd_dhcp6_client, 1);
751 client->n_ref = REFCNT_INIT;
753 client->ia_na.type = DHCP6_OPTION_IA_NA;
757 /* initialize DUID */
758 client->duid.type = htobe16(DHCP6_DUID_EN);
759 client->duid.pen = htobe32(SYSTEMD_PEN);
761 r = sd_id128_get_machine(&machine_id);
765 /* a bit of snake-oil perhaps, but no need to expose the machine-id
767 siphash24(client->duid.id, &machine_id, sizeof(machine_id),