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 "dhcp6-internal.h"
13 #include "dhcp6-lease-internal.h"
14 #include "dhcp6-protocol.h"
15 #include "dns-domain.h"
16 #include "memory-util.h"
17 #include "sparse-endian.h"
19 #include "unaligned.h"
21 typedef struct DHCP6StatusOption
{
22 struct DHCP6Option option
;
25 } _packed_ DHCP6StatusOption
;
27 typedef struct DHCP6AddressOption
{
28 struct DHCP6Option option
;
31 } _packed_ DHCP6AddressOption
;
33 typedef struct DHCP6PDPrefixOption
{
34 struct DHCP6Option option
;
35 struct iapdprefix iapdprefix
;
37 } _packed_ DHCP6PDPrefixOption
;
39 #define DHCP6_OPTION_IA_NA_LEN (sizeof(struct ia_na))
40 #define DHCP6_OPTION_IA_PD_LEN (sizeof(struct ia_pd))
41 #define DHCP6_OPTION_IA_TA_LEN (sizeof(struct ia_ta))
43 static int option_append_hdr(uint8_t **buf
, size_t *buflen
, uint16_t optcode
,
45 DHCP6Option
*option
= (DHCP6Option
*) *buf
;
47 assert_return(buf
, -EINVAL
);
48 assert_return(*buf
, -EINVAL
);
49 assert_return(buflen
, -EINVAL
);
51 if (optlen
> 0xffff || *buflen
< optlen
+ offsetof(DHCP6Option
, data
))
54 option
->code
= htobe16(optcode
);
55 option
->len
= htobe16(optlen
);
57 *buf
+= offsetof(DHCP6Option
, data
);
58 *buflen
-= offsetof(DHCP6Option
, data
);
63 int dhcp6_option_append(uint8_t **buf
, size_t *buflen
, uint16_t code
,
64 size_t optlen
, const void *optval
) {
67 assert_return(optval
|| optlen
== 0, -EINVAL
);
69 r
= option_append_hdr(buf
, buflen
, code
, optlen
);
73 memcpy_safe(*buf
, optval
, optlen
);
81 int dhcp6_option_append_ia(uint8_t **buf
, size_t *buflen
, const DHCP6IA
*ia
) {
84 size_t iaid_offset
, ia_buflen
, ia_addrlen
= 0;
88 assert_return(buf
, -EINVAL
);
89 assert_return(*buf
, -EINVAL
);
90 assert_return(buflen
, -EINVAL
);
91 assert_return(ia
, -EINVAL
);
94 case SD_DHCP6_OPTION_IA_NA
:
95 len
= DHCP6_OPTION_IA_NA_LEN
;
96 iaid_offset
= offsetof(DHCP6IA
, ia_na
);
99 case SD_DHCP6_OPTION_IA_TA
:
100 len
= DHCP6_OPTION_IA_TA_LEN
;
101 iaid_offset
= offsetof(DHCP6IA
, ia_ta
);
108 if (*buflen
< offsetof(DHCP6Option
, data
) + len
)
114 *buf
+= offsetof(DHCP6Option
, data
);
115 *buflen
-= offsetof(DHCP6Option
, data
);
117 memcpy(*buf
, (char*) ia
+ iaid_offset
, len
);
122 LIST_FOREACH(addresses
, addr
, ia
->addresses
) {
123 r
= option_append_hdr(buf
, buflen
, SD_DHCP6_OPTION_IAADDR
,
124 sizeof(addr
->iaaddr
));
128 memcpy(*buf
, &addr
->iaaddr
, sizeof(addr
->iaaddr
));
130 *buf
+= sizeof(addr
->iaaddr
);
131 *buflen
-= sizeof(addr
->iaaddr
);
133 ia_addrlen
+= offsetof(DHCP6Option
, data
) + sizeof(addr
->iaaddr
);
136 r
= option_append_hdr(&ia_hdr
, &ia_buflen
, ia
->type
, len
+ ia_addrlen
);
143 int dhcp6_option_append_fqdn(uint8_t **buf
, size_t *buflen
, const char *fqdn
) {
144 uint8_t buffer
[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX
];
147 assert_return(buf
&& *buf
&& buflen
&& fqdn
, -EINVAL
);
149 buffer
[0] = DHCP6_FQDN_FLAG_S
; /* Request server to perform AAAA RR DNS updates */
151 /* Store domain name after flags field */
152 r
= dns_name_to_wire_format(fqdn
, buffer
+ 1, sizeof(buffer
) - 1, false);
157 * According to RFC 4704, chapter 4.2 only add terminating zero-length
158 * label in case a FQDN is provided. Since dns_name_to_wire_format
159 * always adds terminating zero-length label remove if only a hostname
162 if (dns_name_is_single_label(fqdn
))
165 r
= dhcp6_option_append(buf
, buflen
, SD_DHCP6_OPTION_FQDN
, 1 + r
, buffer
);
170 int dhcp6_option_append_pd(uint8_t *buf
, size_t len
, const DHCP6IA
*pd
, DHCP6Address
*hint_pd_prefix
) {
171 DHCP6Option
*option
= (DHCP6Option
*)buf
;
172 size_t i
= sizeof(*option
) + sizeof(pd
->ia_pd
);
173 DHCP6PDPrefixOption
*prefix_opt
;
174 DHCP6Address
*prefix
;
176 assert_return(buf
, -EINVAL
);
177 assert_return(pd
, -EINVAL
);
178 assert_return(pd
->type
== SD_DHCP6_OPTION_IA_PD
, -EINVAL
);
183 option
->code
= htobe16(SD_DHCP6_OPTION_IA_PD
);
185 memcpy(&option
->data
, &pd
->ia_pd
, sizeof(pd
->ia_pd
));
186 LIST_FOREACH(addresses
, prefix
, pd
->addresses
) {
187 if (len
< i
+ sizeof(*prefix_opt
))
190 prefix_opt
= (DHCP6PDPrefixOption
*)&buf
[i
];
191 prefix_opt
->option
.code
= htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX
);
192 prefix_opt
->option
.len
= htobe16(sizeof(prefix_opt
->iapdprefix
));
194 memcpy(&prefix_opt
->iapdprefix
, &prefix
->iapdprefix
, sizeof(struct iapdprefix
));
195 i
+= sizeof(*prefix_opt
);
198 if (hint_pd_prefix
&& hint_pd_prefix
->iapdprefix
.prefixlen
> 0) {
199 if (len
< i
+ sizeof(*prefix_opt
))
202 prefix_opt
= (DHCP6PDPrefixOption
*)&buf
[i
];
203 prefix_opt
->option
.code
= htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX
);
204 prefix_opt
->option
.len
= htobe16(sizeof(prefix_opt
->iapdprefix
));
206 memcpy(&prefix_opt
->iapdprefix
, &hint_pd_prefix
->iapdprefix
, sizeof(struct iapdprefix
));
207 i
+= sizeof(*prefix_opt
);
210 option
->len
= htobe16(i
- sizeof(*option
));
215 static int option_parse_hdr(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
, size_t *optlen
) {
216 DHCP6Option
*option
= (DHCP6Option
*) *buf
;
219 assert_return(buf
, -EINVAL
);
220 assert_return(optcode
, -EINVAL
);
221 assert_return(optlen
, -EINVAL
);
223 if (*buflen
< offsetof(DHCP6Option
, data
))
226 len
= be16toh(option
->len
);
231 *optcode
= be16toh(option
->code
);
240 int dhcp6_option_parse(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
,
241 size_t *optlen
, uint8_t **optvalue
) {
244 assert_return(buf
&& buflen
&& optcode
&& optlen
&& optvalue
, -EINVAL
);
246 r
= option_parse_hdr(buf
, buflen
, optcode
, optlen
);
250 if (*optlen
> *buflen
)
260 int dhcp6_option_parse_status(DHCP6Option
*option
, size_t len
) {
261 DHCP6StatusOption
*statusopt
= (DHCP6StatusOption
*)option
;
263 if (len
< sizeof(DHCP6StatusOption
) ||
264 be16toh(option
->len
) + offsetof(DHCP6Option
, data
) < sizeof(DHCP6StatusOption
))
267 return be16toh(statusopt
->status
);
270 static int dhcp6_option_parse_address(DHCP6Option
*option
, DHCP6IA
*ia
,
271 uint32_t *lifetime_valid
) {
272 DHCP6AddressOption
*addr_option
= (DHCP6AddressOption
*)option
;
274 uint32_t lt_valid
, lt_pref
;
277 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) < sizeof(*addr_option
))
280 lt_valid
= be32toh(addr_option
->iaaddr
.lifetime_valid
);
281 lt_pref
= be32toh(addr_option
->iaaddr
.lifetime_preferred
);
283 if (lt_valid
== 0 || lt_pref
> lt_valid
) {
284 log_dhcp6_client(client
, "Valid lifetime of an IA address is zero or preferred lifetime %d > valid lifetime %d",
290 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) > sizeof(*addr_option
)) {
291 r
= dhcp6_option_parse_status((DHCP6Option
*)addr_option
->options
, be16toh(option
->len
) + offsetof(DHCP6Option
, data
) - sizeof(*addr_option
));
296 addr
= new0(DHCP6Address
, 1);
300 LIST_INIT(addresses
, addr
);
301 memcpy(&addr
->iaaddr
, option
->data
, sizeof(addr
->iaaddr
));
303 LIST_PREPEND(addresses
, ia
->addresses
, addr
);
305 *lifetime_valid
= be32toh(addr
->iaaddr
.lifetime_valid
);
310 static int dhcp6_option_parse_pdprefix(DHCP6Option
*option
, DHCP6IA
*ia
,
311 uint32_t *lifetime_valid
) {
312 DHCP6PDPrefixOption
*pdprefix_option
= (DHCP6PDPrefixOption
*)option
;
313 DHCP6Address
*prefix
;
314 uint32_t lt_valid
, lt_pref
;
317 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) < sizeof(*pdprefix_option
))
320 lt_valid
= be32toh(pdprefix_option
->iapdprefix
.lifetime_valid
);
321 lt_pref
= be32toh(pdprefix_option
->iapdprefix
.lifetime_preferred
);
323 if (lt_valid
== 0 || lt_pref
> lt_valid
) {
324 log_dhcp6_client(client
, "Valid lifetieme of a PD prefix is zero or preferred lifetime %d > valid lifetime %d",
330 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) > sizeof(*pdprefix_option
)) {
331 r
= dhcp6_option_parse_status((DHCP6Option
*)pdprefix_option
->options
, be16toh(option
->len
) + offsetof(DHCP6Option
, data
) - sizeof(*pdprefix_option
));
336 prefix
= new0(DHCP6Address
, 1);
340 LIST_INIT(addresses
, prefix
);
341 memcpy(&prefix
->iapdprefix
, option
->data
, sizeof(prefix
->iapdprefix
));
343 LIST_PREPEND(addresses
, ia
->addresses
, prefix
);
345 *lifetime_valid
= be32toh(prefix
->iapdprefix
.lifetime_valid
);
350 int dhcp6_option_parse_ia(DHCP6Option
*iaoption
, DHCP6IA
*ia
) {
351 uint16_t iatype
, optlen
;
355 size_t iaaddr_offset
;
356 uint32_t lt_t1
, lt_t2
, lt_valid
= 0, lt_min
= UINT32_MAX
;
358 assert_return(ia
, -EINVAL
);
359 assert_return(!ia
->addresses
, -EINVAL
);
361 iatype
= be16toh(iaoption
->code
);
362 len
= be16toh(iaoption
->len
);
365 case SD_DHCP6_OPTION_IA_NA
:
367 if (len
< DHCP6_OPTION_IA_NA_LEN
)
370 iaaddr_offset
= DHCP6_OPTION_IA_NA_LEN
;
371 memcpy(&ia
->ia_na
, iaoption
->data
, sizeof(ia
->ia_na
));
373 lt_t1
= be32toh(ia
->ia_na
.lifetime_t1
);
374 lt_t2
= be32toh(ia
->ia_na
.lifetime_t2
);
376 if (lt_t1
&& lt_t2
&& lt_t1
> lt_t2
) {
377 log_dhcp6_client(client
, "IA NA T1 %ds > T2 %ds",
384 case SD_DHCP6_OPTION_IA_PD
:
386 if (len
< sizeof(ia
->ia_pd
))
389 iaaddr_offset
= sizeof(ia
->ia_pd
);
390 memcpy(&ia
->ia_pd
, iaoption
->data
, sizeof(ia
->ia_pd
));
392 lt_t1
= be32toh(ia
->ia_pd
.lifetime_t1
);
393 lt_t2
= be32toh(ia
->ia_pd
.lifetime_t2
);
395 if (lt_t1
&& lt_t2
&& lt_t1
> lt_t2
) {
396 log_dhcp6_client(client
, "IA PD T1 %ds > T2 %ds",
403 case SD_DHCP6_OPTION_IA_TA
:
404 if (len
< DHCP6_OPTION_IA_TA_LEN
)
407 iaaddr_offset
= DHCP6_OPTION_IA_TA_LEN
;
408 memcpy(&ia
->ia_ta
.id
, iaoption
->data
, sizeof(ia
->ia_ta
));
420 DHCP6Option
*option
= (DHCP6Option
*)&iaoption
->data
[i
];
422 if (len
< i
+ sizeof(*option
) || len
< i
+ sizeof(*option
) + be16toh(option
->len
))
425 opt
= be16toh(option
->code
);
426 optlen
= be16toh(option
->len
);
429 case SD_DHCP6_OPTION_IAADDR
:
431 if (!IN_SET(ia
->type
, SD_DHCP6_OPTION_IA_NA
, SD_DHCP6_OPTION_IA_TA
)) {
432 log_dhcp6_client(client
, "IA Address option not in IA NA or TA option");
436 r
= dhcp6_option_parse_address(option
, ia
, <_valid
);
440 if (lt_valid
< lt_min
)
445 case SD_DHCP6_OPTION_IA_PD_PREFIX
:
447 if (!IN_SET(ia
->type
, SD_DHCP6_OPTION_IA_PD
)) {
448 log_dhcp6_client(client
, "IA PD Prefix option not in IA PD option");
452 r
= dhcp6_option_parse_pdprefix(option
, ia
, <_valid
);
456 if (lt_valid
< lt_min
)
461 case SD_DHCP6_OPTION_STATUS_CODE
:
463 status
= dhcp6_option_parse_status(option
, optlen
+ offsetof(DHCP6Option
, data
));
467 log_dhcp6_client(client
, "IA status %s",
468 dhcp6_message_status_to_string(status
));
476 log_dhcp6_client(client
, "Unknown IA option %d", opt
);
480 i
+= sizeof(*option
) + optlen
;
484 case SD_DHCP6_OPTION_IA_NA
:
485 if (!ia
->ia_na
.lifetime_t1
&& !ia
->ia_na
.lifetime_t2
) {
487 lt_t2
= lt_min
/ 10 * 8;
488 ia
->ia_na
.lifetime_t1
= htobe32(lt_t1
);
489 ia
->ia_na
.lifetime_t2
= htobe32(lt_t2
);
491 log_dhcp6_client(client
, "Computed IA NA T1 %ds and T2 %ds as both were zero",
497 case SD_DHCP6_OPTION_IA_PD
:
498 if (!ia
->ia_pd
.lifetime_t1
&& !ia
->ia_pd
.lifetime_t2
) {
500 lt_t2
= lt_min
/ 10 * 8;
501 ia
->ia_pd
.lifetime_t1
= htobe32(lt_t1
);
502 ia
->ia_pd
.lifetime_t2
= htobe32(lt_t2
);
504 log_dhcp6_client(client
, "Computed IA PD T1 %ds and T2 %ds as both were zero",
517 int dhcp6_option_parse_ip6addrs(uint8_t *optval
, uint16_t optlen
,
518 struct in6_addr
**addrs
, size_t count
,
521 if (optlen
== 0 || optlen
% sizeof(struct in6_addr
) != 0)
524 if (!GREEDY_REALLOC(*addrs
, *allocated
,
525 count
* sizeof(struct in6_addr
) + optlen
))
528 memcpy(*addrs
+ count
, optval
, optlen
);
530 count
+= optlen
/ sizeof(struct in6_addr
);
535 int dhcp6_option_parse_domainname(const uint8_t *optval
, uint16_t optlen
, char ***str_arr
) {
536 size_t pos
= 0, idx
= 0;
537 _cleanup_strv_free_
char **names
= NULL
;
540 assert_return(optlen
> 1, -ENODATA
);
541 assert_return(optval
[optlen
- 1] == '\0', -EINVAL
);
543 while (pos
< optlen
) {
544 _cleanup_free_
char *ret
= NULL
;
545 size_t n
= 0, allocated
= 0;
561 label
= (const char *)&optval
[pos
];
566 if (!GREEDY_REALLOC(ret
, allocated
, n
+ !first
+ DNS_LABEL_ESCAPED_MAX
))
574 r
= dns_label_escape(label
, c
, ret
+ n
, DNS_LABEL_ESCAPED_MAX
);
584 if (!GREEDY_REALLOC(ret
, allocated
, n
+ 1))
589 r
= strv_extend(&names
, ret
);
596 *str_arr
= TAKE_PTR(names
);
601 static sd_dhcp6_option
* dhcp6_option_free(sd_dhcp6_option
*i
) {
609 int sd_dhcp6_option_new(uint16_t option
, const void *data
, size_t length
, sd_dhcp6_option
**ret
) {
610 assert_return(ret
, -EINVAL
);
611 assert_return(length
== 0 || data
, -EINVAL
);
613 _cleanup_free_
void *q
= memdup(data
, length
);
617 sd_dhcp6_option
*p
= new(sd_dhcp6_option
, 1);
621 *p
= (sd_dhcp6_option
) {
632 DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option
, sd_dhcp6_option
, dhcp6_option_free
);
633 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
634 dhcp6_option_hash_ops
,
637 trivial_compare_func
,
639 sd_dhcp6_option_unref
);