2 This file is part of systemd.
4 Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 #include <netinet/in.h>
24 #include "sd-dhcp6-client.h"
26 #include "alloc-util.h"
27 #include "dhcp6-internal.h"
28 #include "dhcp6-protocol.h"
29 #include "dns-domain.h"
30 #include "sparse-endian.h"
32 #include "unaligned.h"
35 #define DHCP6_OPTION_IA_NA_LEN 12
36 #define DHCP6_OPTION_IA_TA_LEN 4
38 typedef struct DHCP6Option
{
42 } _packed_ DHCP6Option
;
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 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
;
96 case SD_DHCP6_OPTION_IA_TA
:
97 len
= DHCP6_OPTION_IA_TA_LEN
;
110 *buf
+= sizeof(DHCP6Option
);
111 *buflen
-= sizeof(DHCP6Option
);
113 memcpy(*buf
, &ia
->id
, len
);
118 LIST_FOREACH(addresses
, addr
, ia
->addresses
) {
119 r
= option_append_hdr(buf
, buflen
, SD_DHCP6_OPTION_IAADDR
,
120 sizeof(addr
->iaaddr
));
124 memcpy(*buf
, &addr
->iaaddr
, sizeof(addr
->iaaddr
));
126 *buf
+= sizeof(addr
->iaaddr
);
127 *buflen
-= sizeof(addr
->iaaddr
);
129 ia_addrlen
+= sizeof(DHCP6Option
) + sizeof(addr
->iaaddr
);
132 r
= option_append_hdr(&ia_hdr
, &ia_buflen
, ia
->type
, len
+ ia_addrlen
);
140 static int option_parse_hdr(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
, size_t *optlen
) {
141 DHCP6Option
*option
= (DHCP6Option
*) *buf
;
144 assert_return(buf
, -EINVAL
);
145 assert_return(optcode
, -EINVAL
);
146 assert_return(optlen
, -EINVAL
);
148 if (*buflen
< sizeof(DHCP6Option
))
151 len
= be16toh(option
->len
);
156 *optcode
= be16toh(option
->code
);
165 int dhcp6_option_parse(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
,
166 size_t *optlen
, uint8_t **optvalue
) {
169 assert_return(buf
&& buflen
&& optcode
&& optlen
&& optvalue
, -EINVAL
);
171 r
= option_parse_hdr(buf
, buflen
, optcode
, optlen
);
175 if (*optlen
> *buflen
)
185 int dhcp6_option_parse_ia(uint8_t **buf
, size_t *buflen
, uint16_t iatype
,
188 uint16_t opt
, status
;
190 size_t iaaddr_offset
;
192 uint32_t lt_t1
, lt_t2
, lt_valid
, lt_pref
, lt_min
= ~0;
194 assert_return(ia
, -EINVAL
);
195 assert_return(!ia
->addresses
, -EINVAL
);
198 case SD_DHCP6_OPTION_IA_NA
:
200 if (*buflen
< DHCP6_OPTION_IA_NA_LEN
+ sizeof(DHCP6Option
) +
201 sizeof(addr
->iaaddr
)) {
206 iaaddr_offset
= DHCP6_OPTION_IA_NA_LEN
;
207 memcpy(&ia
->id
, *buf
, iaaddr_offset
);
209 lt_t1
= be32toh(ia
->lifetime_t1
);
210 lt_t2
= be32toh(ia
->lifetime_t2
);
212 if (lt_t1
&& lt_t2
&& lt_t1
> lt_t2
) {
213 log_dhcp6_client(client
, "IA T1 %ds > T2 %ds",
221 case SD_DHCP6_OPTION_IA_TA
:
222 if (*buflen
< DHCP6_OPTION_IA_TA_LEN
+ sizeof(DHCP6Option
) +
223 sizeof(addr
->iaaddr
)) {
228 iaaddr_offset
= DHCP6_OPTION_IA_TA_LEN
;
229 memcpy(&ia
->id
, *buf
, iaaddr_offset
);
243 *buflen
-= iaaddr_offset
;
244 *buf
+= iaaddr_offset
;
246 while ((r
= option_parse_hdr(buf
, buflen
, &opt
, &optlen
)) >= 0) {
249 case SD_DHCP6_OPTION_IAADDR
:
251 addr
= new0(DHCP6Address
, 1);
257 LIST_INIT(addresses
, addr
);
259 memcpy(&addr
->iaaddr
, *buf
, sizeof(addr
->iaaddr
));
261 lt_valid
= be32toh(addr
->iaaddr
.lifetime_valid
);
262 lt_pref
= be32toh(addr
->iaaddr
.lifetime_valid
);
264 if (!lt_valid
|| lt_pref
> lt_valid
) {
265 log_dhcp6_client(client
, "IA preferred %ds > valid %ds",
269 LIST_PREPEND(addresses
, ia
->addresses
, addr
);
270 if (lt_valid
< lt_min
)
276 case SD_DHCP6_OPTION_STATUS_CODE
:
277 if (optlen
< sizeof(status
))
280 status
= (*buf
)[0] << 8 | (*buf
)[1];
282 log_dhcp6_client(client
, "IA status %d",
291 log_dhcp6_client(client
, "Unknown IA option %d", opt
);
302 if (!ia
->lifetime_t1
&& !ia
->lifetime_t2
) {
304 lt_t2
= lt_min
/ 10 * 8;
305 ia
->lifetime_t1
= htobe32(lt_t1
);
306 ia
->lifetime_t2
= htobe32(lt_t2
);
308 log_dhcp6_client(client
, "Computed IA T1 %ds and T2 %ds as both were zero",
322 int dhcp6_option_parse_ip6addrs(uint8_t *optval
, uint16_t optlen
,
323 struct in6_addr
**addrs
, size_t count
,
326 if (optlen
== 0 || optlen
% sizeof(struct in6_addr
) != 0)
329 if (!GREEDY_REALLOC(*addrs
, *allocated
,
330 count
* sizeof(struct in6_addr
) + optlen
))
333 memcpy(*addrs
+ count
, optval
, optlen
);
335 count
+= optlen
/ sizeof(struct in6_addr
);
340 int dhcp6_option_parse_domainname(const uint8_t *optval
, uint16_t optlen
, char ***str_arr
) {
341 size_t pos
= 0, idx
= 0;
342 _cleanup_strv_free_
char **names
= NULL
;
345 assert_return(optlen
> 1, -ENODATA
);
346 assert_return(optval
[optlen
- 1] == '\0', -EINVAL
);
348 while (pos
< optlen
) {
349 _cleanup_free_
char *ret
= NULL
;
350 size_t n
= 0, allocated
= 0;
365 label
= (const char *)&optval
[pos
];
370 if (!GREEDY_REALLOC(ret
, allocated
, n
+ !first
+ DNS_LABEL_ESCAPED_MAX
)) {
380 r
= dns_label_escape(label
, c
, ret
+ n
, DNS_LABEL_ESCAPED_MAX
);
392 if (!GREEDY_REALLOC(ret
, allocated
, n
+ 1)) {
399 r
= strv_extend(&names
, ret
);