]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/dhcp6-option.c
core: reduce scope of variants
[thirdparty/systemd.git] / src / libsystemd-network / dhcp6-option.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 Copyright © 2014-2015 Intel Corporation. All rights reserved.
4 ***/
5
6 #include <errno.h>
7 #include <netinet/in.h>
8
9 #include "sd-dhcp6-client.h"
10
11 #include "alloc-util.h"
12 #include "dhcp-identifier.h"
13 #include "dhcp6-internal.h"
14 #include "dhcp6-lease-internal.h"
15 #include "dhcp6-protocol.h"
16 #include "dns-domain.h"
17 #include "memory-util.h"
18 #include "sparse-endian.h"
19 #include "strv.h"
20 #include "unaligned.h"
21
22 typedef struct DHCP6StatusOption {
23 struct DHCP6Option option;
24 be16_t status;
25 char msg[];
26 } _packed_ DHCP6StatusOption;
27
28 typedef struct DHCP6AddressOption {
29 struct DHCP6Option option;
30 struct iaaddr iaaddr;
31 uint8_t options[];
32 } _packed_ DHCP6AddressOption;
33
34 typedef struct DHCP6PDPrefixOption {
35 struct DHCP6Option option;
36 struct iapdprefix iapdprefix;
37 uint8_t options[];
38 } _packed_ DHCP6PDPrefixOption;
39
40 #define DHCP6_OPTION_IA_NA_LEN (sizeof(struct ia_na))
41 #define DHCP6_OPTION_IA_PD_LEN (sizeof(struct ia_pd))
42 #define DHCP6_OPTION_IA_TA_LEN (sizeof(struct ia_ta))
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 + offsetof(DHCP6Option, data))
53 return -ENOBUFS;
54
55 option->code = htobe16(optcode);
56 option->len = htobe16(optlen);
57
58 *buf += offsetof(DHCP6Option, data);
59 *buflen -= offsetof(DHCP6Option, data);
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 memcpy_safe(*buf, optval, optlen);
75
76 *buf += optlen;
77 *buflen -= optlen;
78
79 return 0;
80 }
81
82 int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options) {
83 sd_dhcp6_option *options;
84 int r;
85
86 assert(buf);
87 assert(*buf);
88 assert(buflen);
89 assert(vendor_options);
90
91 ORDERED_HASHMAP_FOREACH(options, vendor_options) {
92 _cleanup_free_ uint8_t *p = NULL;
93 size_t total;
94
95 total = 4 + 2 + 2 + options->length;
96
97 p = malloc(total);
98 if (!p)
99 return -ENOMEM;
100
101 unaligned_write_be32(p, options->enterprise_identifier);
102 unaligned_write_be16(p + 4, options->option);
103 unaligned_write_be16(p + 6, options->length);
104 memcpy(p + 8, options->data, options->length);
105
106 r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_OPTS, total, p);
107 if (r < 0)
108 return r;
109 }
110
111 return 0;
112 }
113
114 int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) {
115 uint16_t len;
116 uint8_t *ia_hdr;
117 size_t iaid_offset, ia_buflen, ia_addrlen = 0;
118 DHCP6Address *addr;
119 int r;
120
121 assert_return(buf, -EINVAL);
122 assert_return(*buf, -EINVAL);
123 assert_return(buflen, -EINVAL);
124 assert_return(ia, -EINVAL);
125
126 switch (ia->type) {
127 case SD_DHCP6_OPTION_IA_NA:
128 len = DHCP6_OPTION_IA_NA_LEN;
129 iaid_offset = offsetof(DHCP6IA, ia_na);
130 break;
131
132 case SD_DHCP6_OPTION_IA_TA:
133 len = DHCP6_OPTION_IA_TA_LEN;
134 iaid_offset = offsetof(DHCP6IA, ia_ta);
135 break;
136
137 default:
138 return -EINVAL;
139 }
140
141 if (*buflen < offsetof(DHCP6Option, data) + len)
142 return -ENOBUFS;
143
144 ia_hdr = *buf;
145 ia_buflen = *buflen;
146
147 *buf += offsetof(DHCP6Option, data);
148 *buflen -= offsetof(DHCP6Option, data);
149
150 memcpy(*buf, (char*) ia + iaid_offset, len);
151
152 *buf += len;
153 *buflen -= len;
154
155 LIST_FOREACH(addresses, addr, ia->addresses) {
156 r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR,
157 sizeof(addr->iaaddr));
158 if (r < 0)
159 return r;
160
161 memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
162
163 *buf += sizeof(addr->iaaddr);
164 *buflen -= sizeof(addr->iaaddr);
165
166 ia_addrlen += offsetof(DHCP6Option, data) + sizeof(addr->iaaddr);
167 }
168
169 r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
170 if (r < 0)
171 return r;
172
173 return 0;
174 }
175
176 int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
177 uint8_t buffer[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX];
178 int r;
179
180 assert_return(buf && *buf && buflen && fqdn, -EINVAL);
181
182 buffer[0] = DHCP6_FQDN_FLAG_S; /* Request server to perform AAAA RR DNS updates */
183
184 /* Store domain name after flags field */
185 r = dns_name_to_wire_format(fqdn, buffer + 1, sizeof(buffer) - 1, false);
186 if (r <= 0)
187 return r;
188
189 /*
190 * According to RFC 4704, chapter 4.2 only add terminating zero-length
191 * label in case a FQDN is provided. Since dns_name_to_wire_format
192 * always adds terminating zero-length label remove if only a hostname
193 * is provided.
194 */
195 if (dns_name_is_single_label(fqdn))
196 r--;
197
198 r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_FQDN, 1 + r, buffer);
199
200 return r;
201 }
202
203 int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char **user_class) {
204 _cleanup_free_ uint8_t *p = NULL;
205 size_t total = 0, offset = 0;
206 char **s;
207
208 assert_return(buf && *buf && buflen && user_class, -EINVAL);
209
210 STRV_FOREACH(s, user_class) {
211 size_t len = strlen(*s);
212 uint8_t *q;
213
214 if (len > 0xffff)
215 return -ENAMETOOLONG;
216 q = realloc(p, total + len + 2);
217 if (!q)
218 return -ENOMEM;
219
220 p = q;
221
222 unaligned_write_be16(&p[offset], len);
223 memcpy(&p[offset + 2], *s, len);
224
225 offset += 2 + len;
226 total += 2 + len;
227 }
228
229 return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_USER_CLASS, total, p);
230 }
231
232 int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char **vendor_class) {
233 _cleanup_free_ uint8_t *p = NULL;
234 uint32_t enterprise_identifier;
235 size_t total, offset;
236 char **s;
237
238 assert(buf);
239 assert(*buf);
240 assert(buflen);
241 assert(vendor_class);
242
243 enterprise_identifier = htobe32(SYSTEMD_PEN);
244
245 p = memdup(&enterprise_identifier, sizeof(enterprise_identifier));
246 if (!p)
247 return -ENOMEM;
248
249 total = sizeof(enterprise_identifier);
250 offset = total;
251
252 STRV_FOREACH(s, vendor_class) {
253 size_t len = strlen(*s);
254 uint8_t *q;
255
256 q = realloc(p, total + len + 2);
257 if (!q)
258 return -ENOMEM;
259
260 p = q;
261
262 unaligned_write_be16(&p[offset], len);
263 memcpy(&p[offset + 2], *s, len);
264
265 offset += 2 + len;
266 total += 2 + len;
267 }
268
269 return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_CLASS, total, p);
270 }
271
272 int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd, DHCP6Address *hint_pd_prefix) {
273 DHCP6Option *option = (DHCP6Option *)buf;
274 size_t i = sizeof(*option) + sizeof(pd->ia_pd);
275 DHCP6PDPrefixOption *prefix_opt;
276 DHCP6Address *prefix;
277
278 assert_return(buf, -EINVAL);
279 assert_return(pd, -EINVAL);
280 assert_return(pd->type == SD_DHCP6_OPTION_IA_PD, -EINVAL);
281
282 if (len < i)
283 return -ENOBUFS;
284
285 option->code = htobe16(SD_DHCP6_OPTION_IA_PD);
286
287 memcpy(&option->data, &pd->ia_pd, sizeof(pd->ia_pd));
288 LIST_FOREACH(addresses, prefix, pd->addresses) {
289 if (len < i + sizeof(*prefix_opt))
290 return -ENOBUFS;
291
292 prefix_opt = (DHCP6PDPrefixOption *)&buf[i];
293 prefix_opt->option.code = htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX);
294 prefix_opt->option.len = htobe16(sizeof(prefix_opt->iapdprefix));
295
296 memcpy(&prefix_opt->iapdprefix, &prefix->iapdprefix, sizeof(struct iapdprefix));
297 i += sizeof(*prefix_opt);
298 }
299
300 if (hint_pd_prefix && hint_pd_prefix->iapdprefix.prefixlen > 0) {
301 if (len < i + sizeof(*prefix_opt))
302 return -ENOBUFS;
303
304 prefix_opt = (DHCP6PDPrefixOption *)&buf[i];
305 prefix_opt->option.code = htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX);
306 prefix_opt->option.len = htobe16(sizeof(prefix_opt->iapdprefix));
307
308 memcpy(&prefix_opt->iapdprefix, &hint_pd_prefix->iapdprefix, sizeof(struct iapdprefix));
309 i += sizeof(*prefix_opt);
310 }
311
312 option->len = htobe16(i - sizeof(*option));
313
314 return i;
315 }
316
317 static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
318 DHCP6Option *option = (DHCP6Option*) *buf;
319 uint16_t len;
320
321 assert_return(buf, -EINVAL);
322 assert_return(optcode, -EINVAL);
323 assert_return(optlen, -EINVAL);
324
325 if (*buflen < offsetof(DHCP6Option, data))
326 return -ENOMSG;
327
328 len = be16toh(option->len);
329
330 if (len > *buflen)
331 return -ENOMSG;
332
333 *optcode = be16toh(option->code);
334 *optlen = len;
335
336 *buf += 4;
337 *buflen -= 4;
338
339 return 0;
340 }
341
342 int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
343 size_t *optlen, uint8_t **optvalue) {
344 int r;
345
346 assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
347
348 r = option_parse_hdr(buf, buflen, optcode, optlen);
349 if (r < 0)
350 return r;
351
352 if (*optlen > *buflen)
353 return -ENOBUFS;
354
355 *optvalue = *buf;
356 *buflen -= *optlen;
357 *buf += *optlen;
358
359 return 0;
360 }
361
362 int dhcp6_option_parse_status(DHCP6Option *option, size_t len) {
363 DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option;
364
365 if (len < sizeof(DHCP6StatusOption) ||
366 be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(DHCP6StatusOption))
367 return -ENOBUFS;
368
369 return be16toh(statusopt->status);
370 }
371
372 static int dhcp6_option_parse_address(DHCP6Option *option, DHCP6IA *ia,
373 uint32_t *lifetime_valid) {
374 DHCP6AddressOption *addr_option = (DHCP6AddressOption *)option;
375 DHCP6Address *addr;
376 uint32_t lt_valid, lt_pref;
377 int r;
378
379 if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*addr_option))
380 return -ENOBUFS;
381
382 lt_valid = be32toh(addr_option->iaaddr.lifetime_valid);
383 lt_pref = be32toh(addr_option->iaaddr.lifetime_preferred);
384
385 if (lt_valid == 0 || lt_pref > lt_valid) {
386 log_dhcp6_client(client, "Valid lifetime of an IA address is zero or preferred lifetime %d > valid lifetime %d",
387 lt_pref, lt_valid);
388
389 return 0;
390 }
391
392 if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*addr_option)) {
393 r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*addr_option));
394 if (r != 0)
395 return r < 0 ? r: 0;
396 }
397
398 addr = new0(DHCP6Address, 1);
399 if (!addr)
400 return -ENOMEM;
401
402 LIST_INIT(addresses, addr);
403 memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr));
404
405 LIST_PREPEND(addresses, ia->addresses, addr);
406
407 *lifetime_valid = be32toh(addr->iaaddr.lifetime_valid);
408
409 return 0;
410 }
411
412 static int dhcp6_option_parse_pdprefix(DHCP6Option *option, DHCP6IA *ia,
413 uint32_t *lifetime_valid) {
414 DHCP6PDPrefixOption *pdprefix_option = (DHCP6PDPrefixOption *)option;
415 DHCP6Address *prefix;
416 uint32_t lt_valid, lt_pref;
417 int r;
418
419 if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*pdprefix_option))
420 return -ENOBUFS;
421
422 lt_valid = be32toh(pdprefix_option->iapdprefix.lifetime_valid);
423 lt_pref = be32toh(pdprefix_option->iapdprefix.lifetime_preferred);
424
425 if (lt_valid == 0 || lt_pref > lt_valid) {
426 log_dhcp6_client(client, "Valid lifetieme of a PD prefix is zero or preferred lifetime %d > valid lifetime %d",
427 lt_pref, lt_valid);
428
429 return 0;
430 }
431
432 if (be16toh(option->len) + offsetof(DHCP6Option, data) > sizeof(*pdprefix_option)) {
433 r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options, be16toh(option->len) + offsetof(DHCP6Option, data) - sizeof(*pdprefix_option));
434 if (r != 0)
435 return r < 0 ? r: 0;
436 }
437
438 prefix = new0(DHCP6Address, 1);
439 if (!prefix)
440 return -ENOMEM;
441
442 LIST_INIT(addresses, prefix);
443 memcpy(&prefix->iapdprefix, option->data, sizeof(prefix->iapdprefix));
444
445 LIST_PREPEND(addresses, ia->addresses, prefix);
446
447 *lifetime_valid = be32toh(prefix->iapdprefix.lifetime_valid);
448
449 return 0;
450 }
451
452 int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia, uint16_t *ret_status_code) {
453 uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX;
454 uint16_t iatype, optlen;
455 size_t iaaddr_offset;
456 int r = 0, status;
457 size_t i, len;
458 uint16_t opt;
459
460 assert_return(ia, -EINVAL);
461 assert_return(!ia->addresses, -EINVAL);
462
463 iatype = be16toh(iaoption->code);
464 len = be16toh(iaoption->len);
465
466 switch (iatype) {
467 case SD_DHCP6_OPTION_IA_NA:
468
469 if (len < DHCP6_OPTION_IA_NA_LEN)
470 return -ENOBUFS;
471
472 iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
473 memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na));
474
475 lt_t1 = be32toh(ia->ia_na.lifetime_t1);
476 lt_t2 = be32toh(ia->ia_na.lifetime_t2);
477
478 if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
479 log_dhcp6_client(client, "IA NA T1 %ds > T2 %ds",
480 lt_t1, lt_t2);
481 return -EINVAL;
482 }
483
484 break;
485
486 case SD_DHCP6_OPTION_IA_PD:
487
488 if (len < sizeof(ia->ia_pd))
489 return -ENOBUFS;
490
491 iaaddr_offset = sizeof(ia->ia_pd);
492 memcpy(&ia->ia_pd, iaoption->data, sizeof(ia->ia_pd));
493
494 lt_t1 = be32toh(ia->ia_pd.lifetime_t1);
495 lt_t2 = be32toh(ia->ia_pd.lifetime_t2);
496
497 if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
498 log_dhcp6_client(client, "IA PD T1 %ds > T2 %ds",
499 lt_t1, lt_t2);
500 return -EINVAL;
501 }
502
503 break;
504
505 case SD_DHCP6_OPTION_IA_TA:
506 if (len < DHCP6_OPTION_IA_TA_LEN)
507 return -ENOBUFS;
508
509 iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
510 memcpy(&ia->ia_ta.id, iaoption->data, sizeof(ia->ia_ta));
511
512 break;
513
514 default:
515 return -ENOMSG;
516 }
517
518 ia->type = iatype;
519 i = iaaddr_offset;
520
521 while (i < len) {
522 DHCP6Option *option = (DHCP6Option *)&iaoption->data[i];
523
524 if (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len))
525 return -ENOBUFS;
526
527 opt = be16toh(option->code);
528 optlen = be16toh(option->len);
529
530 switch (opt) {
531 case SD_DHCP6_OPTION_IAADDR:
532
533 if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) {
534 log_dhcp6_client(client, "IA Address option not in IA NA or TA option");
535 return -EINVAL;
536 }
537
538 r = dhcp6_option_parse_address(option, ia, &lt_valid);
539 if (r < 0)
540 return r;
541
542 if (lt_valid < lt_min)
543 lt_min = lt_valid;
544
545 break;
546
547 case SD_DHCP6_OPTION_IA_PD_PREFIX:
548
549 if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_PD)) {
550 log_dhcp6_client(client, "IA PD Prefix option not in IA PD option");
551 return -EINVAL;
552 }
553
554 r = dhcp6_option_parse_pdprefix(option, ia, &lt_valid);
555 if (r < 0)
556 return r;
557
558 if (lt_valid < lt_min)
559 lt_min = lt_valid;
560
561 break;
562
563 case SD_DHCP6_OPTION_STATUS_CODE:
564
565 status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data));
566 if (status < 0)
567 return status;
568
569 if (status > 0) {
570 if (ret_status_code)
571 *ret_status_code = status;
572
573 log_dhcp6_client(client, "IA status %s",
574 dhcp6_message_status_to_string(status));
575
576 return 0;
577 }
578
579 break;
580
581 default:
582 log_dhcp6_client(client, "Unknown IA option %d", opt);
583 break;
584 }
585
586 i += sizeof(*option) + optlen;
587 }
588
589 switch(iatype) {
590 case SD_DHCP6_OPTION_IA_NA:
591 if (!ia->ia_na.lifetime_t1 && !ia->ia_na.lifetime_t2) {
592 lt_t1 = lt_min / 2;
593 lt_t2 = lt_min / 10 * 8;
594 ia->ia_na.lifetime_t1 = htobe32(lt_t1);
595 ia->ia_na.lifetime_t2 = htobe32(lt_t2);
596
597 log_dhcp6_client(client, "Computed IA NA T1 %ds and T2 %ds as both were zero",
598 lt_t1, lt_t2);
599 }
600
601 break;
602
603 case SD_DHCP6_OPTION_IA_PD:
604 if (!ia->ia_pd.lifetime_t1 && !ia->ia_pd.lifetime_t2) {
605 lt_t1 = lt_min / 2;
606 lt_t2 = lt_min / 10 * 8;
607 ia->ia_pd.lifetime_t1 = htobe32(lt_t1);
608 ia->ia_pd.lifetime_t2 = htobe32(lt_t2);
609
610 log_dhcp6_client(client, "Computed IA PD T1 %ds and T2 %ds as both were zero",
611 lt_t1, lt_t2);
612 }
613
614 break;
615
616 default:
617 break;
618 }
619
620 if (ret_status_code)
621 *ret_status_code = 0;
622
623 return 1;
624 }
625
626 int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
627 struct in6_addr **addrs, size_t count,
628 size_t *allocated) {
629
630 if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0)
631 return -EINVAL;
632
633 if (!GREEDY_REALLOC(*addrs, *allocated,
634 count * sizeof(struct in6_addr) + optlen))
635 return -ENOMEM;
636
637 memcpy(*addrs + count, optval, optlen);
638
639 count += optlen / sizeof(struct in6_addr);
640
641 return count;
642 }
643
644 static int parse_domain(const uint8_t **data, uint16_t *len, char **out_domain) {
645 _cleanup_free_ char *ret = NULL;
646 size_t n = 0, allocated = 0;
647 const uint8_t *optval = *data;
648 uint16_t optlen = *len;
649 bool first = true;
650 int r;
651
652 if (optlen <= 1)
653 return -ENODATA;
654
655 for (;;) {
656 const char *label;
657 uint8_t c;
658
659 if (optlen == 0)
660 break;
661
662 c = *optval;
663 optval++;
664 optlen--;
665
666 if (c == 0)
667 /* End label */
668 break;
669 if (c > 63)
670 return -EBADMSG;
671 if (c > optlen)
672 return -EMSGSIZE;
673
674 /* Literal label */
675 label = (const char *)optval;
676 optval += c;
677 optlen -= c;
678
679 if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
680 return -ENOMEM;
681
682 if (first)
683 first = false;
684 else
685 ret[n++] = '.';
686
687 r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
688 if (r < 0)
689 return r;
690
691 n += r;
692 }
693
694 if (n) {
695 if (!GREEDY_REALLOC(ret, allocated, n + 1))
696 return -ENOMEM;
697 ret[n] = 0;
698 }
699
700 *out_domain = TAKE_PTR(ret);
701 *data = optval;
702 *len = optlen;
703
704 return n;
705 }
706
707 int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char **str) {
708 _cleanup_free_ char *domain = NULL;
709 int r;
710
711 r = parse_domain(&optval, &optlen, &domain);
712 if (r < 0)
713 return r;
714 if (r == 0)
715 return -ENODATA;
716 if (optlen != 0)
717 return -EINVAL;
718
719 *str = TAKE_PTR(domain);
720 return 0;
721 }
722
723 int dhcp6_option_parse_domainname_list(const uint8_t *optval, uint16_t optlen, char ***str_arr) {
724 size_t idx = 0;
725 _cleanup_strv_free_ char **names = NULL;
726 int r;
727
728 if (optlen <= 1)
729 return -ENODATA;
730 if (optval[optlen - 1] != '\0')
731 return -EINVAL;
732
733 while (optlen > 0) {
734 _cleanup_free_ char *ret = NULL;
735
736 r = parse_domain(&optval, &optlen, &ret);
737 if (r < 0)
738 return r;
739 if (r == 0)
740 continue;
741
742 r = strv_extend(&names, ret);
743 if (r < 0)
744 return r;
745
746 idx++;
747 }
748
749 *str_arr = TAKE_PTR(names);
750
751 return idx;
752 }
753
754 static sd_dhcp6_option* dhcp6_option_free(sd_dhcp6_option *i) {
755 if (!i)
756 return NULL;
757
758 free(i->data);
759 return mfree(i);
760 }
761
762 int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret) {
763 assert_return(ret, -EINVAL);
764 assert_return(length == 0 || data, -EINVAL);
765
766 _cleanup_free_ void *q = memdup(data, length);
767 if (!q)
768 return -ENOMEM;
769
770 sd_dhcp6_option *p = new(sd_dhcp6_option, 1);
771 if (!p)
772 return -ENOMEM;
773
774 *p = (sd_dhcp6_option) {
775 .n_ref = 1,
776 .option = option,
777 .enterprise_identifier = enterprise_identifier,
778 .length = length,
779 .data = TAKE_PTR(q),
780 };
781
782 *ret = p;
783 return 0;
784 }
785
786 DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option, sd_dhcp6_option, dhcp6_option_free);
787 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
788 dhcp6_option_hash_ops,
789 void,
790 trivial_hash_func,
791 trivial_compare_func,
792 sd_dhcp6_option,
793 sd_dhcp6_option_unref);