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