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