1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 Copyright © 2014-2015 Intel Corporation. All rights reserved.
7 #include <netinet/in.h>
9 #include "sd-dhcp6-client.h"
11 #include "alloc-util.h"
12 #include "dhcp-identifier.h"
13 #include "dhcp6-internal.h"
14 #include "dhcp6-lease-internal.h"
15 #include "dhcp6-protocol.h"
16 #include "dns-domain.h"
17 #include "memory-util.h"
18 #include "sparse-endian.h"
20 #include "unaligned.h"
22 typedef struct DHCP6StatusOption
{
23 struct DHCP6Option option
;
26 } _packed_ DHCP6StatusOption
;
28 typedef struct DHCP6AddressOption
{
29 struct DHCP6Option option
;
32 } _packed_ DHCP6AddressOption
;
34 typedef struct DHCP6PDPrefixOption
{
35 struct DHCP6Option option
;
36 struct iapdprefix iapdprefix
;
38 } _packed_ DHCP6PDPrefixOption
;
40 #define DHCP6_OPTION_IA_NA_LEN (sizeof(struct ia_na))
41 #define DHCP6_OPTION_IA_PD_LEN (sizeof(struct ia_pd))
42 #define DHCP6_OPTION_IA_TA_LEN (sizeof(struct ia_ta))
44 static int option_append_hdr(uint8_t **buf
, size_t *buflen
, uint16_t optcode
, size_t optlen
) {
47 assert_return(buf
, -EINVAL
);
48 assert_return(*buf
, -EINVAL
);
49 assert_return(buflen
, -EINVAL
);
51 option
= (DHCP6Option
*) *buf
;
53 if (optlen
> 0xffff || *buflen
< optlen
+ offsetof(DHCP6Option
, data
))
56 option
->code
= htobe16(optcode
);
57 option
->len
= htobe16(optlen
);
59 *buf
+= offsetof(DHCP6Option
, data
);
60 *buflen
-= offsetof(DHCP6Option
, data
);
65 int dhcp6_option_append(uint8_t **buf
, size_t *buflen
, uint16_t code
,
66 size_t optlen
, const void *optval
) {
69 assert_return(optval
|| optlen
== 0, -EINVAL
);
71 r
= option_append_hdr(buf
, buflen
, code
, optlen
);
75 memcpy_safe(*buf
, optval
, optlen
);
83 int dhcp6_option_append_vendor_option(uint8_t **buf
, size_t *buflen
, OrderedHashmap
*vendor_options
) {
84 sd_dhcp6_option
*options
;
90 assert(vendor_options
);
92 ORDERED_HASHMAP_FOREACH(options
, vendor_options
) {
93 _cleanup_free_
uint8_t *p
= NULL
;
96 total
= 4 + 2 + 2 + options
->length
;
102 unaligned_write_be32(p
, options
->enterprise_identifier
);
103 unaligned_write_be16(p
+ 4, options
->option
);
104 unaligned_write_be16(p
+ 6, options
->length
);
105 memcpy(p
+ 8, options
->data
, options
->length
);
107 r
= dhcp6_option_append(buf
, buflen
, SD_DHCP6_OPTION_VENDOR_OPTS
, total
, p
);
115 int dhcp6_option_append_ia(uint8_t **buf
, size_t *buflen
, const DHCP6IA
*ia
) {
116 size_t ia_buflen
, ia_addrlen
= 0;
125 assert_return(buf
, -EINVAL
);
126 assert_return(*buf
, -EINVAL
);
127 assert_return(buflen
, -EINVAL
);
128 assert_return(ia
, -EINVAL
);
130 /* client should not send set T1 and T2. See, RFC 8415, and issue #18090. */
133 case SD_DHCP6_OPTION_IA_NA
:
134 len
= DHCP6_OPTION_IA_NA_LEN
;
135 ia_na
= (struct ia_na
) {
141 case SD_DHCP6_OPTION_IA_TA
:
142 len
= DHCP6_OPTION_IA_TA_LEN
;
143 ia_ta
= (struct ia_ta
) {
153 if (*buflen
< offsetof(DHCP6Option
, data
) + len
)
159 *buf
+= offsetof(DHCP6Option
, data
);
160 *buflen
-= offsetof(DHCP6Option
, data
);
162 memcpy(*buf
, p
, len
);
167 LIST_FOREACH(addresses
, addr
, ia
->addresses
) {
169 .address
= addr
->iaaddr
.address
,
172 r
= option_append_hdr(buf
, buflen
, SD_DHCP6_OPTION_IAADDR
, sizeof(struct iaaddr
));
176 memcpy(*buf
, &a
, sizeof(struct iaaddr
));
178 *buf
+= sizeof(struct iaaddr
);
179 *buflen
-= sizeof(struct iaaddr
);
181 ia_addrlen
+= offsetof(DHCP6Option
, data
) + sizeof(struct iaaddr
);
184 return option_append_hdr(&ia_hdr
, &ia_buflen
, ia
->type
, len
+ ia_addrlen
);
187 static int option_append_pd_prefix(uint8_t **buf
, size_t *buflen
, const DHCP6Address
*prefix
) {
196 if (prefix
->iapdprefix
.prefixlen
== 0)
199 /* Do not append T1 and T2. */
201 p
= (struct iapdprefix
) {
202 .prefixlen
= prefix
->iapdprefix
.prefixlen
,
203 .address
= prefix
->iapdprefix
.address
,
206 r
= option_append_hdr(buf
, buflen
, SD_DHCP6_OPTION_IA_PD_PREFIX
, sizeof(struct iapdprefix
));
210 memcpy(*buf
, &p
, sizeof(struct iapdprefix
));
212 *buf
+= sizeof(struct iapdprefix
);
213 *buflen
-= sizeof(struct iapdprefix
);
215 return offsetof(DHCP6Option
, data
) + sizeof(struct iapdprefix
);
218 int dhcp6_option_append_pd(uint8_t **buf
, size_t *buflen
, const DHCP6IA
*pd
, const DHCP6Address
*hint_pd_prefix
) {
220 size_t len
, pd_buflen
;
224 assert_return(buf
, -EINVAL
);
225 assert_return(*buf
, -EINVAL
);
226 assert_return(buflen
, -EINVAL
);
227 assert_return(pd
, -EINVAL
);
228 assert_return(pd
->type
== SD_DHCP6_OPTION_IA_PD
, -EINVAL
);
230 /* Do not set T1 and T2. */
231 ia_pd
= (struct ia_pd
) {
234 len
= sizeof(struct ia_pd
);
236 if (*buflen
< offsetof(DHCP6Option
, data
) + len
)
242 /* The header will be written at the end of this function. */
243 *buf
+= offsetof(DHCP6Option
, data
);
244 *buflen
-= offsetof(DHCP6Option
, data
);
246 memcpy(*buf
, &ia_pd
, len
);
248 *buf
+= sizeof(struct ia_pd
);
249 *buflen
-= sizeof(struct ia_pd
);
251 DHCP6Address
*prefix
;
252 LIST_FOREACH(addresses
, prefix
, pd
->addresses
) {
253 r
= option_append_pd_prefix(buf
, buflen
, prefix
);
260 if (hint_pd_prefix
&& hint_pd_prefix
->iapdprefix
.prefixlen
> 0) {
261 r
= option_append_pd_prefix(buf
, buflen
, hint_pd_prefix
);
268 return option_append_hdr(&pd_hdr
, &pd_buflen
, pd
->type
, len
);
271 int dhcp6_option_append_fqdn(uint8_t **buf
, size_t *buflen
, const char *fqdn
) {
272 uint8_t buffer
[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX
];
275 assert_return(buf
&& *buf
&& buflen
&& fqdn
, -EINVAL
);
277 buffer
[0] = DHCP6_FQDN_FLAG_S
; /* Request server to perform AAAA RR DNS updates */
279 /* Store domain name after flags field */
280 r
= dns_name_to_wire_format(fqdn
, buffer
+ 1, sizeof(buffer
) - 1, false);
285 * According to RFC 4704, chapter 4.2 only add terminating zero-length
286 * label in case a FQDN is provided. Since dns_name_to_wire_format
287 * always adds terminating zero-length label remove if only a hostname
290 if (dns_name_is_single_label(fqdn
))
293 r
= dhcp6_option_append(buf
, buflen
, SD_DHCP6_OPTION_FQDN
, 1 + r
, buffer
);
298 int dhcp6_option_append_user_class(uint8_t **buf
, size_t *buflen
, char * const *user_class
) {
299 _cleanup_free_
uint8_t *p
= NULL
;
300 size_t total
= 0, offset
= 0;
306 assert(!strv_isempty(user_class
));
308 STRV_FOREACH(s
, user_class
) {
309 size_t len
= strlen(*s
);
312 if (len
> 0xffff || len
== 0)
314 q
= realloc(p
, total
+ len
+ 2);
320 unaligned_write_be16(&p
[offset
], len
);
321 memcpy(&p
[offset
+ 2], *s
, len
);
327 return dhcp6_option_append(buf
, buflen
, SD_DHCP6_OPTION_USER_CLASS
, total
, p
);
330 int dhcp6_option_append_vendor_class(uint8_t **buf
, size_t *buflen
, char * const *vendor_class
) {
331 _cleanup_free_
uint8_t *p
= NULL
;
332 uint32_t enterprise_identifier
;
333 size_t total
, offset
;
339 assert(!strv_isempty(vendor_class
));
341 enterprise_identifier
= htobe32(SYSTEMD_PEN
);
343 p
= memdup(&enterprise_identifier
, sizeof(enterprise_identifier
));
347 total
= sizeof(enterprise_identifier
);
350 STRV_FOREACH(s
, vendor_class
) {
351 size_t len
= strlen(*s
);
354 if (len
> UINT16_MAX
|| len
== 0)
357 q
= realloc(p
, total
+ len
+ 2);
363 unaligned_write_be16(&p
[offset
], len
);
364 memcpy(&p
[offset
+ 2], *s
, len
);
370 return dhcp6_option_append(buf
, buflen
, SD_DHCP6_OPTION_VENDOR_CLASS
, total
, p
);
373 static int option_parse_hdr(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
, size_t *optlen
) {
374 DHCP6Option
*option
= (DHCP6Option
*) *buf
;
377 assert_return(buf
, -EINVAL
);
378 assert_return(optcode
, -EINVAL
);
379 assert_return(optlen
, -EINVAL
);
381 if (*buflen
< offsetof(DHCP6Option
, data
))
384 len
= be16toh(option
->len
);
389 *optcode
= be16toh(option
->code
);
398 int dhcp6_option_parse(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
,
399 size_t *optlen
, uint8_t **optvalue
) {
402 assert_return(buf
&& buflen
&& optcode
&& optlen
&& optvalue
, -EINVAL
);
404 r
= option_parse_hdr(buf
, buflen
, optcode
, optlen
);
408 if (*optlen
> *buflen
)
418 int dhcp6_option_parse_status(DHCP6Option
*option
, size_t len
) {
419 DHCP6StatusOption
*statusopt
= (DHCP6StatusOption
*)option
;
421 if (len
< sizeof(DHCP6StatusOption
) ||
422 be16toh(option
->len
) + offsetof(DHCP6Option
, data
) < sizeof(DHCP6StatusOption
))
425 return be16toh(statusopt
->status
);
428 static int dhcp6_option_parse_address(sd_dhcp6_client
*client
, DHCP6Option
*option
, DHCP6IA
*ia
, uint32_t *ret_lifetime_valid
) {
429 DHCP6AddressOption
*addr_option
= (DHCP6AddressOption
*)option
;
431 uint32_t lt_valid
, lt_pref
;
434 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) < sizeof(*addr_option
))
437 lt_valid
= be32toh(addr_option
->iaaddr
.lifetime_valid
);
438 lt_pref
= be32toh(addr_option
->iaaddr
.lifetime_preferred
);
440 if (lt_valid
== 0 || lt_pref
> lt_valid
)
441 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
442 "Valid lifetime of an IA address is zero or "
443 "preferred lifetime %"PRIu32
" > valid lifetime %"PRIu32
,
446 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) > sizeof(*addr_option
)) {
447 r
= dhcp6_option_parse_status((DHCP6Option
*)addr_option
->options
, be16toh(option
->len
) + offsetof(DHCP6Option
, data
) - sizeof(*addr_option
));
451 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
452 "Non-zero status code '%s' for address is received",
453 dhcp6_message_status_to_string(r
));
456 addr
= new0(DHCP6Address
, 1);
460 LIST_INIT(addresses
, addr
);
461 memcpy(&addr
->iaaddr
, option
->data
, sizeof(addr
->iaaddr
));
463 LIST_PREPEND(addresses
, ia
->addresses
, addr
);
465 *ret_lifetime_valid
= be32toh(addr
->iaaddr
.lifetime_valid
);
470 static int dhcp6_option_parse_pdprefix(sd_dhcp6_client
*client
, DHCP6Option
*option
, DHCP6IA
*ia
, uint32_t *ret_lifetime_valid
) {
471 DHCP6PDPrefixOption
*pdprefix_option
= (DHCP6PDPrefixOption
*)option
;
472 DHCP6Address
*prefix
;
473 uint32_t lt_valid
, lt_pref
;
476 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) < sizeof(*pdprefix_option
))
479 lt_valid
= be32toh(pdprefix_option
->iapdprefix
.lifetime_valid
);
480 lt_pref
= be32toh(pdprefix_option
->iapdprefix
.lifetime_preferred
);
482 if (lt_valid
== 0 || lt_pref
> lt_valid
)
483 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
484 "Valid lifetieme of a PD prefix is zero or "
485 "preferred lifetime %"PRIu32
" > valid lifetime %"PRIu32
,
488 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) > sizeof(*pdprefix_option
)) {
489 r
= dhcp6_option_parse_status((DHCP6Option
*)pdprefix_option
->options
, be16toh(option
->len
) + offsetof(DHCP6Option
, data
) - sizeof(*pdprefix_option
));
493 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
494 "Non-zero status code '%s' for PD prefix is received",
495 dhcp6_message_status_to_string(r
));
498 prefix
= new0(DHCP6Address
, 1);
502 LIST_INIT(addresses
, prefix
);
503 memcpy(&prefix
->iapdprefix
, option
->data
, sizeof(prefix
->iapdprefix
));
505 LIST_PREPEND(addresses
, ia
->addresses
, prefix
);
507 *ret_lifetime_valid
= be32toh(prefix
->iapdprefix
.lifetime_valid
);
512 int dhcp6_option_parse_ia(sd_dhcp6_client
*client
, DHCP6Option
*iaoption
, DHCP6IA
*ia
, uint16_t *ret_status_code
) {
513 uint32_t lt_t1
, lt_t2
, lt_valid
= 0, lt_min
= UINT32_MAX
;
514 uint16_t iatype
, optlen
;
515 size_t iaaddr_offset
;
520 assert_return(ia
, -EINVAL
);
521 assert_return(!ia
->addresses
, -EINVAL
);
523 iatype
= be16toh(iaoption
->code
);
524 len
= be16toh(iaoption
->len
);
527 case SD_DHCP6_OPTION_IA_NA
:
529 if (len
< DHCP6_OPTION_IA_NA_LEN
)
532 iaaddr_offset
= DHCP6_OPTION_IA_NA_LEN
;
533 memcpy(&ia
->ia_na
, iaoption
->data
, sizeof(ia
->ia_na
));
535 lt_t1
= be32toh(ia
->ia_na
.lifetime_t1
);
536 lt_t2
= be32toh(ia
->ia_na
.lifetime_t2
);
539 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
540 "IA NA T1 %"PRIu32
"sec > T2 %"PRIu32
"sec",
545 case SD_DHCP6_OPTION_IA_PD
:
547 if (len
< sizeof(ia
->ia_pd
))
550 iaaddr_offset
= sizeof(ia
->ia_pd
);
551 memcpy(&ia
->ia_pd
, iaoption
->data
, sizeof(ia
->ia_pd
));
553 lt_t1
= be32toh(ia
->ia_pd
.lifetime_t1
);
554 lt_t2
= be32toh(ia
->ia_pd
.lifetime_t2
);
557 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
558 "IA PD T1 %"PRIu32
"sec > T2 %"PRIu32
"sec",
563 case SD_DHCP6_OPTION_IA_TA
:
564 if (len
< DHCP6_OPTION_IA_TA_LEN
)
567 iaaddr_offset
= DHCP6_OPTION_IA_TA_LEN
;
568 memcpy(&ia
->ia_ta
.id
, iaoption
->data
, sizeof(ia
->ia_ta
));
580 DHCP6Option
*option
= (DHCP6Option
*)&iaoption
->data
[i
];
582 if (len
< i
+ sizeof(*option
) || len
< i
+ sizeof(*option
) + be16toh(option
->len
))
585 opt
= be16toh(option
->code
);
586 optlen
= be16toh(option
->len
);
589 case SD_DHCP6_OPTION_IAADDR
:
591 if (!IN_SET(ia
->type
, SD_DHCP6_OPTION_IA_NA
, SD_DHCP6_OPTION_IA_TA
))
592 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
593 "IA Address option not in IA NA or TA option");
595 r
= dhcp6_option_parse_address(client
, option
, ia
, <_valid
);
596 if (r
< 0 && r
!= -EINVAL
)
598 if (r
>= 0 && lt_valid
< lt_min
)
603 case SD_DHCP6_OPTION_IA_PD_PREFIX
:
605 if (!IN_SET(ia
->type
, SD_DHCP6_OPTION_IA_PD
))
606 return log_dhcp6_client_errno(client
, SYNTHETIC_ERRNO(EINVAL
),
607 "IA PD Prefix option not in IA PD option");
609 r
= dhcp6_option_parse_pdprefix(client
, option
, ia
, <_valid
);
610 if (r
< 0 && r
!= -EINVAL
)
612 if (r
>= 0 && lt_valid
< lt_min
)
617 case SD_DHCP6_OPTION_STATUS_CODE
:
619 status
= dhcp6_option_parse_status(option
, optlen
+ offsetof(DHCP6Option
, data
));
625 *ret_status_code
= status
;
627 log_dhcp6_client(client
, "IA status %s",
628 dhcp6_message_status_to_string(status
));
636 log_dhcp6_client(client
, "Unknown IA option %d", opt
);
640 i
+= sizeof(*option
) + optlen
;
644 case SD_DHCP6_OPTION_IA_NA
:
645 if (ia
->ia_na
.lifetime_t1
== 0 && ia
->ia_na
.lifetime_t2
== 0 && lt_min
!= UINT32_MAX
) {
647 lt_t2
= lt_min
/ 10 * 8;
648 ia
->ia_na
.lifetime_t1
= htobe32(lt_t1
);
649 ia
->ia_na
.lifetime_t2
= htobe32(lt_t2
);
651 log_dhcp6_client(client
, "Computed IA NA T1 %"PRIu32
"sec and T2 %"PRIu32
"sec as both were zero",
657 case SD_DHCP6_OPTION_IA_PD
:
658 if (ia
->ia_pd
.lifetime_t1
== 0 && ia
->ia_pd
.lifetime_t2
== 0 && lt_min
!= UINT32_MAX
) {
660 lt_t2
= lt_min
/ 10 * 8;
661 ia
->ia_pd
.lifetime_t1
= htobe32(lt_t1
);
662 ia
->ia_pd
.lifetime_t2
= htobe32(lt_t2
);
664 log_dhcp6_client(client
, "Computed IA PD T1 %"PRIu32
"sec and T2 %"PRIu32
"sec as both were zero",
675 *ret_status_code
= 0;
680 int dhcp6_option_parse_ip6addrs(uint8_t *optval
, uint16_t optlen
,
681 struct in6_addr
**addrs
, size_t count
,
684 if (optlen
== 0 || optlen
% sizeof(struct in6_addr
) != 0)
687 if (!GREEDY_REALLOC(*addrs
, *allocated
,
688 count
* sizeof(struct in6_addr
) + optlen
))
691 memcpy(*addrs
+ count
, optval
, optlen
);
693 count
+= optlen
/ sizeof(struct in6_addr
);
698 static int parse_domain(const uint8_t **data
, uint16_t *len
, char **out_domain
) {
699 _cleanup_free_
char *ret
= NULL
;
700 size_t n
= 0, allocated
= 0;
701 const uint8_t *optval
= *data
;
702 uint16_t optlen
= *len
;
729 label
= (const char *)optval
;
733 if (!GREEDY_REALLOC(ret
, allocated
, n
+ !first
+ DNS_LABEL_ESCAPED_MAX
))
741 r
= dns_label_escape(label
, c
, ret
+ n
, DNS_LABEL_ESCAPED_MAX
);
749 if (!GREEDY_REALLOC(ret
, allocated
, n
+ 1))
754 *out_domain
= TAKE_PTR(ret
);
761 int dhcp6_option_parse_domainname(const uint8_t *optval
, uint16_t optlen
, char **str
) {
762 _cleanup_free_
char *domain
= NULL
;
765 r
= parse_domain(&optval
, &optlen
, &domain
);
773 *str
= TAKE_PTR(domain
);
777 int dhcp6_option_parse_domainname_list(const uint8_t *optval
, uint16_t optlen
, char ***str_arr
) {
779 _cleanup_strv_free_
char **names
= NULL
;
784 if (optval
[optlen
- 1] != '\0')
788 _cleanup_free_
char *ret
= NULL
;
790 r
= parse_domain(&optval
, &optlen
, &ret
);
796 r
= strv_extend(&names
, ret
);
803 *str_arr
= TAKE_PTR(names
);
808 static sd_dhcp6_option
* dhcp6_option_free(sd_dhcp6_option
*i
) {
816 int sd_dhcp6_option_new(uint16_t option
, const void *data
, size_t length
, uint32_t enterprise_identifier
, sd_dhcp6_option
**ret
) {
817 assert_return(ret
, -EINVAL
);
818 assert_return(length
== 0 || data
, -EINVAL
);
820 _cleanup_free_
void *q
= memdup(data
, length
);
824 sd_dhcp6_option
*p
= new(sd_dhcp6_option
, 1);
828 *p
= (sd_dhcp6_option
) {
831 .enterprise_identifier
= enterprise_identifier
,
840 DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option
, sd_dhcp6_option
, dhcp6_option_free
);
841 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
842 dhcp6_option_hash_ops
,
845 trivial_compare_func
,
847 sd_dhcp6_option_unref
);