Line data Source code
1 : /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2 :
3 : /***
4 : This file is part of systemd.
5 :
6 : Copyright (C) 2014 Intel Corporation. All rights reserved.
7 :
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.
12 :
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.
17 :
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/>.
20 : ***/
21 :
22 : #include <netinet/in.h>
23 : #include <errno.h>
24 : #include <string.h>
25 :
26 : #include "sparse-endian.h"
27 : #include "unaligned.h"
28 : #include "util.h"
29 :
30 : #include "dhcp6-internal.h"
31 : #include "dhcp6-protocol.h"
32 :
33 : #define DHCP6_OPTION_IA_NA_LEN 12
34 : #define DHCP6_OPTION_IA_TA_LEN 4
35 :
36 : typedef struct DHCP6Option {
37 : be16_t code;
38 : be16_t len;
39 : uint8_t data[];
40 : } _packed_ DHCP6Option;
41 :
42 16 : static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
43 : size_t optlen) {
44 16 : DHCP6Option *option = (DHCP6Option*) *buf;
45 :
46 16 : assert_return(buf, -EINVAL);
47 16 : assert_return(*buf, -EINVAL);
48 16 : assert_return(buflen, -EINVAL);
49 :
50 16 : if (optlen > 0xffff || *buflen < optlen + sizeof(DHCP6Option))
51 0 : return -ENOBUFS;
52 :
53 16 : option->code = htobe16(optcode);
54 16 : option->len = htobe16(optlen);
55 :
56 16 : *buf += sizeof(DHCP6Option);
57 16 : *buflen -= sizeof(DHCP6Option);
58 :
59 16 : return 0;
60 : }
61 :
62 13 : int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
63 : size_t optlen, const void *optval) {
64 : int r;
65 :
66 13 : assert_return(optval || optlen == 0, -EINVAL);
67 :
68 13 : r = option_append_hdr(buf, buflen, code, optlen);
69 13 : if (r < 0)
70 0 : return r;
71 :
72 13 : if (optval)
73 12 : memcpy(*buf, optval, optlen);
74 :
75 13 : *buf += optlen;
76 13 : *buflen -= optlen;
77 :
78 13 : return 0;
79 : }
80 :
81 2 : int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
82 : uint16_t len;
83 : uint8_t *ia_hdr;
84 2 : size_t ia_buflen, ia_addrlen = 0;
85 : DHCP6Address *addr;
86 : int r;
87 :
88 2 : assert_return(buf && *buf && buflen && ia, -EINVAL);
89 :
90 2 : switch (ia->type) {
91 : case DHCP6_OPTION_IA_NA:
92 2 : len = DHCP6_OPTION_IA_NA_LEN;
93 2 : break;
94 :
95 : case DHCP6_OPTION_IA_TA:
96 0 : len = DHCP6_OPTION_IA_TA_LEN;
97 0 : break;
98 :
99 : default:
100 0 : return -EINVAL;
101 : }
102 :
103 2 : if (*buflen < len)
104 0 : return -ENOBUFS;
105 :
106 2 : ia_hdr = *buf;
107 2 : ia_buflen = *buflen;
108 :
109 2 : *buf += sizeof(DHCP6Option);
110 2 : *buflen -= sizeof(DHCP6Option);
111 :
112 2 : memcpy(*buf, &ia->id, len);
113 :
114 2 : *buf += len;
115 2 : *buflen -= len;
116 :
117 3 : LIST_FOREACH(addresses, addr, ia->addresses) {
118 1 : r = option_append_hdr(buf, buflen, DHCP6_OPTION_IAADDR,
119 : sizeof(addr->iaaddr));
120 1 : if (r < 0)
121 0 : return r;
122 :
123 1 : memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
124 :
125 1 : *buf += sizeof(addr->iaaddr);
126 1 : *buflen -= sizeof(addr->iaaddr);
127 :
128 1 : ia_addrlen += sizeof(DHCP6Option) + sizeof(addr->iaaddr);
129 : }
130 :
131 2 : r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
132 2 : if (r < 0)
133 0 : return r;
134 :
135 2 : return 0;
136 : }
137 :
138 :
139 60 : static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
140 60 : DHCP6Option *option = (DHCP6Option*) *buf;
141 : uint16_t len;
142 :
143 60 : assert_return(buf, -EINVAL);
144 60 : assert_return(optcode, -EINVAL);
145 60 : assert_return(optlen, -EINVAL);
146 :
147 60 : if (*buflen < sizeof(DHCP6Option))
148 12 : return -ENOMSG;
149 :
150 48 : len = be16toh(option->len);
151 :
152 48 : if (len > *buflen)
153 0 : return -ENOMSG;
154 :
155 48 : *optcode = be16toh(option->code);
156 48 : *optlen = len;
157 :
158 48 : *buf += 4;
159 48 : *buflen -= 4;
160 :
161 48 : return 0;
162 : }
163 :
164 49 : int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
165 : size_t *optlen, uint8_t **optvalue) {
166 : int r;
167 :
168 49 : assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
169 :
170 49 : r = option_parse_hdr(buf, buflen, optcode, optlen);
171 49 : if (r < 0)
172 8 : return r;
173 :
174 41 : if (*optlen > *buflen)
175 0 : return -ENOBUFS;
176 :
177 41 : *optvalue = *buf;
178 41 : *buflen -= *optlen;
179 41 : *buf += *optlen;
180 :
181 41 : return 0;
182 : }
183 :
184 4 : int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
185 : DHCP6IA *ia) {
186 : int r;
187 : uint16_t opt, status;
188 : size_t optlen;
189 : size_t iaaddr_offset;
190 : DHCP6Address *addr;
191 4 : uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0;
192 :
193 4 : assert_return(ia, -EINVAL);
194 4 : assert_return(!ia->addresses, -EINVAL);
195 :
196 4 : switch (iatype) {
197 : case DHCP6_OPTION_IA_NA:
198 :
199 4 : if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) +
200 : sizeof(addr->iaaddr)) {
201 0 : r = -ENOBUFS;
202 0 : goto error;
203 : }
204 :
205 4 : iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
206 4 : memcpy(&ia->id, *buf, iaaddr_offset);
207 :
208 4 : lt_t1 = be32toh(ia->lifetime_t1);
209 4 : lt_t2 = be32toh(ia->lifetime_t2);
210 :
211 4 : if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
212 0 : log_dhcp6_client(client, "IA T1 %ds > T2 %ds",
213 : lt_t1, lt_t2);
214 0 : r = -EINVAL;
215 0 : goto error;
216 : }
217 :
218 4 : break;
219 :
220 : case DHCP6_OPTION_IA_TA:
221 0 : if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) +
222 : sizeof(addr->iaaddr)) {
223 0 : r = -ENOBUFS;
224 0 : goto error;
225 : }
226 :
227 0 : iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
228 0 : memcpy(&ia->id, *buf, iaaddr_offset);
229 :
230 0 : ia->lifetime_t1 = 0;
231 0 : ia->lifetime_t2 = 0;
232 :
233 0 : break;
234 :
235 : default:
236 0 : r = -ENOMSG;
237 0 : goto error;
238 : }
239 :
240 4 : ia->type = iatype;
241 :
242 4 : *buflen -= iaaddr_offset;
243 4 : *buf += iaaddr_offset;
244 :
245 15 : while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) {
246 :
247 7 : switch (opt) {
248 : case DHCP6_OPTION_IAADDR:
249 :
250 4 : addr = new0(DHCP6Address, 1);
251 4 : if (!addr) {
252 0 : r = -ENOMEM;
253 0 : goto error;
254 : }
255 :
256 4 : LIST_INIT(addresses, addr);
257 :
258 4 : memcpy(&addr->iaaddr, *buf, sizeof(addr->iaaddr));
259 :
260 4 : lt_valid = be32toh(addr->iaaddr.lifetime_valid);
261 4 : lt_pref = be32toh(addr->iaaddr.lifetime_valid);
262 :
263 4 : if (!lt_valid || lt_pref > lt_valid) {
264 0 : log_dhcp6_client(client, "IA preferred %ds > valid %ds",
265 : lt_pref, lt_valid);
266 0 : free(addr);
267 : } else {
268 4 : LIST_PREPEND(addresses, ia->addresses, addr);
269 4 : if (lt_valid < lt_min)
270 4 : lt_min = lt_valid;
271 : }
272 :
273 4 : break;
274 :
275 : case DHCP6_OPTION_STATUS_CODE:
276 3 : if (optlen < sizeof(status))
277 0 : break;
278 :
279 3 : status = (*buf)[0] << 8 | (*buf)[1];
280 3 : if (status) {
281 0 : log_dhcp6_client(client, "IA status %d",
282 : status);
283 0 : r = -EINVAL;
284 0 : goto error;
285 : }
286 :
287 3 : break;
288 :
289 : default:
290 0 : log_dhcp6_client(client, "Unknown IA option %d", opt);
291 0 : break;
292 : }
293 :
294 7 : *buflen -= optlen;
295 7 : *buf += optlen;
296 : }
297 :
298 4 : if (r == -ENOMSG)
299 4 : r = 0;
300 :
301 4 : if (!ia->lifetime_t1 && !ia->lifetime_t2) {
302 0 : lt_t1 = lt_min / 2;
303 0 : lt_t2 = lt_min / 10 * 8;
304 0 : ia->lifetime_t1 = htobe32(lt_t1);
305 0 : ia->lifetime_t2 = htobe32(lt_t2);
306 :
307 0 : log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as both were zero",
308 : lt_t1, lt_t2);
309 : }
310 :
311 4 : if (*buflen)
312 0 : r = -ENOMSG;
313 :
314 : error:
315 4 : *buf += *buflen;
316 4 : *buflen = 0;
317 :
318 4 : return r;
319 : }
|