]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/dhcp6-option.c
Merge pull request #9017 from keszybz/man-coredump
[thirdparty/systemd.git] / src / libsystemd-network / dhcp6-option.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
f12ed3bf
PF
2/***
3 This file is part of systemd.
4
b553817c 5 Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
f12ed3bf
PF
6***/
7
f12ed3bf 8#include <errno.h>
cf0fbc49 9#include <netinet/in.h>
f12ed3bf
PF
10#include <string.h>
11
2c1ab8ca
BG
12#include "sd-dhcp6-client.h"
13
b5efdb8a 14#include "alloc-util.h"
f12ed3bf 15#include "dhcp6-internal.h"
c6b4f32a 16#include "dhcp6-lease-internal.h"
f12ed3bf 17#include "dhcp6-protocol.h"
f96ccab7 18#include "dns-domain.h"
b5efdb8a
LP
19#include "sparse-endian.h"
20#include "strv.h"
21#include "unaligned.h"
22#include "util.h"
f12ed3bf 23
c6b4f32a
PF
24typedef struct DHCP6StatusOption {
25 struct DHCP6Option option;
26 be16_t status;
27 char msg[];
28} _packed_ DHCP6StatusOption;
29
0dfe2a4b
PF
30typedef struct DHCP6AddressOption {
31 struct DHCP6Option option;
32 struct iaaddr iaaddr;
33 uint8_t options[];
34} _packed_ DHCP6AddressOption;
35
f8ad4dd4
PF
36typedef struct DHCP6PDPrefixOption {
37 struct DHCP6Option option;
38 struct iapdprefix iapdprefix;
39 uint8_t options[];
40} _packed_ DHCP6PDPrefixOption;
41
42#define DHCP6_OPTION_IA_NA_LEN (sizeof(struct ia_na))
43#define DHCP6_OPTION_IA_PD_LEN (sizeof(struct ia_pd))
44#define DHCP6_OPTION_IA_TA_LEN (sizeof(struct ia_ta))
f12ed3bf
PF
45
46static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
47 size_t optlen) {
c962cb68 48 DHCP6Option *option = (DHCP6Option*) *buf;
4903a73c 49
f12ed3bf
PF
50 assert_return(buf, -EINVAL);
51 assert_return(*buf, -EINVAL);
52 assert_return(buflen, -EINVAL);
53
4903a73c 54 if (optlen > 0xffff || *buflen < optlen + sizeof(DHCP6Option))
f12ed3bf
PF
55 return -ENOBUFS;
56
c962cb68
TG
57 option->code = htobe16(optcode);
58 option->len = htobe16(optlen);
f12ed3bf 59
4903a73c
TG
60 *buf += sizeof(DHCP6Option);
61 *buflen -= sizeof(DHCP6Option);
f12ed3bf
PF
62
63 return 0;
64}
65
66int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
67 size_t optlen, const void *optval) {
68 int r;
69
ed6ee219 70 assert_return(optval || optlen == 0, -EINVAL);
f12ed3bf
PF
71
72 r = option_append_hdr(buf, buflen, code, optlen);
73 if (r < 0)
74 return r;
75
75f32f04 76 memcpy_safe(*buf, optval, optlen);
f12ed3bf
PF
77
78 *buf += optlen;
79 *buflen -= optlen;
80
81 return 0;
82}
83
84int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
85 uint16_t len;
86 uint8_t *ia_hdr;
976fade6 87 size_t iaid_offset, ia_buflen, ia_addrlen = 0;
f12ed3bf
PF
88 DHCP6Address *addr;
89 int r;
90
91 assert_return(buf && *buf && buflen && ia, -EINVAL);
92
93 switch (ia->type) {
2c1ab8ca 94 case SD_DHCP6_OPTION_IA_NA:
f12ed3bf 95 len = DHCP6_OPTION_IA_NA_LEN;
976fade6 96 iaid_offset = offsetof(DHCP6IA, ia_na);
f12ed3bf
PF
97 break;
98
2c1ab8ca 99 case SD_DHCP6_OPTION_IA_TA:
f12ed3bf 100 len = DHCP6_OPTION_IA_TA_LEN;
976fade6 101 iaid_offset = offsetof(DHCP6IA, ia_ta);
f12ed3bf
PF
102 break;
103
104 default:
105 return -EINVAL;
106 }
107
108 if (*buflen < len)
109 return -ENOBUFS;
110
111 ia_hdr = *buf;
112 ia_buflen = *buflen;
113
4903a73c
TG
114 *buf += sizeof(DHCP6Option);
115 *buflen -= sizeof(DHCP6Option);
f12ed3bf 116
976fade6 117 memcpy(*buf, (char*) ia + iaid_offset, len);
f12ed3bf
PF
118
119 *buf += len;
120 *buflen -= len;
121
122 LIST_FOREACH(addresses, addr, ia->addresses) {
2c1ab8ca 123 r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR,
ee3a5027 124 sizeof(addr->iaaddr));
f12ed3bf
PF
125 if (r < 0)
126 return r;
127
ee3a5027 128 memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
f12ed3bf 129
ee3a5027
PF
130 *buf += sizeof(addr->iaaddr);
131 *buflen -= sizeof(addr->iaaddr);
f12ed3bf 132
4903a73c 133 ia_addrlen += sizeof(DHCP6Option) + sizeof(addr->iaaddr);
f12ed3bf
PF
134 }
135
136 r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
137 if (r < 0)
138 return r;
139
140 return 0;
141}
142
8006aa32
SA
143int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
144 uint8_t buffer[1 + DNS_WIRE_FOMAT_HOSTNAME_MAX];
145 int r;
146
147 assert_return(buf && *buf && buflen && fqdn, -EINVAL);
148
149 buffer[0] = DHCP6_FQDN_FLAG_S; /* Request server to perform AAAA RR DNS updates */
150
151 /* Store domain name after flags field */
152 r = dns_name_to_wire_format(fqdn, buffer + 1, sizeof(buffer) - 1, false);
153 if (r <= 0)
154 return r;
155
156 /*
157 * According to RFC 4704, chapter 4.2 only add terminating zero-length
158 * label in case a FQDN is provided. Since dns_name_to_wire_format
159 * always adds terminating zero-length label remove if only a hostname
160 * is provided.
161 */
162 if (dns_name_is_single_label(fqdn))
163 r--;
164
165 r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_FQDN, 1 + r, buffer);
166
167 return r;
168}
169
c77e3db1
PF
170int dhcp6_option_append_pd(uint8_t *buf, size_t len, DHCP6IA *pd) {
171 DHCP6Option *option = (DHCP6Option *)buf;
172 size_t i = sizeof(*option) + sizeof(pd->ia_pd);
173 DHCP6Address *prefix;
174
175 assert_return(buf, -EINVAL);
176 assert_return(pd, -EINVAL);
177 assert_return(pd->type == SD_DHCP6_OPTION_IA_PD, -EINVAL);
178
179 if (len < i)
180 return -ENOBUFS;
181
182 option->code = htobe16(SD_DHCP6_OPTION_IA_PD);
183
184 memcpy(&option->data, &pd->ia_pd, sizeof(pd->ia_pd));
185
186 LIST_FOREACH(addresses, prefix, pd->addresses) {
187 DHCP6PDPrefixOption *prefix_opt;
188
189 if (len < i + sizeof(*prefix_opt))
190 return -ENOBUFS;
191
192 prefix_opt = (DHCP6PDPrefixOption *)&buf[i];
193 prefix_opt->option.code = htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX);
194 prefix_opt->option.len = htobe16(sizeof(prefix_opt->iapdprefix));
195
196 memcpy(&prefix_opt->iapdprefix, &prefix->iapdprefix,
197 sizeof(struct iapdprefix));
198
199 i += sizeof(*prefix_opt);
200 }
201
202 option->len = htobe16(i - sizeof(*option));
203
204 return i;
205}
c6affce8 206
c962cb68
TG
207static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
208 DHCP6Option *option = (DHCP6Option*) *buf;
c6affce8
PF
209 uint16_t len;
210
211 assert_return(buf, -EINVAL);
4903a73c 212 assert_return(optcode, -EINVAL);
c6affce8
PF
213 assert_return(optlen, -EINVAL);
214
4903a73c 215 if (*buflen < sizeof(DHCP6Option))
c6affce8
PF
216 return -ENOMSG;
217
c962cb68 218 len = be16toh(option->len);
c6affce8
PF
219
220 if (len > *buflen)
221 return -ENOMSG;
222
c962cb68 223 *optcode = be16toh(option->code);
c6affce8
PF
224 *optlen = len;
225
226 *buf += 4;
227 *buflen -= 4;
228
229 return 0;
230}
231
f12ed3bf
PF
232int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
233 size_t *optlen, uint8_t **optvalue) {
c6affce8 234 int r;
f12ed3bf 235
c6affce8 236 assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
f12ed3bf 237
c6affce8
PF
238 r = option_parse_hdr(buf, buflen, optcode, optlen);
239 if (r < 0)
240 return r;
f12ed3bf 241
c6affce8 242 if (*optlen > *buflen)
f12ed3bf
PF
243 return -ENOBUFS;
244
c6affce8
PF
245 *optvalue = *buf;
246 *buflen -= *optlen;
247 *buf += *optlen;
f12ed3bf
PF
248
249 return 0;
250}
c6affce8 251
c6b4f32a
PF
252int dhcp6_option_parse_status(DHCP6Option *option) {
253 DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option;
254
255 if (be16toh(option->len) + sizeof(DHCP6Option) < sizeof(*statusopt))
256 return -ENOBUFS;
257
258 return be16toh(statusopt->status);
259}
260
0dfe2a4b
PF
261static int dhcp6_option_parse_address(DHCP6Option *option, DHCP6IA *ia,
262 uint32_t *lifetime_valid) {
263 DHCP6AddressOption *addr_option = (DHCP6AddressOption *)option;
264 DHCP6Address *addr;
265 uint32_t lt_valid, lt_pref;
266 int r;
267
268 if (be16toh(option->len) + sizeof(DHCP6Option) < sizeof(*addr_option))
269 return -ENOBUFS;
270
271 lt_valid = be32toh(addr_option->iaaddr.lifetime_valid);
272 lt_pref = be32toh(addr_option->iaaddr.lifetime_preferred);
273
274 if (lt_valid == 0 || lt_pref > lt_valid) {
275 log_dhcp6_client(client, "Valid lifetime of an IA address is zero or preferred lifetime %d > valid lifetime %d",
276 lt_pref, lt_valid);
277
278 return 0;
279 }
280
281 if (be16toh(option->len) + sizeof(DHCP6Option) > sizeof(*addr_option)) {
282 r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options);
283 if (r != 0)
284 return r < 0 ? r: 0;
285 }
286
287 addr = new0(DHCP6Address, 1);
288 if (!addr)
289 return -ENOMEM;
290
291 LIST_INIT(addresses, addr);
292 memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr));
293
294 LIST_PREPEND(addresses, ia->addresses, addr);
295
296 *lifetime_valid = be32toh(addr->iaaddr.lifetime_valid);
297
298 return 0;
299}
300
f8ad4dd4
PF
301static int dhcp6_option_parse_pdprefix(DHCP6Option *option, DHCP6IA *ia,
302 uint32_t *lifetime_valid) {
303 DHCP6PDPrefixOption *pdprefix_option = (DHCP6PDPrefixOption *)option;
304 DHCP6Address *prefix;
305 uint32_t lt_valid, lt_pref;
306 int r;
307
308 if (be16toh(option->len) + sizeof(DHCP6Option) < sizeof(*pdprefix_option))
309 return -ENOBUFS;
310
311 lt_valid = be32toh(pdprefix_option->iapdprefix.lifetime_valid);
312 lt_pref = be32toh(pdprefix_option->iapdprefix.lifetime_preferred);
313
314 if (lt_valid == 0 || lt_pref > lt_valid) {
315 log_dhcp6_client(client, "Valid lifetieme of a PD prefix is zero or preferred lifetime %d > valid lifetime %d",
316 lt_pref, lt_valid);
317
318 return 0;
319 }
320
321 if (be16toh(option->len) + sizeof(DHCP6Option) > sizeof(*pdprefix_option)) {
322 r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options);
323 if (r != 0)
324 return r < 0 ? r: 0;
325 }
326
327 prefix = new0(DHCP6Address, 1);
328 if (!prefix)
329 return -ENOMEM;
330
331 LIST_INIT(addresses, prefix);
332 memcpy(&prefix->iapdprefix, option->data, sizeof(prefix->iapdprefix));
333
334 LIST_PREPEND(addresses, ia->addresses, prefix);
335
336 *lifetime_valid = be32toh(prefix->iapdprefix.lifetime_valid);
337
338 return 0;
339}
340
3bc424a3
PF
341int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
342 uint16_t iatype, optlen;
343 size_t i, len;
344 int r = 0, status;
345 uint16_t opt;
c6affce8 346 size_t iaaddr_offset;
3c035649 347 uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX;
c6affce8
PF
348
349 assert_return(ia, -EINVAL);
350 assert_return(!ia->addresses, -EINVAL);
351
3bc424a3
PF
352 iatype = be16toh(iaoption->code);
353 len = be16toh(iaoption->len);
354
c6affce8 355 switch (iatype) {
2c1ab8ca 356 case SD_DHCP6_OPTION_IA_NA:
c6affce8 357
3bc424a3 358 if (len < DHCP6_OPTION_IA_NA_LEN) {
c6affce8
PF
359 r = -ENOBUFS;
360 goto error;
361 }
362
363 iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
3bc424a3 364 memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na));
c6affce8 365
e0026dcb
PF
366 lt_t1 = be32toh(ia->ia_na.lifetime_t1);
367 lt_t2 = be32toh(ia->ia_na.lifetime_t2);
c6affce8
PF
368
369 if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
f8ad4dd4
PF
370 log_dhcp6_client(client, "IA NA T1 %ds > T2 %ds",
371 lt_t1, lt_t2);
372 r = -EINVAL;
373 goto error;
374 }
375
376 break;
377
378 case SD_DHCP6_OPTION_IA_PD:
379
380 if (len < sizeof(ia->ia_pd)) {
381 r = -ENOBUFS;
382 goto error;
383 }
384
385 iaaddr_offset = sizeof(ia->ia_pd);
386 memcpy(&ia->ia_pd, iaoption->data, sizeof(ia->ia_pd));
387
388 lt_t1 = be32toh(ia->ia_pd.lifetime_t1);
389 lt_t2 = be32toh(ia->ia_pd.lifetime_t2);
390
391 if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
392 log_dhcp6_client(client, "IA PD T1 %ds > T2 %ds",
c6affce8
PF
393 lt_t1, lt_t2);
394 r = -EINVAL;
395 goto error;
396 }
397
398 break;
399
2c1ab8ca 400 case SD_DHCP6_OPTION_IA_TA:
3bc424a3 401 if (len < DHCP6_OPTION_IA_TA_LEN) {
c6affce8
PF
402 r = -ENOBUFS;
403 goto error;
404 }
405
406 iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
3bc424a3 407 memcpy(&ia->ia_ta.id, iaoption->data, sizeof(ia->ia_ta));
c6affce8
PF
408
409 break;
410
411 default:
412 r = -ENOMSG;
413 goto error;
414 }
415
416 ia->type = iatype;
3bc424a3
PF
417 i = iaaddr_offset;
418
419 while (i < len) {
420 DHCP6Option *option = (DHCP6Option *)&iaoption->data[i];
c6affce8 421
3bc424a3
PF
422 if (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len)) {
423 r = -ENOBUFS;
424 goto error;
425 }
c6affce8 426
3bc424a3
PF
427 opt = be16toh(option->code);
428 optlen = be16toh(option->len);
c6affce8
PF
429
430 switch (opt) {
2c1ab8ca 431 case SD_DHCP6_OPTION_IAADDR:
0dfe2a4b 432
f8ad4dd4
PF
433 if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) {
434 log_dhcp6_client(client, "IA Address option not in IA NA or TA option");
435 r = -EINVAL;
436 goto error;
437 }
438
0dfe2a4b
PF
439 r = dhcp6_option_parse_address(option, ia, &lt_valid);
440 if (r < 0)
c6affce8 441 goto error;
0dfe2a4b
PF
442
443 if (lt_valid < lt_min)
444 lt_min = lt_valid;
c6affce8
PF
445
446 break;
447
f8ad4dd4
PF
448 case SD_DHCP6_OPTION_IA_PD_PREFIX:
449
450 if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_PD)) {
451 log_dhcp6_client(client, "IA PD Prefix option not in IA PD option");
452 r = -EINVAL;
453 goto error;
454 }
455
456 r = dhcp6_option_parse_pdprefix(option, ia, &lt_valid);
457 if (r < 0)
458 goto error;
459
460 if (lt_valid < lt_min)
461 lt_min = lt_valid;
462
463 break;
464
2c1ab8ca 465 case SD_DHCP6_OPTION_STATUS_CODE:
c6affce8 466
c6b4f32a 467 status = dhcp6_option_parse_status(option);
c6affce8
PF
468 if (status) {
469 log_dhcp6_client(client, "IA status %d",
470 status);
c6b4f32a
PF
471
472 dhcp6_lease_free_ia(ia);
473
c6affce8
PF
474 r = -EINVAL;
475 goto error;
476 }
477
478 break;
479
480 default:
481 log_dhcp6_client(client, "Unknown IA option %d", opt);
482 break;
483 }
484
3bc424a3 485 i += sizeof(*option) + optlen;
c6affce8
PF
486 }
487
f8ad4dd4
PF
488 switch(iatype) {
489 case SD_DHCP6_OPTION_IA_NA:
490 if (!ia->ia_na.lifetime_t1 && !ia->ia_na.lifetime_t2) {
491 lt_t1 = lt_min / 2;
492 lt_t2 = lt_min / 10 * 8;
493 ia->ia_na.lifetime_t1 = htobe32(lt_t1);
494 ia->ia_na.lifetime_t2 = htobe32(lt_t2);
495
496 log_dhcp6_client(client, "Computed IA NA T1 %ds and T2 %ds as both were zero",
497 lt_t1, lt_t2);
498 }
499
500 break;
501
502 case SD_DHCP6_OPTION_IA_PD:
503 if (!ia->ia_pd.lifetime_t1 && !ia->ia_pd.lifetime_t2) {
504 lt_t1 = lt_min / 2;
505 lt_t2 = lt_min / 10 * 8;
506 ia->ia_pd.lifetime_t1 = htobe32(lt_t1);
507 ia->ia_pd.lifetime_t2 = htobe32(lt_t2);
508
509 log_dhcp6_client(client, "Computed IA PD T1 %ds and T2 %ds as both were zero",
510 lt_t1, lt_t2);
511 }
c6affce8 512
f8ad4dd4
PF
513 break;
514
515 default:
516 break;
c6affce8
PF
517 }
518
c6affce8 519error:
c6affce8
PF
520 return r;
521}
b553817c
PF
522
523int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
524 struct in6_addr **addrs, size_t count,
525 size_t *allocated) {
526
527 if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0)
528 return -EINVAL;
529
530 if (!GREEDY_REALLOC(*addrs, *allocated,
531 count * sizeof(struct in6_addr) + optlen))
532 return -ENOMEM;
533
534 memcpy(*addrs + count, optval, optlen);
535
536 count += optlen / sizeof(struct in6_addr);
537
538 return count;
539}
f96ccab7 540
52efd56a 541int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char ***str_arr) {
f96ccab7 542 size_t pos = 0, idx = 0;
419eaa8f 543 _cleanup_strv_free_ char **names = NULL;
f96ccab7
PF
544 int r;
545
546 assert_return(optlen > 1, -ENODATA);
93f660da 547 assert_return(optval[optlen - 1] == '\0', -EINVAL);
f96ccab7
PF
548
549 while (pos < optlen) {
550 _cleanup_free_ char *ret = NULL;
551 size_t n = 0, allocated = 0;
552 bool first = true;
553
554 for (;;) {
555 uint8_t c;
556
557 c = optval[pos++];
558
559 if (c == 0)
560 /* End of name */
561 break;
562 else if (c <= 63) {
f96ccab7
PF
563 const char *label;
564
565 /* Literal label */
566 label = (const char *)&optval[pos];
567 pos += c;
568 if (pos > optlen)
569 return -EMSGSIZE;
570
422baca0 571 if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) {
f96ccab7
PF
572 r = -ENOMEM;
573 goto fail;
574 }
575
422baca0 576 if (first)
f96ccab7 577 first = false;
422baca0
LP
578 else
579 ret[n++] = '.';
580
581 r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
582 if (r < 0)
583 goto fail;
f96ccab7 584
f96ccab7
PF
585 n += r;
586 continue;
587 } else {
588 r = -EBADMSG;
589 goto fail;
590 }
591 }
592
593 if (!GREEDY_REALLOC(ret, allocated, n + 1)) {
594 r = -ENOMEM;
595 goto fail;
596 }
597
598 ret[n] = 0;
599
600 r = strv_extend(&names, ret);
601 if (r < 0)
602 goto fail;
603
f96ccab7
PF
604 idx++;
605 }
606
ae2a15bc 607 *str_arr = TAKE_PTR(names);
f96ccab7
PF
608
609 return idx;
610
611fail:
612 return r;
613}