1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
7 #include <netinet/in.h>
10 #include "sd-dhcp6-client.h"
12 #include "alloc-util.h"
13 #include "dhcp6-internal.h"
14 #include "dhcp6-lease-internal.h"
15 #include "dhcp6-protocol.h"
16 #include "dns-domain.h"
17 #include "sparse-endian.h"
19 #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
+ sizeof(DHCP6Option
))
55 option
->code
= htobe16(optcode
);
56 option
->len
= htobe16(optlen
);
58 *buf
+= sizeof(DHCP6Option
);
59 *buflen
-= sizeof(DHCP6Option
);
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_ia(uint8_t **buf
, size_t *buflen
, DHCP6IA
*ia
) {
85 size_t iaid_offset
, ia_buflen
, ia_addrlen
= 0;
89 assert_return(buf
&& *buf
&& buflen
&& ia
, -EINVAL
);
92 case SD_DHCP6_OPTION_IA_NA
:
93 len
= DHCP6_OPTION_IA_NA_LEN
;
94 iaid_offset
= offsetof(DHCP6IA
, ia_na
);
97 case SD_DHCP6_OPTION_IA_TA
:
98 len
= DHCP6_OPTION_IA_TA_LEN
;
99 iaid_offset
= offsetof(DHCP6IA
, ia_ta
);
112 *buf
+= sizeof(DHCP6Option
);
113 *buflen
-= sizeof(DHCP6Option
);
115 memcpy(*buf
, (char*) ia
+ iaid_offset
, len
);
120 LIST_FOREACH(addresses
, addr
, ia
->addresses
) {
121 r
= option_append_hdr(buf
, buflen
, SD_DHCP6_OPTION_IAADDR
,
122 sizeof(addr
->iaaddr
));
126 memcpy(*buf
, &addr
->iaaddr
, sizeof(addr
->iaaddr
));
128 *buf
+= sizeof(addr
->iaaddr
);
129 *buflen
-= sizeof(addr
->iaaddr
);
131 ia_addrlen
+= sizeof(DHCP6Option
) + sizeof(addr
->iaaddr
);
134 r
= option_append_hdr(&ia_hdr
, &ia_buflen
, ia
->type
, len
+ ia_addrlen
);
141 int dhcp6_option_append_fqdn(uint8_t **buf
, size_t *buflen
, const char *fqdn
) {
142 uint8_t buffer
[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX
];
145 assert_return(buf
&& *buf
&& buflen
&& fqdn
, -EINVAL
);
147 buffer
[0] = DHCP6_FQDN_FLAG_S
; /* Request server to perform AAAA RR DNS updates */
149 /* Store domain name after flags field */
150 r
= dns_name_to_wire_format(fqdn
, buffer
+ 1, sizeof(buffer
) - 1, false);
155 * According to RFC 4704, chapter 4.2 only add terminating zero-length
156 * label in case a FQDN is provided. Since dns_name_to_wire_format
157 * always adds terminating zero-length label remove if only a hostname
160 if (dns_name_is_single_label(fqdn
))
163 r
= dhcp6_option_append(buf
, buflen
, SD_DHCP6_OPTION_FQDN
, 1 + r
, buffer
);
168 int dhcp6_option_append_pd(uint8_t *buf
, size_t len
, DHCP6IA
*pd
) {
169 DHCP6Option
*option
= (DHCP6Option
*)buf
;
170 size_t i
= sizeof(*option
) + sizeof(pd
->ia_pd
);
171 DHCP6Address
*prefix
;
173 assert_return(buf
, -EINVAL
);
174 assert_return(pd
, -EINVAL
);
175 assert_return(pd
->type
== SD_DHCP6_OPTION_IA_PD
, -EINVAL
);
180 option
->code
= htobe16(SD_DHCP6_OPTION_IA_PD
);
182 memcpy(&option
->data
, &pd
->ia_pd
, sizeof(pd
->ia_pd
));
184 LIST_FOREACH(addresses
, prefix
, pd
->addresses
) {
185 DHCP6PDPrefixOption
*prefix_opt
;
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
,
195 sizeof(struct iapdprefix
));
197 i
+= sizeof(*prefix_opt
);
200 option
->len
= htobe16(i
- sizeof(*option
));
205 static int option_parse_hdr(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
, size_t *optlen
) {
206 DHCP6Option
*option
= (DHCP6Option
*) *buf
;
209 assert_return(buf
, -EINVAL
);
210 assert_return(optcode
, -EINVAL
);
211 assert_return(optlen
, -EINVAL
);
213 if (*buflen
< sizeof(DHCP6Option
))
216 len
= be16toh(option
->len
);
221 *optcode
= be16toh(option
->code
);
230 int dhcp6_option_parse(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
,
231 size_t *optlen
, uint8_t **optvalue
) {
234 assert_return(buf
&& buflen
&& optcode
&& optlen
&& optvalue
, -EINVAL
);
236 r
= option_parse_hdr(buf
, buflen
, optcode
, optlen
);
240 if (*optlen
> *buflen
)
250 int dhcp6_option_parse_status(DHCP6Option
*option
) {
251 DHCP6StatusOption
*statusopt
= (DHCP6StatusOption
*)option
;
253 if (be16toh(option
->len
) + sizeof(DHCP6Option
) < sizeof(*statusopt
))
256 return be16toh(statusopt
->status
);
259 static int dhcp6_option_parse_address(DHCP6Option
*option
, DHCP6IA
*ia
,
260 uint32_t *lifetime_valid
) {
261 DHCP6AddressOption
*addr_option
= (DHCP6AddressOption
*)option
;
263 uint32_t lt_valid
, lt_pref
;
266 if (be16toh(option
->len
) + sizeof(DHCP6Option
) < sizeof(*addr_option
))
269 lt_valid
= be32toh(addr_option
->iaaddr
.lifetime_valid
);
270 lt_pref
= be32toh(addr_option
->iaaddr
.lifetime_preferred
);
272 if (lt_valid
== 0 || lt_pref
> lt_valid
) {
273 log_dhcp6_client(client
, "Valid lifetime of an IA address is zero or preferred lifetime %d > valid lifetime %d",
279 if (be16toh(option
->len
) + sizeof(DHCP6Option
) > sizeof(*addr_option
)) {
280 r
= dhcp6_option_parse_status((DHCP6Option
*)addr_option
->options
);
285 addr
= new0(DHCP6Address
, 1);
289 LIST_INIT(addresses
, addr
);
290 memcpy(&addr
->iaaddr
, option
->data
, sizeof(addr
->iaaddr
));
292 LIST_PREPEND(addresses
, ia
->addresses
, addr
);
294 *lifetime_valid
= be32toh(addr
->iaaddr
.lifetime_valid
);
299 static int dhcp6_option_parse_pdprefix(DHCP6Option
*option
, DHCP6IA
*ia
,
300 uint32_t *lifetime_valid
) {
301 DHCP6PDPrefixOption
*pdprefix_option
= (DHCP6PDPrefixOption
*)option
;
302 DHCP6Address
*prefix
;
303 uint32_t lt_valid
, lt_pref
;
306 if (be16toh(option
->len
) + sizeof(DHCP6Option
) < sizeof(*pdprefix_option
))
309 lt_valid
= be32toh(pdprefix_option
->iapdprefix
.lifetime_valid
);
310 lt_pref
= be32toh(pdprefix_option
->iapdprefix
.lifetime_preferred
);
312 if (lt_valid
== 0 || lt_pref
> lt_valid
) {
313 log_dhcp6_client(client
, "Valid lifetieme of a PD prefix is zero or preferred lifetime %d > valid lifetime %d",
319 if (be16toh(option
->len
) + sizeof(DHCP6Option
) > sizeof(*pdprefix_option
)) {
320 r
= dhcp6_option_parse_status((DHCP6Option
*)pdprefix_option
->options
);
325 prefix
= new0(DHCP6Address
, 1);
329 LIST_INIT(addresses
, prefix
);
330 memcpy(&prefix
->iapdprefix
, option
->data
, sizeof(prefix
->iapdprefix
));
332 LIST_PREPEND(addresses
, ia
->addresses
, prefix
);
334 *lifetime_valid
= be32toh(prefix
->iapdprefix
.lifetime_valid
);
339 int dhcp6_option_parse_ia(DHCP6Option
*iaoption
, DHCP6IA
*ia
) {
340 uint16_t iatype
, optlen
;
344 size_t iaaddr_offset
;
345 uint32_t lt_t1
, lt_t2
, lt_valid
= 0, lt_min
= UINT32_MAX
;
347 assert_return(ia
, -EINVAL
);
348 assert_return(!ia
->addresses
, -EINVAL
);
350 iatype
= be16toh(iaoption
->code
);
351 len
= be16toh(iaoption
->len
);
354 case SD_DHCP6_OPTION_IA_NA
:
356 if (len
< DHCP6_OPTION_IA_NA_LEN
) {
361 iaaddr_offset
= DHCP6_OPTION_IA_NA_LEN
;
362 memcpy(&ia
->ia_na
, iaoption
->data
, sizeof(ia
->ia_na
));
364 lt_t1
= be32toh(ia
->ia_na
.lifetime_t1
);
365 lt_t2
= be32toh(ia
->ia_na
.lifetime_t2
);
367 if (lt_t1
&& lt_t2
&& lt_t1
> lt_t2
) {
368 log_dhcp6_client(client
, "IA NA T1 %ds > T2 %ds",
376 case SD_DHCP6_OPTION_IA_PD
:
378 if (len
< sizeof(ia
->ia_pd
)) {
383 iaaddr_offset
= sizeof(ia
->ia_pd
);
384 memcpy(&ia
->ia_pd
, iaoption
->data
, sizeof(ia
->ia_pd
));
386 lt_t1
= be32toh(ia
->ia_pd
.lifetime_t1
);
387 lt_t2
= be32toh(ia
->ia_pd
.lifetime_t2
);
389 if (lt_t1
&& lt_t2
&& lt_t1
> lt_t2
) {
390 log_dhcp6_client(client
, "IA PD T1 %ds > T2 %ds",
398 case SD_DHCP6_OPTION_IA_TA
:
399 if (len
< DHCP6_OPTION_IA_TA_LEN
) {
404 iaaddr_offset
= DHCP6_OPTION_IA_TA_LEN
;
405 memcpy(&ia
->ia_ta
.id
, iaoption
->data
, sizeof(ia
->ia_ta
));
418 DHCP6Option
*option
= (DHCP6Option
*)&iaoption
->data
[i
];
420 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");
437 r
= dhcp6_option_parse_address(option
, ia
, <_valid
);
441 if (lt_valid
< lt_min
)
446 case SD_DHCP6_OPTION_IA_PD_PREFIX
:
448 if (!IN_SET(ia
->type
, SD_DHCP6_OPTION_IA_PD
)) {
449 log_dhcp6_client(client
, "IA PD Prefix option not in IA PD option");
454 r
= dhcp6_option_parse_pdprefix(option
, ia
, <_valid
);
458 if (lt_valid
< lt_min
)
463 case SD_DHCP6_OPTION_STATUS_CODE
:
465 status
= dhcp6_option_parse_status(option
);
467 log_dhcp6_client(client
, "IA status %d",
470 dhcp6_lease_free_ia(ia
);
479 log_dhcp6_client(client
, "Unknown IA option %d", opt
);
483 i
+= sizeof(*option
) + optlen
;
487 case SD_DHCP6_OPTION_IA_NA
:
488 if (!ia
->ia_na
.lifetime_t1
&& !ia
->ia_na
.lifetime_t2
) {
490 lt_t2
= lt_min
/ 10 * 8;
491 ia
->ia_na
.lifetime_t1
= htobe32(lt_t1
);
492 ia
->ia_na
.lifetime_t2
= htobe32(lt_t2
);
494 log_dhcp6_client(client
, "Computed IA NA T1 %ds and T2 %ds as both were zero",
500 case SD_DHCP6_OPTION_IA_PD
:
501 if (!ia
->ia_pd
.lifetime_t1
&& !ia
->ia_pd
.lifetime_t2
) {
503 lt_t2
= lt_min
/ 10 * 8;
504 ia
->ia_pd
.lifetime_t1
= htobe32(lt_t1
);
505 ia
->ia_pd
.lifetime_t2
= htobe32(lt_t2
);
507 log_dhcp6_client(client
, "Computed IA PD T1 %ds and T2 %ds as both were zero",
521 int dhcp6_option_parse_ip6addrs(uint8_t *optval
, uint16_t optlen
,
522 struct in6_addr
**addrs
, size_t count
,
525 if (optlen
== 0 || optlen
% sizeof(struct in6_addr
) != 0)
528 if (!GREEDY_REALLOC(*addrs
, *allocated
,
529 count
* sizeof(struct in6_addr
) + optlen
))
532 memcpy(*addrs
+ count
, optval
, optlen
);
534 count
+= optlen
/ sizeof(struct in6_addr
);
539 int dhcp6_option_parse_domainname(const uint8_t *optval
, uint16_t optlen
, char ***str_arr
) {
540 size_t pos
= 0, idx
= 0;
541 _cleanup_strv_free_
char **names
= NULL
;
544 assert_return(optlen
> 1, -ENODATA
);
545 assert_return(optval
[optlen
- 1] == '\0', -EINVAL
);
547 while (pos
< optlen
) {
548 _cleanup_free_
char *ret
= NULL
;
549 size_t n
= 0, allocated
= 0;
564 label
= (const char *)&optval
[pos
];
569 if (!GREEDY_REALLOC(ret
, allocated
, n
+ !first
+ DNS_LABEL_ESCAPED_MAX
)) {
579 r
= dns_label_escape(label
, c
, ret
+ n
, DNS_LABEL_ESCAPED_MAX
);
591 if (!GREEDY_REALLOC(ret
, allocated
, n
+ 1)) {
598 r
= strv_extend(&names
, ret
);
605 *str_arr
= TAKE_PTR(names
);