]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/dhcp6-option.c
Merge pull request #1062 from poettering/cgroups-show
[thirdparty/systemd.git] / src / libsystemd-network / dhcp6-option.c
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-2015 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 #include "strv.h"
30
31 #include "dhcp6-internal.h"
32 #include "dhcp6-protocol.h"
33 #include "dns-domain.h"
34
35 #define DHCP6_OPTION_IA_NA_LEN 12
36 #define DHCP6_OPTION_IA_TA_LEN 4
37
38 typedef struct DHCP6Option {
39 be16_t code;
40 be16_t len;
41 uint8_t data[];
42 } _packed_ DHCP6Option;
43
44 static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
45 size_t optlen) {
46 DHCP6Option *option = (DHCP6Option*) *buf;
47
48 assert_return(buf, -EINVAL);
49 assert_return(*buf, -EINVAL);
50 assert_return(buflen, -EINVAL);
51
52 if (optlen > 0xffff || *buflen < optlen + sizeof(DHCP6Option))
53 return -ENOBUFS;
54
55 option->code = htobe16(optcode);
56 option->len = htobe16(optlen);
57
58 *buf += sizeof(DHCP6Option);
59 *buflen -= sizeof(DHCP6Option);
60
61 return 0;
62 }
63
64 int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
65 size_t optlen, const void *optval) {
66 int r;
67
68 assert_return(optval || optlen == 0, -EINVAL);
69
70 r = option_append_hdr(buf, buflen, code, optlen);
71 if (r < 0)
72 return r;
73
74 if (optval)
75 memcpy(*buf, optval, optlen);
76
77 *buf += optlen;
78 *buflen -= optlen;
79
80 return 0;
81 }
82
83 int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
84 uint16_t len;
85 uint8_t *ia_hdr;
86 size_t ia_buflen, ia_addrlen = 0;
87 DHCP6Address *addr;
88 int r;
89
90 assert_return(buf && *buf && buflen && ia, -EINVAL);
91
92 switch (ia->type) {
93 case DHCP6_OPTION_IA_NA:
94 len = DHCP6_OPTION_IA_NA_LEN;
95 break;
96
97 case DHCP6_OPTION_IA_TA:
98 len = DHCP6_OPTION_IA_TA_LEN;
99 break;
100
101 default:
102 return -EINVAL;
103 }
104
105 if (*buflen < len)
106 return -ENOBUFS;
107
108 ia_hdr = *buf;
109 ia_buflen = *buflen;
110
111 *buf += sizeof(DHCP6Option);
112 *buflen -= sizeof(DHCP6Option);
113
114 memcpy(*buf, &ia->id, len);
115
116 *buf += len;
117 *buflen -= len;
118
119 LIST_FOREACH(addresses, addr, ia->addresses) {
120 r = option_append_hdr(buf, buflen, DHCP6_OPTION_IAADDR,
121 sizeof(addr->iaaddr));
122 if (r < 0)
123 return r;
124
125 memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
126
127 *buf += sizeof(addr->iaaddr);
128 *buflen -= sizeof(addr->iaaddr);
129
130 ia_addrlen += sizeof(DHCP6Option) + sizeof(addr->iaaddr);
131 }
132
133 r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
134 if (r < 0)
135 return r;
136
137 return 0;
138 }
139
140
141 static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
142 DHCP6Option *option = (DHCP6Option*) *buf;
143 uint16_t len;
144
145 assert_return(buf, -EINVAL);
146 assert_return(optcode, -EINVAL);
147 assert_return(optlen, -EINVAL);
148
149 if (*buflen < sizeof(DHCP6Option))
150 return -ENOMSG;
151
152 len = be16toh(option->len);
153
154 if (len > *buflen)
155 return -ENOMSG;
156
157 *optcode = be16toh(option->code);
158 *optlen = len;
159
160 *buf += 4;
161 *buflen -= 4;
162
163 return 0;
164 }
165
166 int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
167 size_t *optlen, uint8_t **optvalue) {
168 int r;
169
170 assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
171
172 r = option_parse_hdr(buf, buflen, optcode, optlen);
173 if (r < 0)
174 return r;
175
176 if (*optlen > *buflen)
177 return -ENOBUFS;
178
179 *optvalue = *buf;
180 *buflen -= *optlen;
181 *buf += *optlen;
182
183 return 0;
184 }
185
186 int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
187 DHCP6IA *ia) {
188 int r;
189 uint16_t opt, status;
190 size_t optlen;
191 size_t iaaddr_offset;
192 DHCP6Address *addr;
193 uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0;
194
195 assert_return(ia, -EINVAL);
196 assert_return(!ia->addresses, -EINVAL);
197
198 switch (iatype) {
199 case DHCP6_OPTION_IA_NA:
200
201 if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) +
202 sizeof(addr->iaaddr)) {
203 r = -ENOBUFS;
204 goto error;
205 }
206
207 iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
208 memcpy(&ia->id, *buf, iaaddr_offset);
209
210 lt_t1 = be32toh(ia->lifetime_t1);
211 lt_t2 = be32toh(ia->lifetime_t2);
212
213 if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
214 log_dhcp6_client(client, "IA T1 %ds > T2 %ds",
215 lt_t1, lt_t2);
216 r = -EINVAL;
217 goto error;
218 }
219
220 break;
221
222 case DHCP6_OPTION_IA_TA:
223 if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) +
224 sizeof(addr->iaaddr)) {
225 r = -ENOBUFS;
226 goto error;
227 }
228
229 iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
230 memcpy(&ia->id, *buf, iaaddr_offset);
231
232 ia->lifetime_t1 = 0;
233 ia->lifetime_t2 = 0;
234
235 break;
236
237 default:
238 r = -ENOMSG;
239 goto error;
240 }
241
242 ia->type = iatype;
243
244 *buflen -= iaaddr_offset;
245 *buf += iaaddr_offset;
246
247 while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) {
248
249 switch (opt) {
250 case DHCP6_OPTION_IAADDR:
251
252 addr = new0(DHCP6Address, 1);
253 if (!addr) {
254 r = -ENOMEM;
255 goto error;
256 }
257
258 LIST_INIT(addresses, addr);
259
260 memcpy(&addr->iaaddr, *buf, sizeof(addr->iaaddr));
261
262 lt_valid = be32toh(addr->iaaddr.lifetime_valid);
263 lt_pref = be32toh(addr->iaaddr.lifetime_valid);
264
265 if (!lt_valid || lt_pref > lt_valid) {
266 log_dhcp6_client(client, "IA preferred %ds > valid %ds",
267 lt_pref, lt_valid);
268 free(addr);
269 } else {
270 LIST_PREPEND(addresses, ia->addresses, addr);
271 if (lt_valid < lt_min)
272 lt_min = lt_valid;
273 }
274
275 break;
276
277 case DHCP6_OPTION_STATUS_CODE:
278 if (optlen < sizeof(status))
279 break;
280
281 status = (*buf)[0] << 8 | (*buf)[1];
282 if (status) {
283 log_dhcp6_client(client, "IA status %d",
284 status);
285 r = -EINVAL;
286 goto error;
287 }
288
289 break;
290
291 default:
292 log_dhcp6_client(client, "Unknown IA option %d", opt);
293 break;
294 }
295
296 *buflen -= optlen;
297 *buf += optlen;
298 }
299
300 if (r == -ENOMSG)
301 r = 0;
302
303 if (!ia->lifetime_t1 && !ia->lifetime_t2) {
304 lt_t1 = lt_min / 2;
305 lt_t2 = lt_min / 10 * 8;
306 ia->lifetime_t1 = htobe32(lt_t1);
307 ia->lifetime_t2 = htobe32(lt_t2);
308
309 log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as both were zero",
310 lt_t1, lt_t2);
311 }
312
313 if (*buflen)
314 r = -ENOMSG;
315
316 error:
317 *buf += *buflen;
318 *buflen = 0;
319
320 return r;
321 }
322
323 int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
324 struct in6_addr **addrs, size_t count,
325 size_t *allocated) {
326
327 if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0)
328 return -EINVAL;
329
330 if (!GREEDY_REALLOC(*addrs, *allocated,
331 count * sizeof(struct in6_addr) + optlen))
332 return -ENOMEM;
333
334 memcpy(*addrs + count, optval, optlen);
335
336 count += optlen / sizeof(struct in6_addr);
337
338 return count;
339 }
340
341 int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char ***str_arr) {
342 size_t pos = 0, idx = 0;
343 _cleanup_free_ char **names = NULL;
344 int r;
345
346 assert_return(optlen > 1, -ENODATA);
347 assert_return(optval[optlen] == '\0', -EINVAL);
348
349 while (pos < optlen) {
350 _cleanup_free_ char *ret = NULL;
351 size_t n = 0, allocated = 0;
352 bool first = true;
353
354 for (;;) {
355 uint8_t c;
356
357 c = optval[pos++];
358
359 if (c == 0)
360 /* End of name */
361 break;
362 else if (c <= 63) {
363 _cleanup_free_ char *t = NULL;
364 const char *label;
365
366 /* Literal label */
367 label = (const char *)&optval[pos];
368 pos += c;
369 if (pos > optlen)
370 return -EMSGSIZE;
371
372 r = dns_label_escape(label, c, &t);
373 if (r < 0)
374 goto fail;
375
376 if (!GREEDY_REALLOC0(ret, allocated, n + !first + strlen(t) + 1)) {
377 r = -ENOMEM;
378 goto fail;
379 }
380
381 if (!first)
382 ret[n++] = '.';
383 else
384 first = false;
385
386 memcpy(ret + n, t, r);
387 n += r;
388 continue;
389 } else {
390 r = -EBADMSG;
391 goto fail;
392 }
393 }
394
395 if (!GREEDY_REALLOC(ret, allocated, n + 1)) {
396 r = -ENOMEM;
397 goto fail;
398 }
399
400 ret[n] = 0;
401
402 r = strv_extend(&names, ret);
403 if (r < 0)
404 goto fail;
405
406 idx++;
407 }
408
409 *str_arr = names;
410 names = NULL;
411
412 return idx;
413
414 fail:
415 return r;
416 }