1 /* SPDX-License-Identifier: LGPL-2.1+ */
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
,
46 DHCP6Option
*option
= (DHCP6Option
*) *buf
;
48 assert_return(buf
, -EINVAL
);
49 assert_return(*buf
, -EINVAL
);
50 assert_return(buflen
, -EINVAL
);
52 if (optlen
> 0xffff || *buflen
< optlen
+ offsetof(DHCP6Option
, data
))
55 option
->code
= htobe16(optcode
);
56 option
->len
= htobe16(optlen
);
58 *buf
+= offsetof(DHCP6Option
, data
);
59 *buflen
-= offsetof(DHCP6Option
, data
);
64 int dhcp6_option_append(uint8_t **buf
, size_t *buflen
, uint16_t code
,
65 size_t optlen
, const void *optval
) {
68 assert_return(optval
|| optlen
== 0, -EINVAL
);
70 r
= option_append_hdr(buf
, buflen
, code
, optlen
);
74 memcpy_safe(*buf
, optval
, optlen
);
82 int dhcp6_option_append_vendor_option(uint8_t **buf
, size_t *buflen
, OrderedHashmap
*vendor_options
) {
83 sd_dhcp6_option
*options
;
89 assert(vendor_options
);
91 ORDERED_HASHMAP_FOREACH(options
, vendor_options
) {
92 _cleanup_free_
uint8_t *p
= NULL
;
95 total
= 4 + 2 + 2 + options
->length
;
101 unaligned_write_be32(p
, options
->enterprise_identifier
);
102 unaligned_write_be16(p
+ 4, options
->option
);
103 unaligned_write_be16(p
+ 6, options
->length
);
104 memcpy(p
+ 8, options
->data
, options
->length
);
106 r
= dhcp6_option_append(buf
, buflen
, SD_DHCP6_OPTION_VENDOR_OPTS
, total
, p
);
114 int dhcp6_option_append_ia(uint8_t **buf
, size_t *buflen
, const DHCP6IA
*ia
) {
117 size_t iaid_offset
, ia_buflen
, ia_addrlen
= 0;
121 assert_return(buf
, -EINVAL
);
122 assert_return(*buf
, -EINVAL
);
123 assert_return(buflen
, -EINVAL
);
124 assert_return(ia
, -EINVAL
);
127 case SD_DHCP6_OPTION_IA_NA
:
128 len
= DHCP6_OPTION_IA_NA_LEN
;
129 iaid_offset
= offsetof(DHCP6IA
, ia_na
);
132 case SD_DHCP6_OPTION_IA_TA
:
133 len
= DHCP6_OPTION_IA_TA_LEN
;
134 iaid_offset
= offsetof(DHCP6IA
, ia_ta
);
141 if (*buflen
< offsetof(DHCP6Option
, data
) + len
)
147 *buf
+= offsetof(DHCP6Option
, data
);
148 *buflen
-= offsetof(DHCP6Option
, data
);
150 memcpy(*buf
, (char*) ia
+ iaid_offset
, len
);
155 LIST_FOREACH(addresses
, addr
, ia
->addresses
) {
156 r
= option_append_hdr(buf
, buflen
, SD_DHCP6_OPTION_IAADDR
,
157 sizeof(addr
->iaaddr
));
161 memcpy(*buf
, &addr
->iaaddr
, sizeof(addr
->iaaddr
));
163 *buf
+= sizeof(addr
->iaaddr
);
164 *buflen
-= sizeof(addr
->iaaddr
);
166 ia_addrlen
+= offsetof(DHCP6Option
, data
) + sizeof(addr
->iaaddr
);
169 r
= option_append_hdr(&ia_hdr
, &ia_buflen
, ia
->type
, len
+ ia_addrlen
);
176 int dhcp6_option_append_fqdn(uint8_t **buf
, size_t *buflen
, const char *fqdn
) {
177 uint8_t buffer
[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX
];
180 assert_return(buf
&& *buf
&& buflen
&& fqdn
, -EINVAL
);
182 buffer
[0] = DHCP6_FQDN_FLAG_S
; /* Request server to perform AAAA RR DNS updates */
184 /* Store domain name after flags field */
185 r
= dns_name_to_wire_format(fqdn
, buffer
+ 1, sizeof(buffer
) - 1, false);
190 * According to RFC 4704, chapter 4.2 only add terminating zero-length
191 * label in case a FQDN is provided. Since dns_name_to_wire_format
192 * always adds terminating zero-length label remove if only a hostname
195 if (dns_name_is_single_label(fqdn
))
198 r
= dhcp6_option_append(buf
, buflen
, SD_DHCP6_OPTION_FQDN
, 1 + r
, buffer
);
203 int dhcp6_option_append_user_class(uint8_t **buf
, size_t *buflen
, char **user_class
) {
204 _cleanup_free_
uint8_t *p
= NULL
;
205 size_t total
= 0, offset
= 0;
208 assert_return(buf
&& *buf
&& buflen
&& user_class
, -EINVAL
);
210 STRV_FOREACH(s
, user_class
) {
211 size_t len
= strlen(*s
);
215 return -ENAMETOOLONG
;
216 q
= realloc(p
, total
+ len
+ 2);
222 unaligned_write_be16(&p
[offset
], len
);
223 memcpy(&p
[offset
+ 2], *s
, len
);
229 return dhcp6_option_append(buf
, buflen
, SD_DHCP6_OPTION_USER_CLASS
, total
, p
);
232 int dhcp6_option_append_vendor_class(uint8_t **buf
, size_t *buflen
, char **vendor_class
) {
233 _cleanup_free_
uint8_t *p
= NULL
;
234 uint32_t enterprise_identifier
;
235 size_t total
, offset
;
241 assert(vendor_class
);
243 enterprise_identifier
= htobe32(SYSTEMD_PEN
);
245 p
= memdup(&enterprise_identifier
, sizeof(enterprise_identifier
));
249 total
= sizeof(enterprise_identifier
);
252 STRV_FOREACH(s
, vendor_class
) {
253 size_t len
= strlen(*s
);
256 q
= realloc(p
, total
+ len
+ 2);
262 unaligned_write_be16(&p
[offset
], len
);
263 memcpy(&p
[offset
+ 2], *s
, len
);
269 return dhcp6_option_append(buf
, buflen
, SD_DHCP6_OPTION_VENDOR_CLASS
, total
, p
);
272 int dhcp6_option_append_pd(uint8_t *buf
, size_t len
, const DHCP6IA
*pd
, DHCP6Address
*hint_pd_prefix
) {
273 DHCP6Option
*option
= (DHCP6Option
*)buf
;
274 size_t i
= sizeof(*option
) + sizeof(pd
->ia_pd
);
275 DHCP6PDPrefixOption
*prefix_opt
;
276 DHCP6Address
*prefix
;
278 assert_return(buf
, -EINVAL
);
279 assert_return(pd
, -EINVAL
);
280 assert_return(pd
->type
== SD_DHCP6_OPTION_IA_PD
, -EINVAL
);
285 option
->code
= htobe16(SD_DHCP6_OPTION_IA_PD
);
287 memcpy(&option
->data
, &pd
->ia_pd
, sizeof(pd
->ia_pd
));
288 LIST_FOREACH(addresses
, prefix
, pd
->addresses
) {
289 if (len
< i
+ sizeof(*prefix_opt
))
292 prefix_opt
= (DHCP6PDPrefixOption
*)&buf
[i
];
293 prefix_opt
->option
.code
= htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX
);
294 prefix_opt
->option
.len
= htobe16(sizeof(prefix_opt
->iapdprefix
));
296 memcpy(&prefix_opt
->iapdprefix
, &prefix
->iapdprefix
, sizeof(struct iapdprefix
));
297 i
+= sizeof(*prefix_opt
);
300 if (hint_pd_prefix
&& hint_pd_prefix
->iapdprefix
.prefixlen
> 0) {
301 if (len
< i
+ sizeof(*prefix_opt
))
304 prefix_opt
= (DHCP6PDPrefixOption
*)&buf
[i
];
305 prefix_opt
->option
.code
= htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX
);
306 prefix_opt
->option
.len
= htobe16(sizeof(prefix_opt
->iapdprefix
));
308 memcpy(&prefix_opt
->iapdprefix
, &hint_pd_prefix
->iapdprefix
, sizeof(struct iapdprefix
));
309 i
+= sizeof(*prefix_opt
);
312 option
->len
= htobe16(i
- sizeof(*option
));
317 static int option_parse_hdr(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
, size_t *optlen
) {
318 DHCP6Option
*option
= (DHCP6Option
*) *buf
;
321 assert_return(buf
, -EINVAL
);
322 assert_return(optcode
, -EINVAL
);
323 assert_return(optlen
, -EINVAL
);
325 if (*buflen
< offsetof(DHCP6Option
, data
))
328 len
= be16toh(option
->len
);
333 *optcode
= be16toh(option
->code
);
342 int dhcp6_option_parse(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
,
343 size_t *optlen
, uint8_t **optvalue
) {
346 assert_return(buf
&& buflen
&& optcode
&& optlen
&& optvalue
, -EINVAL
);
348 r
= option_parse_hdr(buf
, buflen
, optcode
, optlen
);
352 if (*optlen
> *buflen
)
362 int dhcp6_option_parse_status(DHCP6Option
*option
, size_t len
) {
363 DHCP6StatusOption
*statusopt
= (DHCP6StatusOption
*)option
;
365 if (len
< sizeof(DHCP6StatusOption
) ||
366 be16toh(option
->len
) + offsetof(DHCP6Option
, data
) < sizeof(DHCP6StatusOption
))
369 return be16toh(statusopt
->status
);
372 static int dhcp6_option_parse_address(DHCP6Option
*option
, DHCP6IA
*ia
,
373 uint32_t *lifetime_valid
) {
374 DHCP6AddressOption
*addr_option
= (DHCP6AddressOption
*)option
;
376 uint32_t lt_valid
, lt_pref
;
379 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) < sizeof(*addr_option
))
382 lt_valid
= be32toh(addr_option
->iaaddr
.lifetime_valid
);
383 lt_pref
= be32toh(addr_option
->iaaddr
.lifetime_preferred
);
385 if (lt_valid
== 0 || lt_pref
> lt_valid
) {
386 log_dhcp6_client(client
, "Valid lifetime of an IA address is zero or preferred lifetime %d > valid lifetime %d",
392 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) > sizeof(*addr_option
)) {
393 r
= dhcp6_option_parse_status((DHCP6Option
*)addr_option
->options
, be16toh(option
->len
) + offsetof(DHCP6Option
, data
) - sizeof(*addr_option
));
398 addr
= new0(DHCP6Address
, 1);
402 LIST_INIT(addresses
, addr
);
403 memcpy(&addr
->iaaddr
, option
->data
, sizeof(addr
->iaaddr
));
405 LIST_PREPEND(addresses
, ia
->addresses
, addr
);
407 *lifetime_valid
= be32toh(addr
->iaaddr
.lifetime_valid
);
412 static int dhcp6_option_parse_pdprefix(DHCP6Option
*option
, DHCP6IA
*ia
,
413 uint32_t *lifetime_valid
) {
414 DHCP6PDPrefixOption
*pdprefix_option
= (DHCP6PDPrefixOption
*)option
;
415 DHCP6Address
*prefix
;
416 uint32_t lt_valid
, lt_pref
;
419 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) < sizeof(*pdprefix_option
))
422 lt_valid
= be32toh(pdprefix_option
->iapdprefix
.lifetime_valid
);
423 lt_pref
= be32toh(pdprefix_option
->iapdprefix
.lifetime_preferred
);
425 if (lt_valid
== 0 || lt_pref
> lt_valid
) {
426 log_dhcp6_client(client
, "Valid lifetieme of a PD prefix is zero or preferred lifetime %d > valid lifetime %d",
432 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) > sizeof(*pdprefix_option
)) {
433 r
= dhcp6_option_parse_status((DHCP6Option
*)pdprefix_option
->options
, be16toh(option
->len
) + offsetof(DHCP6Option
, data
) - sizeof(*pdprefix_option
));
438 prefix
= new0(DHCP6Address
, 1);
442 LIST_INIT(addresses
, prefix
);
443 memcpy(&prefix
->iapdprefix
, option
->data
, sizeof(prefix
->iapdprefix
));
445 LIST_PREPEND(addresses
, ia
->addresses
, prefix
);
447 *lifetime_valid
= be32toh(prefix
->iapdprefix
.lifetime_valid
);
452 int dhcp6_option_parse_ia(DHCP6Option
*iaoption
, DHCP6IA
*ia
, uint16_t *ret_status_code
) {
453 uint32_t lt_t1
, lt_t2
, lt_valid
= 0, lt_min
= UINT32_MAX
;
454 uint16_t iatype
, optlen
;
455 size_t iaaddr_offset
;
460 assert_return(ia
, -EINVAL
);
461 assert_return(!ia
->addresses
, -EINVAL
);
463 iatype
= be16toh(iaoption
->code
);
464 len
= be16toh(iaoption
->len
);
467 case SD_DHCP6_OPTION_IA_NA
:
469 if (len
< DHCP6_OPTION_IA_NA_LEN
)
472 iaaddr_offset
= DHCP6_OPTION_IA_NA_LEN
;
473 memcpy(&ia
->ia_na
, iaoption
->data
, sizeof(ia
->ia_na
));
475 lt_t1
= be32toh(ia
->ia_na
.lifetime_t1
);
476 lt_t2
= be32toh(ia
->ia_na
.lifetime_t2
);
478 if (lt_t1
&& lt_t2
&& lt_t1
> lt_t2
) {
479 log_dhcp6_client(client
, "IA NA T1 %ds > T2 %ds",
486 case SD_DHCP6_OPTION_IA_PD
:
488 if (len
< sizeof(ia
->ia_pd
))
491 iaaddr_offset
= sizeof(ia
->ia_pd
);
492 memcpy(&ia
->ia_pd
, iaoption
->data
, sizeof(ia
->ia_pd
));
494 lt_t1
= be32toh(ia
->ia_pd
.lifetime_t1
);
495 lt_t2
= be32toh(ia
->ia_pd
.lifetime_t2
);
497 if (lt_t1
&& lt_t2
&& lt_t1
> lt_t2
) {
498 log_dhcp6_client(client
, "IA PD T1 %ds > T2 %ds",
505 case SD_DHCP6_OPTION_IA_TA
:
506 if (len
< DHCP6_OPTION_IA_TA_LEN
)
509 iaaddr_offset
= DHCP6_OPTION_IA_TA_LEN
;
510 memcpy(&ia
->ia_ta
.id
, iaoption
->data
, sizeof(ia
->ia_ta
));
522 DHCP6Option
*option
= (DHCP6Option
*)&iaoption
->data
[i
];
524 if (len
< i
+ sizeof(*option
) || len
< i
+ sizeof(*option
) + be16toh(option
->len
))
527 opt
= be16toh(option
->code
);
528 optlen
= be16toh(option
->len
);
531 case SD_DHCP6_OPTION_IAADDR
:
533 if (!IN_SET(ia
->type
, SD_DHCP6_OPTION_IA_NA
, SD_DHCP6_OPTION_IA_TA
)) {
534 log_dhcp6_client(client
, "IA Address option not in IA NA or TA option");
538 r
= dhcp6_option_parse_address(option
, ia
, <_valid
);
542 if (lt_valid
< lt_min
)
547 case SD_DHCP6_OPTION_IA_PD_PREFIX
:
549 if (!IN_SET(ia
->type
, SD_DHCP6_OPTION_IA_PD
)) {
550 log_dhcp6_client(client
, "IA PD Prefix option not in IA PD option");
554 r
= dhcp6_option_parse_pdprefix(option
, ia
, <_valid
);
558 if (lt_valid
< lt_min
)
563 case SD_DHCP6_OPTION_STATUS_CODE
:
565 status
= dhcp6_option_parse_status(option
, optlen
+ offsetof(DHCP6Option
, data
));
571 *ret_status_code
= status
;
573 log_dhcp6_client(client
, "IA status %s",
574 dhcp6_message_status_to_string(status
));
582 log_dhcp6_client(client
, "Unknown IA option %d", opt
);
586 i
+= sizeof(*option
) + optlen
;
590 case SD_DHCP6_OPTION_IA_NA
:
591 if (!ia
->ia_na
.lifetime_t1
&& !ia
->ia_na
.lifetime_t2
) {
593 lt_t2
= lt_min
/ 10 * 8;
594 ia
->ia_na
.lifetime_t1
= htobe32(lt_t1
);
595 ia
->ia_na
.lifetime_t2
= htobe32(lt_t2
);
597 log_dhcp6_client(client
, "Computed IA NA T1 %ds and T2 %ds as both were zero",
603 case SD_DHCP6_OPTION_IA_PD
:
604 if (!ia
->ia_pd
.lifetime_t1
&& !ia
->ia_pd
.lifetime_t2
) {
606 lt_t2
= lt_min
/ 10 * 8;
607 ia
->ia_pd
.lifetime_t1
= htobe32(lt_t1
);
608 ia
->ia_pd
.lifetime_t2
= htobe32(lt_t2
);
610 log_dhcp6_client(client
, "Computed IA PD T1 %ds and T2 %ds as both were zero",
621 *ret_status_code
= 0;
626 int dhcp6_option_parse_ip6addrs(uint8_t *optval
, uint16_t optlen
,
627 struct in6_addr
**addrs
, size_t count
,
630 if (optlen
== 0 || optlen
% sizeof(struct in6_addr
) != 0)
633 if (!GREEDY_REALLOC(*addrs
, *allocated
,
634 count
* sizeof(struct in6_addr
) + optlen
))
637 memcpy(*addrs
+ count
, optval
, optlen
);
639 count
+= optlen
/ sizeof(struct in6_addr
);
644 static int parse_domain(const uint8_t **data
, uint16_t *len
, char **out_domain
) {
645 _cleanup_free_
char *ret
= NULL
;
646 size_t n
= 0, allocated
= 0;
647 const uint8_t *optval
= *data
;
648 uint16_t optlen
= *len
;
675 label
= (const char *)optval
;
679 if (!GREEDY_REALLOC(ret
, allocated
, n
+ !first
+ DNS_LABEL_ESCAPED_MAX
))
687 r
= dns_label_escape(label
, c
, ret
+ n
, DNS_LABEL_ESCAPED_MAX
);
695 if (!GREEDY_REALLOC(ret
, allocated
, n
+ 1))
700 *out_domain
= TAKE_PTR(ret
);
707 int dhcp6_option_parse_domainname(const uint8_t *optval
, uint16_t optlen
, char **str
) {
708 _cleanup_free_
char *domain
= NULL
;
711 r
= parse_domain(&optval
, &optlen
, &domain
);
719 *str
= TAKE_PTR(domain
);
723 int dhcp6_option_parse_domainname_list(const uint8_t *optval
, uint16_t optlen
, char ***str_arr
) {
725 _cleanup_strv_free_
char **names
= NULL
;
730 if (optval
[optlen
- 1] != '\0')
734 _cleanup_free_
char *ret
= NULL
;
736 r
= parse_domain(&optval
, &optlen
, &ret
);
742 r
= strv_extend(&names
, ret
);
749 *str_arr
= TAKE_PTR(names
);
754 static sd_dhcp6_option
* dhcp6_option_free(sd_dhcp6_option
*i
) {
762 int sd_dhcp6_option_new(uint16_t option
, const void *data
, size_t length
, uint32_t enterprise_identifier
, sd_dhcp6_option
**ret
) {
763 assert_return(ret
, -EINVAL
);
764 assert_return(length
== 0 || data
, -EINVAL
);
766 _cleanup_free_
void *q
= memdup(data
, length
);
770 sd_dhcp6_option
*p
= new(sd_dhcp6_option
, 1);
774 *p
= (sd_dhcp6_option
) {
777 .enterprise_identifier
= enterprise_identifier
,
786 DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option
, sd_dhcp6_option
, dhcp6_option_free
);
787 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
788 dhcp6_option_hash_ops
,
791 trivial_compare_func
,
793 sd_dhcp6_option_unref
);