1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 Copyright © 2014-2015 Intel Corporation. All rights reserved.
9 #include <linux/if_arp.h> /* Must be included after <net/if.h> */
10 #include <linux/if_infiniband.h> /* Must be included after <net/if.h> */
12 #include "sd-dhcp6-client.h"
14 #include "alloc-util.h"
15 #include "device-util.h"
16 #include "dhcp-identifier.h"
17 #include "dhcp6-internal.h"
18 #include "dhcp6-lease-internal.h"
19 #include "dns-domain.h"
20 #include "event-util.h"
22 #include "hexdecoct.h"
23 #include "hostname-util.h"
24 #include "in-addr-util.h"
26 #include "random-util.h"
27 #include "socket-util.h"
28 #include "sort-util.h"
32 #define DHCP6_CLIENT_DONT_DESTROY(client) \
33 _cleanup_(sd_dhcp6_client_unrefp) _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client)
35 static int client_start_transaction(sd_dhcp6_client
*client
, DHCP6State state
);
37 int sd_dhcp6_client_set_callback(
38 sd_dhcp6_client
*client
,
39 sd_dhcp6_client_callback_t cb
,
42 assert_return(client
, -EINVAL
);
44 client
->callback
= cb
;
45 client
->userdata
= userdata
;
50 int sd_dhcp6_client_set_ifindex(sd_dhcp6_client
*client
, int ifindex
) {
51 assert_return(client
, -EINVAL
);
52 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
53 assert_return(ifindex
> 0, -EINVAL
);
55 client
->ifindex
= ifindex
;
59 int sd_dhcp6_client_set_ifname(sd_dhcp6_client
*client
, const char *ifname
) {
60 assert_return(client
, -EINVAL
);
61 assert_return(ifname
, -EINVAL
);
63 if (!ifname_valid_full(ifname
, IFNAME_VALID_ALTERNATIVE
))
66 return free_and_strdup(&client
->ifname
, ifname
);
69 int sd_dhcp6_client_get_ifname(sd_dhcp6_client
*client
, const char **ret
) {
72 assert_return(client
, -EINVAL
);
74 r
= get_ifname(client
->ifindex
, &client
->ifname
);
79 *ret
= client
->ifname
;
84 int sd_dhcp6_client_set_local_address(
85 sd_dhcp6_client
*client
,
86 const struct in6_addr
*local_address
) {
88 assert_return(client
, -EINVAL
);
89 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
90 assert_return(local_address
, -EINVAL
);
91 assert_return(in6_addr_is_link_local(local_address
) > 0, -EINVAL
);
93 client
->local_address
= *local_address
;
98 int sd_dhcp6_client_set_mac(
99 sd_dhcp6_client
*client
,
104 assert_return(client
, -EINVAL
);
105 assert_return(addr
, -EINVAL
);
106 assert_return(addr_len
<= sizeof(client
->hw_addr
.bytes
), -EINVAL
);
108 /* Unlike the other setters, it is OK to set a new MAC address while the client is running,
109 * as the MAC address is used only when setting DUID or IAID. */
111 if (arp_type
== ARPHRD_ETHER
)
112 assert_return(addr_len
== ETH_ALEN
, -EINVAL
);
113 else if (arp_type
== ARPHRD_INFINIBAND
)
114 assert_return(addr_len
== INFINIBAND_ALEN
, -EINVAL
);
116 client
->arp_type
= ARPHRD_NONE
;
117 client
->hw_addr
.length
= 0;
121 client
->arp_type
= arp_type
;
122 hw_addr_set(&client
->hw_addr
, addr
, addr_len
);
127 int sd_dhcp6_client_set_prefix_delegation_hint(
128 sd_dhcp6_client
*client
,
130 const struct in6_addr
*pd_prefix
) {
132 _cleanup_free_ DHCP6Address
*prefix
= NULL
;
134 assert_return(client
, -EINVAL
);
135 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
138 /* clear previous assignments. */
139 dhcp6_ia_clear_addresses(&client
->ia_pd
);
143 assert_return(prefixlen
> 0 && prefixlen
<= 128, -EINVAL
);
145 prefix
= new(DHCP6Address
, 1);
149 *prefix
= (DHCP6Address
) {
150 .iapdprefix
.address
= *pd_prefix
,
151 .iapdprefix
.prefixlen
= prefixlen
,
154 LIST_PREPEND(addresses
, client
->ia_pd
.addresses
, TAKE_PTR(prefix
));
158 int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client
*client
, sd_dhcp6_option
*v
) {
161 assert_return(client
, -EINVAL
);
162 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
165 /* Clear the previous assignments. */
166 ordered_set_clear(client
->vendor_options
);
170 r
= ordered_set_ensure_put(&client
->vendor_options
, &dhcp6_option_hash_ops
, v
);
174 sd_dhcp6_option_ref(v
);
179 static int client_ensure_duid(sd_dhcp6_client
*client
) {
182 if (client
->duid_len
!= 0)
185 return dhcp_identifier_set_duid_en(client
->test_mode
, &client
->duid
, &client
->duid_len
);
189 * Sets DUID. If duid is non-null, the DUID is set to duid_type + duid
190 * without further modification. Otherwise, if duid_type is supported, DUID
191 * is set based on that type. Otherwise, an error is returned.
193 static int dhcp6_client_set_duid_internal(
194 sd_dhcp6_client
*client
,
201 assert_return(client
, -EINVAL
);
202 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
203 assert_return(duid_len
== 0 || duid
, -EINVAL
);
206 r
= dhcp_validate_duid_len(duid_type
, duid_len
, true);
208 r
= dhcp_validate_duid_len(duid_type
, duid_len
, false);
210 return log_dhcp6_client_errno(client
, r
, "Failed to validate length of DUID: %m");
212 log_dhcp6_client(client
, "Using DUID of type %i of incorrect length, proceeding.", duid_type
);
215 client
->duid
.type
= htobe16(duid_type
);
216 memcpy(&client
->duid
.raw
.data
, duid
, duid_len
);
217 client
->duid_len
= sizeof(client
->duid
.type
) + duid_len
;
220 r
= dhcp_identifier_set_duid(duid_type
, &client
->hw_addr
, client
->arp_type
, llt_time
,
221 client
->test_mode
, &client
->duid
, &client
->duid_len
);
222 if (r
== -EOPNOTSUPP
)
223 return log_dhcp6_client_errno(client
, r
,
224 "Failed to set %s. MAC address is not set or "
225 "interface type is not supported.",
226 duid_type_to_string(duid_type
));
228 return log_dhcp6_client_errno(client
, r
, "Failed to set %s: %m",
229 duid_type_to_string(duid_type
));
235 int sd_dhcp6_client_set_duid(
236 sd_dhcp6_client
*client
,
240 return dhcp6_client_set_duid_internal(client
, duid_type
, duid
, duid_len
, 0);
243 int sd_dhcp6_client_set_duid_llt(
244 sd_dhcp6_client
*client
,
246 return dhcp6_client_set_duid_internal(client
, DUID_TYPE_LLT
, NULL
, 0, llt_time
);
249 int sd_dhcp6_client_duid_as_string(
250 sd_dhcp6_client
*client
,
252 _cleanup_free_
char *p
= NULL
, *s
= NULL
, *t
= NULL
;
256 assert_return(client
, -EINVAL
);
257 assert_return(client
->duid_len
> 0, -ENODATA
);
258 assert_return(duid
, -EINVAL
);
260 v
= duid_type_to_string(be16toh(client
->duid
.type
));
266 r
= asprintf(&s
, "%0x", client
->duid
.type
);
271 t
= hexmem(&client
->duid
.raw
.data
, client
->duid_len
);
275 p
= strjoin(s
, ":", t
);
284 int sd_dhcp6_client_set_iaid(sd_dhcp6_client
*client
, uint32_t iaid
) {
285 assert_return(client
, -EINVAL
);
286 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
288 client
->ia_na
.header
.id
= htobe32(iaid
);
289 client
->ia_pd
.header
.id
= htobe32(iaid
);
290 client
->iaid_set
= true;
295 static int client_ensure_iaid(sd_dhcp6_client
*client
) {
301 if (client
->iaid_set
)
304 r
= dhcp_identifier_set_iaid(client
->dev
, &client
->hw_addr
,
305 /* legacy_unstable_byteorder = */ true,
310 client
->ia_na
.header
.id
= iaid
;
311 client
->ia_pd
.header
.id
= iaid
;
312 client
->iaid_set
= true;
317 int sd_dhcp6_client_get_iaid(sd_dhcp6_client
*client
, uint32_t *iaid
) {
318 assert_return(client
, -EINVAL
);
319 assert_return(iaid
, -EINVAL
);
321 if (!client
->iaid_set
)
324 *iaid
= be32toh(client
->ia_na
.header
.id
);
329 void dhcp6_client_set_test_mode(sd_dhcp6_client
*client
, bool test_mode
) {
332 client
->test_mode
= test_mode
;
335 int sd_dhcp6_client_set_fqdn(
336 sd_dhcp6_client
*client
,
339 assert_return(client
, -EINVAL
);
340 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
342 /* Make sure FQDN qualifies as DNS and as Linux hostname */
344 !(hostname_is_valid(fqdn
, 0) && dns_name_is_valid(fqdn
) > 0))
347 return free_and_strdup(&client
->fqdn
, fqdn
);
350 int sd_dhcp6_client_set_information_request(sd_dhcp6_client
*client
, int enabled
) {
351 assert_return(client
, -EINVAL
);
352 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
354 client
->information_request
= enabled
;
359 int sd_dhcp6_client_get_information_request(sd_dhcp6_client
*client
, int *enabled
) {
360 assert_return(client
, -EINVAL
);
361 assert_return(enabled
, -EINVAL
);
363 *enabled
= client
->information_request
;
368 static int be16_compare_func(const be16_t
*a
, const be16_t
*b
) {
369 return CMP(be16toh(*a
), be16toh(*b
));
372 int sd_dhcp6_client_set_request_option(sd_dhcp6_client
*client
, uint16_t option
) {
375 assert_return(client
, -EINVAL
);
376 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
378 if (!dhcp6_option_can_request(option
))
381 opt
= htobe16(option
);
382 if (typesafe_bsearch(&opt
, client
->req_opts
, client
->n_req_opts
, be16_compare_func
))
385 if (!GREEDY_REALLOC(client
->req_opts
, client
->n_req_opts
+ 1))
388 client
->req_opts
[client
->n_req_opts
++] = opt
;
390 /* Sort immediately to make the above binary search will work for the next time. */
391 typesafe_qsort(client
->req_opts
, client
->n_req_opts
, be16_compare_func
);
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_set_rapid_commit(sd_dhcp6_client
*client
, int enable
) {
495 assert_return(client
, -EINVAL
);
496 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
498 client
->rapid_commit
= enable
;
502 int sd_dhcp6_client_set_send_release(sd_dhcp6_client
*client
, int enable
) {
503 assert_return(client
, -EINVAL
);
504 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
506 client
->send_release
= enable
;
510 int sd_dhcp6_client_get_lease(sd_dhcp6_client
*client
, sd_dhcp6_lease
**ret
) {
511 assert_return(client
, -EINVAL
);
517 *ret
= client
->lease
;
522 int sd_dhcp6_client_add_option(sd_dhcp6_client
*client
, sd_dhcp6_option
*v
) {
525 assert_return(client
, -EINVAL
);
526 assert_return(v
, -EINVAL
);
528 r
= ordered_hashmap_ensure_put(&client
->extra_options
, &dhcp6_option_hash_ops
, UINT_TO_PTR(v
->option
), v
);
532 sd_dhcp6_option_ref(v
);
536 static void client_set_state(sd_dhcp6_client
*client
, DHCP6State state
) {
539 if (client
->state
== state
)
542 log_dhcp6_client(client
, "State changed: %s -> %s",
543 dhcp6_state_to_string(client
->state
), dhcp6_state_to_string(state
));
545 client
->state
= state
;
548 static void client_notify(sd_dhcp6_client
*client
, int event
) {
551 if (client
->callback
)
552 client
->callback(client
, event
, client
->userdata
);
555 static void client_cleanup(sd_dhcp6_client
*client
) {
558 client
->lease
= sd_dhcp6_lease_unref(client
->lease
);
560 /* Reset IRT here. Otherwise, we cannot restart the client in the information requesting mode,
561 * even though the lease is freed below. */
562 client
->information_request_time_usec
= 0;
563 client
->information_refresh_time_usec
= 0;
565 (void) event_source_disable(client
->receive_message
);
566 (void) event_source_disable(client
->timeout_resend
);
567 (void) event_source_disable(client
->timeout_expire
);
568 (void) event_source_disable(client
->timeout_t1
);
569 (void) event_source_disable(client
->timeout_t2
);
571 client_set_state(client
, DHCP6_STATE_STOPPED
);
574 static void client_stop(sd_dhcp6_client
*client
, int error
) {
575 DHCP6_CLIENT_DONT_DESTROY(client
);
579 client_notify(client
, error
);
581 client_cleanup(client
);
584 static int client_append_common_options_in_managed_mode(
585 sd_dhcp6_client
*client
,
588 const DHCP6IA
*ia_na
,
589 const DHCP6IA
*ia_pd
) {
594 assert(IN_SET(client
->state
,
595 DHCP6_STATE_SOLICITATION
,
599 DHCP6_STATE_STOPPING
));
604 if (FLAGS_SET(client
->request_ia
, DHCP6_REQUEST_IA_NA
) && ia_na
) {
605 r
= dhcp6_option_append_ia(buf
, offset
, ia_na
);
610 if (FLAGS_SET(client
->request_ia
, DHCP6_REQUEST_IA_PD
) && ia_pd
) {
611 r
= dhcp6_option_append_ia(buf
, offset
, ia_pd
);
616 if (client
->state
!= DHCP6_STATE_STOPPING
) {
617 r
= dhcp6_option_append_fqdn(buf
, offset
, client
->fqdn
);
622 r
= dhcp6_option_append_user_class(buf
, offset
, client
->user_class
);
626 r
= dhcp6_option_append_vendor_class(buf
, offset
, client
->vendor_class
);
630 r
= dhcp6_option_append_vendor_option(buf
, offset
, client
->vendor_options
);
637 static DHCP6MessageType
client_message_type_from_state(sd_dhcp6_client
*client
) {
640 switch (client
->state
) {
641 case DHCP6_STATE_INFORMATION_REQUEST
:
642 return DHCP6_MESSAGE_INFORMATION_REQUEST
;
643 case DHCP6_STATE_SOLICITATION
:
644 return DHCP6_MESSAGE_SOLICIT
;
645 case DHCP6_STATE_REQUEST
:
646 return DHCP6_MESSAGE_REQUEST
;
647 case DHCP6_STATE_RENEW
:
648 return DHCP6_MESSAGE_RENEW
;
649 case DHCP6_STATE_REBIND
:
650 return DHCP6_MESSAGE_REBIND
;
651 case DHCP6_STATE_STOPPING
:
652 return DHCP6_MESSAGE_RELEASE
;
654 assert_not_reached();
658 static int client_append_oro(sd_dhcp6_client
*client
, uint8_t **buf
, size_t *offset
) {
659 _cleanup_free_ be16_t
*p
= NULL
;
668 switch (client
->state
) {
669 case DHCP6_STATE_INFORMATION_REQUEST
:
670 n
= client
->n_req_opts
;
671 p
= new(be16_t
, n
+ 2);
675 memcpy_safe(p
, client
->req_opts
, n
* sizeof(be16_t
));
676 p
[n
++] = htobe16(SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME
); /* RFC 8415 section 21.23 */
677 p
[n
++] = htobe16(SD_DHCP6_OPTION_INF_MAX_RT
); /* RFC 8415 section 21.25 */
679 typesafe_qsort(p
, n
, be16_compare_func
);
683 case DHCP6_STATE_SOLICITATION
:
684 n
= client
->n_req_opts
;
685 p
= new(be16_t
, n
+ 1);
689 memcpy_safe(p
, client
->req_opts
, n
* sizeof(be16_t
));
690 p
[n
++] = htobe16(SD_DHCP6_OPTION_SOL_MAX_RT
); /* RFC 8415 section 21.24 */
692 typesafe_qsort(p
, n
, be16_compare_func
);
696 case DHCP6_STATE_STOPPING
:
700 n
= client
->n_req_opts
;
701 req_opts
= client
->req_opts
;
707 return dhcp6_option_append(buf
, offset
, SD_DHCP6_OPTION_ORO
, n
* sizeof(be16_t
), req_opts
);
710 static int client_append_mudurl(sd_dhcp6_client
*client
, uint8_t **buf
, size_t *offset
) {
719 if (client
->state
== DHCP6_STATE_STOPPING
)
722 return dhcp6_option_append(buf
, offset
, SD_DHCP6_OPTION_MUD_URL_V6
,
723 strlen(client
->mudurl
), client
->mudurl
);
726 int dhcp6_client_send_message(sd_dhcp6_client
*client
) {
727 _cleanup_free_
uint8_t *buf
= NULL
;
728 struct in6_addr all_servers
=
729 IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT
;
730 struct sd_dhcp6_option
*j
;
731 usec_t elapsed_usec
, time_now
;
733 DHCP6Message
*message
;
738 assert(client
->event
);
740 r
= sd_event_now(client
->event
, CLOCK_BOOTTIME
, &time_now
);
744 if (!GREEDY_REALLOC0(buf
, offsetof(DHCP6Message
, options
)))
747 message
= (DHCP6Message
*) buf
;
748 message
->transaction_id
= client
->transaction_id
;
749 message
->type
= client_message_type_from_state(client
);
750 offset
= offsetof(DHCP6Message
, options
);
752 switch (client
->state
) {
753 case DHCP6_STATE_INFORMATION_REQUEST
:
756 case DHCP6_STATE_SOLICITATION
:
757 if (client
->rapid_commit
) {
758 r
= dhcp6_option_append(&buf
, &offset
, SD_DHCP6_OPTION_RAPID_COMMIT
, 0, NULL
);
763 r
= client_append_common_options_in_managed_mode(client
, &buf
, &offset
,
764 &client
->ia_na
, &client
->ia_pd
);
769 case DHCP6_STATE_REQUEST
:
770 case DHCP6_STATE_RENEW
:
771 case DHCP6_STATE_STOPPING
:
772 r
= dhcp6_option_append(&buf
, &offset
, SD_DHCP6_OPTION_SERVERID
,
773 client
->lease
->serverid_len
,
774 client
->lease
->serverid
);
779 case DHCP6_STATE_REBIND
:
781 assert(client
->lease
);
783 r
= client_append_common_options_in_managed_mode(client
, &buf
, &offset
,
784 client
->lease
->ia_na
, client
->lease
->ia_pd
);
789 case DHCP6_STATE_BOUND
:
790 case DHCP6_STATE_STOPPED
:
792 assert_not_reached();
795 r
= client_append_mudurl(client
, &buf
, &offset
);
799 r
= client_append_oro(client
, &buf
, &offset
);
803 assert(client
->duid_len
> 0);
804 r
= dhcp6_option_append(&buf
, &offset
, SD_DHCP6_OPTION_CLIENTID
,
805 client
->duid_len
, &client
->duid
);
809 ORDERED_HASHMAP_FOREACH(j
, client
->extra_options
) {
810 r
= dhcp6_option_append(&buf
, &offset
, j
->option
, j
->length
, j
->data
);
815 /* RFC 8415 Section 21.9.
816 * A client MUST include an Elapsed Time option in messages to indicate how long the client has
817 * been trying to complete a DHCP message exchange. */
818 elapsed_usec
= MIN(usec_sub_unsigned(time_now
, client
->transaction_start
) / USEC_PER_MSEC
/ 10, (usec_t
) UINT16_MAX
);
819 elapsed_time
= htobe16(elapsed_usec
);
820 r
= dhcp6_option_append(&buf
, &offset
, SD_DHCP6_OPTION_ELAPSED_TIME
, sizeof(elapsed_time
), &elapsed_time
);
824 r
= dhcp6_network_send_udp_socket(client
->fd
, &all_servers
, buf
, offset
);
828 log_dhcp6_client(client
, "Sent %s",
829 dhcp6_message_type_to_string(client_message_type_from_state(client
)));
833 static usec_t
client_timeout_compute_random(usec_t val
) {
834 return usec_sub_unsigned(val
, random_u64_range(val
/ 10));
837 static int client_timeout_resend(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
838 sd_dhcp6_client
*client
= ASSERT_PTR(userdata
);
839 usec_t init_retransmit_time
, max_retransmit_time
;
842 assert(client
->event
);
844 switch (client
->state
) {
845 case DHCP6_STATE_INFORMATION_REQUEST
:
846 init_retransmit_time
= DHCP6_INF_TIMEOUT
;
847 max_retransmit_time
= DHCP6_INF_MAX_RT
;
850 case DHCP6_STATE_SOLICITATION
:
852 if (client
->retransmit_count
> 0 && client
->lease
) {
853 (void) client_start_transaction(client
, DHCP6_STATE_REQUEST
);
857 init_retransmit_time
= DHCP6_SOL_TIMEOUT
;
858 max_retransmit_time
= DHCP6_SOL_MAX_RT
;
861 case DHCP6_STATE_REQUEST
:
863 if (client
->retransmit_count
>= DHCP6_REQ_MAX_RC
) {
864 client_stop(client
, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX
);
868 init_retransmit_time
= DHCP6_REQ_TIMEOUT
;
869 max_retransmit_time
= DHCP6_REQ_MAX_RT
;
872 case DHCP6_STATE_RENEW
:
873 init_retransmit_time
= DHCP6_REN_TIMEOUT
;
874 max_retransmit_time
= DHCP6_REN_MAX_RT
;
876 /* RFC 3315, section 18.1.3. says max retransmit duration will
877 be the remaining time until T2. Instead of setting MRD,
878 wait for T2 to trigger with the same end result */
881 case DHCP6_STATE_REBIND
:
882 init_retransmit_time
= DHCP6_REB_TIMEOUT
;
883 max_retransmit_time
= DHCP6_REB_MAX_RT
;
885 /* Also, instead of setting MRD, the expire timer is already set in client_enter_bound_state(). */
888 case DHCP6_STATE_STOPPED
:
889 case DHCP6_STATE_STOPPING
:
890 case DHCP6_STATE_BOUND
:
892 assert_not_reached();
895 r
= dhcp6_client_send_message(client
);
897 client
->retransmit_count
++;
899 if (client
->retransmit_time
== 0) {
900 client
->retransmit_time
= client_timeout_compute_random(init_retransmit_time
);
902 if (client
->state
== DHCP6_STATE_SOLICITATION
)
903 client
->retransmit_time
+= init_retransmit_time
/ 10;
905 } else if (client
->retransmit_time
> max_retransmit_time
/ 2)
906 client
->retransmit_time
= client_timeout_compute_random(max_retransmit_time
);
908 client
->retransmit_time
+= client_timeout_compute_random(client
->retransmit_time
);
910 log_dhcp6_client(client
, "Next retransmission in %s",
911 FORMAT_TIMESPAN(client
->retransmit_time
, USEC_PER_SEC
));
913 r
= event_reset_time_relative(client
->event
, &client
->timeout_resend
,
915 client
->retransmit_time
, 10 * USEC_PER_MSEC
,
916 client_timeout_resend
, client
,
917 client
->event_priority
, "dhcp6-resend-timer", true);
919 client_stop(client
, r
);
924 static int client_start_transaction(sd_dhcp6_client
*client
, DHCP6State state
) {
928 assert(client
->event
);
931 case DHCP6_STATE_INFORMATION_REQUEST
:
932 case DHCP6_STATE_SOLICITATION
:
933 assert(client
->state
== DHCP6_STATE_STOPPED
);
935 case DHCP6_STATE_REQUEST
:
936 assert(client
->state
== DHCP6_STATE_SOLICITATION
);
938 case DHCP6_STATE_RENEW
:
939 assert(client
->state
== DHCP6_STATE_BOUND
);
941 case DHCP6_STATE_REBIND
:
942 assert(IN_SET(client
->state
, DHCP6_STATE_BOUND
, DHCP6_STATE_RENEW
));
944 case DHCP6_STATE_STOPPED
:
945 case DHCP6_STATE_STOPPING
:
946 case DHCP6_STATE_BOUND
:
948 assert_not_reached();
951 client_set_state(client
, state
);
953 client
->retransmit_time
= 0;
954 client
->retransmit_count
= 0;
955 client
->transaction_id
= random_u32() & htobe32(0x00ffffff);
957 r
= sd_event_now(client
->event
, CLOCK_BOOTTIME
, &client
->transaction_start
);
961 r
= event_reset_time(client
->event
, &client
->timeout_resend
,
964 client_timeout_resend
, client
,
965 client
->event_priority
, "dhcp6-resend-timeout", true);
969 r
= sd_event_source_set_enabled(client
->receive_message
, SD_EVENT_ON
);
976 client_stop(client
, r
);
980 static int client_timeout_expire(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
981 sd_dhcp6_client
*client
= ASSERT_PTR(userdata
);
982 DHCP6_CLIENT_DONT_DESTROY(client
);
985 (void) event_source_disable(client
->timeout_expire
);
986 (void) event_source_disable(client
->timeout_t2
);
987 (void) event_source_disable(client
->timeout_t1
);
989 state
= client
->state
;
991 client_stop(client
, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE
);
993 /* RFC 3315, section 18.1.4., says that "...the client may choose to
994 use a Solicit message to locate a new DHCP server..." */
995 if (state
== DHCP6_STATE_REBIND
)
996 (void) client_start_transaction(client
, DHCP6_STATE_SOLICITATION
);
1001 static int client_timeout_t2(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
1002 sd_dhcp6_client
*client
= ASSERT_PTR(userdata
);
1004 (void) event_source_disable(client
->timeout_t2
);
1005 (void) event_source_disable(client
->timeout_t1
);
1007 log_dhcp6_client(client
, "Timeout T2");
1009 (void) client_start_transaction(client
, DHCP6_STATE_REBIND
);
1014 static int client_timeout_t1(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
1015 sd_dhcp6_client
*client
= ASSERT_PTR(userdata
);
1017 (void) event_source_disable(client
->timeout_t1
);
1019 log_dhcp6_client(client
, "Timeout T1");
1021 (void) client_start_transaction(client
, DHCP6_STATE_RENEW
);
1026 static int client_enter_bound_state(sd_dhcp6_client
*client
) {
1027 usec_t lifetime_t1
, lifetime_t2
, lifetime_valid
;
1031 assert(client
->lease
);
1032 assert(IN_SET(client
->state
,
1033 DHCP6_STATE_SOLICITATION
,
1034 DHCP6_STATE_REQUEST
,
1036 DHCP6_STATE_REBIND
));
1038 (void) event_source_disable(client
->receive_message
);
1039 (void) event_source_disable(client
->timeout_resend
);
1041 r
= dhcp6_lease_get_lifetime(client
->lease
, &lifetime_t1
, &lifetime_t2
, &lifetime_valid
);
1045 lifetime_t2
= client_timeout_compute_random(lifetime_t2
);
1046 lifetime_t1
= client_timeout_compute_random(MIN(lifetime_t1
, lifetime_t2
));
1048 if (lifetime_t1
== USEC_INFINITY
) {
1049 log_dhcp6_client(client
, "Infinite T1");
1050 event_source_disable(client
->timeout_t1
);
1052 log_dhcp6_client(client
, "T1 expires in %s", FORMAT_TIMESPAN(lifetime_t1
, USEC_PER_SEC
));
1053 r
= event_reset_time_relative(client
->event
, &client
->timeout_t1
,
1055 lifetime_t1
, 10 * USEC_PER_SEC
,
1056 client_timeout_t1
, client
,
1057 client
->event_priority
, "dhcp6-t1-timeout", true);
1062 if (lifetime_t2
== USEC_INFINITY
) {
1063 log_dhcp6_client(client
, "Infinite T2");
1064 event_source_disable(client
->timeout_t2
);
1066 log_dhcp6_client(client
, "T2 expires in %s", FORMAT_TIMESPAN(lifetime_t2
, USEC_PER_SEC
));
1067 r
= event_reset_time_relative(client
->event
, &client
->timeout_t2
,
1069 lifetime_t2
, 10 * USEC_PER_SEC
,
1070 client_timeout_t2
, client
,
1071 client
->event_priority
, "dhcp6-t2-timeout", true);
1076 if (lifetime_valid
== USEC_INFINITY
) {
1077 log_dhcp6_client(client
, "Infinite valid lifetime");
1078 event_source_disable(client
->timeout_expire
);
1080 log_dhcp6_client(client
, "Valid lifetime expires in %s", FORMAT_TIMESPAN(lifetime_valid
, USEC_PER_SEC
));
1082 r
= event_reset_time_relative(client
->event
, &client
->timeout_expire
,
1084 lifetime_valid
, USEC_PER_SEC
,
1085 client_timeout_expire
, client
,
1086 client
->event_priority
, "dhcp6-lease-expire", true);
1091 client_set_state(client
, DHCP6_STATE_BOUND
);
1092 client_notify(client
, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE
);
1096 client_stop(client
, r
);
1100 static int log_invalid_message_type(sd_dhcp6_client
*client
, const DHCP6Message
*message
) {
1101 const char *type_str
;
1106 type_str
= dhcp6_message_type_to_string(message
->type
);
1108 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
1109 "Received unexpected %s message, ignoring.", type_str
);
1111 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
1112 "Received unsupported message type %u, ignoring.", message
->type
);
1115 static int client_process_information(
1116 sd_dhcp6_client
*client
,
1117 DHCP6Message
*message
,
1119 const triple_timestamp
*timestamp
,
1120 const struct in6_addr
*server_address
) {
1122 _cleanup_(sd_dhcp6_lease_unrefp
) sd_dhcp6_lease
*lease
= NULL
;
1128 if (message
->type
!= DHCP6_MESSAGE_REPLY
)
1129 return log_invalid_message_type(client
, message
);
1131 r
= dhcp6_lease_new_from_message(client
, message
, len
, timestamp
, server_address
, &lease
);
1133 return log_dhcp6_client_errno(client
, r
, "Failed to process received reply message, ignoring: %m");
1135 log_dhcp6_client(client
, "Processed %s message", dhcp6_message_type_to_string(message
->type
));
1137 sd_dhcp6_lease_unref(client
->lease
);
1138 client
->lease
= TAKE_PTR(lease
);
1140 /* Do not call client_stop() here, as it frees the acquired lease. */
1141 (void) event_source_disable(client
->receive_message
);
1142 (void) event_source_disable(client
->timeout_resend
);
1143 client_set_state(client
, DHCP6_STATE_STOPPED
);
1145 client_notify(client
, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST
);
1149 static int client_process_reply(
1150 sd_dhcp6_client
*client
,
1151 DHCP6Message
*message
,
1153 const triple_timestamp
*timestamp
,
1154 const struct in6_addr
*server_address
) {
1156 _cleanup_(sd_dhcp6_lease_unrefp
) sd_dhcp6_lease
*lease
= NULL
;
1162 if (message
->type
!= DHCP6_MESSAGE_REPLY
)
1163 return log_invalid_message_type(client
, message
);
1165 r
= dhcp6_lease_new_from_message(client
, message
, len
, timestamp
, server_address
, &lease
);
1166 if (r
== -EADDRNOTAVAIL
) {
1168 /* If NoBinding status code is received, we cannot request the address anymore.
1169 * Let's restart transaction from the beginning. */
1171 if (client
->state
== DHCP6_STATE_REQUEST
)
1172 /* The lease is not acquired yet, hence it is not necessary to notify the restart. */
1173 client_cleanup(client
);
1175 /* We need to notify the previous lease was expired. */
1176 client_stop(client
, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE
);
1178 return client_start_transaction(client
, DHCP6_STATE_SOLICITATION
);
1181 return log_dhcp6_client_errno(client
, r
, "Failed to process received reply message, ignoring: %m");
1183 log_dhcp6_client(client
, "Processed %s message", dhcp6_message_type_to_string(message
->type
));
1185 sd_dhcp6_lease_unref(client
->lease
);
1186 client
->lease
= TAKE_PTR(lease
);
1188 return client_enter_bound_state(client
);
1191 static int client_process_advertise_or_rapid_commit_reply(
1192 sd_dhcp6_client
*client
,
1193 DHCP6Message
*message
,
1195 const triple_timestamp
*timestamp
,
1196 const struct in6_addr
*server_address
) {
1198 _cleanup_(sd_dhcp6_lease_unrefp
) sd_dhcp6_lease
*lease
= NULL
;
1199 uint8_t pref_advertise
, pref_lease
= 0;
1205 if (!IN_SET(message
->type
, DHCP6_MESSAGE_ADVERTISE
, DHCP6_MESSAGE_REPLY
))
1206 return log_invalid_message_type(client
, message
);
1208 r
= dhcp6_lease_new_from_message(client
, message
, len
, timestamp
, server_address
, &lease
);
1210 return log_dhcp6_client_errno(client
, r
, "Failed to process received %s message, ignoring: %m",
1211 dhcp6_message_type_to_string(message
->type
));
1213 if (message
->type
== DHCP6_MESSAGE_REPLY
) {
1216 if (!client
->rapid_commit
)
1217 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
1218 "Received unexpected reply message, even we sent a solicit message without the rapid commit option, ignoring.");
1220 r
= dhcp6_lease_get_rapid_commit(lease
, &rapid_commit
);
1225 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
1226 "Received reply message without rapid commit flag, ignoring.");
1228 log_dhcp6_client(client
, "Processed %s message", dhcp6_message_type_to_string(message
->type
));
1230 sd_dhcp6_lease_unref(client
->lease
);
1231 client
->lease
= TAKE_PTR(lease
);
1233 return client_enter_bound_state(client
);
1236 r
= dhcp6_lease_get_preference(lease
, &pref_advertise
);
1240 if (client
->lease
) {
1241 r
= dhcp6_lease_get_preference(client
->lease
, &pref_lease
);
1246 log_dhcp6_client(client
, "Processed %s message", dhcp6_message_type_to_string(message
->type
));
1248 if (!client
->lease
|| pref_advertise
> pref_lease
) {
1249 /* If this is the first advertise message or has higher preference, then save the lease. */
1250 sd_dhcp6_lease_unref(client
->lease
);
1251 client
->lease
= TAKE_PTR(lease
);
1254 if (pref_advertise
== 255 || client
->retransmit_count
> 1)
1255 (void) client_start_transaction(client
, DHCP6_STATE_REQUEST
);
1260 static int client_receive_message(
1266 sd_dhcp6_client
*client
= ASSERT_PTR(userdata
);
1267 DHCP6_CLIENT_DONT_DESTROY(client
);
1268 /* This needs to be initialized with zero. See #20741. */
1269 CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL
) control
= {};
1271 union sockaddr_union sa
= {};
1272 struct msghdr msg
= {
1274 .msg_namelen
= sizeof(sa
),
1277 .msg_control
= &control
,
1278 .msg_controllen
= sizeof(control
),
1280 triple_timestamp t
= {};
1281 _cleanup_free_ DHCP6Message
*message
= NULL
;
1282 struct in6_addr
*server_address
= NULL
;
1283 ssize_t buflen
, len
;
1285 buflen
= next_datagram_size_fd(fd
);
1286 if (ERRNO_IS_NEG_TRANSIENT(buflen
) || ERRNO_IS_NEG_DISCONNECT(buflen
))
1289 log_dhcp6_client_errno(client
, buflen
, "Failed to determine datagram size to read, ignoring: %m");
1293 message
= malloc(buflen
);
1297 iov
= IOVEC_MAKE(message
, buflen
);
1299 len
= recvmsg_safe(fd
, &msg
, MSG_DONTWAIT
);
1300 if (ERRNO_IS_NEG_TRANSIENT(len
) || ERRNO_IS_NEG_DISCONNECT(len
))
1303 log_dhcp6_client_errno(client
, len
, "Could not receive message from UDP socket, ignoring: %m");
1306 if ((size_t) len
< sizeof(DHCP6Message
)) {
1307 log_dhcp6_client(client
, "Too small to be DHCP6 message: ignoring");
1311 /* msg_namelen == 0 happens when running the test-suite over a socketpair */
1312 if (msg
.msg_namelen
> 0) {
1313 if (msg
.msg_namelen
!= sizeof(struct sockaddr_in6
) || sa
.in6
.sin6_family
!= AF_INET6
) {
1314 log_dhcp6_client(client
, "Received message from invalid source, ignoring.");
1318 server_address
= &sa
.in6
.sin6_addr
;
1321 struct timeval
*tv
= CMSG_FIND_AND_COPY_DATA(&msg
, SOL_SOCKET
, SCM_TIMESTAMP
, struct timeval
);
1323 triple_timestamp_from_realtime(&t
, timeval_load(tv
));
1325 if (client
->transaction_id
!= (message
->transaction_id
& htobe32(0x00ffffff)))
1328 switch (client
->state
) {
1329 case DHCP6_STATE_INFORMATION_REQUEST
:
1330 if (client_process_information(client
, message
, len
, &t
, server_address
) < 0)
1334 case DHCP6_STATE_SOLICITATION
:
1335 if (client_process_advertise_or_rapid_commit_reply(client
, message
, len
, &t
, server_address
) < 0)
1339 case DHCP6_STATE_REQUEST
:
1340 case DHCP6_STATE_RENEW
:
1341 case DHCP6_STATE_REBIND
:
1342 if (client_process_reply(client
, message
, len
, &t
, server_address
) < 0)
1346 case DHCP6_STATE_BOUND
:
1347 case DHCP6_STATE_STOPPED
:
1348 case DHCP6_STATE_STOPPING
:
1350 assert_not_reached();
1356 static int client_send_release(sd_dhcp6_client
*client
) {
1357 sd_dhcp6_lease
*lease
;
1361 if (!client
->send_release
)
1364 if (sd_dhcp6_client_get_lease(client
, &lease
) < 0)
1367 if (!lease
->ia_na
&& !lease
->ia_pd
)
1370 client_set_state(client
, DHCP6_STATE_STOPPING
);
1371 return dhcp6_client_send_message(client
);
1374 int sd_dhcp6_client_stop(sd_dhcp6_client
*client
) {
1380 /* Intentionally ignoring failure to send DHCP6 release. The DHCPv6 client
1381 * engine is about to release its UDP socket unconditionally. */
1382 r
= client_send_release(client
);
1384 log_dhcp6_client_errno(client
, r
,
1385 "Failed to send DHCP6 release message, ignoring: %m");
1387 client_stop(client
, SD_DHCP6_CLIENT_EVENT_STOP
);
1389 client
->receive_message
= sd_event_source_unref(client
->receive_message
);
1390 client
->fd
= safe_close(client
->fd
);
1395 int sd_dhcp6_client_is_running(sd_dhcp6_client
*client
) {
1396 assert_return(client
, -EINVAL
);
1398 return client
->state
!= DHCP6_STATE_STOPPED
;
1401 int sd_dhcp6_client_start(sd_dhcp6_client
*client
) {
1402 DHCP6State state
= DHCP6_STATE_SOLICITATION
;
1405 assert_return(client
, -EINVAL
);
1406 assert_return(client
->event
, -EINVAL
);
1407 assert_return(client
->ifindex
> 0, -EINVAL
);
1408 assert_return(in6_addr_is_link_local(&client
->local_address
) > 0, -EINVAL
);
1409 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
1410 assert_return(client
->information_request
|| client
->request_ia
!= 0, -EINVAL
);
1412 /* Even if the client is in the STOPPED state, the lease acquired in the previous information
1413 * request may be stored. */
1414 client
->lease
= sd_dhcp6_lease_unref(client
->lease
);
1416 r
= client_ensure_iaid(client
);
1420 r
= client_ensure_duid(client
);
1424 if (client
->fd
< 0) {
1425 r
= dhcp6_network_bind_udp_socket(client
->ifindex
, &client
->local_address
);
1427 return log_dhcp6_client_errno(client
, r
,
1428 "Failed to bind to UDP socket at address %s: %m",
1429 IN6_ADDR_TO_STRING(&client
->local_address
));
1434 if (!client
->receive_message
) {
1435 _cleanup_(sd_event_source_disable_unrefp
) sd_event_source
*s
= NULL
;
1437 r
= sd_event_add_io(client
->event
, &s
, client
->fd
, EPOLLIN
, client_receive_message
, client
);
1441 r
= sd_event_source_set_priority(s
, client
->event_priority
);
1445 r
= sd_event_source_set_description(s
, "dhcp6-receive-message");
1449 client
->receive_message
= TAKE_PTR(s
);
1452 if (client
->information_request
) {
1453 usec_t t
= now(CLOCK_MONOTONIC
);
1455 if (t
< usec_add(client
->information_request_time_usec
, client
->information_refresh_time_usec
))
1458 client
->information_request_time_usec
= t
;
1459 state
= DHCP6_STATE_INFORMATION_REQUEST
;
1462 log_dhcp6_client(client
, "Starting in %s mode",
1463 client
->information_request
? "Information request" : "Solicit");
1465 return client_start_transaction(client
, state
);
1468 int sd_dhcp6_client_attach_event(sd_dhcp6_client
*client
, sd_event
*event
, int64_t priority
) {
1471 assert_return(client
, -EINVAL
);
1472 assert_return(!client
->event
, -EBUSY
);
1473 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
1476 client
->event
= sd_event_ref(event
);
1478 r
= sd_event_default(&client
->event
);
1483 client
->event_priority
= priority
;
1488 int sd_dhcp6_client_detach_event(sd_dhcp6_client
*client
) {
1489 assert_return(client
, -EINVAL
);
1490 assert_return(!sd_dhcp6_client_is_running(client
), -EBUSY
);
1492 client
->event
= sd_event_unref(client
->event
);
1497 sd_event
*sd_dhcp6_client_get_event(sd_dhcp6_client
*client
) {
1498 assert_return(client
, NULL
);
1500 return client
->event
;
1503 int sd_dhcp6_client_attach_device(sd_dhcp6_client
*client
, sd_device
*dev
) {
1504 assert_return(client
, -EINVAL
);
1506 return device_unref_and_replace(client
->dev
, dev
);
1509 static sd_dhcp6_client
*dhcp6_client_free(sd_dhcp6_client
*client
) {
1513 sd_dhcp6_lease_unref(client
->lease
);
1515 sd_event_source_disable_unref(client
->receive_message
);
1516 sd_event_source_disable_unref(client
->timeout_resend
);
1517 sd_event_source_disable_unref(client
->timeout_expire
);
1518 sd_event_source_disable_unref(client
->timeout_t1
);
1519 sd_event_source_disable_unref(client
->timeout_t2
);
1520 sd_event_unref(client
->event
);
1522 client
->fd
= safe_close(client
->fd
);
1524 sd_device_unref(client
->dev
);
1526 free(client
->req_opts
);
1528 free(client
->mudurl
);
1529 dhcp6_ia_clear_addresses(&client
->ia_pd
);
1530 ordered_hashmap_free(client
->extra_options
);
1531 ordered_set_free(client
->vendor_options
);
1532 strv_free(client
->user_class
);
1533 strv_free(client
->vendor_class
);
1534 free(client
->ifname
);
1536 return mfree(client
);
1539 DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_client
, sd_dhcp6_client
, dhcp6_client_free
);
1541 int sd_dhcp6_client_new(sd_dhcp6_client
**ret
) {
1542 _cleanup_(sd_dhcp6_client_unrefp
) sd_dhcp6_client
*client
= NULL
;
1544 assert_return(ret
, -EINVAL
);
1546 client
= new(sd_dhcp6_client
, 1);
1550 *client
= (sd_dhcp6_client
) {
1552 .ia_na
.type
= SD_DHCP6_OPTION_IA_NA
,
1553 .ia_pd
.type
= SD_DHCP6_OPTION_IA_PD
,
1555 .request_ia
= DHCP6_REQUEST_IA_NA
| DHCP6_REQUEST_IA_PD
,
1557 .rapid_commit
= true,
1560 *ret
= TAKE_PTR(client
);