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"
29 #include "dhcp6-internal.h"
30 #include "dhcp6-protocol.h"
32 #define DHCP6_OPTION_HDR_LEN 4
33 #define DHCP6_OPTION_IA_NA_LEN 12
34 #define DHCP6_OPTION_IA_TA_LEN 4
35 #define DHCP6_OPTION_IAADDR_LEN 24
37 static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
39 assert_return(buf, -EINVAL);
40 assert_return(*buf, -EINVAL);
41 assert_return(buflen, -EINVAL);
43 if (optlen > 0xffff || *buflen < optlen + DHCP6_OPTION_HDR_LEN)
46 (*buf)[0] = optcode >> 8;
47 (*buf)[1] = optcode & 0xff;
48 (*buf)[2] = optlen >> 8;
49 (*buf)[3] = optlen & 0xff;
51 *buf += DHCP6_OPTION_HDR_LEN;
52 *buflen -= DHCP6_OPTION_HDR_LEN;
57 int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
58 size_t optlen, const void *optval) {
61 assert_return(optval || optlen == 0, -EINVAL);
63 r = option_append_hdr(buf, buflen, code, optlen);
68 memcpy(*buf, optval, optlen);
76 int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
79 size_t ia_buflen, ia_addrlen = 0;
83 assert_return(buf && *buf && buflen && ia, -EINVAL);
86 case DHCP6_OPTION_IA_NA:
87 len = DHCP6_OPTION_IA_NA_LEN;
90 case DHCP6_OPTION_IA_TA:
91 len = DHCP6_OPTION_IA_TA_LEN;
104 *buf += DHCP6_OPTION_HDR_LEN;
105 *buflen -= DHCP6_OPTION_HDR_LEN;
107 memcpy(*buf, &ia->id, len);
112 LIST_FOREACH(addresses, addr, ia->addresses) {
113 r = option_append_hdr(buf, buflen, DHCP6_OPTION_IAADDR,
114 DHCP6_OPTION_IAADDR_LEN);
118 memcpy(*buf, &addr->address, DHCP6_OPTION_IAADDR_LEN);
120 *buf += DHCP6_OPTION_IAADDR_LEN;
121 *buflen -= DHCP6_OPTION_IAADDR_LEN;
123 ia_addrlen += DHCP6_OPTION_HDR_LEN + DHCP6_OPTION_IAADDR_LEN;
126 r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
134 static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *opt,
138 assert_return(buf, -EINVAL);
139 assert_return(opt, -EINVAL);
140 assert_return(optlen, -EINVAL);
145 len = (*buf)[2] << 8 | (*buf)[3];
150 *opt = (*buf)[0] << 8 | (*buf)[1];
159 int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
160 size_t *optlen, uint8_t **optvalue) {
163 assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
165 r = option_parse_hdr(buf, buflen, optcode, optlen);
169 if (*optlen > *buflen)
179 int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
182 uint16_t opt, status;
184 size_t iaaddr_offset;
186 uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0;
188 assert_return(ia, -EINVAL);
189 assert_return(!ia->addresses, -EINVAL);
192 case DHCP6_OPTION_IA_NA:
194 if (*buflen < DHCP6_OPTION_IA_NA_LEN + DHCP6_OPTION_HDR_LEN +
195 DHCP6_OPTION_IAADDR_LEN) {
200 iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
201 memcpy(&ia->id, *buf, iaaddr_offset);
203 lt_t1 = be32toh(ia->lifetime_t1);
204 lt_t2 = be32toh(ia->lifetime_t2);
206 if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
207 log_dhcp6_client(client, "IA T1 %ds > T2 %ds",
215 case DHCP6_OPTION_IA_TA:
216 if (*buflen < DHCP6_OPTION_IA_TA_LEN + DHCP6_OPTION_HDR_LEN +
217 DHCP6_OPTION_IAADDR_LEN) {
222 iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
223 memcpy(&ia->id, *buf, iaaddr_offset);
237 *buflen -= iaaddr_offset;
238 *buf += iaaddr_offset;
240 while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) {
243 case DHCP6_OPTION_IAADDR:
245 addr = new0(DHCP6Address, 1);
251 LIST_INIT(addresses, addr);
253 memcpy(&addr->address, *buf, DHCP6_OPTION_IAADDR_LEN);
255 lt_valid = be32toh(addr->lifetime_valid);
256 lt_pref = be32toh(addr->lifetime_valid);
258 if (!lt_valid || lt_pref > lt_valid) {
259 log_dhcp6_client(client, "IA preferred %ds > valid %ds",
263 LIST_PREPEND(addresses, ia->addresses, addr);
264 if (lt_valid < lt_min)
270 case DHCP6_OPTION_STATUS_CODE:
271 if (optlen < sizeof(status))
274 status = (*buf)[0] << 8 | (*buf)[1];
276 log_dhcp6_client(client, "IA status %d",
285 log_dhcp6_client(client, "Unknown IA option %d", opt);
296 if (!ia->lifetime_t1 && !ia->lifetime_t2) {
298 lt_t2 = lt_min / 10 * 8;
299 ia->lifetime_t1 = htobe32(lt_t1);
300 ia->lifetime_t2 = htobe32(lt_t2);
302 log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as both were zero",