1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2014 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"
30 #include "dhcp6-internal.h"
31 #include "dhcp6-protocol.h"
33 #define DHCP6_OPTION_IA_NA_LEN 12
34 #define DHCP6_OPTION_IA_TA_LEN 4
36 typedef struct DHCP6Option {
42 static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
44 DHCP6Option *option = (DHCP6Option*) *buf; /* unaligned! */
46 assert_return(buf, -EINVAL);
47 assert_return(*buf, -EINVAL);
48 assert_return(buflen, -EINVAL);
50 if (optlen > 0xffff || *buflen < optlen + sizeof(DHCP6Option))
53 unaligned_write_be16(&option->code, optcode);
54 unaligned_write_be16(&option->len, (uint16_t) optlen);
56 *buf += sizeof(DHCP6Option);
57 *buflen -= sizeof(DHCP6Option);
62 int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
63 size_t optlen, const void *optval) {
66 assert_return(optval || optlen == 0, -EINVAL);
68 r = option_append_hdr(buf, buflen, code, optlen);
73 memcpy(*buf, optval, optlen);
81 int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
84 size_t ia_buflen, ia_addrlen = 0;
88 assert_return(buf && *buf && buflen && ia, -EINVAL);
91 case DHCP6_OPTION_IA_NA:
92 len = DHCP6_OPTION_IA_NA_LEN;
95 case DHCP6_OPTION_IA_TA:
96 len = DHCP6_OPTION_IA_TA_LEN;
109 *buf += sizeof(DHCP6Option);
110 *buflen -= sizeof(DHCP6Option);
112 memcpy(*buf, &ia->id, len);
117 LIST_FOREACH(addresses, addr, ia->addresses) {
118 r = option_append_hdr(buf, buflen, DHCP6_OPTION_IAADDR,
119 sizeof(addr->iaaddr));
123 memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
125 *buf += sizeof(addr->iaaddr);
126 *buflen -= sizeof(addr->iaaddr);
128 ia_addrlen += sizeof(DHCP6Option) + sizeof(addr->iaaddr);
131 r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
139 static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode,
141 DHCP6Option *option = (DHCP6Option*) *buf; /* unaligned! */
144 assert_return(buf, -EINVAL);
145 assert_return(optcode, -EINVAL);
146 assert_return(optlen, -EINVAL);
148 if (*buflen < sizeof(DHCP6Option))
151 len = unaligned_read_be16(&option->len);
156 *optcode = unaligned_read_be16(&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 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 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 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 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",