]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/dhcp6-option.c
tree-wide: sort includes
[thirdparty/systemd.git] / src / libsystemd-network / dhcp6-option.c
CommitLineData
f12ed3bf
PF
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
b553817c 6 Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
f12ed3bf
PF
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
f12ed3bf 22#include <errno.h>
cf0fbc49 23#include <netinet/in.h>
f12ed3bf
PF
24#include <string.h>
25
b5efdb8a 26#include "alloc-util.h"
f12ed3bf
PF
27#include "dhcp6-internal.h"
28#include "dhcp6-protocol.h"
f96ccab7 29#include "dns-domain.h"
b5efdb8a
LP
30#include "sparse-endian.h"
31#include "strv.h"
32#include "unaligned.h"
33#include "util.h"
f12ed3bf 34
f12ed3bf
PF
35#define DHCP6_OPTION_IA_NA_LEN 12
36#define DHCP6_OPTION_IA_TA_LEN 4
f12ed3bf 37
4903a73c
TG
38typedef struct DHCP6Option {
39 be16_t code;
40 be16_t len;
41 uint8_t data[];
c962cb68 42} _packed_ DHCP6Option;
4903a73c 43
f12ed3bf
PF
44static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
45 size_t optlen) {
c962cb68 46 DHCP6Option *option = (DHCP6Option*) *buf;
4903a73c 47
f12ed3bf
PF
48 assert_return(buf, -EINVAL);
49 assert_return(*buf, -EINVAL);
50 assert_return(buflen, -EINVAL);
51
4903a73c 52 if (optlen > 0xffff || *buflen < optlen + sizeof(DHCP6Option))
f12ed3bf
PF
53 return -ENOBUFS;
54
c962cb68
TG
55 option->code = htobe16(optcode);
56 option->len = htobe16(optlen);
f12ed3bf 57
4903a73c
TG
58 *buf += sizeof(DHCP6Option);
59 *buflen -= sizeof(DHCP6Option);
f12ed3bf
PF
60
61 return 0;
62}
63
64int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
65 size_t optlen, const void *optval) {
66 int r;
67
ed6ee219 68 assert_return(optval || optlen == 0, -EINVAL);
f12ed3bf
PF
69
70 r = option_append_hdr(buf, buflen, code, optlen);
71 if (r < 0)
72 return r;
73
ed6ee219
PF
74 if (optval)
75 memcpy(*buf, optval, optlen);
f12ed3bf
PF
76
77 *buf += optlen;
78 *buflen -= optlen;
79
80 return 0;
81}
82
83int 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
4903a73c
TG
111 *buf += sizeof(DHCP6Option);
112 *buflen -= sizeof(DHCP6Option);
f12ed3bf
PF
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,
ee3a5027 121 sizeof(addr->iaaddr));
f12ed3bf
PF
122 if (r < 0)
123 return r;
124
ee3a5027 125 memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
f12ed3bf 126
ee3a5027
PF
127 *buf += sizeof(addr->iaaddr);
128 *buflen -= sizeof(addr->iaaddr);
f12ed3bf 129
4903a73c 130 ia_addrlen += sizeof(DHCP6Option) + sizeof(addr->iaaddr);
f12ed3bf
PF
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
c6affce8 140
c962cb68
TG
141static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
142 DHCP6Option *option = (DHCP6Option*) *buf;
c6affce8
PF
143 uint16_t len;
144
145 assert_return(buf, -EINVAL);
4903a73c 146 assert_return(optcode, -EINVAL);
c6affce8
PF
147 assert_return(optlen, -EINVAL);
148
4903a73c 149 if (*buflen < sizeof(DHCP6Option))
c6affce8
PF
150 return -ENOMSG;
151
c962cb68 152 len = be16toh(option->len);
c6affce8
PF
153
154 if (len > *buflen)
155 return -ENOMSG;
156
c962cb68 157 *optcode = be16toh(option->code);
c6affce8
PF
158 *optlen = len;
159
160 *buf += 4;
161 *buflen -= 4;
162
163 return 0;
164}
165
f12ed3bf
PF
166int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
167 size_t *optlen, uint8_t **optvalue) {
c6affce8 168 int r;
f12ed3bf 169
c6affce8 170 assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
f12ed3bf 171
c6affce8
PF
172 r = option_parse_hdr(buf, buflen, optcode, optlen);
173 if (r < 0)
174 return r;
f12ed3bf 175
c6affce8 176 if (*optlen > *buflen)
f12ed3bf
PF
177 return -ENOBUFS;
178
c6affce8
PF
179 *optvalue = *buf;
180 *buflen -= *optlen;
181 *buf += *optlen;
f12ed3bf
PF
182
183 return 0;
184}
c6affce8
PF
185
186int 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
4903a73c 201 if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) +
ee3a5027 202 sizeof(addr->iaaddr)) {
c6affce8
PF
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:
4903a73c 223 if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) +
ee3a5027 224 sizeof(addr->iaaddr)) {
c6affce8
PF
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
ee3a5027 260 memcpy(&addr->iaaddr, *buf, sizeof(addr->iaaddr));
c6affce8 261
ee3a5027
PF
262 lt_valid = be32toh(addr->iaaddr.lifetime_valid);
263 lt_pref = be32toh(addr->iaaddr.lifetime_valid);
c6affce8
PF
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
316error:
317 *buf += *buflen;
318 *buflen = 0;
319
320 return r;
321}
b553817c
PF
322
323int 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}
f96ccab7 340
52efd56a 341int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char ***str_arr) {
f96ccab7
PF
342 size_t pos = 0, idx = 0;
343 _cleanup_free_ char **names = NULL;
344 int r;
345
346 assert_return(optlen > 1, -ENODATA);
93f660da 347 assert_return(optval[optlen - 1] == '\0', -EINVAL);
f96ccab7
PF
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
f96ccab7
PF
406 idx++;
407 }
408
409 *str_arr = names;
410 names = NULL;
411
412 return idx;
413
414fail:
415 return r;
416}