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 "dhcp-identifier.h"
15 #include "dhcp6-internal.h"
16 #include "dhcp6-lease-internal.h"
17 #include "dns-domain.h"
18 #include "event-util.h"
20 #include "hexdecoct.h"
21 #include "hostname-util.h"
22 #include "in-addr-util.h"
24 #include "random-util.h"
25 #include "socket-util.h"
29 static const uint16_t default_req_opts
[] = {
30 SD_DHCP6_OPTION_DNS_SERVERS
,
31 SD_DHCP6_OPTION_DOMAIN_LIST
,
32 SD_DHCP6_OPTION_NTP_SERVER
,
33 SD_DHCP6_OPTION_SNTP_SERVERS
,
36 #define DHCP6_CLIENT_DONT_DESTROY(client) \
37 _cleanup_(sd_dhcp6_client_unrefp) _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client)
39 static int client_start_transaction(sd_dhcp6_client
*client
, DHCP6State state
);
41 int sd_dhcp6_client_set_callback(
42 sd_dhcp6_client
*client
,
43 sd_dhcp6_client_callback_t cb
,
46 assert_return(client
, -EINVAL
);
48 client
->callback
= cb
;
49 client
->userdata
= userdata
;
54 int sd_dhcp6_client_set_ifindex(sd_dhcp6_client
*client
, int ifindex
) {
55 assert_return(client
, -EINVAL
);
56 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
57 assert_return(ifindex
> 0, -EINVAL
);
59 client
->ifindex
= ifindex
;
63 int sd_dhcp6_client_set_ifname(sd_dhcp6_client
*client
, const char *ifname
) {
64 assert_return(client
, -EINVAL
);
65 assert_return(ifname
, -EINVAL
);
67 if (!ifname_valid_full(ifname
, IFNAME_VALID_ALTERNATIVE
))
70 return free_and_strdup(&client
->ifname
, ifname
);
73 int sd_dhcp6_client_get_ifname(sd_dhcp6_client
*client
, const char **ret
) {
76 assert_return(client
, -EINVAL
);
78 r
= get_ifname(client
->ifindex
, &client
->ifname
);
83 *ret
= client
->ifname
;
88 int sd_dhcp6_client_set_local_address(
89 sd_dhcp6_client
*client
,
90 const struct in6_addr
*local_address
) {
92 assert_return(client
, -EINVAL
);
93 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
94 assert_return(local_address
, -EINVAL
);
95 assert_return(in6_addr_is_link_local(local_address
) > 0, -EINVAL
);
97 client
->local_address
= *local_address
;
102 int sd_dhcp6_client_set_mac(
103 sd_dhcp6_client
*client
,
108 assert_return(client
, -EINVAL
);
109 assert_return(addr
, -EINVAL
);
110 assert_return(addr_len
<= sizeof(client
->hw_addr
.bytes
), -EINVAL
);
112 /* Unlike the other setters, it is OK to set a new MAC address while the client is running,
113 * as the MAC address is used only when setting DUID or IAID. */
115 if (arp_type
== ARPHRD_ETHER
)
116 assert_return(addr_len
== ETH_ALEN
, -EINVAL
);
117 else if (arp_type
== ARPHRD_INFINIBAND
)
118 assert_return(addr_len
== INFINIBAND_ALEN
, -EINVAL
);
120 client
->arp_type
= ARPHRD_NONE
;
121 client
->hw_addr
.length
= 0;
125 memcpy(client
->hw_addr
.bytes
, addr
, addr_len
);
126 client
->hw_addr
.length
= addr_len
;
127 client
->arp_type
= arp_type
;
132 int sd_dhcp6_client_set_prefix_delegation_hint(
133 sd_dhcp6_client
*client
,
135 const struct in6_addr
*pd_prefix
) {
137 _cleanup_free_ DHCP6Address
*prefix
= NULL
;
139 assert_return(client
, -EINVAL
);
140 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
143 /* clear previous assignments. */
144 dhcp6_ia_clear_addresses(&client
->ia_pd
);
148 assert_return(prefixlen
> 0 && prefixlen
<= 128, -EINVAL
);
150 prefix
= new(DHCP6Address
, 1);
154 *prefix
= (DHCP6Address
) {
155 .iapdprefix
.address
= *pd_prefix
,
156 .iapdprefix
.prefixlen
= prefixlen
,
159 LIST_PREPEND(addresses
, client
->ia_pd
.addresses
, TAKE_PTR(prefix
));
163 int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client
*client
, sd_dhcp6_option
*v
) {
166 assert_return(client
, -EINVAL
);
167 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
170 /* Clear the previous assignments. */
171 ordered_set_clear(client
->vendor_options
);
175 r
= ordered_set_ensure_put(&client
->vendor_options
, &dhcp6_option_hash_ops
, v
);
179 sd_dhcp6_option_ref(v
);
184 static int client_ensure_duid(sd_dhcp6_client
*client
) {
187 if (client
->duid_len
!= 0)
190 return dhcp_identifier_set_duid_en(client
->test_mode
, &client
->duid
, &client
->duid_len
);
194 * Sets DUID. If duid is non-null, the DUID is set to duid_type + duid
195 * without further modification. Otherwise, if duid_type is supported, DUID
196 * is set based on that type. Otherwise, an error is returned.
198 static int dhcp6_client_set_duid_internal(
199 sd_dhcp6_client
*client
,
206 assert_return(client
, -EINVAL
);
207 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
208 assert_return(duid_len
== 0 || duid
, -EINVAL
);
211 r
= dhcp_validate_duid_len(duid_type
, duid_len
, true);
213 r
= dhcp_validate_duid_len(duid_type
, duid_len
, false);
215 return log_dhcp6_client_errno(client
, r
, "Failed to validate length of DUID: %m");
217 log_dhcp6_client(client
, "Using DUID of type %u of incorrect length, proceeding.", duid_type
);
220 client
->duid
.type
= htobe16(duid_type
);
221 memcpy(&client
->duid
.raw
.data
, duid
, duid_len
);
222 client
->duid_len
= sizeof(client
->duid
.type
) + duid_len
;
225 r
= dhcp_identifier_set_duid(duid_type
, client
->hw_addr
.bytes
, client
->hw_addr
.length
,
226 client
->arp_type
, llt_time
, client
->test_mode
, &client
->duid
, &client
->duid_len
);
227 if (r
== -EOPNOTSUPP
)
228 return log_dhcp6_client_errno(client
, r
,
229 "Failed to set %s. MAC address is not set or "
230 "interface type is not supported.",
231 duid_type_to_string(duid_type
));
233 return log_dhcp6_client_errno(client
, r
, "Failed to set %s: %m",
234 duid_type_to_string(duid_type
));
240 int sd_dhcp6_client_set_duid(
241 sd_dhcp6_client
*client
,
245 return dhcp6_client_set_duid_internal(client
, duid_type
, duid
, duid_len
, 0);
248 int sd_dhcp6_client_set_duid_llt(
249 sd_dhcp6_client
*client
,
251 return dhcp6_client_set_duid_internal(client
, DUID_TYPE_LLT
, NULL
, 0, llt_time
);
254 int sd_dhcp6_client_duid_as_string(
255 sd_dhcp6_client
*client
,
257 _cleanup_free_
char *p
= NULL
, *s
= NULL
, *t
= NULL
;
261 assert_return(client
, -EINVAL
);
262 assert_return(client
->duid_len
> 0, -ENODATA
);
263 assert_return(duid
, -EINVAL
);
265 v
= duid_type_to_string(be16toh(client
->duid
.type
));
271 r
= asprintf(&s
, "%0x", client
->duid
.type
);
276 t
= hexmem(&client
->duid
.raw
.data
, client
->duid_len
);
280 p
= strjoin(s
, ":", t
);
289 int sd_dhcp6_client_set_iaid(sd_dhcp6_client
*client
, uint32_t iaid
) {
290 assert_return(client
, -EINVAL
);
291 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
293 client
->ia_na
.header
.id
= htobe32(iaid
);
294 client
->ia_pd
.header
.id
= htobe32(iaid
);
295 client
->iaid_set
= true;
300 static int client_ensure_iaid(sd_dhcp6_client
*client
) {
306 if (client
->iaid_set
)
309 r
= dhcp_identifier_set_iaid(client
->ifindex
, client
->hw_addr
.bytes
, client
->hw_addr
.length
,
310 /* legacy_unstable_byteorder = */ true,
311 /* use_mac = */ client
->test_mode
,
316 client
->ia_na
.header
.id
= iaid
;
317 client
->ia_pd
.header
.id
= iaid
;
318 client
->iaid_set
= true;
323 int sd_dhcp6_client_get_iaid(sd_dhcp6_client
*client
, uint32_t *iaid
) {
324 assert_return(client
, -EINVAL
);
325 assert_return(iaid
, -EINVAL
);
327 if (!client
->iaid_set
)
330 *iaid
= be32toh(client
->ia_na
.header
.id
);
335 void dhcp6_client_set_test_mode(sd_dhcp6_client
*client
, bool test_mode
) {
338 client
->test_mode
= test_mode
;
341 int sd_dhcp6_client_set_fqdn(
342 sd_dhcp6_client
*client
,
345 assert_return(client
, -EINVAL
);
346 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
348 /* Make sure FQDN qualifies as DNS and as Linux hostname */
350 !(hostname_is_valid(fqdn
, 0) && dns_name_is_valid(fqdn
) > 0))
353 return free_and_strdup(&client
->fqdn
, fqdn
);
356 int sd_dhcp6_client_set_information_request(sd_dhcp6_client
*client
, int enabled
) {
357 assert_return(client
, -EINVAL
);
358 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
360 client
->information_request
= enabled
;
365 int sd_dhcp6_client_get_information_request(sd_dhcp6_client
*client
, int *enabled
) {
366 assert_return(client
, -EINVAL
);
367 assert_return(enabled
, -EINVAL
);
369 *enabled
= client
->information_request
;
374 int sd_dhcp6_client_set_request_option(sd_dhcp6_client
*client
, uint16_t option
) {
377 assert_return(client
, -EINVAL
);
378 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
380 if (!dhcp6_option_can_request(option
))
383 for (t
= 0; t
< client
->req_opts_len
; t
++)
384 if (client
->req_opts
[t
] == htobe16(option
))
387 if (!GREEDY_REALLOC(client
->req_opts
, client
->req_opts_len
+ 1))
390 client
->req_opts
[client
->req_opts_len
++] = htobe16(option
);
395 int sd_dhcp6_client_set_request_mud_url(sd_dhcp6_client
*client
, const char *mudurl
) {
396 assert_return(client
, -EINVAL
);
397 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
398 assert_return(mudurl
, -EINVAL
);
399 assert_return(strlen(mudurl
) <= UINT8_MAX
, -EINVAL
);
400 assert_return(http_url_is_valid(mudurl
), -EINVAL
);
402 return free_and_strdup(&client
->mudurl
, mudurl
);
405 int sd_dhcp6_client_set_request_user_class(sd_dhcp6_client
*client
, char * const *user_class
) {
408 assert_return(client
, -EINVAL
);
409 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
410 assert_return(!strv_isempty(user_class
), -EINVAL
);
412 STRV_FOREACH(p
, user_class
) {
413 size_t len
= strlen(*p
);
415 if (len
> UINT16_MAX
|| len
== 0)
419 s
= strv_copy(user_class
);
423 return strv_free_and_replace(client
->user_class
, s
);
426 int sd_dhcp6_client_set_request_vendor_class(sd_dhcp6_client
*client
, char * const *vendor_class
) {
429 assert_return(client
, -EINVAL
);
430 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
431 assert_return(!strv_isempty(vendor_class
), -EINVAL
);
433 STRV_FOREACH(p
, vendor_class
) {
434 size_t len
= strlen(*p
);
436 if (len
> UINT16_MAX
|| len
== 0)
440 s
= strv_copy(vendor_class
);
444 return strv_free_and_replace(client
->vendor_class
, s
);
447 int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client
*client
, int *delegation
) {
448 assert_return(client
, -EINVAL
);
449 assert_return(delegation
, -EINVAL
);
451 *delegation
= FLAGS_SET(client
->request_ia
, DHCP6_REQUEST_IA_PD
);
456 int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client
*client
, int delegation
) {
457 assert_return(client
, -EINVAL
);
458 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
460 SET_FLAG(client
->request_ia
, DHCP6_REQUEST_IA_PD
, delegation
);
465 int sd_dhcp6_client_get_address_request(sd_dhcp6_client
*client
, int *request
) {
466 assert_return(client
, -EINVAL
);
467 assert_return(request
, -EINVAL
);
469 *request
= FLAGS_SET(client
->request_ia
, DHCP6_REQUEST_IA_NA
);
474 int sd_dhcp6_client_set_address_request(sd_dhcp6_client
*client
, int request
) {
475 assert_return(client
, -EINVAL
);
476 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
478 SET_FLAG(client
->request_ia
, DHCP6_REQUEST_IA_NA
, request
);
483 int dhcp6_client_set_transaction_id(sd_dhcp6_client
*client
, uint32_t transaction_id
) {
485 assert(client
->test_mode
);
487 /* This is for tests or fuzzers. */
489 client
->transaction_id
= transaction_id
& htobe32(0x00ffffff);
494 int sd_dhcp6_client_get_lease(sd_dhcp6_client
*client
, sd_dhcp6_lease
**ret
) {
495 assert_return(client
, -EINVAL
);
501 *ret
= client
->lease
;
506 int sd_dhcp6_client_add_option(sd_dhcp6_client
*client
, sd_dhcp6_option
*v
) {
509 assert_return(client
, -EINVAL
);
510 assert_return(v
, -EINVAL
);
512 r
= ordered_hashmap_ensure_put(&client
->extra_options
, &dhcp6_option_hash_ops
, UINT_TO_PTR(v
->option
), v
);
516 sd_dhcp6_option_ref(v
);
520 static void client_set_state(sd_dhcp6_client
*client
, DHCP6State state
) {
523 if (client
->state
== state
)
526 log_dhcp6_client(client
, "State changed: %s -> %s",
527 dhcp6_state_to_string(client
->state
), dhcp6_state_to_string(state
));
529 client
->state
= state
;
532 static void client_notify(sd_dhcp6_client
*client
, int event
) {
535 if (client
->callback
)
536 client
->callback(client
, event
, client
->userdata
);
539 static void client_stop(sd_dhcp6_client
*client
, int error
) {
540 DHCP6_CLIENT_DONT_DESTROY(client
);
544 client_notify(client
, error
);
546 client
->lease
= sd_dhcp6_lease_unref(client
->lease
);
548 /* Reset IRT here. Otherwise, we cannot restart the client in the information requesting mode,
549 * even though the lease is freed below. */
550 client
->information_request_time_usec
= 0;
551 client
->information_refresh_time_usec
= 0;
553 (void) event_source_disable(client
->receive_message
);
554 (void) event_source_disable(client
->timeout_resend
);
555 (void) event_source_disable(client
->timeout_expire
);
556 (void) event_source_disable(client
->timeout_t1
);
557 (void) event_source_disable(client
->timeout_t2
);
559 client_set_state(client
, DHCP6_STATE_STOPPED
);
562 static int client_append_common_options_in_managed_mode(
563 sd_dhcp6_client
*client
,
566 const DHCP6IA
*ia_na
,
567 const DHCP6IA
*ia_pd
) {
572 assert(IN_SET(client
->state
,
573 DHCP6_STATE_SOLICITATION
,
576 DHCP6_STATE_REBIND
));
580 if (FLAGS_SET(client
->request_ia
, DHCP6_REQUEST_IA_NA
) && ia_na
) {
581 r
= dhcp6_option_append_ia(opt
, optlen
, ia_na
);
586 if (FLAGS_SET(client
->request_ia
, DHCP6_REQUEST_IA_PD
) && ia_pd
) {
587 r
= dhcp6_option_append_ia(opt
, optlen
, ia_pd
);
593 r
= dhcp6_option_append_fqdn(opt
, optlen
, client
->fqdn
);
598 if (client
->user_class
) {
599 r
= dhcp6_option_append_user_class(opt
, optlen
, client
->user_class
);
604 if (client
->vendor_class
) {
605 r
= dhcp6_option_append_vendor_class(opt
, optlen
, client
->vendor_class
);
610 if (!ordered_set_isempty(client
->vendor_options
)) {
611 r
= dhcp6_option_append_vendor_option(opt
, optlen
, client
->vendor_options
);
619 static DHCP6MessageType
client_message_type_from_state(sd_dhcp6_client
*client
) {
622 switch (client
->state
) {
623 case DHCP6_STATE_INFORMATION_REQUEST
:
624 return DHCP6_MESSAGE_INFORMATION_REQUEST
;
625 case DHCP6_STATE_SOLICITATION
:
626 return DHCP6_MESSAGE_SOLICIT
;
627 case DHCP6_STATE_REQUEST
:
628 return DHCP6_MESSAGE_REQUEST
;
629 case DHCP6_STATE_RENEW
:
630 return DHCP6_MESSAGE_RENEW
;
631 case DHCP6_STATE_REBIND
:
632 return DHCP6_MESSAGE_REBIND
;
634 assert_not_reached();
638 int dhcp6_client_send_message(sd_dhcp6_client
*client
) {
639 _cleanup_free_ DHCP6Message
*message
= NULL
;
640 struct in6_addr all_servers
=
641 IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT
;
642 struct sd_dhcp6_option
*j
;
643 size_t len
, optlen
= 512;
645 usec_t elapsed_usec
, time_now
;
650 assert(client
->event
);
652 r
= sd_event_now(client
->event
, clock_boottime_or_monotonic(), &time_now
);
656 len
= sizeof(DHCP6Message
) + optlen
;
658 message
= malloc0(len
);
662 opt
= (uint8_t *)(message
+ 1);
664 message
->transaction_id
= client
->transaction_id
;
665 message
->type
= client_message_type_from_state(client
);
667 switch (client
->state
) {
668 case DHCP6_STATE_INFORMATION_REQUEST
:
671 case DHCP6_STATE_SOLICITATION
:
672 r
= dhcp6_option_append(&opt
, &optlen
, SD_DHCP6_OPTION_RAPID_COMMIT
, 0, NULL
);
676 r
= client_append_common_options_in_managed_mode(client
, &opt
, &optlen
,
677 &client
->ia_na
, &client
->ia_pd
);
682 case DHCP6_STATE_REQUEST
:
683 case DHCP6_STATE_RENEW
:
685 r
= dhcp6_option_append(&opt
, &optlen
, SD_DHCP6_OPTION_SERVERID
,
686 client
->lease
->serverid_len
,
687 client
->lease
->serverid
);
692 case DHCP6_STATE_REBIND
:
694 assert(client
->lease
);
696 r
= client_append_common_options_in_managed_mode(client
, &opt
, &optlen
,
697 client
->lease
->ia_na
, client
->lease
->ia_pd
);
702 case DHCP6_STATE_STOPPED
:
703 case DHCP6_STATE_BOUND
:
705 assert_not_reached();
708 if (client
->mudurl
) {
709 r
= dhcp6_option_append(&opt
, &optlen
, SD_DHCP6_OPTION_MUD_URL_V6
,
710 strlen(client
->mudurl
), client
->mudurl
);
715 r
= dhcp6_option_append(&opt
, &optlen
, SD_DHCP6_OPTION_ORO
,
716 client
->req_opts_len
* sizeof(be16_t
),
721 assert(client
->duid_len
);
722 r
= dhcp6_option_append(&opt
, &optlen
, SD_DHCP6_OPTION_CLIENTID
,
723 client
->duid_len
, &client
->duid
);
727 ORDERED_HASHMAP_FOREACH(j
, client
->extra_options
) {
728 r
= dhcp6_option_append(&opt
, &optlen
, j
->option
, j
->length
, j
->data
);
733 /* RFC 8415 Section 21.9.
734 * A client MUST include an Elapsed Time option in messages to indicate how long the client has
735 * been trying to complete a DHCP message exchange. */
736 elapsed_usec
= MIN(usec_sub_unsigned(time_now
, client
->transaction_start
) / USEC_PER_MSEC
/ 10, (usec_t
) UINT16_MAX
);
737 elapsed_time
= htobe16(elapsed_usec
);
738 r
= dhcp6_option_append(&opt
, &optlen
, SD_DHCP6_OPTION_ELAPSED_TIME
, sizeof(elapsed_time
), &elapsed_time
);
742 r
= dhcp6_network_send_udp_socket(client
->fd
, &all_servers
, message
,
747 log_dhcp6_client(client
, "Sent %s",
748 dhcp6_message_type_to_string(message
->type
));
753 static usec_t
client_timeout_compute_random(usec_t val
) {
754 return usec_sub_unsigned(val
, random_u64_range(val
/ 10));
757 static int client_timeout_resend(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
758 sd_dhcp6_client
*client
= ASSERT_PTR(userdata
);
759 usec_t init_retransmit_time
, max_retransmit_time
;
762 assert(client
->event
);
764 switch (client
->state
) {
765 case DHCP6_STATE_INFORMATION_REQUEST
:
766 init_retransmit_time
= DHCP6_INF_TIMEOUT
;
767 max_retransmit_time
= DHCP6_INF_MAX_RT
;
770 case DHCP6_STATE_SOLICITATION
:
772 if (client
->retransmit_count
> 0 && client
->lease
) {
773 (void) client_start_transaction(client
, DHCP6_STATE_REQUEST
);
777 init_retransmit_time
= DHCP6_SOL_TIMEOUT
;
778 max_retransmit_time
= DHCP6_SOL_MAX_RT
;
781 case DHCP6_STATE_REQUEST
:
783 if (client
->retransmit_count
>= DHCP6_REQ_MAX_RC
) {
784 client_stop(client
, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX
);
788 init_retransmit_time
= DHCP6_REQ_TIMEOUT
;
789 max_retransmit_time
= DHCP6_REQ_MAX_RT
;
792 case DHCP6_STATE_RENEW
:
793 init_retransmit_time
= DHCP6_REN_TIMEOUT
;
794 max_retransmit_time
= DHCP6_REN_MAX_RT
;
796 /* RFC 3315, section 18.1.3. says max retransmit duration will
797 be the remaining time until T2. Instead of setting MRD,
798 wait for T2 to trigger with the same end result */
801 case DHCP6_STATE_REBIND
:
802 init_retransmit_time
= DHCP6_REB_TIMEOUT
;
803 max_retransmit_time
= DHCP6_REB_MAX_RT
;
805 /* Also, instead of setting MRD, the expire timer is already set in client_enter_bound_state(). */
808 case DHCP6_STATE_STOPPED
:
809 case DHCP6_STATE_BOUND
:
811 assert_not_reached();
814 r
= dhcp6_client_send_message(client
);
816 client
->retransmit_count
++;
818 if (client
->retransmit_time
== 0) {
819 client
->retransmit_time
= client_timeout_compute_random(init_retransmit_time
);
821 if (client
->state
== DHCP6_STATE_SOLICITATION
)
822 client
->retransmit_time
+= init_retransmit_time
/ 10;
824 } else if (client
->retransmit_time
> max_retransmit_time
/ 2)
825 client
->retransmit_time
= client_timeout_compute_random(max_retransmit_time
);
827 client
->retransmit_time
+= client_timeout_compute_random(client
->retransmit_time
);
829 log_dhcp6_client(client
, "Next retransmission in %s",
830 FORMAT_TIMESPAN(client
->retransmit_time
, USEC_PER_SEC
));
832 r
= event_reset_time_relative(client
->event
, &client
->timeout_resend
,
833 clock_boottime_or_monotonic(),
834 client
->retransmit_time
, 10 * USEC_PER_MSEC
,
835 client_timeout_resend
, client
,
836 client
->event_priority
, "dhcp6-resend-timer", true);
838 client_stop(client
, r
);
843 static int client_start_transaction(sd_dhcp6_client
*client
, DHCP6State state
) {
847 assert(client
->event
);
850 case DHCP6_STATE_INFORMATION_REQUEST
:
851 case DHCP6_STATE_SOLICITATION
:
852 assert(client
->state
== DHCP6_STATE_STOPPED
);
854 case DHCP6_STATE_REQUEST
:
855 assert(client
->state
== DHCP6_STATE_SOLICITATION
);
857 case DHCP6_STATE_RENEW
:
858 assert(client
->state
== DHCP6_STATE_BOUND
);
860 case DHCP6_STATE_REBIND
:
861 assert(IN_SET(client
->state
, DHCP6_STATE_BOUND
, DHCP6_STATE_RENEW
));
863 case DHCP6_STATE_STOPPED
:
864 case DHCP6_STATE_BOUND
:
866 assert_not_reached();
869 client_set_state(client
, state
);
871 client
->retransmit_time
= 0;
872 client
->retransmit_count
= 0;
873 client
->transaction_id
= random_u32() & htobe32(0x00ffffff);
875 r
= sd_event_now(client
->event
, clock_boottime_or_monotonic(), &client
->transaction_start
);
879 r
= event_reset_time(client
->event
, &client
->timeout_resend
,
880 clock_boottime_or_monotonic(),
882 client_timeout_resend
, client
,
883 client
->event_priority
, "dhcp6-resend-timeout", true);
887 r
= sd_event_source_set_enabled(client
->receive_message
, SD_EVENT_ON
);
894 client_stop(client
, r
);
898 static int client_timeout_expire(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
899 sd_dhcp6_client
*client
= ASSERT_PTR(userdata
);
900 DHCP6_CLIENT_DONT_DESTROY(client
);
903 (void) event_source_disable(client
->timeout_expire
);
904 (void) event_source_disable(client
->timeout_t2
);
905 (void) event_source_disable(client
->timeout_t1
);
907 state
= client
->state
;
909 client_stop(client
, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE
);
911 /* RFC 3315, section 18.1.4., says that "...the client may choose to
912 use a Solicit message to locate a new DHCP server..." */
913 if (state
== DHCP6_STATE_REBIND
)
914 (void) client_start_transaction(client
, DHCP6_STATE_SOLICITATION
);
919 static int client_timeout_t2(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
920 sd_dhcp6_client
*client
= ASSERT_PTR(userdata
);
922 (void) event_source_disable(client
->timeout_t2
);
923 (void) event_source_disable(client
->timeout_t1
);
925 log_dhcp6_client(client
, "Timeout T2");
927 (void) client_start_transaction(client
, DHCP6_STATE_REBIND
);
932 static int client_timeout_t1(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
933 sd_dhcp6_client
*client
= ASSERT_PTR(userdata
);
935 (void) event_source_disable(client
->timeout_t1
);
937 log_dhcp6_client(client
, "Timeout T1");
939 (void) client_start_transaction(client
, DHCP6_STATE_RENEW
);
944 static int client_enter_bound_state(sd_dhcp6_client
*client
) {
945 usec_t lifetime_t1
, lifetime_t2
, lifetime_valid
;
949 assert(client
->lease
);
950 assert(IN_SET(client
->state
,
951 DHCP6_STATE_SOLICITATION
,
954 DHCP6_STATE_REBIND
));
956 (void) event_source_disable(client
->receive_message
);
957 (void) event_source_disable(client
->timeout_resend
);
959 r
= dhcp6_lease_get_lifetime(client
->lease
, &lifetime_t1
, &lifetime_t2
, &lifetime_valid
);
963 lifetime_t2
= client_timeout_compute_random(lifetime_t2
);
964 lifetime_t1
= client_timeout_compute_random(MIN(lifetime_t1
, lifetime_t2
));
966 if (lifetime_t1
== USEC_INFINITY
) {
967 log_dhcp6_client(client
, "Infinite T1");
968 event_source_disable(client
->timeout_t1
);
970 log_dhcp6_client(client
, "T1 expires in %s", FORMAT_TIMESPAN(lifetime_t1
, USEC_PER_SEC
));
971 r
= event_reset_time_relative(client
->event
, &client
->timeout_t1
,
972 clock_boottime_or_monotonic(),
973 lifetime_t1
, 10 * USEC_PER_SEC
,
974 client_timeout_t1
, client
,
975 client
->event_priority
, "dhcp6-t1-timeout", true);
980 if (lifetime_t2
== USEC_INFINITY
) {
981 log_dhcp6_client(client
, "Infinite T2");
982 event_source_disable(client
->timeout_t2
);
984 log_dhcp6_client(client
, "T2 expires in %s", FORMAT_TIMESPAN(lifetime_t2
, USEC_PER_SEC
));
985 r
= event_reset_time_relative(client
->event
, &client
->timeout_t2
,
986 clock_boottime_or_monotonic(),
987 lifetime_t2
, 10 * USEC_PER_SEC
,
988 client_timeout_t2
, client
,
989 client
->event_priority
, "dhcp6-t2-timeout", true);
994 if (lifetime_valid
== USEC_INFINITY
) {
995 log_dhcp6_client(client
, "Infinite valid lifetime");
996 event_source_disable(client
->timeout_expire
);
998 log_dhcp6_client(client
, "Valid lifetime expires in %s", FORMAT_TIMESPAN(lifetime_valid
, USEC_PER_SEC
));
1000 r
= event_reset_time_relative(client
->event
, &client
->timeout_expire
,
1001 clock_boottime_or_monotonic(),
1002 lifetime_valid
, USEC_PER_SEC
,
1003 client_timeout_expire
, client
,
1004 client
->event_priority
, "dhcp6-lease-expire", true);
1009 client_set_state(client
, DHCP6_STATE_BOUND
);
1010 client_notify(client
, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE
);
1014 client_stop(client
, r
);
1018 static int log_invalid_message_type(sd_dhcp6_client
*client
, const DHCP6Message
*message
) {
1019 const char *type_str
;
1024 type_str
= dhcp6_message_type_to_string(message
->type
);
1026 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
1027 "Received unexpected %s message, ignoring.", type_str
);
1029 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
1030 "Received unsupported message type %u, ignoring.", message
->type
);
1033 static int client_process_information(
1034 sd_dhcp6_client
*client
,
1035 DHCP6Message
*message
,
1037 const triple_timestamp
*timestamp
,
1038 const struct in6_addr
*server_address
) {
1040 _cleanup_(sd_dhcp6_lease_unrefp
) sd_dhcp6_lease
*lease
= NULL
;
1046 if (message
->type
!= DHCP6_MESSAGE_REPLY
)
1047 return log_invalid_message_type(client
, message
);
1049 r
= dhcp6_lease_new_from_message(client
, message
, len
, timestamp
, server_address
, &lease
);
1051 return log_dhcp6_client_errno(client
, r
, "Failed to process received reply message, ignoring: %m");
1053 log_dhcp6_client(client
, "Processed %s message", dhcp6_message_type_to_string(message
->type
));
1055 sd_dhcp6_lease_unref(client
->lease
);
1056 client
->lease
= TAKE_PTR(lease
);
1058 /* Do not call client_stop() here, as it frees the acquired lease. */
1059 (void) event_source_disable(client
->receive_message
);
1060 (void) event_source_disable(client
->timeout_resend
);
1061 client_set_state(client
, DHCP6_STATE_STOPPED
);
1063 client_notify(client
, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST
);
1067 static int client_process_reply(
1068 sd_dhcp6_client
*client
,
1069 DHCP6Message
*message
,
1071 const triple_timestamp
*timestamp
,
1072 const struct in6_addr
*server_address
) {
1074 _cleanup_(sd_dhcp6_lease_unrefp
) sd_dhcp6_lease
*lease
= NULL
;
1080 if (message
->type
!= DHCP6_MESSAGE_REPLY
)
1081 return log_invalid_message_type(client
, message
);
1083 r
= dhcp6_lease_new_from_message(client
, message
, len
, timestamp
, server_address
, &lease
);
1085 return log_dhcp6_client_errno(client
, r
, "Failed to process received reply message, ignoring: %m");
1087 log_dhcp6_client(client
, "Processed %s message", dhcp6_message_type_to_string(message
->type
));
1089 sd_dhcp6_lease_unref(client
->lease
);
1090 client
->lease
= TAKE_PTR(lease
);
1092 return client_enter_bound_state(client
);
1095 static int client_process_advertise_or_rapid_commit_reply(
1096 sd_dhcp6_client
*client
,
1097 DHCP6Message
*message
,
1099 const triple_timestamp
*timestamp
,
1100 const struct in6_addr
*server_address
) {
1102 _cleanup_(sd_dhcp6_lease_unrefp
) sd_dhcp6_lease
*lease
= NULL
;
1103 uint8_t pref_advertise
, pref_lease
= 0;
1109 if (!IN_SET(message
->type
, DHCP6_MESSAGE_ADVERTISE
, DHCP6_MESSAGE_REPLY
))
1110 return log_invalid_message_type(client
, message
);
1112 r
= dhcp6_lease_new_from_message(client
, message
, len
, timestamp
, server_address
, &lease
);
1114 return log_dhcp6_client_errno(client
, r
, "Failed to process received %s message, ignoring: %m",
1115 dhcp6_message_type_to_string(message
->type
));
1117 if (message
->type
== DHCP6_MESSAGE_REPLY
) {
1120 r
= dhcp6_lease_get_rapid_commit(lease
, &rapid_commit
);
1125 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
1126 "Received reply message without rapid commit flag, ignoring.");
1128 log_dhcp6_client(client
, "Processed %s message", dhcp6_message_type_to_string(message
->type
));
1130 sd_dhcp6_lease_unref(client
->lease
);
1131 client
->lease
= TAKE_PTR(lease
);
1133 return client_enter_bound_state(client
);
1136 r
= dhcp6_lease_get_preference(lease
, &pref_advertise
);
1140 if (client
->lease
) {
1141 r
= dhcp6_lease_get_preference(client
->lease
, &pref_lease
);
1146 log_dhcp6_client(client
, "Processed %s message", dhcp6_message_type_to_string(message
->type
));
1148 if (!client
->lease
|| pref_advertise
> pref_lease
) {
1149 /* If this is the first advertise message or has higher preference, then save the lease. */
1150 sd_dhcp6_lease_unref(client
->lease
);
1151 client
->lease
= TAKE_PTR(lease
);
1154 if (pref_advertise
== 255 || client
->retransmit_count
> 1)
1155 (void) client_start_transaction(client
, DHCP6_STATE_REQUEST
);
1160 static int client_receive_message(
1166 sd_dhcp6_client
*client
= ASSERT_PTR(userdata
);
1167 DHCP6_CLIENT_DONT_DESTROY(client
);
1168 /* This needs to be initialized with zero. See #20741. */
1169 CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL
) control
= {};
1171 union sockaddr_union sa
= {};
1172 struct msghdr msg
= {
1174 .msg_namelen
= sizeof(sa
),
1177 .msg_control
= &control
,
1178 .msg_controllen
= sizeof(control
),
1180 struct cmsghdr
*cmsg
;
1181 triple_timestamp t
= {};
1182 _cleanup_free_ DHCP6Message
*message
= NULL
;
1183 struct in6_addr
*server_address
= NULL
;
1184 ssize_t buflen
, len
;
1186 buflen
= next_datagram_size_fd(fd
);
1188 if (ERRNO_IS_TRANSIENT(buflen
) || ERRNO_IS_DISCONNECT(buflen
))
1191 log_dhcp6_client_errno(client
, buflen
, "Failed to determine datagram size to read, ignoring: %m");
1195 message
= malloc(buflen
);
1199 iov
= IOVEC_MAKE(message
, buflen
);
1201 len
= recvmsg_safe(fd
, &msg
, MSG_DONTWAIT
);
1203 if (ERRNO_IS_TRANSIENT(len
) || ERRNO_IS_DISCONNECT(len
))
1206 log_dhcp6_client_errno(client
, len
, "Could not receive message from UDP socket, ignoring: %m");
1209 if ((size_t) len
< sizeof(DHCP6Message
)) {
1210 log_dhcp6_client(client
, "Too small to be DHCP6 message: ignoring");
1214 /* msg_namelen == 0 happens when running the test-suite over a socketpair */
1215 if (msg
.msg_namelen
> 0) {
1216 if (msg
.msg_namelen
!= sizeof(struct sockaddr_in6
) || sa
.in6
.sin6_family
!= AF_INET6
) {
1217 log_dhcp6_client(client
, "Received message from invalid source, ignoring.");
1221 server_address
= &sa
.in6
.sin6_addr
;
1224 CMSG_FOREACH(cmsg
, &msg
) {
1225 if (cmsg
->cmsg_level
== SOL_SOCKET
&&
1226 cmsg
->cmsg_type
== SO_TIMESTAMP
&&
1227 cmsg
->cmsg_len
== CMSG_LEN(sizeof(struct timeval
)))
1228 triple_timestamp_from_realtime(&t
, timeval_load((struct timeval
*) CMSG_DATA(cmsg
)));
1231 if (client
->transaction_id
!= (message
->transaction_id
& htobe32(0x00ffffff)))
1234 switch (client
->state
) {
1235 case DHCP6_STATE_INFORMATION_REQUEST
:
1236 if (client_process_information(client
, message
, len
, &t
, server_address
) < 0)
1240 case DHCP6_STATE_SOLICITATION
:
1241 if (client_process_advertise_or_rapid_commit_reply(client
, message
, len
, &t
, server_address
) < 0)
1245 case DHCP6_STATE_REQUEST
:
1246 case DHCP6_STATE_RENEW
:
1247 case DHCP6_STATE_REBIND
:
1248 if (client_process_reply(client
, message
, len
, &t
, server_address
) < 0)
1252 case DHCP6_STATE_BOUND
:
1253 case DHCP6_STATE_STOPPED
:
1255 assert_not_reached();
1261 int sd_dhcp6_client_stop(sd_dhcp6_client
*client
) {
1265 client_stop(client
, SD_DHCP6_CLIENT_EVENT_STOP
);
1267 client
->receive_message
= sd_event_source_unref(client
->receive_message
);
1268 client
->fd
= safe_close(client
->fd
);
1273 int sd_dhcp6_client_is_running(sd_dhcp6_client
*client
) {
1274 assert_return(client
, -EINVAL
);
1276 return client
->state
!= DHCP6_STATE_STOPPED
;
1279 int sd_dhcp6_client_start(sd_dhcp6_client
*client
) {
1280 DHCP6State state
= DHCP6_STATE_SOLICITATION
;
1283 assert_return(client
, -EINVAL
);
1284 assert_return(client
->event
, -EINVAL
);
1285 assert_return(client
->ifindex
> 0, -EINVAL
);
1286 assert_return(in6_addr_is_link_local(&client
->local_address
) > 0, -EINVAL
);
1287 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
1288 assert_return(client
->information_request
|| client
->request_ia
!= 0, -EINVAL
);
1290 /* Even if the client is in the STOPPED state, the lease acquired in the previous information
1291 * request may be stored. */
1292 client
->lease
= sd_dhcp6_lease_unref(client
->lease
);
1294 r
= client_ensure_iaid(client
);
1298 r
= client_ensure_duid(client
);
1302 if (client
->fd
< 0) {
1303 r
= dhcp6_network_bind_udp_socket(client
->ifindex
, &client
->local_address
);
1305 _cleanup_free_
char *p
= NULL
;
1307 (void) in6_addr_to_string(&client
->local_address
, &p
);
1308 return log_dhcp6_client_errno(client
, r
,
1309 "Failed to bind to UDP socket at address %s: %m", strna(p
));
1315 if (!client
->receive_message
) {
1316 _cleanup_(sd_event_source_disable_unrefp
) sd_event_source
*s
= NULL
;
1318 r
= sd_event_add_io(client
->event
, &s
, client
->fd
, EPOLLIN
, client_receive_message
, client
);
1322 r
= sd_event_source_set_priority(s
, client
->event_priority
);
1326 r
= sd_event_source_set_description(s
, "dhcp6-receive-message");
1330 client
->receive_message
= TAKE_PTR(s
);
1333 if (client
->information_request
) {
1334 usec_t t
= now(CLOCK_MONOTONIC
);
1336 if (t
< usec_add(client
->information_request_time_usec
, client
->information_refresh_time_usec
))
1339 client
->information_request_time_usec
= t
;
1340 state
= DHCP6_STATE_INFORMATION_REQUEST
;
1343 log_dhcp6_client(client
, "Starting in %s mode",
1344 client
->information_request
? "Information request" : "Managed");
1346 return client_start_transaction(client
, state
);
1349 int sd_dhcp6_client_attach_event(sd_dhcp6_client
*client
, sd_event
*event
, int64_t priority
) {
1352 assert_return(client
, -EINVAL
);
1353 assert_return(!client
->event
, -EBUSY
);
1354 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
1357 client
->event
= sd_event_ref(event
);
1359 r
= sd_event_default(&client
->event
);
1364 client
->event_priority
= priority
;
1369 int sd_dhcp6_client_detach_event(sd_dhcp6_client
*client
) {
1370 assert_return(client
, -EINVAL
);
1371 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
1373 client
->event
= sd_event_unref(client
->event
);
1378 sd_event
*sd_dhcp6_client_get_event(sd_dhcp6_client
*client
) {
1379 assert_return(client
, NULL
);
1381 return client
->event
;
1384 static sd_dhcp6_client
*dhcp6_client_free(sd_dhcp6_client
*client
) {
1388 sd_dhcp6_lease_unref(client
->lease
);
1390 sd_event_source_disable_unref(client
->receive_message
);
1391 sd_event_source_disable_unref(client
->timeout_resend
);
1392 sd_event_source_disable_unref(client
->timeout_expire
);
1393 sd_event_source_disable_unref(client
->timeout_t1
);
1394 sd_event_source_disable_unref(client
->timeout_t2
);
1395 sd_event_unref(client
->event
);
1397 client
->fd
= safe_close(client
->fd
);
1399 free(client
->req_opts
);
1401 free(client
->mudurl
);
1402 dhcp6_ia_clear_addresses(&client
->ia_pd
);
1403 ordered_hashmap_free(client
->extra_options
);
1404 ordered_set_free(client
->vendor_options
);
1405 strv_free(client
->user_class
);
1406 strv_free(client
->vendor_class
);
1407 free(client
->ifname
);
1409 return mfree(client
);
1412 DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_client
, sd_dhcp6_client
, dhcp6_client_free
);
1414 int sd_dhcp6_client_new(sd_dhcp6_client
**ret
) {
1415 _cleanup_(sd_dhcp6_client_unrefp
) sd_dhcp6_client
*client
= NULL
;
1416 _cleanup_free_ be16_t
*req_opts
= NULL
;
1419 assert_return(ret
, -EINVAL
);
1421 req_opts
= new(be16_t
, ELEMENTSOF(default_req_opts
));
1425 for (t
= 0; t
< ELEMENTSOF(default_req_opts
); t
++)
1426 req_opts
[t
] = htobe16(default_req_opts
[t
]);
1428 client
= new(sd_dhcp6_client
, 1);
1432 *client
= (sd_dhcp6_client
) {
1434 .ia_na
.type
= SD_DHCP6_OPTION_IA_NA
,
1435 .ia_pd
.type
= SD_DHCP6_OPTION_IA_PD
,
1437 .request_ia
= DHCP6_REQUEST_IA_NA
| DHCP6_REQUEST_IA_PD
,
1439 .req_opts_len
= ELEMENTSOF(default_req_opts
),
1440 .req_opts
= TAKE_PTR(req_opts
),
1443 *ret
= TAKE_PTR(client
);