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