1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include <netinet/in.h>
25 #include "sd-dhcp6-client.h"
27 #include "alloc-util.h"
28 #include "dhcp6-internal.h"
29 #include "dhcp6-protocol.h"
30 #include "dns-domain.h"
31 #include "sparse-endian.h"
33 #include "unaligned.h"
36 #define DHCP6_OPTION_IA_NA_LEN 12
37 #define DHCP6_OPTION_IA_TA_LEN 4
39 typedef struct DHCP6Option
{
43 } _packed_ DHCP6Option
;
45 static int option_append_hdr(uint8_t **buf
, size_t *buflen
, uint16_t optcode
,
47 DHCP6Option
*option
= (DHCP6Option
*) *buf
;
49 assert_return(buf
, -EINVAL
);
50 assert_return(*buf
, -EINVAL
);
51 assert_return(buflen
, -EINVAL
);
53 if (optlen
> 0xffff || *buflen
< optlen
+ sizeof(DHCP6Option
))
56 option
->code
= htobe16(optcode
);
57 option
->len
= htobe16(optlen
);
59 *buf
+= sizeof(DHCP6Option
);
60 *buflen
-= sizeof(DHCP6Option
);
65 int dhcp6_option_append(uint8_t **buf
, size_t *buflen
, uint16_t code
,
66 size_t optlen
, const void *optval
) {
69 assert_return(optval
|| optlen
== 0, -EINVAL
);
71 r
= option_append_hdr(buf
, buflen
, code
, optlen
);
75 memcpy_safe(*buf
, optval
, optlen
);
83 int dhcp6_option_append_ia(uint8_t **buf
, size_t *buflen
, DHCP6IA
*ia
) {
86 size_t ia_buflen
, ia_addrlen
= 0;
90 assert_return(buf
&& *buf
&& buflen
&& ia
, -EINVAL
);
93 case SD_DHCP6_OPTION_IA_NA
:
94 len
= DHCP6_OPTION_IA_NA_LEN
;
97 case SD_DHCP6_OPTION_IA_TA
:
98 len
= DHCP6_OPTION_IA_TA_LEN
;
111 *buf
+= sizeof(DHCP6Option
);
112 *buflen
-= sizeof(DHCP6Option
);
114 memcpy(*buf
, &ia
->id
, len
);
119 LIST_FOREACH(addresses
, addr
, ia
->addresses
) {
120 r
= option_append_hdr(buf
, buflen
, SD_DHCP6_OPTION_IAADDR
,
121 sizeof(addr
->iaaddr
));
125 memcpy(*buf
, &addr
->iaaddr
, sizeof(addr
->iaaddr
));
127 *buf
+= sizeof(addr
->iaaddr
);
128 *buflen
-= sizeof(addr
->iaaddr
);
130 ia_addrlen
+= sizeof(DHCP6Option
) + sizeof(addr
->iaaddr
);
133 r
= option_append_hdr(&ia_hdr
, &ia_buflen
, ia
->type
, len
+ ia_addrlen
);
140 int dhcp6_option_append_fqdn(uint8_t **buf
, size_t *buflen
, const char *fqdn
) {
141 uint8_t buffer
[1 + DNS_WIRE_FOMAT_HOSTNAME_MAX
];
144 assert_return(buf
&& *buf
&& buflen
&& fqdn
, -EINVAL
);
146 buffer
[0] = DHCP6_FQDN_FLAG_S
; /* Request server to perform AAAA RR DNS updates */
148 /* Store domain name after flags field */
149 r
= dns_name_to_wire_format(fqdn
, buffer
+ 1, sizeof(buffer
) - 1, false);
154 * According to RFC 4704, chapter 4.2 only add terminating zero-length
155 * label in case a FQDN is provided. Since dns_name_to_wire_format
156 * always adds terminating zero-length label remove if only a hostname
159 if (dns_name_is_single_label(fqdn
))
162 r
= dhcp6_option_append(buf
, buflen
, SD_DHCP6_OPTION_FQDN
, 1 + r
, buffer
);
168 static int option_parse_hdr(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
, size_t *optlen
) {
169 DHCP6Option
*option
= (DHCP6Option
*) *buf
;
172 assert_return(buf
, -EINVAL
);
173 assert_return(optcode
, -EINVAL
);
174 assert_return(optlen
, -EINVAL
);
176 if (*buflen
< sizeof(DHCP6Option
))
179 len
= be16toh(option
->len
);
184 *optcode
= be16toh(option
->code
);
193 int dhcp6_option_parse(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
,
194 size_t *optlen
, uint8_t **optvalue
) {
197 assert_return(buf
&& buflen
&& optcode
&& optlen
&& optvalue
, -EINVAL
);
199 r
= option_parse_hdr(buf
, buflen
, optcode
, optlen
);
203 if (*optlen
> *buflen
)
213 int dhcp6_option_parse_ia(uint8_t **buf
, size_t *buflen
, uint16_t iatype
,
216 uint16_t opt
, status
;
218 size_t iaaddr_offset
;
220 uint32_t lt_t1
, lt_t2
, lt_valid
, lt_pref
, lt_min
= ~0;
222 assert_return(ia
, -EINVAL
);
223 assert_return(!ia
->addresses
, -EINVAL
);
226 case SD_DHCP6_OPTION_IA_NA
:
228 if (*buflen
< DHCP6_OPTION_IA_NA_LEN
+ sizeof(DHCP6Option
) +
229 sizeof(addr
->iaaddr
)) {
234 iaaddr_offset
= DHCP6_OPTION_IA_NA_LEN
;
235 memcpy(&ia
->id
, *buf
, iaaddr_offset
);
237 lt_t1
= be32toh(ia
->lifetime_t1
);
238 lt_t2
= be32toh(ia
->lifetime_t2
);
240 if (lt_t1
&& lt_t2
&& lt_t1
> lt_t2
) {
241 log_dhcp6_client(client
, "IA T1 %ds > T2 %ds",
249 case SD_DHCP6_OPTION_IA_TA
:
250 if (*buflen
< DHCP6_OPTION_IA_TA_LEN
+ sizeof(DHCP6Option
) +
251 sizeof(addr
->iaaddr
)) {
256 iaaddr_offset
= DHCP6_OPTION_IA_TA_LEN
;
257 memcpy(&ia
->id
, *buf
, iaaddr_offset
);
271 *buflen
-= iaaddr_offset
;
272 *buf
+= iaaddr_offset
;
274 while ((r
= option_parse_hdr(buf
, buflen
, &opt
, &optlen
)) >= 0) {
277 case SD_DHCP6_OPTION_IAADDR
:
279 addr
= new0(DHCP6Address
, 1);
285 LIST_INIT(addresses
, addr
);
287 memcpy(&addr
->iaaddr
, *buf
, sizeof(addr
->iaaddr
));
289 lt_valid
= be32toh(addr
->iaaddr
.lifetime_valid
);
290 lt_pref
= be32toh(addr
->iaaddr
.lifetime_valid
);
292 if (!lt_valid
|| lt_pref
> lt_valid
) {
293 log_dhcp6_client(client
, "IA preferred %ds > valid %ds",
297 LIST_PREPEND(addresses
, ia
->addresses
, addr
);
298 if (lt_valid
< lt_min
)
304 case SD_DHCP6_OPTION_STATUS_CODE
:
305 if (optlen
< sizeof(status
))
308 status
= (*buf
)[0] << 8 | (*buf
)[1];
310 log_dhcp6_client(client
, "IA status %d",
319 log_dhcp6_client(client
, "Unknown IA option %d", opt
);
330 if (!ia
->lifetime_t1
&& !ia
->lifetime_t2
) {
332 lt_t2
= lt_min
/ 10 * 8;
333 ia
->lifetime_t1
= htobe32(lt_t1
);
334 ia
->lifetime_t2
= htobe32(lt_t2
);
336 log_dhcp6_client(client
, "Computed IA T1 %ds and T2 %ds as both were zero",
350 int dhcp6_option_parse_ip6addrs(uint8_t *optval
, uint16_t optlen
,
351 struct in6_addr
**addrs
, size_t count
,
354 if (optlen
== 0 || optlen
% sizeof(struct in6_addr
) != 0)
357 if (!GREEDY_REALLOC(*addrs
, *allocated
,
358 count
* sizeof(struct in6_addr
) + optlen
))
361 memcpy(*addrs
+ count
, optval
, optlen
);
363 count
+= optlen
/ sizeof(struct in6_addr
);
368 int dhcp6_option_parse_domainname(const uint8_t *optval
, uint16_t optlen
, char ***str_arr
) {
369 size_t pos
= 0, idx
= 0;
370 _cleanup_strv_free_
char **names
= NULL
;
373 assert_return(optlen
> 1, -ENODATA
);
374 assert_return(optval
[optlen
- 1] == '\0', -EINVAL
);
376 while (pos
< optlen
) {
377 _cleanup_free_
char *ret
= NULL
;
378 size_t n
= 0, allocated
= 0;
393 label
= (const char *)&optval
[pos
];
398 if (!GREEDY_REALLOC(ret
, allocated
, n
+ !first
+ DNS_LABEL_ESCAPED_MAX
)) {
408 r
= dns_label_escape(label
, c
, ret
+ n
, DNS_LABEL_ESCAPED_MAX
);
420 if (!GREEDY_REALLOC(ret
, allocated
, n
+ 1)) {
427 r
= strv_extend(&names
, ret
);