1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 Copyright © 2014-2015 Intel Corporation. All rights reserved.
8 #include <linux/if_arp.h>
9 #include <linux/if_infiniband.h>
11 #include "sd-dhcp6-client.h"
13 #include "alloc-util.h"
14 #include "device-util.h"
15 #include "dhcp-identifier.h"
16 #include "dhcp6-internal.h"
17 #include "dhcp6-lease-internal.h"
18 #include "dns-domain.h"
19 #include "event-util.h"
21 #include "hexdecoct.h"
22 #include "hostname-util.h"
23 #include "in-addr-util.h"
25 #include "random-util.h"
26 #include "socket-util.h"
27 #include "sort-util.h"
31 #define DHCP6_CLIENT_DONT_DESTROY(client) \
32 _cleanup_(sd_dhcp6_client_unrefp) _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client)
34 static int client_start_transaction(sd_dhcp6_client
*client
, DHCP6State state
);
36 int sd_dhcp6_client_set_callback(
37 sd_dhcp6_client
*client
,
38 sd_dhcp6_client_callback_t cb
,
41 assert_return(client
, -EINVAL
);
43 client
->callback
= cb
;
44 client
->userdata
= userdata
;
49 int sd_dhcp6_client_set_ifindex(sd_dhcp6_client
*client
, int ifindex
) {
50 assert_return(client
, -EINVAL
);
51 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
52 assert_return(ifindex
> 0, -EINVAL
);
54 client
->ifindex
= ifindex
;
58 int sd_dhcp6_client_set_ifname(sd_dhcp6_client
*client
, const char *ifname
) {
59 assert_return(client
, -EINVAL
);
60 assert_return(ifname
, -EINVAL
);
62 if (!ifname_valid_full(ifname
, IFNAME_VALID_ALTERNATIVE
))
65 return free_and_strdup(&client
->ifname
, ifname
);
68 int sd_dhcp6_client_get_ifname(sd_dhcp6_client
*client
, const char **ret
) {
71 assert_return(client
, -EINVAL
);
73 r
= get_ifname(client
->ifindex
, &client
->ifname
);
78 *ret
= client
->ifname
;
83 int sd_dhcp6_client_set_local_address(
84 sd_dhcp6_client
*client
,
85 const struct in6_addr
*local_address
) {
87 assert_return(client
, -EINVAL
);
88 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
89 assert_return(local_address
, -EINVAL
);
90 assert_return(in6_addr_is_link_local(local_address
) > 0, -EINVAL
);
92 client
->local_address
= *local_address
;
97 int sd_dhcp6_client_set_mac(
98 sd_dhcp6_client
*client
,
103 assert_return(client
, -EINVAL
);
104 assert_return(addr
, -EINVAL
);
105 assert_return(addr_len
<= sizeof(client
->hw_addr
.bytes
), -EINVAL
);
107 /* Unlike the other setters, it is OK to set a new MAC address while the client is running,
108 * as the MAC address is used only when setting DUID or IAID. */
110 if (arp_type
== ARPHRD_ETHER
)
111 assert_return(addr_len
== ETH_ALEN
, -EINVAL
);
112 else if (arp_type
== ARPHRD_INFINIBAND
)
113 assert_return(addr_len
== INFINIBAND_ALEN
, -EINVAL
);
115 client
->arp_type
= ARPHRD_NONE
;
116 client
->hw_addr
.length
= 0;
120 client
->arp_type
= arp_type
;
121 hw_addr_set(&client
->hw_addr
, addr
, addr_len
);
126 int sd_dhcp6_client_set_prefix_delegation_hint(
127 sd_dhcp6_client
*client
,
129 const struct in6_addr
*pd_prefix
) {
131 _cleanup_free_ DHCP6Address
*prefix
= NULL
;
133 assert_return(client
, -EINVAL
);
134 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
137 /* clear previous assignments. */
138 dhcp6_ia_clear_addresses(&client
->ia_pd
);
142 assert_return(prefixlen
> 0 && prefixlen
<= 128, -EINVAL
);
144 prefix
= new(DHCP6Address
, 1);
148 *prefix
= (DHCP6Address
) {
149 .iapdprefix
.address
= *pd_prefix
,
150 .iapdprefix
.prefixlen
= prefixlen
,
153 LIST_PREPEND(addresses
, client
->ia_pd
.addresses
, TAKE_PTR(prefix
));
157 int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client
*client
, sd_dhcp6_option
*v
) {
160 assert_return(client
, -EINVAL
);
161 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
164 /* Clear the previous assignments. */
165 ordered_set_clear(client
->vendor_options
);
169 r
= ordered_set_ensure_put(&client
->vendor_options
, &dhcp6_option_hash_ops
, v
);
173 sd_dhcp6_option_ref(v
);
178 static int client_ensure_duid(sd_dhcp6_client
*client
) {
181 if (client
->duid_len
!= 0)
184 return dhcp_identifier_set_duid_en(client
->test_mode
, &client
->duid
, &client
->duid_len
);
188 * Sets DUID. If duid is non-null, the DUID is set to duid_type + duid
189 * without further modification. Otherwise, if duid_type is supported, DUID
190 * is set based on that type. Otherwise, an error is returned.
192 static int dhcp6_client_set_duid_internal(
193 sd_dhcp6_client
*client
,
200 assert_return(client
, -EINVAL
);
201 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
202 assert_return(duid_len
== 0 || duid
, -EINVAL
);
205 r
= dhcp_validate_duid_len(duid_type
, duid_len
, true);
207 r
= dhcp_validate_duid_len(duid_type
, duid_len
, false);
209 return log_dhcp6_client_errno(client
, r
, "Failed to validate length of DUID: %m");
211 log_dhcp6_client(client
, "Using DUID of type %i of incorrect length, proceeding.", duid_type
);
214 client
->duid
.type
= htobe16(duid_type
);
215 memcpy(&client
->duid
.raw
.data
, duid
, duid_len
);
216 client
->duid_len
= sizeof(client
->duid
.type
) + duid_len
;
219 r
= dhcp_identifier_set_duid(duid_type
, &client
->hw_addr
, client
->arp_type
, llt_time
,
220 client
->test_mode
, &client
->duid
, &client
->duid_len
);
221 if (r
== -EOPNOTSUPP
)
222 return log_dhcp6_client_errno(client
, r
,
223 "Failed to set %s. MAC address is not set or "
224 "interface type is not supported.",
225 duid_type_to_string(duid_type
));
227 return log_dhcp6_client_errno(client
, r
, "Failed to set %s: %m",
228 duid_type_to_string(duid_type
));
234 int sd_dhcp6_client_set_duid(
235 sd_dhcp6_client
*client
,
239 return dhcp6_client_set_duid_internal(client
, duid_type
, duid
, duid_len
, 0);
242 int sd_dhcp6_client_set_duid_llt(
243 sd_dhcp6_client
*client
,
245 return dhcp6_client_set_duid_internal(client
, DUID_TYPE_LLT
, NULL
, 0, llt_time
);
248 int sd_dhcp6_client_duid_as_string(
249 sd_dhcp6_client
*client
,
251 _cleanup_free_
char *p
= NULL
, *s
= NULL
, *t
= NULL
;
255 assert_return(client
, -EINVAL
);
256 assert_return(client
->duid_len
> 0, -ENODATA
);
257 assert_return(duid
, -EINVAL
);
259 v
= duid_type_to_string(be16toh(client
->duid
.type
));
265 r
= asprintf(&s
, "%0x", client
->duid
.type
);
270 t
= hexmem(&client
->duid
.raw
.data
, client
->duid_len
);
274 p
= strjoin(s
, ":", t
);
283 int sd_dhcp6_client_set_iaid(sd_dhcp6_client
*client
, uint32_t iaid
) {
284 assert_return(client
, -EINVAL
);
285 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
287 client
->ia_na
.header
.id
= htobe32(iaid
);
288 client
->ia_pd
.header
.id
= htobe32(iaid
);
289 client
->iaid_set
= true;
294 static int client_ensure_iaid(sd_dhcp6_client
*client
) {
300 if (client
->iaid_set
)
303 r
= dhcp_identifier_set_iaid(client
->dev
, &client
->hw_addr
,
304 /* legacy_unstable_byteorder = */ true,
309 client
->ia_na
.header
.id
= iaid
;
310 client
->ia_pd
.header
.id
= iaid
;
311 client
->iaid_set
= true;
316 int sd_dhcp6_client_get_iaid(sd_dhcp6_client
*client
, uint32_t *iaid
) {
317 assert_return(client
, -EINVAL
);
318 assert_return(iaid
, -EINVAL
);
320 if (!client
->iaid_set
)
323 *iaid
= be32toh(client
->ia_na
.header
.id
);
328 void dhcp6_client_set_test_mode(sd_dhcp6_client
*client
, bool test_mode
) {
331 client
->test_mode
= test_mode
;
334 int sd_dhcp6_client_set_fqdn(
335 sd_dhcp6_client
*client
,
338 assert_return(client
, -EINVAL
);
339 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
341 /* Make sure FQDN qualifies as DNS and as Linux hostname */
343 !(hostname_is_valid(fqdn
, 0) && dns_name_is_valid(fqdn
) > 0))
346 return free_and_strdup(&client
->fqdn
, fqdn
);
349 int sd_dhcp6_client_set_information_request(sd_dhcp6_client
*client
, int enabled
) {
350 assert_return(client
, -EINVAL
);
351 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
353 client
->information_request
= enabled
;
358 int sd_dhcp6_client_get_information_request(sd_dhcp6_client
*client
, int *enabled
) {
359 assert_return(client
, -EINVAL
);
360 assert_return(enabled
, -EINVAL
);
362 *enabled
= client
->information_request
;
367 static int be16_compare_func(const be16_t
*a
, const be16_t
*b
) {
368 return CMP(be16toh(*a
), be16toh(*b
));
371 int sd_dhcp6_client_set_request_option(sd_dhcp6_client
*client
, uint16_t option
) {
374 assert_return(client
, -EINVAL
);
375 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
377 if (!dhcp6_option_can_request(option
))
380 opt
= htobe16(option
);
381 if (typesafe_bsearch(&opt
, client
->req_opts
, client
->n_req_opts
, be16_compare_func
))
384 if (!GREEDY_REALLOC(client
->req_opts
, client
->n_req_opts
+ 1))
387 client
->req_opts
[client
->n_req_opts
++] = opt
;
389 /* Sort immediately to make the above binary search will work for the next time. */
390 typesafe_qsort(client
->req_opts
, client
->n_req_opts
, be16_compare_func
);
394 int sd_dhcp6_client_set_request_mud_url(sd_dhcp6_client
*client
, const char *mudurl
) {
395 assert_return(client
, -EINVAL
);
396 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
397 assert_return(mudurl
, -EINVAL
);
398 assert_return(strlen(mudurl
) <= UINT8_MAX
, -EINVAL
);
399 assert_return(http_url_is_valid(mudurl
), -EINVAL
);
401 return free_and_strdup(&client
->mudurl
, mudurl
);
404 int sd_dhcp6_client_set_request_user_class(sd_dhcp6_client
*client
, char * const *user_class
) {
407 assert_return(client
, -EINVAL
);
408 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
409 assert_return(!strv_isempty(user_class
), -EINVAL
);
411 STRV_FOREACH(p
, user_class
) {
412 size_t len
= strlen(*p
);
414 if (len
> UINT16_MAX
|| len
== 0)
418 s
= strv_copy(user_class
);
422 return strv_free_and_replace(client
->user_class
, s
);
425 int sd_dhcp6_client_set_request_vendor_class(sd_dhcp6_client
*client
, char * const *vendor_class
) {
428 assert_return(client
, -EINVAL
);
429 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
430 assert_return(!strv_isempty(vendor_class
), -EINVAL
);
432 STRV_FOREACH(p
, vendor_class
) {
433 size_t len
= strlen(*p
);
435 if (len
> UINT16_MAX
|| len
== 0)
439 s
= strv_copy(vendor_class
);
443 return strv_free_and_replace(client
->vendor_class
, s
);
446 int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client
*client
, int *delegation
) {
447 assert_return(client
, -EINVAL
);
448 assert_return(delegation
, -EINVAL
);
450 *delegation
= FLAGS_SET(client
->request_ia
, DHCP6_REQUEST_IA_PD
);
455 int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client
*client
, int delegation
) {
456 assert_return(client
, -EINVAL
);
457 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
459 SET_FLAG(client
->request_ia
, DHCP6_REQUEST_IA_PD
, delegation
);
464 int sd_dhcp6_client_get_address_request(sd_dhcp6_client
*client
, int *request
) {
465 assert_return(client
, -EINVAL
);
466 assert_return(request
, -EINVAL
);
468 *request
= FLAGS_SET(client
->request_ia
, DHCP6_REQUEST_IA_NA
);
473 int sd_dhcp6_client_set_address_request(sd_dhcp6_client
*client
, int request
) {
474 assert_return(client
, -EINVAL
);
475 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
477 SET_FLAG(client
->request_ia
, DHCP6_REQUEST_IA_NA
, request
);
482 int dhcp6_client_set_transaction_id(sd_dhcp6_client
*client
, uint32_t transaction_id
) {
484 assert(client
->test_mode
);
486 /* This is for tests or fuzzers. */
488 client
->transaction_id
= transaction_id
& htobe32(0x00ffffff);
493 int sd_dhcp6_client_set_rapid_commit(sd_dhcp6_client
*client
, int enable
) {
494 assert_return(client
, -EINVAL
);
495 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
497 client
->rapid_commit
= enable
;
501 int sd_dhcp6_client_get_lease(sd_dhcp6_client
*client
, sd_dhcp6_lease
**ret
) {
502 assert_return(client
, -EINVAL
);
508 *ret
= client
->lease
;
513 int sd_dhcp6_client_add_option(sd_dhcp6_client
*client
, sd_dhcp6_option
*v
) {
516 assert_return(client
, -EINVAL
);
517 assert_return(v
, -EINVAL
);
519 r
= ordered_hashmap_ensure_put(&client
->extra_options
, &dhcp6_option_hash_ops
, UINT_TO_PTR(v
->option
), v
);
523 sd_dhcp6_option_ref(v
);
527 static void client_set_state(sd_dhcp6_client
*client
, DHCP6State state
) {
530 if (client
->state
== state
)
533 log_dhcp6_client(client
, "State changed: %s -> %s",
534 dhcp6_state_to_string(client
->state
), dhcp6_state_to_string(state
));
536 client
->state
= state
;
539 static void client_notify(sd_dhcp6_client
*client
, int event
) {
542 if (client
->callback
)
543 client
->callback(client
, event
, client
->userdata
);
546 static void client_cleanup(sd_dhcp6_client
*client
) {
549 client
->lease
= sd_dhcp6_lease_unref(client
->lease
);
551 /* Reset IRT here. Otherwise, we cannot restart the client in the information requesting mode,
552 * even though the lease is freed below. */
553 client
->information_request_time_usec
= 0;
554 client
->information_refresh_time_usec
= 0;
556 (void) event_source_disable(client
->receive_message
);
557 (void) event_source_disable(client
->timeout_resend
);
558 (void) event_source_disable(client
->timeout_expire
);
559 (void) event_source_disable(client
->timeout_t1
);
560 (void) event_source_disable(client
->timeout_t2
);
562 client_set_state(client
, DHCP6_STATE_STOPPED
);
565 static void client_stop(sd_dhcp6_client
*client
, int error
) {
566 DHCP6_CLIENT_DONT_DESTROY(client
);
570 client_notify(client
, error
);
572 client_cleanup(client
);
575 static int client_append_common_options_in_managed_mode(
576 sd_dhcp6_client
*client
,
579 const DHCP6IA
*ia_na
,
580 const DHCP6IA
*ia_pd
) {
585 assert(IN_SET(client
->state
,
586 DHCP6_STATE_SOLICITATION
,
589 DHCP6_STATE_REBIND
));
594 if (FLAGS_SET(client
->request_ia
, DHCP6_REQUEST_IA_NA
) && ia_na
) {
595 r
= dhcp6_option_append_ia(buf
, offset
, ia_na
);
600 if (FLAGS_SET(client
->request_ia
, DHCP6_REQUEST_IA_PD
) && ia_pd
) {
601 r
= dhcp6_option_append_ia(buf
, offset
, ia_pd
);
606 r
= dhcp6_option_append_fqdn(buf
, offset
, client
->fqdn
);
610 r
= dhcp6_option_append_user_class(buf
, offset
, client
->user_class
);
614 r
= dhcp6_option_append_vendor_class(buf
, offset
, client
->vendor_class
);
618 r
= dhcp6_option_append_vendor_option(buf
, offset
, client
->vendor_options
);
625 static DHCP6MessageType
client_message_type_from_state(sd_dhcp6_client
*client
) {
628 switch (client
->state
) {
629 case DHCP6_STATE_INFORMATION_REQUEST
:
630 return DHCP6_MESSAGE_INFORMATION_REQUEST
;
631 case DHCP6_STATE_SOLICITATION
:
632 return DHCP6_MESSAGE_SOLICIT
;
633 case DHCP6_STATE_REQUEST
:
634 return DHCP6_MESSAGE_REQUEST
;
635 case DHCP6_STATE_RENEW
:
636 return DHCP6_MESSAGE_RENEW
;
637 case DHCP6_STATE_REBIND
:
638 return DHCP6_MESSAGE_REBIND
;
640 assert_not_reached();
644 static int client_append_oro(sd_dhcp6_client
*client
, uint8_t **buf
, size_t *offset
) {
645 _cleanup_free_ be16_t
*p
= NULL
;
654 switch (client
->state
) {
655 case DHCP6_STATE_INFORMATION_REQUEST
:
656 n
= client
->n_req_opts
;
657 p
= new(be16_t
, n
+ 2);
661 memcpy_safe(p
, client
->req_opts
, n
* sizeof(be16_t
));
662 p
[n
++] = htobe16(SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME
); /* RFC 8415 section 21.23 */
663 p
[n
++] = htobe16(SD_DHCP6_OPTION_INF_MAX_RT
); /* RFC 8415 section 21.25 */
665 typesafe_qsort(p
, n
, be16_compare_func
);
669 case DHCP6_STATE_SOLICITATION
:
670 n
= client
->n_req_opts
;
671 p
= new(be16_t
, n
+ 1);
675 memcpy_safe(p
, client
->req_opts
, n
* sizeof(be16_t
));
676 p
[n
++] = htobe16(SD_DHCP6_OPTION_SOL_MAX_RT
); /* RFC 8415 section 21.24 */
678 typesafe_qsort(p
, n
, be16_compare_func
);
683 n
= client
->n_req_opts
;
684 req_opts
= client
->req_opts
;
690 return dhcp6_option_append(buf
, offset
, SD_DHCP6_OPTION_ORO
, n
* sizeof(be16_t
), req_opts
);
693 int dhcp6_client_send_message(sd_dhcp6_client
*client
) {
694 _cleanup_free_
uint8_t *buf
= NULL
;
695 struct in6_addr all_servers
=
696 IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT
;
697 struct sd_dhcp6_option
*j
;
698 usec_t elapsed_usec
, time_now
;
700 DHCP6Message
*message
;
705 assert(client
->event
);
707 r
= sd_event_now(client
->event
, CLOCK_BOOTTIME
, &time_now
);
711 if (!GREEDY_REALLOC0(buf
, offsetof(DHCP6Message
, options
)))
714 message
= (DHCP6Message
*) buf
;
715 message
->transaction_id
= client
->transaction_id
;
716 message
->type
= client_message_type_from_state(client
);
717 offset
= offsetof(DHCP6Message
, options
);
719 switch (client
->state
) {
720 case DHCP6_STATE_INFORMATION_REQUEST
:
723 case DHCP6_STATE_SOLICITATION
:
724 if (client
->rapid_commit
) {
725 r
= dhcp6_option_append(&buf
, &offset
, SD_DHCP6_OPTION_RAPID_COMMIT
, 0, NULL
);
730 r
= client_append_common_options_in_managed_mode(client
, &buf
, &offset
,
731 &client
->ia_na
, &client
->ia_pd
);
736 case DHCP6_STATE_REQUEST
:
737 case DHCP6_STATE_RENEW
:
739 r
= dhcp6_option_append(&buf
, &offset
, SD_DHCP6_OPTION_SERVERID
,
740 client
->lease
->serverid_len
,
741 client
->lease
->serverid
);
746 case DHCP6_STATE_REBIND
:
748 assert(client
->lease
);
750 r
= client_append_common_options_in_managed_mode(client
, &buf
, &offset
,
751 client
->lease
->ia_na
, client
->lease
->ia_pd
);
756 case DHCP6_STATE_STOPPED
:
757 case DHCP6_STATE_BOUND
:
759 assert_not_reached();
762 if (client
->mudurl
) {
763 r
= dhcp6_option_append(&buf
, &offset
, SD_DHCP6_OPTION_MUD_URL_V6
,
764 strlen(client
->mudurl
), client
->mudurl
);
769 r
= client_append_oro(client
, &buf
, &offset
);
773 assert(client
->duid_len
> 0);
774 r
= dhcp6_option_append(&buf
, &offset
, SD_DHCP6_OPTION_CLIENTID
,
775 client
->duid_len
, &client
->duid
);
779 ORDERED_HASHMAP_FOREACH(j
, client
->extra_options
) {
780 r
= dhcp6_option_append(&buf
, &offset
, j
->option
, j
->length
, j
->data
);
785 /* RFC 8415 Section 21.9.
786 * A client MUST include an Elapsed Time option in messages to indicate how long the client has
787 * been trying to complete a DHCP message exchange. */
788 elapsed_usec
= MIN(usec_sub_unsigned(time_now
, client
->transaction_start
) / USEC_PER_MSEC
/ 10, (usec_t
) UINT16_MAX
);
789 elapsed_time
= htobe16(elapsed_usec
);
790 r
= dhcp6_option_append(&buf
, &offset
, SD_DHCP6_OPTION_ELAPSED_TIME
, sizeof(elapsed_time
), &elapsed_time
);
794 r
= dhcp6_network_send_udp_socket(client
->fd
, &all_servers
, buf
, offset
);
798 log_dhcp6_client(client
, "Sent %s",
799 dhcp6_message_type_to_string(client_message_type_from_state(client
)));
803 static usec_t
client_timeout_compute_random(usec_t val
) {
804 return usec_sub_unsigned(val
, random_u64_range(val
/ 10));
807 static int client_timeout_resend(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
808 sd_dhcp6_client
*client
= ASSERT_PTR(userdata
);
809 usec_t init_retransmit_time
, max_retransmit_time
;
812 assert(client
->event
);
814 switch (client
->state
) {
815 case DHCP6_STATE_INFORMATION_REQUEST
:
816 init_retransmit_time
= DHCP6_INF_TIMEOUT
;
817 max_retransmit_time
= DHCP6_INF_MAX_RT
;
820 case DHCP6_STATE_SOLICITATION
:
822 if (client
->retransmit_count
> 0 && client
->lease
) {
823 (void) client_start_transaction(client
, DHCP6_STATE_REQUEST
);
827 init_retransmit_time
= DHCP6_SOL_TIMEOUT
;
828 max_retransmit_time
= DHCP6_SOL_MAX_RT
;
831 case DHCP6_STATE_REQUEST
:
833 if (client
->retransmit_count
>= DHCP6_REQ_MAX_RC
) {
834 client_stop(client
, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX
);
838 init_retransmit_time
= DHCP6_REQ_TIMEOUT
;
839 max_retransmit_time
= DHCP6_REQ_MAX_RT
;
842 case DHCP6_STATE_RENEW
:
843 init_retransmit_time
= DHCP6_REN_TIMEOUT
;
844 max_retransmit_time
= DHCP6_REN_MAX_RT
;
846 /* RFC 3315, section 18.1.3. says max retransmit duration will
847 be the remaining time until T2. Instead of setting MRD,
848 wait for T2 to trigger with the same end result */
851 case DHCP6_STATE_REBIND
:
852 init_retransmit_time
= DHCP6_REB_TIMEOUT
;
853 max_retransmit_time
= DHCP6_REB_MAX_RT
;
855 /* Also, instead of setting MRD, the expire timer is already set in client_enter_bound_state(). */
858 case DHCP6_STATE_STOPPED
:
859 case DHCP6_STATE_BOUND
:
861 assert_not_reached();
864 r
= dhcp6_client_send_message(client
);
866 client
->retransmit_count
++;
868 if (client
->retransmit_time
== 0) {
869 client
->retransmit_time
= client_timeout_compute_random(init_retransmit_time
);
871 if (client
->state
== DHCP6_STATE_SOLICITATION
)
872 client
->retransmit_time
+= init_retransmit_time
/ 10;
874 } else if (client
->retransmit_time
> max_retransmit_time
/ 2)
875 client
->retransmit_time
= client_timeout_compute_random(max_retransmit_time
);
877 client
->retransmit_time
+= client_timeout_compute_random(client
->retransmit_time
);
879 log_dhcp6_client(client
, "Next retransmission in %s",
880 FORMAT_TIMESPAN(client
->retransmit_time
, USEC_PER_SEC
));
882 r
= event_reset_time_relative(client
->event
, &client
->timeout_resend
,
884 client
->retransmit_time
, 10 * USEC_PER_MSEC
,
885 client_timeout_resend
, client
,
886 client
->event_priority
, "dhcp6-resend-timer", true);
888 client_stop(client
, r
);
893 static int client_start_transaction(sd_dhcp6_client
*client
, DHCP6State state
) {
897 assert(client
->event
);
900 case DHCP6_STATE_INFORMATION_REQUEST
:
901 case DHCP6_STATE_SOLICITATION
:
902 assert(client
->state
== DHCP6_STATE_STOPPED
);
904 case DHCP6_STATE_REQUEST
:
905 assert(client
->state
== DHCP6_STATE_SOLICITATION
);
907 case DHCP6_STATE_RENEW
:
908 assert(client
->state
== DHCP6_STATE_BOUND
);
910 case DHCP6_STATE_REBIND
:
911 assert(IN_SET(client
->state
, DHCP6_STATE_BOUND
, DHCP6_STATE_RENEW
));
913 case DHCP6_STATE_STOPPED
:
914 case DHCP6_STATE_BOUND
:
916 assert_not_reached();
919 client_set_state(client
, state
);
921 client
->retransmit_time
= 0;
922 client
->retransmit_count
= 0;
923 client
->transaction_id
= random_u32() & htobe32(0x00ffffff);
925 r
= sd_event_now(client
->event
, CLOCK_BOOTTIME
, &client
->transaction_start
);
929 r
= event_reset_time(client
->event
, &client
->timeout_resend
,
932 client_timeout_resend
, client
,
933 client
->event_priority
, "dhcp6-resend-timeout", true);
937 r
= sd_event_source_set_enabled(client
->receive_message
, SD_EVENT_ON
);
944 client_stop(client
, r
);
948 static int client_timeout_expire(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
949 sd_dhcp6_client
*client
= ASSERT_PTR(userdata
);
950 DHCP6_CLIENT_DONT_DESTROY(client
);
953 (void) event_source_disable(client
->timeout_expire
);
954 (void) event_source_disable(client
->timeout_t2
);
955 (void) event_source_disable(client
->timeout_t1
);
957 state
= client
->state
;
959 client_stop(client
, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE
);
961 /* RFC 3315, section 18.1.4., says that "...the client may choose to
962 use a Solicit message to locate a new DHCP server..." */
963 if (state
== DHCP6_STATE_REBIND
)
964 (void) client_start_transaction(client
, DHCP6_STATE_SOLICITATION
);
969 static int client_timeout_t2(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
970 sd_dhcp6_client
*client
= ASSERT_PTR(userdata
);
972 (void) event_source_disable(client
->timeout_t2
);
973 (void) event_source_disable(client
->timeout_t1
);
975 log_dhcp6_client(client
, "Timeout T2");
977 (void) client_start_transaction(client
, DHCP6_STATE_REBIND
);
982 static int client_timeout_t1(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
983 sd_dhcp6_client
*client
= ASSERT_PTR(userdata
);
985 (void) event_source_disable(client
->timeout_t1
);
987 log_dhcp6_client(client
, "Timeout T1");
989 (void) client_start_transaction(client
, DHCP6_STATE_RENEW
);
994 static int client_enter_bound_state(sd_dhcp6_client
*client
) {
995 usec_t lifetime_t1
, lifetime_t2
, lifetime_valid
;
999 assert(client
->lease
);
1000 assert(IN_SET(client
->state
,
1001 DHCP6_STATE_SOLICITATION
,
1002 DHCP6_STATE_REQUEST
,
1004 DHCP6_STATE_REBIND
));
1006 (void) event_source_disable(client
->receive_message
);
1007 (void) event_source_disable(client
->timeout_resend
);
1009 r
= dhcp6_lease_get_lifetime(client
->lease
, &lifetime_t1
, &lifetime_t2
, &lifetime_valid
);
1013 lifetime_t2
= client_timeout_compute_random(lifetime_t2
);
1014 lifetime_t1
= client_timeout_compute_random(MIN(lifetime_t1
, lifetime_t2
));
1016 if (lifetime_t1
== USEC_INFINITY
) {
1017 log_dhcp6_client(client
, "Infinite T1");
1018 event_source_disable(client
->timeout_t1
);
1020 log_dhcp6_client(client
, "T1 expires in %s", FORMAT_TIMESPAN(lifetime_t1
, USEC_PER_SEC
));
1021 r
= event_reset_time_relative(client
->event
, &client
->timeout_t1
,
1023 lifetime_t1
, 10 * USEC_PER_SEC
,
1024 client_timeout_t1
, client
,
1025 client
->event_priority
, "dhcp6-t1-timeout", true);
1030 if (lifetime_t2
== USEC_INFINITY
) {
1031 log_dhcp6_client(client
, "Infinite T2");
1032 event_source_disable(client
->timeout_t2
);
1034 log_dhcp6_client(client
, "T2 expires in %s", FORMAT_TIMESPAN(lifetime_t2
, USEC_PER_SEC
));
1035 r
= event_reset_time_relative(client
->event
, &client
->timeout_t2
,
1037 lifetime_t2
, 10 * USEC_PER_SEC
,
1038 client_timeout_t2
, client
,
1039 client
->event_priority
, "dhcp6-t2-timeout", true);
1044 if (lifetime_valid
== USEC_INFINITY
) {
1045 log_dhcp6_client(client
, "Infinite valid lifetime");
1046 event_source_disable(client
->timeout_expire
);
1048 log_dhcp6_client(client
, "Valid lifetime expires in %s", FORMAT_TIMESPAN(lifetime_valid
, USEC_PER_SEC
));
1050 r
= event_reset_time_relative(client
->event
, &client
->timeout_expire
,
1052 lifetime_valid
, USEC_PER_SEC
,
1053 client_timeout_expire
, client
,
1054 client
->event_priority
, "dhcp6-lease-expire", true);
1059 client_set_state(client
, DHCP6_STATE_BOUND
);
1060 client_notify(client
, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE
);
1064 client_stop(client
, r
);
1068 static int log_invalid_message_type(sd_dhcp6_client
*client
, const DHCP6Message
*message
) {
1069 const char *type_str
;
1074 type_str
= dhcp6_message_type_to_string(message
->type
);
1076 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
1077 "Received unexpected %s message, ignoring.", type_str
);
1079 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
1080 "Received unsupported message type %u, ignoring.", message
->type
);
1083 static int client_process_information(
1084 sd_dhcp6_client
*client
,
1085 DHCP6Message
*message
,
1087 const triple_timestamp
*timestamp
,
1088 const struct in6_addr
*server_address
) {
1090 _cleanup_(sd_dhcp6_lease_unrefp
) sd_dhcp6_lease
*lease
= NULL
;
1096 if (message
->type
!= DHCP6_MESSAGE_REPLY
)
1097 return log_invalid_message_type(client
, message
);
1099 r
= dhcp6_lease_new_from_message(client
, message
, len
, timestamp
, server_address
, &lease
);
1101 return log_dhcp6_client_errno(client
, r
, "Failed to process received reply message, ignoring: %m");
1103 log_dhcp6_client(client
, "Processed %s message", dhcp6_message_type_to_string(message
->type
));
1105 sd_dhcp6_lease_unref(client
->lease
);
1106 client
->lease
= TAKE_PTR(lease
);
1108 /* Do not call client_stop() here, as it frees the acquired lease. */
1109 (void) event_source_disable(client
->receive_message
);
1110 (void) event_source_disable(client
->timeout_resend
);
1111 client_set_state(client
, DHCP6_STATE_STOPPED
);
1113 client_notify(client
, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST
);
1117 static int client_process_reply(
1118 sd_dhcp6_client
*client
,
1119 DHCP6Message
*message
,
1121 const triple_timestamp
*timestamp
,
1122 const struct in6_addr
*server_address
) {
1124 _cleanup_(sd_dhcp6_lease_unrefp
) sd_dhcp6_lease
*lease
= NULL
;
1130 if (message
->type
!= DHCP6_MESSAGE_REPLY
)
1131 return log_invalid_message_type(client
, message
);
1133 r
= dhcp6_lease_new_from_message(client
, message
, len
, timestamp
, server_address
, &lease
);
1134 if (r
== -EADDRNOTAVAIL
) {
1136 /* If NoBinding status code is received, we cannot request the address anymore.
1137 * Let's restart transaction from the beginning. */
1139 if (client
->state
== DHCP6_STATE_REQUEST
)
1140 /* The lease is not acquired yet, hence it is not necessary to notify the restart. */
1141 client_cleanup(client
);
1143 /* We need to notify the previous lease was expired. */
1144 client_stop(client
, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE
);
1146 return client_start_transaction(client
, DHCP6_STATE_SOLICITATION
);
1149 return log_dhcp6_client_errno(client
, r
, "Failed to process received reply message, ignoring: %m");
1151 log_dhcp6_client(client
, "Processed %s message", dhcp6_message_type_to_string(message
->type
));
1153 sd_dhcp6_lease_unref(client
->lease
);
1154 client
->lease
= TAKE_PTR(lease
);
1156 return client_enter_bound_state(client
);
1159 static int client_process_advertise_or_rapid_commit_reply(
1160 sd_dhcp6_client
*client
,
1161 DHCP6Message
*message
,
1163 const triple_timestamp
*timestamp
,
1164 const struct in6_addr
*server_address
) {
1166 _cleanup_(sd_dhcp6_lease_unrefp
) sd_dhcp6_lease
*lease
= NULL
;
1167 uint8_t pref_advertise
, pref_lease
= 0;
1173 if (!IN_SET(message
->type
, DHCP6_MESSAGE_ADVERTISE
, DHCP6_MESSAGE_REPLY
))
1174 return log_invalid_message_type(client
, message
);
1176 r
= dhcp6_lease_new_from_message(client
, message
, len
, timestamp
, server_address
, &lease
);
1178 return log_dhcp6_client_errno(client
, r
, "Failed to process received %s message, ignoring: %m",
1179 dhcp6_message_type_to_string(message
->type
));
1181 if (message
->type
== DHCP6_MESSAGE_REPLY
) {
1184 if (!client
->rapid_commit
)
1185 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
1186 "Received unexpected reply message, even we sent a solicit message without the rapid commit option, ignoring.");
1188 r
= dhcp6_lease_get_rapid_commit(lease
, &rapid_commit
);
1193 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
1194 "Received reply message without rapid commit flag, ignoring.");
1196 log_dhcp6_client(client
, "Processed %s message", dhcp6_message_type_to_string(message
->type
));
1198 sd_dhcp6_lease_unref(client
->lease
);
1199 client
->lease
= TAKE_PTR(lease
);
1201 return client_enter_bound_state(client
);
1204 r
= dhcp6_lease_get_preference(lease
, &pref_advertise
);
1208 if (client
->lease
) {
1209 r
= dhcp6_lease_get_preference(client
->lease
, &pref_lease
);
1214 log_dhcp6_client(client
, "Processed %s message", dhcp6_message_type_to_string(message
->type
));
1216 if (!client
->lease
|| pref_advertise
> pref_lease
) {
1217 /* If this is the first advertise message or has higher preference, then save the lease. */
1218 sd_dhcp6_lease_unref(client
->lease
);
1219 client
->lease
= TAKE_PTR(lease
);
1222 if (pref_advertise
== 255 || client
->retransmit_count
> 1)
1223 (void) client_start_transaction(client
, DHCP6_STATE_REQUEST
);
1228 static int client_receive_message(
1234 sd_dhcp6_client
*client
= ASSERT_PTR(userdata
);
1235 DHCP6_CLIENT_DONT_DESTROY(client
);
1236 /* This needs to be initialized with zero. See #20741. */
1237 CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL
) control
= {};
1239 union sockaddr_union sa
= {};
1240 struct msghdr msg
= {
1242 .msg_namelen
= sizeof(sa
),
1245 .msg_control
= &control
,
1246 .msg_controllen
= sizeof(control
),
1248 struct cmsghdr
*cmsg
;
1249 triple_timestamp t
= {};
1250 _cleanup_free_ DHCP6Message
*message
= NULL
;
1251 struct in6_addr
*server_address
= NULL
;
1252 ssize_t buflen
, len
;
1254 buflen
= next_datagram_size_fd(fd
);
1256 if (ERRNO_IS_TRANSIENT(buflen
) || ERRNO_IS_DISCONNECT(buflen
))
1259 log_dhcp6_client_errno(client
, buflen
, "Failed to determine datagram size to read, ignoring: %m");
1263 message
= malloc(buflen
);
1267 iov
= IOVEC_MAKE(message
, buflen
);
1269 len
= recvmsg_safe(fd
, &msg
, MSG_DONTWAIT
);
1271 if (ERRNO_IS_TRANSIENT(len
) || ERRNO_IS_DISCONNECT(len
))
1274 log_dhcp6_client_errno(client
, len
, "Could not receive message from UDP socket, ignoring: %m");
1277 if ((size_t) len
< sizeof(DHCP6Message
)) {
1278 log_dhcp6_client(client
, "Too small to be DHCP6 message: ignoring");
1282 /* msg_namelen == 0 happens when running the test-suite over a socketpair */
1283 if (msg
.msg_namelen
> 0) {
1284 if (msg
.msg_namelen
!= sizeof(struct sockaddr_in6
) || sa
.in6
.sin6_family
!= AF_INET6
) {
1285 log_dhcp6_client(client
, "Received message from invalid source, ignoring.");
1289 server_address
= &sa
.in6
.sin6_addr
;
1292 CMSG_FOREACH(cmsg
, &msg
) {
1293 if (cmsg
->cmsg_level
== SOL_SOCKET
&&
1294 cmsg
->cmsg_type
== SO_TIMESTAMP
&&
1295 cmsg
->cmsg_len
== CMSG_LEN(sizeof(struct timeval
)))
1296 triple_timestamp_from_realtime(&t
, timeval_load(CMSG_TYPED_DATA(cmsg
, struct timeval
)));
1299 if (client
->transaction_id
!= (message
->transaction_id
& htobe32(0x00ffffff)))
1302 switch (client
->state
) {
1303 case DHCP6_STATE_INFORMATION_REQUEST
:
1304 if (client_process_information(client
, message
, len
, &t
, server_address
) < 0)
1308 case DHCP6_STATE_SOLICITATION
:
1309 if (client_process_advertise_or_rapid_commit_reply(client
, message
, len
, &t
, server_address
) < 0)
1313 case DHCP6_STATE_REQUEST
:
1314 case DHCP6_STATE_RENEW
:
1315 case DHCP6_STATE_REBIND
:
1316 if (client_process_reply(client
, message
, len
, &t
, server_address
) < 0)
1320 case DHCP6_STATE_BOUND
:
1321 case DHCP6_STATE_STOPPED
:
1323 assert_not_reached();
1329 int sd_dhcp6_client_stop(sd_dhcp6_client
*client
) {
1333 client_stop(client
, SD_DHCP6_CLIENT_EVENT_STOP
);
1335 client
->receive_message
= sd_event_source_unref(client
->receive_message
);
1336 client
->fd
= safe_close(client
->fd
);
1341 int sd_dhcp6_client_is_running(sd_dhcp6_client
*client
) {
1342 assert_return(client
, -EINVAL
);
1344 return client
->state
!= DHCP6_STATE_STOPPED
;
1347 int sd_dhcp6_client_start(sd_dhcp6_client
*client
) {
1348 DHCP6State state
= DHCP6_STATE_SOLICITATION
;
1351 assert_return(client
, -EINVAL
);
1352 assert_return(client
->event
, -EINVAL
);
1353 assert_return(client
->ifindex
> 0, -EINVAL
);
1354 assert_return(in6_addr_is_link_local(&client
->local_address
) > 0, -EINVAL
);
1355 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
1356 assert_return(client
->information_request
|| client
->request_ia
!= 0, -EINVAL
);
1358 /* Even if the client is in the STOPPED state, the lease acquired in the previous information
1359 * request may be stored. */
1360 client
->lease
= sd_dhcp6_lease_unref(client
->lease
);
1362 r
= client_ensure_iaid(client
);
1366 r
= client_ensure_duid(client
);
1370 if (client
->fd
< 0) {
1371 r
= dhcp6_network_bind_udp_socket(client
->ifindex
, &client
->local_address
);
1373 return log_dhcp6_client_errno(client
, r
,
1374 "Failed to bind to UDP socket at address %s: %m",
1375 IN6_ADDR_TO_STRING(&client
->local_address
));
1380 if (!client
->receive_message
) {
1381 _cleanup_(sd_event_source_disable_unrefp
) sd_event_source
*s
= NULL
;
1383 r
= sd_event_add_io(client
->event
, &s
, client
->fd
, EPOLLIN
, client_receive_message
, client
);
1387 r
= sd_event_source_set_priority(s
, client
->event_priority
);
1391 r
= sd_event_source_set_description(s
, "dhcp6-receive-message");
1395 client
->receive_message
= TAKE_PTR(s
);
1398 if (client
->information_request
) {
1399 usec_t t
= now(CLOCK_MONOTONIC
);
1401 if (t
< usec_add(client
->information_request_time_usec
, client
->information_refresh_time_usec
))
1404 client
->information_request_time_usec
= t
;
1405 state
= DHCP6_STATE_INFORMATION_REQUEST
;
1408 log_dhcp6_client(client
, "Starting in %s mode",
1409 client
->information_request
? "Information request" : "Solicit");
1411 return client_start_transaction(client
, state
);
1414 int sd_dhcp6_client_attach_event(sd_dhcp6_client
*client
, sd_event
*event
, int64_t priority
) {
1417 assert_return(client
, -EINVAL
);
1418 assert_return(!client
->event
, -EBUSY
);
1419 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
1422 client
->event
= sd_event_ref(event
);
1424 r
= sd_event_default(&client
->event
);
1429 client
->event_priority
= priority
;
1434 int sd_dhcp6_client_detach_event(sd_dhcp6_client
*client
) {
1435 assert_return(client
, -EINVAL
);
1436 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
1438 client
->event
= sd_event_unref(client
->event
);
1443 sd_event
*sd_dhcp6_client_get_event(sd_dhcp6_client
*client
) {
1444 assert_return(client
, NULL
);
1446 return client
->event
;
1449 int sd_dhcp6_client_attach_device(sd_dhcp6_client
*client
, sd_device
*dev
) {
1450 assert_return(client
, -EINVAL
);
1452 return device_unref_and_replace(client
->dev
, dev
);
1455 static sd_dhcp6_client
*dhcp6_client_free(sd_dhcp6_client
*client
) {
1459 sd_dhcp6_lease_unref(client
->lease
);
1461 sd_event_source_disable_unref(client
->receive_message
);
1462 sd_event_source_disable_unref(client
->timeout_resend
);
1463 sd_event_source_disable_unref(client
->timeout_expire
);
1464 sd_event_source_disable_unref(client
->timeout_t1
);
1465 sd_event_source_disable_unref(client
->timeout_t2
);
1466 sd_event_unref(client
->event
);
1468 client
->fd
= safe_close(client
->fd
);
1470 sd_device_unref(client
->dev
);
1472 free(client
->req_opts
);
1474 free(client
->mudurl
);
1475 dhcp6_ia_clear_addresses(&client
->ia_pd
);
1476 ordered_hashmap_free(client
->extra_options
);
1477 ordered_set_free(client
->vendor_options
);
1478 strv_free(client
->user_class
);
1479 strv_free(client
->vendor_class
);
1480 free(client
->ifname
);
1482 return mfree(client
);
1485 DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_client
, sd_dhcp6_client
, dhcp6_client_free
);
1487 int sd_dhcp6_client_new(sd_dhcp6_client
**ret
) {
1488 _cleanup_(sd_dhcp6_client_unrefp
) sd_dhcp6_client
*client
= NULL
;
1490 assert_return(ret
, -EINVAL
);
1492 client
= new(sd_dhcp6_client
, 1);
1496 *client
= (sd_dhcp6_client
) {
1498 .ia_na
.type
= SD_DHCP6_OPTION_IA_NA
,
1499 .ia_pd
.type
= SD_DHCP6_OPTION_IA_PD
,
1501 .request_ia
= DHCP6_REQUEST_IA_NA
| DHCP6_REQUEST_IA_PD
,
1503 .rapid_commit
= true,
1506 *ret
= TAKE_PTR(client
);