1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 Copyright © 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
+ 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_ia(uint8_t **buf
, size_t *buflen
, const DHCP6IA
*ia
) {
85 size_t iaid_offset
, ia_buflen
, ia_addrlen
= 0;
89 assert_return(buf
, -EINVAL
);
90 assert_return(*buf
, -EINVAL
);
91 assert_return(buflen
, -EINVAL
);
92 assert_return(ia
, -EINVAL
);
95 case SD_DHCP6_OPTION_IA_NA
:
96 len
= DHCP6_OPTION_IA_NA_LEN
;
97 iaid_offset
= offsetof(DHCP6IA
, ia_na
);
100 case SD_DHCP6_OPTION_IA_TA
:
101 len
= DHCP6_OPTION_IA_TA_LEN
;
102 iaid_offset
= offsetof(DHCP6IA
, ia_ta
);
109 if (*buflen
< offsetof(DHCP6Option
, data
) + len
)
115 *buf
+= offsetof(DHCP6Option
, data
);
116 *buflen
-= offsetof(DHCP6Option
, data
);
118 memcpy(*buf
, (char*) ia
+ iaid_offset
, len
);
123 LIST_FOREACH(addresses
, addr
, ia
->addresses
) {
124 r
= option_append_hdr(buf
, buflen
, SD_DHCP6_OPTION_IAADDR
,
125 sizeof(addr
->iaaddr
));
129 memcpy(*buf
, &addr
->iaaddr
, sizeof(addr
->iaaddr
));
131 *buf
+= sizeof(addr
->iaaddr
);
132 *buflen
-= sizeof(addr
->iaaddr
);
134 ia_addrlen
+= offsetof(DHCP6Option
, data
) + sizeof(addr
->iaaddr
);
137 r
= option_append_hdr(&ia_hdr
, &ia_buflen
, ia
->type
, len
+ ia_addrlen
);
144 int dhcp6_option_append_fqdn(uint8_t **buf
, size_t *buflen
, const char *fqdn
) {
145 uint8_t buffer
[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX
];
148 assert_return(buf
&& *buf
&& buflen
&& fqdn
, -EINVAL
);
150 buffer
[0] = DHCP6_FQDN_FLAG_S
; /* Request server to perform AAAA RR DNS updates */
152 /* Store domain name after flags field */
153 r
= dns_name_to_wire_format(fqdn
, buffer
+ 1, sizeof(buffer
) - 1, false);
158 * According to RFC 4704, chapter 4.2 only add terminating zero-length
159 * label in case a FQDN is provided. Since dns_name_to_wire_format
160 * always adds terminating zero-length label remove if only a hostname
163 if (dns_name_is_single_label(fqdn
))
166 r
= dhcp6_option_append(buf
, buflen
, SD_DHCP6_OPTION_FQDN
, 1 + r
, buffer
);
171 int dhcp6_option_append_pd(uint8_t *buf
, size_t len
, const DHCP6IA
*pd
) {
172 DHCP6Option
*option
= (DHCP6Option
*)buf
;
173 size_t i
= sizeof(*option
) + sizeof(pd
->ia_pd
);
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
));
187 LIST_FOREACH(addresses
, prefix
, pd
->addresses
) {
188 DHCP6PDPrefixOption
*prefix_opt
;
190 if (len
< i
+ sizeof(*prefix_opt
))
193 prefix_opt
= (DHCP6PDPrefixOption
*)&buf
[i
];
194 prefix_opt
->option
.code
= htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX
);
195 prefix_opt
->option
.len
= htobe16(sizeof(prefix_opt
->iapdprefix
));
197 memcpy(&prefix_opt
->iapdprefix
, &prefix
->iapdprefix
,
198 sizeof(struct iapdprefix
));
200 i
+= sizeof(*prefix_opt
);
203 option
->len
= htobe16(i
- sizeof(*option
));
208 static int option_parse_hdr(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
, size_t *optlen
) {
209 DHCP6Option
*option
= (DHCP6Option
*) *buf
;
212 assert_return(buf
, -EINVAL
);
213 assert_return(optcode
, -EINVAL
);
214 assert_return(optlen
, -EINVAL
);
216 if (*buflen
< offsetof(DHCP6Option
, data
))
219 len
= be16toh(option
->len
);
224 *optcode
= be16toh(option
->code
);
233 int dhcp6_option_parse(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
,
234 size_t *optlen
, uint8_t **optvalue
) {
237 assert_return(buf
&& buflen
&& optcode
&& optlen
&& optvalue
, -EINVAL
);
239 r
= option_parse_hdr(buf
, buflen
, optcode
, optlen
);
243 if (*optlen
> *buflen
)
253 int dhcp6_option_parse_status(DHCP6Option
*option
, size_t len
) {
254 DHCP6StatusOption
*statusopt
= (DHCP6StatusOption
*)option
;
256 if (len
< sizeof(DHCP6StatusOption
) ||
257 be16toh(option
->len
) + offsetof(DHCP6Option
, data
) < sizeof(DHCP6StatusOption
))
260 return be16toh(statusopt
->status
);
263 static int dhcp6_option_parse_address(DHCP6Option
*option
, DHCP6IA
*ia
,
264 uint32_t *lifetime_valid
) {
265 DHCP6AddressOption
*addr_option
= (DHCP6AddressOption
*)option
;
267 uint32_t lt_valid
, lt_pref
;
270 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) < sizeof(*addr_option
))
273 lt_valid
= be32toh(addr_option
->iaaddr
.lifetime_valid
);
274 lt_pref
= be32toh(addr_option
->iaaddr
.lifetime_preferred
);
276 if (lt_valid
== 0 || lt_pref
> lt_valid
) {
277 log_dhcp6_client(client
, "Valid lifetime of an IA address is zero or preferred lifetime %d > valid lifetime %d",
283 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) > sizeof(*addr_option
)) {
284 r
= dhcp6_option_parse_status((DHCP6Option
*)addr_option
->options
, be16toh(option
->len
) + offsetof(DHCP6Option
, data
) - sizeof(*addr_option
));
289 addr
= new0(DHCP6Address
, 1);
293 LIST_INIT(addresses
, addr
);
294 memcpy(&addr
->iaaddr
, option
->data
, sizeof(addr
->iaaddr
));
296 LIST_PREPEND(addresses
, ia
->addresses
, addr
);
298 *lifetime_valid
= be32toh(addr
->iaaddr
.lifetime_valid
);
303 static int dhcp6_option_parse_pdprefix(DHCP6Option
*option
, DHCP6IA
*ia
,
304 uint32_t *lifetime_valid
) {
305 DHCP6PDPrefixOption
*pdprefix_option
= (DHCP6PDPrefixOption
*)option
;
306 DHCP6Address
*prefix
;
307 uint32_t lt_valid
, lt_pref
;
310 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) < sizeof(*pdprefix_option
))
313 lt_valid
= be32toh(pdprefix_option
->iapdprefix
.lifetime_valid
);
314 lt_pref
= be32toh(pdprefix_option
->iapdprefix
.lifetime_preferred
);
316 if (lt_valid
== 0 || lt_pref
> lt_valid
) {
317 log_dhcp6_client(client
, "Valid lifetieme of a PD prefix is zero or preferred lifetime %d > valid lifetime %d",
323 if (be16toh(option
->len
) + offsetof(DHCP6Option
, data
) > sizeof(*pdprefix_option
)) {
324 r
= dhcp6_option_parse_status((DHCP6Option
*)pdprefix_option
->options
, be16toh(option
->len
) + offsetof(DHCP6Option
, data
) - sizeof(*pdprefix_option
));
329 prefix
= new0(DHCP6Address
, 1);
333 LIST_INIT(addresses
, prefix
);
334 memcpy(&prefix
->iapdprefix
, option
->data
, sizeof(prefix
->iapdprefix
));
336 LIST_PREPEND(addresses
, ia
->addresses
, prefix
);
338 *lifetime_valid
= be32toh(prefix
->iapdprefix
.lifetime_valid
);
343 int dhcp6_option_parse_ia(DHCP6Option
*iaoption
, DHCP6IA
*ia
) {
344 uint16_t iatype
, optlen
;
348 size_t iaaddr_offset
;
349 uint32_t lt_t1
, lt_t2
, lt_valid
= 0, lt_min
= UINT32_MAX
;
351 assert_return(ia
, -EINVAL
);
352 assert_return(!ia
->addresses
, -EINVAL
);
354 iatype
= be16toh(iaoption
->code
);
355 len
= be16toh(iaoption
->len
);
358 case SD_DHCP6_OPTION_IA_NA
:
360 if (len
< DHCP6_OPTION_IA_NA_LEN
)
363 iaaddr_offset
= DHCP6_OPTION_IA_NA_LEN
;
364 memcpy(&ia
->ia_na
, iaoption
->data
, sizeof(ia
->ia_na
));
366 lt_t1
= be32toh(ia
->ia_na
.lifetime_t1
);
367 lt_t2
= be32toh(ia
->ia_na
.lifetime_t2
);
369 if (lt_t1
&& lt_t2
&& lt_t1
> lt_t2
) {
370 log_dhcp6_client(client
, "IA NA T1 %ds > T2 %ds",
377 case SD_DHCP6_OPTION_IA_PD
:
379 if (len
< sizeof(ia
->ia_pd
))
382 iaaddr_offset
= sizeof(ia
->ia_pd
);
383 memcpy(&ia
->ia_pd
, iaoption
->data
, sizeof(ia
->ia_pd
));
385 lt_t1
= be32toh(ia
->ia_pd
.lifetime_t1
);
386 lt_t2
= be32toh(ia
->ia_pd
.lifetime_t2
);
388 if (lt_t1
&& lt_t2
&& lt_t1
> lt_t2
) {
389 log_dhcp6_client(client
, "IA PD T1 %ds > T2 %ds",
396 case SD_DHCP6_OPTION_IA_TA
:
397 if (len
< DHCP6_OPTION_IA_TA_LEN
)
400 iaaddr_offset
= DHCP6_OPTION_IA_TA_LEN
;
401 memcpy(&ia
->ia_ta
.id
, iaoption
->data
, sizeof(ia
->ia_ta
));
413 DHCP6Option
*option
= (DHCP6Option
*)&iaoption
->data
[i
];
415 if (len
< i
+ sizeof(*option
) || len
< i
+ sizeof(*option
) + be16toh(option
->len
))
418 opt
= be16toh(option
->code
);
419 optlen
= be16toh(option
->len
);
422 case SD_DHCP6_OPTION_IAADDR
:
424 if (!IN_SET(ia
->type
, SD_DHCP6_OPTION_IA_NA
, SD_DHCP6_OPTION_IA_TA
)) {
425 log_dhcp6_client(client
, "IA Address option not in IA NA or TA option");
429 r
= dhcp6_option_parse_address(option
, ia
, <_valid
);
433 if (lt_valid
< lt_min
)
438 case SD_DHCP6_OPTION_IA_PD_PREFIX
:
440 if (!IN_SET(ia
->type
, SD_DHCP6_OPTION_IA_PD
)) {
441 log_dhcp6_client(client
, "IA PD Prefix option not in IA PD option");
445 r
= dhcp6_option_parse_pdprefix(option
, ia
, <_valid
);
449 if (lt_valid
< lt_min
)
454 case SD_DHCP6_OPTION_STATUS_CODE
:
456 status
= dhcp6_option_parse_status(option
, optlen
+ offsetof(DHCP6Option
, data
));
460 log_dhcp6_client(client
, "IA status %d",
469 log_dhcp6_client(client
, "Unknown IA option %d", opt
);
473 i
+= sizeof(*option
) + optlen
;
477 case SD_DHCP6_OPTION_IA_NA
:
478 if (!ia
->ia_na
.lifetime_t1
&& !ia
->ia_na
.lifetime_t2
) {
480 lt_t2
= lt_min
/ 10 * 8;
481 ia
->ia_na
.lifetime_t1
= htobe32(lt_t1
);
482 ia
->ia_na
.lifetime_t2
= htobe32(lt_t2
);
484 log_dhcp6_client(client
, "Computed IA NA T1 %ds and T2 %ds as both were zero",
490 case SD_DHCP6_OPTION_IA_PD
:
491 if (!ia
->ia_pd
.lifetime_t1
&& !ia
->ia_pd
.lifetime_t2
) {
493 lt_t2
= lt_min
/ 10 * 8;
494 ia
->ia_pd
.lifetime_t1
= htobe32(lt_t1
);
495 ia
->ia_pd
.lifetime_t2
= htobe32(lt_t2
);
497 log_dhcp6_client(client
, "Computed IA PD T1 %ds and T2 %ds as both were zero",
510 int dhcp6_option_parse_ip6addrs(uint8_t *optval
, uint16_t optlen
,
511 struct in6_addr
**addrs
, size_t count
,
514 if (optlen
== 0 || optlen
% sizeof(struct in6_addr
) != 0)
517 if (!GREEDY_REALLOC(*addrs
, *allocated
,
518 count
* sizeof(struct in6_addr
) + optlen
))
521 memcpy(*addrs
+ count
, optval
, optlen
);
523 count
+= optlen
/ sizeof(struct in6_addr
);
528 int dhcp6_option_parse_domainname(const uint8_t *optval
, uint16_t optlen
, char ***str_arr
) {
529 size_t pos
= 0, idx
= 0;
530 _cleanup_strv_free_
char **names
= NULL
;
533 assert_return(optlen
> 1, -ENODATA
);
534 assert_return(optval
[optlen
- 1] == '\0', -EINVAL
);
536 while (pos
< optlen
) {
537 _cleanup_free_
char *ret
= NULL
;
538 size_t n
= 0, allocated
= 0;
554 label
= (const char *)&optval
[pos
];
559 if (!GREEDY_REALLOC(ret
, allocated
, n
+ !first
+ DNS_LABEL_ESCAPED_MAX
))
567 r
= dns_label_escape(label
, c
, ret
+ n
, DNS_LABEL_ESCAPED_MAX
);
577 if (!GREEDY_REALLOC(ret
, allocated
, n
+ 1))
582 r
= strv_extend(&names
, ret
);
589 *str_arr
= TAKE_PTR(names
);