]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/dhcp6-option.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include <netinet/in.h>
26 #include "sparse-endian.h"
27 #include "unaligned.h"
31 #include "dhcp6-internal.h"
32 #include "dhcp6-protocol.h"
33 #include "dns-domain.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
);
75 memcpy(*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 DHCP6_OPTION_IA_NA
:
94 len
= DHCP6_OPTION_IA_NA_LEN
;
97 case 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
, 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
);
141 static int option_parse_hdr(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
, size_t *optlen
) {
142 DHCP6Option
*option
= (DHCP6Option
*) *buf
;
145 assert_return(buf
, -EINVAL
);
146 assert_return(optcode
, -EINVAL
);
147 assert_return(optlen
, -EINVAL
);
149 if (*buflen
< sizeof(DHCP6Option
))
152 len
= be16toh(option
->len
);
157 *optcode
= be16toh(option
->code
);
166 int dhcp6_option_parse(uint8_t **buf
, size_t *buflen
, uint16_t *optcode
,
167 size_t *optlen
, uint8_t **optvalue
) {
170 assert_return(buf
&& buflen
&& optcode
&& optlen
&& optvalue
, -EINVAL
);
172 r
= option_parse_hdr(buf
, buflen
, optcode
, optlen
);
176 if (*optlen
> *buflen
)
186 int dhcp6_option_parse_ia(uint8_t **buf
, size_t *buflen
, uint16_t iatype
,
189 uint16_t opt
, status
;
191 size_t iaaddr_offset
;
193 uint32_t lt_t1
, lt_t2
, lt_valid
, lt_pref
, lt_min
= ~0;
195 assert_return(ia
, -EINVAL
);
196 assert_return(!ia
->addresses
, -EINVAL
);
199 case DHCP6_OPTION_IA_NA
:
201 if (*buflen
< DHCP6_OPTION_IA_NA_LEN
+ sizeof(DHCP6Option
) +
202 sizeof(addr
->iaaddr
)) {
207 iaaddr_offset
= DHCP6_OPTION_IA_NA_LEN
;
208 memcpy(&ia
->id
, *buf
, iaaddr_offset
);
210 lt_t1
= be32toh(ia
->lifetime_t1
);
211 lt_t2
= be32toh(ia
->lifetime_t2
);
213 if (lt_t1
&& lt_t2
&& lt_t1
> lt_t2
) {
214 log_dhcp6_client(client
, "IA T1 %ds > T2 %ds",
222 case DHCP6_OPTION_IA_TA
:
223 if (*buflen
< DHCP6_OPTION_IA_TA_LEN
+ sizeof(DHCP6Option
) +
224 sizeof(addr
->iaaddr
)) {
229 iaaddr_offset
= DHCP6_OPTION_IA_TA_LEN
;
230 memcpy(&ia
->id
, *buf
, iaaddr_offset
);
244 *buflen
-= iaaddr_offset
;
245 *buf
+= iaaddr_offset
;
247 while ((r
= option_parse_hdr(buf
, buflen
, &opt
, &optlen
)) >= 0) {
250 case DHCP6_OPTION_IAADDR
:
252 addr
= new0(DHCP6Address
, 1);
258 LIST_INIT(addresses
, addr
);
260 memcpy(&addr
->iaaddr
, *buf
, sizeof(addr
->iaaddr
));
262 lt_valid
= be32toh(addr
->iaaddr
.lifetime_valid
);
263 lt_pref
= be32toh(addr
->iaaddr
.lifetime_valid
);
265 if (!lt_valid
|| lt_pref
> lt_valid
) {
266 log_dhcp6_client(client
, "IA preferred %ds > valid %ds",
270 LIST_PREPEND(addresses
, ia
->addresses
, addr
);
271 if (lt_valid
< lt_min
)
277 case DHCP6_OPTION_STATUS_CODE
:
278 if (optlen
< sizeof(status
))
281 status
= (*buf
)[0] << 8 | (*buf
)[1];
283 log_dhcp6_client(client
, "IA status %d",
292 log_dhcp6_client(client
, "Unknown IA option %d", opt
);
303 if (!ia
->lifetime_t1
&& !ia
->lifetime_t2
) {
305 lt_t2
= lt_min
/ 10 * 8;
306 ia
->lifetime_t1
= htobe32(lt_t1
);
307 ia
->lifetime_t2
= htobe32(lt_t2
);
309 log_dhcp6_client(client
, "Computed IA T1 %ds and T2 %ds as both were zero",
323 int dhcp6_option_parse_ip6addrs(uint8_t *optval
, uint16_t optlen
,
324 struct in6_addr
**addrs
, size_t count
,
327 if (optlen
== 0 || optlen
% sizeof(struct in6_addr
) != 0)
330 if (!GREEDY_REALLOC(*addrs
, *allocated
,
331 count
* sizeof(struct in6_addr
) + optlen
))
334 memcpy(*addrs
+ count
, optval
, optlen
);
336 count
+= optlen
/ sizeof(struct in6_addr
);
341 int dhcp6_option_parse_domainname(const uint8_t *optval
, uint16_t optlen
,
344 size_t pos
= 0, idx
= 0;
345 _cleanup_free_
char **names
= NULL
;
348 assert_return(optlen
> 1, -ENODATA
);
349 assert_return(optval
[optlen
] == '\0', -EINVAL
);
351 while (pos
< optlen
) {
352 _cleanup_free_
char *ret
= NULL
;
353 size_t n
= 0, allocated
= 0;
365 _cleanup_free_
char *t
= NULL
;
369 label
= (const char *)&optval
[pos
];
374 r
= dns_label_escape(label
, c
, &t
);
378 if (!GREEDY_REALLOC0(ret
, allocated
, n
+ !first
+ strlen(t
) + 1)) {
388 memcpy(ret
+ n
, t
, r
);
397 if (!GREEDY_REALLOC(ret
, allocated
, n
+ 1)) {
404 r
= strv_extend(&names
, ret
);