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
;
90 assert(vendor_options
);
92 ORDERED_HASHMAP_FOREACH(options
, vendor_options
, i
) {
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
) {
118 size_t iaid_offset
, ia_buflen
, ia_addrlen
= 0;
122 assert_return(buf
, -EINVAL
);
123 assert_return(*buf
, -EINVAL
);
124 assert_return(buflen
, -EINVAL
);
125 assert_return(ia
, -EINVAL
);
128 case SD_DHCP6_OPTION_IA_NA
:
129 len
= DHCP6_OPTION_IA_NA_LEN
;
130 iaid_offset
= offsetof(DHCP6IA
, ia_na
);
133 case SD_DHCP6_OPTION_IA_TA
:
134 len
= DHCP6_OPTION_IA_TA_LEN
;
135 iaid_offset
= offsetof(DHCP6IA
, ia_ta
);
142 if (*buflen
< offsetof(DHCP6Option
, data
) + len
)
148 *buf
+= offsetof(DHCP6Option
, data
);
149 *buflen
-= offsetof(DHCP6Option
, data
);
151 memcpy(*buf
, (char*) ia
+ iaid_offset
, len
);
156 LIST_FOREACH(addresses
, addr
, ia
->addresses
) {
157 r
= option_append_hdr(buf
, buflen
, SD_DHCP6_OPTION_IAADDR
,
158 sizeof(addr
->iaaddr
));
162 memcpy(*buf
, &addr
->iaaddr
, sizeof(addr
->iaaddr
));
164 *buf
+= sizeof(addr
->iaaddr
);
165 *buflen
-= sizeof(addr
->iaaddr
);
167 ia_addrlen
+= offsetof(DHCP6Option
, data
) + sizeof(addr
->iaaddr
);
170 r
= option_append_hdr(&ia_hdr
, &ia_buflen
, ia
->type
, len
+ ia_addrlen
);
177 int dhcp6_option_append_fqdn(uint8_t **buf
, size_t *buflen
, const char *fqdn
) {
178 uint8_t buffer
[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX
];
181 assert_return(buf
&& *buf
&& buflen
&& fqdn
, -EINVAL
);
183 buffer
[0] = DHCP6_FQDN_FLAG_S
; /* Request server to perform AAAA RR DNS updates */
185 /* Store domain name after flags field */
186 r
= dns_name_to_wire_format(fqdn
, buffer
+ 1, sizeof(buffer
) - 1, false);
191 * According to RFC 4704, chapter 4.2 only add terminating zero-length
192 * label in case a FQDN is provided. Since dns_name_to_wire_format
193 * always adds terminating zero-length label remove if only a hostname
196 if (dns_name_is_single_label(fqdn
))
199 r
= dhcp6_option_append(buf
, buflen
, SD_DHCP6_OPTION_FQDN
, 1 + r
, buffer
);
204 int dhcp6_option_append_user_class(uint8_t **buf
, size_t *buflen
, char **user_class
) {
205 _cleanup_free_
uint8_t *p
= NULL
;
206 size_t total
= 0, offset
= 0;
209 assert_return(buf
&& *buf
&& buflen
&& user_class
, -EINVAL
);
211 STRV_FOREACH(s
, user_class
) {
212 size_t len
= strlen(*s
);
216 return -ENAMETOOLONG
;
217 q
= realloc(p
, total
+ len
+ 2);
223 unaligned_write_be16(&p
[offset
], len
);
224 memcpy(&p
[offset
+ 2], *s
, len
);
230 return dhcp6_option_append(buf
, buflen
, SD_DHCP6_OPTION_USER_CLASS
, total
, p
);
233 int dhcp6_option_append_vendor_class(uint8_t **buf
, size_t *buflen
, char **vendor_class
) {
234 _cleanup_free_
uint8_t *p
= NULL
;
235 uint32_t enterprise_identifier
;
236 size_t total
, offset
;
242 assert(vendor_class
);
244 enterprise_identifier
= htobe32(SYSTEMD_PEN
);
246 p
= memdup(&enterprise_identifier
, sizeof(enterprise_identifier
));
250 total
= sizeof(enterprise_identifier
);
253 STRV_FOREACH(s
, vendor_class
) {
254 size_t len
= strlen(*s
);
257 q
= realloc(p
, total
+ len
+ 2);
263 unaligned_write_be16(&p
[offset
], len
);
264 memcpy(&p
[offset
+ 2], *s
, len
);
270 return dhcp6_option_append(buf
, buflen
, SD_DHCP6_OPTION_VENDOR_CLASS
, total
, p
);
273 int dhcp6_option_append_pd(uint8_t *buf
, size_t len
, const DHCP6IA
*pd
, DHCP6Address
*hint_pd_prefix
) {
274 DHCP6Option
*option
= (DHCP6Option
*)buf
;
275 size_t i
= sizeof(*option
) + sizeof(pd
->ia_pd
);
276 DHCP6PDPrefixOption
*prefix_opt
;
277 DHCP6Address
*prefix
;
279 assert_return(buf
, -EINVAL
);
280 assert_return(pd
, -EINVAL
);
281 assert_return(pd
->type
== SD_DHCP6_OPTION_IA_PD
, -EINVAL
);
286 option
->code
= htobe16(SD_DHCP6_OPTION_IA_PD
);
288 memcpy(&option
->data
, &pd
->ia_pd
, sizeof(pd
->ia_pd
));
289 LIST_FOREACH(addresses
, prefix
, pd
->addresses
) {
290 if (len
< i
+ sizeof(*prefix_opt
))
293 prefix_opt
= (DHCP6PDPrefixOption
*)&buf
[i
];
294 prefix_opt
->option
.code
= htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX
);
295 prefix_opt
->option
.len
= htobe16(sizeof(prefix_opt
->iapdprefix
));
297 memcpy(&prefix_opt
->iapdprefix
, &prefix
->iapdprefix
, sizeof(struct iapdprefix
));
298 i
+= sizeof(*prefix_opt
);
301 if (hint_pd_prefix
&& hint_pd_prefix
->iapdprefix
.prefixlen
> 0) {
302 if (len
< i
+ sizeof(*prefix_opt
))
305 prefix_opt
= (DHCP6PDPrefixOption
*)&buf
[i
];
306 prefix_opt
->option
.code
= htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX
);
307 prefix_opt
->option
.len
= htobe16(sizeof(prefix_opt
->iapdprefix
));
309 memcpy(&prefix_opt
->iapdprefix
, &hint_pd_prefix
->iapdprefix
, sizeof(struct iapdprefix
));
310 i
+= sizeof(*prefix_opt
);
313 option
->len
= htobe16(i
- sizeof(*option
));
318 static int option_parse_hdr(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
, size_t *optlen
) {
319 DHCP6Option
*option
= (DHCP6Option
*) *buf
;
322 assert_return(buf
, -EINVAL
);
323 assert_return(optcode
, -EINVAL
);
324 assert_return(optlen
, -EINVAL
);
326 if (*buflen
< offsetof(DHCP6Option
, data
))
329 len
= be16toh(option
->len
);
334 *optcode
= be16toh(option
->code
);
343 int dhcp6_option_parse(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
,
344 size_t *optlen
, uint8_t **optvalue
) {
347 assert_return(buf
&& buflen
&& optcode
&& optlen
&& optvalue
, -EINVAL
);
349 r
= option_parse_hdr(buf
, buflen
, optcode
, optlen
);
353 if (*optlen
> *buflen
)
363 int dhcp6_option_parse_status(DHCP6Option
*option
, size_t len
) {
364 DHCP6StatusOption
*statusopt
= (DHCP6StatusOption
*)option
;
366 if (len
< sizeof(DHCP6StatusOption
) ||
367 be16toh(option
->len
) + offsetof(DHCP6Option
, data
) < sizeof(DHCP6StatusOption
))
370 return be16toh(statusopt
->status
);
373 static int dhcp6_option_parse_address(DHCP6Option
*option
, DHCP6IA
*ia
,
374 uint32_t *lifetime_valid
) {
375 DHCP6AddressOption
*addr_option
= (DHCP6AddressOption
*)option
;
377 uint32_t lt_valid
, lt_pref
;
380 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) < sizeof(*addr_option
))
383 lt_valid
= be32toh(addr_option
->iaaddr
.lifetime_valid
);
384 lt_pref
= be32toh(addr_option
->iaaddr
.lifetime_preferred
);
386 if (lt_valid
== 0 || lt_pref
> lt_valid
) {
387 log_dhcp6_client(client
, "Valid lifetime of an IA address is zero or preferred lifetime %d > valid lifetime %d",
393 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) > sizeof(*addr_option
)) {
394 r
= dhcp6_option_parse_status((DHCP6Option
*)addr_option
->options
, be16toh(option
->len
) + offsetof(DHCP6Option
, data
) - sizeof(*addr_option
));
399 addr
= new0(DHCP6Address
, 1);
403 LIST_INIT(addresses
, addr
);
404 memcpy(&addr
->iaaddr
, option
->data
, sizeof(addr
->iaaddr
));
406 LIST_PREPEND(addresses
, ia
->addresses
, addr
);
408 *lifetime_valid
= be32toh(addr
->iaaddr
.lifetime_valid
);
413 static int dhcp6_option_parse_pdprefix(DHCP6Option
*option
, DHCP6IA
*ia
,
414 uint32_t *lifetime_valid
) {
415 DHCP6PDPrefixOption
*pdprefix_option
= (DHCP6PDPrefixOption
*)option
;
416 DHCP6Address
*prefix
;
417 uint32_t lt_valid
, lt_pref
;
420 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) < sizeof(*pdprefix_option
))
423 lt_valid
= be32toh(pdprefix_option
->iapdprefix
.lifetime_valid
);
424 lt_pref
= be32toh(pdprefix_option
->iapdprefix
.lifetime_preferred
);
426 if (lt_valid
== 0 || lt_pref
> lt_valid
) {
427 log_dhcp6_client(client
, "Valid lifetieme of a PD prefix is zero or preferred lifetime %d > valid lifetime %d",
433 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) > sizeof(*pdprefix_option
)) {
434 r
= dhcp6_option_parse_status((DHCP6Option
*)pdprefix_option
->options
, be16toh(option
->len
) + offsetof(DHCP6Option
, data
) - sizeof(*pdprefix_option
));
439 prefix
= new0(DHCP6Address
, 1);
443 LIST_INIT(addresses
, prefix
);
444 memcpy(&prefix
->iapdprefix
, option
->data
, sizeof(prefix
->iapdprefix
));
446 LIST_PREPEND(addresses
, ia
->addresses
, prefix
);
448 *lifetime_valid
= be32toh(prefix
->iapdprefix
.lifetime_valid
);
453 int dhcp6_option_parse_ia(DHCP6Option
*iaoption
, DHCP6IA
*ia
, uint16_t *ret_status_code
) {
454 uint32_t lt_t1
, lt_t2
, lt_valid
= 0, lt_min
= UINT32_MAX
;
455 uint16_t iatype
, optlen
;
456 size_t iaaddr_offset
;
461 assert_return(ia
, -EINVAL
);
462 assert_return(!ia
->addresses
, -EINVAL
);
464 iatype
= be16toh(iaoption
->code
);
465 len
= be16toh(iaoption
->len
);
468 case SD_DHCP6_OPTION_IA_NA
:
470 if (len
< DHCP6_OPTION_IA_NA_LEN
)
473 iaaddr_offset
= DHCP6_OPTION_IA_NA_LEN
;
474 memcpy(&ia
->ia_na
, iaoption
->data
, sizeof(ia
->ia_na
));
476 lt_t1
= be32toh(ia
->ia_na
.lifetime_t1
);
477 lt_t2
= be32toh(ia
->ia_na
.lifetime_t2
);
479 if (lt_t1
&& lt_t2
&& lt_t1
> lt_t2
) {
480 log_dhcp6_client(client
, "IA NA T1 %ds > T2 %ds",
487 case SD_DHCP6_OPTION_IA_PD
:
489 if (len
< sizeof(ia
->ia_pd
))
492 iaaddr_offset
= sizeof(ia
->ia_pd
);
493 memcpy(&ia
->ia_pd
, iaoption
->data
, sizeof(ia
->ia_pd
));
495 lt_t1
= be32toh(ia
->ia_pd
.lifetime_t1
);
496 lt_t2
= be32toh(ia
->ia_pd
.lifetime_t2
);
498 if (lt_t1
&& lt_t2
&& lt_t1
> lt_t2
) {
499 log_dhcp6_client(client
, "IA PD T1 %ds > T2 %ds",
506 case SD_DHCP6_OPTION_IA_TA
:
507 if (len
< DHCP6_OPTION_IA_TA_LEN
)
510 iaaddr_offset
= DHCP6_OPTION_IA_TA_LEN
;
511 memcpy(&ia
->ia_ta
.id
, iaoption
->data
, sizeof(ia
->ia_ta
));
523 DHCP6Option
*option
= (DHCP6Option
*)&iaoption
->data
[i
];
525 if (len
< i
+ sizeof(*option
) || len
< i
+ sizeof(*option
) + be16toh(option
->len
))
528 opt
= be16toh(option
->code
);
529 optlen
= be16toh(option
->len
);
532 case SD_DHCP6_OPTION_IAADDR
:
534 if (!IN_SET(ia
->type
, SD_DHCP6_OPTION_IA_NA
, SD_DHCP6_OPTION_IA_TA
)) {
535 log_dhcp6_client(client
, "IA Address option not in IA NA or TA option");
539 r
= dhcp6_option_parse_address(option
, ia
, <_valid
);
543 if (lt_valid
< lt_min
)
548 case SD_DHCP6_OPTION_IA_PD_PREFIX
:
550 if (!IN_SET(ia
->type
, SD_DHCP6_OPTION_IA_PD
)) {
551 log_dhcp6_client(client
, "IA PD Prefix option not in IA PD option");
555 r
= dhcp6_option_parse_pdprefix(option
, ia
, <_valid
);
559 if (lt_valid
< lt_min
)
564 case SD_DHCP6_OPTION_STATUS_CODE
:
566 status
= dhcp6_option_parse_status(option
, optlen
+ offsetof(DHCP6Option
, data
));
572 *ret_status_code
= status
;
574 log_dhcp6_client(client
, "IA status %s",
575 dhcp6_message_status_to_string(status
));
583 log_dhcp6_client(client
, "Unknown IA option %d", opt
);
587 i
+= sizeof(*option
) + optlen
;
591 case SD_DHCP6_OPTION_IA_NA
:
592 if (!ia
->ia_na
.lifetime_t1
&& !ia
->ia_na
.lifetime_t2
) {
594 lt_t2
= lt_min
/ 10 * 8;
595 ia
->ia_na
.lifetime_t1
= htobe32(lt_t1
);
596 ia
->ia_na
.lifetime_t2
= htobe32(lt_t2
);
598 log_dhcp6_client(client
, "Computed IA NA T1 %ds and T2 %ds as both were zero",
604 case SD_DHCP6_OPTION_IA_PD
:
605 if (!ia
->ia_pd
.lifetime_t1
&& !ia
->ia_pd
.lifetime_t2
) {
607 lt_t2
= lt_min
/ 10 * 8;
608 ia
->ia_pd
.lifetime_t1
= htobe32(lt_t1
);
609 ia
->ia_pd
.lifetime_t2
= htobe32(lt_t2
);
611 log_dhcp6_client(client
, "Computed IA PD T1 %ds and T2 %ds as both were zero",
622 *ret_status_code
= 0;
627 int dhcp6_option_parse_ip6addrs(uint8_t *optval
, uint16_t optlen
,
628 struct in6_addr
**addrs
, size_t count
,
631 if (optlen
== 0 || optlen
% sizeof(struct in6_addr
) != 0)
634 if (!GREEDY_REALLOC(*addrs
, *allocated
,
635 count
* sizeof(struct in6_addr
) + optlen
))
638 memcpy(*addrs
+ count
, optval
, optlen
);
640 count
+= optlen
/ sizeof(struct in6_addr
);
645 int dhcp6_option_parse_domainname(const uint8_t *optval
, uint16_t optlen
, char ***str_arr
) {
646 size_t pos
= 0, idx
= 0;
647 _cleanup_strv_free_
char **names
= NULL
;
650 assert_return(optlen
> 1, -ENODATA
);
651 assert_return(optval
[optlen
- 1] == '\0', -EINVAL
);
653 while (pos
< optlen
) {
654 _cleanup_free_
char *ret
= NULL
;
655 size_t n
= 0, allocated
= 0;
671 label
= (const char *)&optval
[pos
];
676 if (!GREEDY_REALLOC(ret
, allocated
, n
+ !first
+ DNS_LABEL_ESCAPED_MAX
))
684 r
= dns_label_escape(label
, c
, ret
+ n
, DNS_LABEL_ESCAPED_MAX
);
694 if (!GREEDY_REALLOC(ret
, allocated
, n
+ 1))
699 r
= strv_extend(&names
, ret
);
706 *str_arr
= TAKE_PTR(names
);
711 static sd_dhcp6_option
* dhcp6_option_free(sd_dhcp6_option
*i
) {
719 int sd_dhcp6_option_new(uint16_t option
, const void *data
, size_t length
, uint32_t enterprise_identifier
, sd_dhcp6_option
**ret
) {
720 assert_return(ret
, -EINVAL
);
721 assert_return(length
== 0 || data
, -EINVAL
);
723 _cleanup_free_
void *q
= memdup(data
, length
);
727 sd_dhcp6_option
*p
= new(sd_dhcp6_option
, 1);
731 *p
= (sd_dhcp6_option
) {
734 .enterprise_identifier
= enterprise_identifier
,
743 DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option
, sd_dhcp6_option
, dhcp6_option_free
);
744 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
745 dhcp6_option_hash_ops
,
748 trivial_compare_func
,
750 sd_dhcp6_option_unref
);