]>
Commit | Line | Data |
---|---|---|
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 |
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; | |
99ccb8ff SS |
84 | int r; |
85 | ||
86 | assert(buf); | |
87 | assert(*buf); | |
88 | assert(buflen); | |
89 | assert(vendor_options); | |
90 | ||
90e74a66 | 91 | ORDERED_HASHMAP_FOREACH(options, vendor_options) { |
99ccb8ff SS |
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 | ||
e0a18b74 | 114 | int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) { |
f12ed3bf PF |
115 | uint16_t len; |
116 | uint8_t *ia_hdr; | |
976fade6 | 117 | size_t iaid_offset, ia_buflen, ia_addrlen = 0; |
f12ed3bf PF |
118 | DHCP6Address *addr; |
119 | int r; | |
120 | ||
3c290c03 LP |
121 | assert_return(buf, -EINVAL); |
122 | assert_return(*buf, -EINVAL); | |
123 | assert_return(buflen, -EINVAL); | |
124 | assert_return(ia, -EINVAL); | |
f12ed3bf PF |
125 | |
126 | switch (ia->type) { | |
2c1ab8ca | 127 | case SD_DHCP6_OPTION_IA_NA: |
f12ed3bf | 128 | len = DHCP6_OPTION_IA_NA_LEN; |
976fade6 | 129 | iaid_offset = offsetof(DHCP6IA, ia_na); |
f12ed3bf PF |
130 | break; |
131 | ||
2c1ab8ca | 132 | case SD_DHCP6_OPTION_IA_TA: |
f12ed3bf | 133 | len = DHCP6_OPTION_IA_TA_LEN; |
976fade6 | 134 | iaid_offset = offsetof(DHCP6IA, ia_ta); |
f12ed3bf PF |
135 | break; |
136 | ||
137 | default: | |
138 | return -EINVAL; | |
139 | } | |
140 | ||
4dac5eab | 141 | if (*buflen < offsetof(DHCP6Option, data) + len) |
f12ed3bf PF |
142 | return -ENOBUFS; |
143 | ||
144 | ia_hdr = *buf; | |
145 | ia_buflen = *buflen; | |
146 | ||
20b55f85 LP |
147 | *buf += offsetof(DHCP6Option, data); |
148 | *buflen -= offsetof(DHCP6Option, data); | |
f12ed3bf | 149 | |
976fade6 | 150 | memcpy(*buf, (char*) ia + iaid_offset, len); |
f12ed3bf PF |
151 | |
152 | *buf += len; | |
153 | *buflen -= len; | |
154 | ||
155 | LIST_FOREACH(addresses, addr, ia->addresses) { | |
2c1ab8ca | 156 | r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR, |
ee3a5027 | 157 | sizeof(addr->iaaddr)); |
f12ed3bf PF |
158 | if (r < 0) |
159 | return r; | |
160 | ||
ee3a5027 | 161 | memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr)); |
f12ed3bf | 162 | |
ee3a5027 PF |
163 | *buf += sizeof(addr->iaaddr); |
164 | *buflen -= sizeof(addr->iaaddr); | |
f12ed3bf | 165 | |
20b55f85 | 166 | ia_addrlen += offsetof(DHCP6Option, data) + sizeof(addr->iaaddr); |
f12ed3bf PF |
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 | ||
8006aa32 | 176 | int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) { |
5e55cde9 | 177 | uint8_t buffer[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX]; |
8006aa32 SA |
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 | ||
33923925 SS |
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; | |
33923925 SS |
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 | ||
73c8ced7 SS |
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 | ||
2805536b | 272 | int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd, DHCP6Address *hint_pd_prefix) { |
c77e3db1 PF |
273 | DHCP6Option *option = (DHCP6Option *)buf; |
274 | size_t i = sizeof(*option) + sizeof(pd->ia_pd); | |
2805536b | 275 | DHCP6PDPrefixOption *prefix_opt; |
c77e3db1 PF |
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)); | |
c77e3db1 | 288 | LIST_FOREACH(addresses, prefix, pd->addresses) { |
c77e3db1 PF |
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 | ||
2805536b SS |
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)); | |
c77e3db1 | 307 | |
2805536b | 308 | memcpy(&prefix_opt->iapdprefix, &hint_pd_prefix->iapdprefix, sizeof(struct iapdprefix)); |
c77e3db1 PF |
309 | i += sizeof(*prefix_opt); |
310 | } | |
311 | ||
312 | option->len = htobe16(i - sizeof(*option)); | |
313 | ||
314 | return i; | |
315 | } | |
c6affce8 | 316 | |
c962cb68 TG |
317 | static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) { |
318 | DHCP6Option *option = (DHCP6Option*) *buf; | |
c6affce8 PF |
319 | uint16_t len; |
320 | ||
321 | assert_return(buf, -EINVAL); | |
4903a73c | 322 | assert_return(optcode, -EINVAL); |
c6affce8 PF |
323 | assert_return(optlen, -EINVAL); |
324 | ||
20b55f85 | 325 | if (*buflen < offsetof(DHCP6Option, data)) |
c6affce8 PF |
326 | return -ENOMSG; |
327 | ||
c962cb68 | 328 | len = be16toh(option->len); |
c6affce8 PF |
329 | |
330 | if (len > *buflen) | |
331 | return -ENOMSG; | |
332 | ||
c962cb68 | 333 | *optcode = be16toh(option->code); |
c6affce8 PF |
334 | *optlen = len; |
335 | ||
336 | *buf += 4; | |
337 | *buflen -= 4; | |
338 | ||
339 | return 0; | |
340 | } | |
341 | ||
f12ed3bf PF |
342 | int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, |
343 | size_t *optlen, uint8_t **optvalue) { | |
c6affce8 | 344 | int r; |
f12ed3bf | 345 | |
c6affce8 | 346 | assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL); |
f12ed3bf | 347 | |
c6affce8 PF |
348 | r = option_parse_hdr(buf, buflen, optcode, optlen); |
349 | if (r < 0) | |
350 | return r; | |
f12ed3bf | 351 | |
c6affce8 | 352 | if (*optlen > *buflen) |
f12ed3bf PF |
353 | return -ENOBUFS; |
354 | ||
c6affce8 PF |
355 | *optvalue = *buf; |
356 | *buflen -= *optlen; | |
357 | *buf += *optlen; | |
f12ed3bf PF |
358 | |
359 | return 0; | |
360 | } | |
c6affce8 | 361 | |
84452783 | 362 | int dhcp6_option_parse_status(DHCP6Option *option, size_t len) { |
c6b4f32a PF |
363 | DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option; |
364 | ||
84452783 | 365 | if (len < sizeof(DHCP6StatusOption) || |
20b55f85 | 366 | be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(DHCP6StatusOption)) |
c6b4f32a PF |
367 | return -ENOBUFS; |
368 | ||
369 | return be16toh(statusopt->status); | |
370 | } | |
371 | ||
0dfe2a4b PF |
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 | ||
20b55f85 | 379 | if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*addr_option)) |
0dfe2a4b PF |
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 | ||
20b55f85 LP |
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)); | |
0dfe2a4b PF |
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 | ||
f8ad4dd4 PF |
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 | ||
20b55f85 | 419 | if (be16toh(option->len) + offsetof(DHCP6Option, data) < sizeof(*pdprefix_option)) |
f8ad4dd4 PF |
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 | ||
20b55f85 LP |
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)); | |
f8ad4dd4 PF |
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 | ||
5c95a913 SS |
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; | |
3bc424a3 | 454 | uint16_t iatype, optlen; |
5c95a913 | 455 | size_t iaaddr_offset; |
3bc424a3 | 456 | int r = 0, status; |
5c95a913 | 457 | size_t i, len; |
3bc424a3 | 458 | uint16_t opt; |
c6affce8 PF |
459 | |
460 | assert_return(ia, -EINVAL); | |
461 | assert_return(!ia->addresses, -EINVAL); | |
462 | ||
3bc424a3 PF |
463 | iatype = be16toh(iaoption->code); |
464 | len = be16toh(iaoption->len); | |
465 | ||
c6affce8 | 466 | switch (iatype) { |
2c1ab8ca | 467 | case SD_DHCP6_OPTION_IA_NA: |
c6affce8 | 468 | |
aae1fa5c YW |
469 | if (len < DHCP6_OPTION_IA_NA_LEN) |
470 | return -ENOBUFS; | |
c6affce8 PF |
471 | |
472 | iaaddr_offset = DHCP6_OPTION_IA_NA_LEN; | |
3bc424a3 | 473 | memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na)); |
c6affce8 | 474 | |
e0026dcb PF |
475 | lt_t1 = be32toh(ia->ia_na.lifetime_t1); |
476 | lt_t2 = be32toh(ia->ia_na.lifetime_t2); | |
c6affce8 PF |
477 | |
478 | if (lt_t1 && lt_t2 && lt_t1 > lt_t2) { | |
f8ad4dd4 PF |
479 | log_dhcp6_client(client, "IA NA T1 %ds > T2 %ds", |
480 | lt_t1, lt_t2); | |
aae1fa5c | 481 | return -EINVAL; |
f8ad4dd4 PF |
482 | } |
483 | ||
484 | break; | |
485 | ||
486 | case SD_DHCP6_OPTION_IA_PD: | |
487 | ||
aae1fa5c YW |
488 | if (len < sizeof(ia->ia_pd)) |
489 | return -ENOBUFS; | |
f8ad4dd4 PF |
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", | |
c6affce8 | 499 | lt_t1, lt_t2); |
aae1fa5c | 500 | return -EINVAL; |
c6affce8 PF |
501 | } |
502 | ||
503 | break; | |
504 | ||
2c1ab8ca | 505 | case SD_DHCP6_OPTION_IA_TA: |
aae1fa5c YW |
506 | if (len < DHCP6_OPTION_IA_TA_LEN) |
507 | return -ENOBUFS; | |
c6affce8 PF |
508 | |
509 | iaaddr_offset = DHCP6_OPTION_IA_TA_LEN; | |
3bc424a3 | 510 | memcpy(&ia->ia_ta.id, iaoption->data, sizeof(ia->ia_ta)); |
c6affce8 PF |
511 | |
512 | break; | |
513 | ||
514 | default: | |
aae1fa5c | 515 | return -ENOMSG; |
c6affce8 PF |
516 | } |
517 | ||
518 | ia->type = iatype; | |
3bc424a3 PF |
519 | i = iaaddr_offset; |
520 | ||
521 | while (i < len) { | |
522 | DHCP6Option *option = (DHCP6Option *)&iaoption->data[i]; | |
c6affce8 | 523 | |
aae1fa5c YW |
524 | if (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len)) |
525 | return -ENOBUFS; | |
c6affce8 | 526 | |
3bc424a3 PF |
527 | opt = be16toh(option->code); |
528 | optlen = be16toh(option->len); | |
c6affce8 PF |
529 | |
530 | switch (opt) { | |
2c1ab8ca | 531 | case SD_DHCP6_OPTION_IAADDR: |
0dfe2a4b | 532 | |
f8ad4dd4 PF |
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"); | |
aae1fa5c | 535 | return -EINVAL; |
f8ad4dd4 PF |
536 | } |
537 | ||
0dfe2a4b PF |
538 | r = dhcp6_option_parse_address(option, ia, <_valid); |
539 | if (r < 0) | |
aae1fa5c | 540 | return r; |
0dfe2a4b PF |
541 | |
542 | if (lt_valid < lt_min) | |
543 | lt_min = lt_valid; | |
c6affce8 PF |
544 | |
545 | break; | |
546 | ||
f8ad4dd4 PF |
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"); | |
aae1fa5c | 551 | return -EINVAL; |
f8ad4dd4 PF |
552 | } |
553 | ||
554 | r = dhcp6_option_parse_pdprefix(option, ia, <_valid); | |
555 | if (r < 0) | |
aae1fa5c | 556 | return r; |
f8ad4dd4 PF |
557 | |
558 | if (lt_valid < lt_min) | |
559 | lt_min = lt_valid; | |
560 | ||
561 | break; | |
562 | ||
2c1ab8ca | 563 | case SD_DHCP6_OPTION_STATUS_CODE: |
c6affce8 | 564 | |
20b55f85 | 565 | status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data)); |
aae1fa5c YW |
566 | if (status < 0) |
567 | return status; | |
5c95a913 | 568 | |
91c43f39 | 569 | if (status > 0) { |
5c95a913 SS |
570 | if (ret_status_code) |
571 | *ret_status_code = status; | |
572 | ||
667ceb9d SS |
573 | log_dhcp6_client(client, "IA status %s", |
574 | dhcp6_message_status_to_string(status)); | |
c6b4f32a | 575 | |
5c95a913 | 576 | return 0; |
c6affce8 PF |
577 | } |
578 | ||
579 | break; | |
580 | ||
581 | default: | |
582 | log_dhcp6_client(client, "Unknown IA option %d", opt); | |
583 | break; | |
584 | } | |
585 | ||
3bc424a3 | 586 | i += sizeof(*option) + optlen; |
c6affce8 PF |
587 | } |
588 | ||
f8ad4dd4 PF |
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 | } | |
c6affce8 | 613 | |
f8ad4dd4 PF |
614 | break; |
615 | ||
616 | default: | |
617 | break; | |
c6affce8 PF |
618 | } |
619 | ||
5c95a913 SS |
620 | if (ret_status_code) |
621 | *ret_status_code = 0; | |
622 | ||
623 | return 1; | |
c6affce8 | 624 | } |
b553817c PF |
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 | } | |
f96ccab7 | 643 | |
c43eea9f BG |
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; | |
f96ccab7 PF |
650 | int r; |
651 | ||
af710b53 BG |
652 | if (optlen <= 1) |
653 | return -ENODATA; | |
f96ccab7 | 654 | |
c43eea9f BG |
655 | for (;;) { |
656 | const char *label; | |
657 | uint8_t c; | |
f96ccab7 | 658 | |
c43eea9f BG |
659 | if (optlen == 0) |
660 | break; | |
f96ccab7 | 661 | |
c43eea9f BG |
662 | c = *optval; |
663 | optval++; | |
664 | optlen--; | |
3c72b6ed | 665 | |
c43eea9f BG |
666 | if (c == 0) |
667 | /* End label */ | |
668 | break; | |
669 | if (c > 63) | |
670 | return -EBADMSG; | |
671 | if (c > optlen) | |
672 | return -EMSGSIZE; | |
3c72b6ed | 673 | |
c43eea9f BG |
674 | /* Literal label */ |
675 | label = (const char *)optval; | |
676 | optval += c; | |
677 | optlen -= c; | |
3c72b6ed | 678 | |
c43eea9f BG |
679 | if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) |
680 | return -ENOMEM; | |
3c72b6ed | 681 | |
c43eea9f BG |
682 | if (first) |
683 | first = false; | |
684 | else | |
685 | ret[n++] = '.'; | |
f96ccab7 | 686 | |
c43eea9f BG |
687 | r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); |
688 | if (r < 0) | |
689 | return r; | |
f96ccab7 | 690 | |
c43eea9f BG |
691 | n += r; |
692 | } | |
3c72b6ed | 693 | |
c43eea9f | 694 | if (n) { |
3c72b6ed YW |
695 | if (!GREEDY_REALLOC(ret, allocated, n + 1)) |
696 | return -ENOMEM; | |
f96ccab7 | 697 | ret[n] = 0; |
c43eea9f BG |
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; | |
f96ccab7 PF |
741 | |
742 | r = strv_extend(&names, ret); | |
743 | if (r < 0) | |
3c72b6ed | 744 | return r; |
f96ccab7 | 745 | |
f96ccab7 PF |
746 | idx++; |
747 | } | |
748 | ||
ae2a15bc | 749 | *str_arr = TAKE_PTR(names); |
f96ccab7 PF |
750 | |
751 | return idx; | |
f96ccab7 | 752 | } |
e7d5fe17 AD |
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 | ||
99ccb8ff | 762 | int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret) { |
e7d5fe17 AD |
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, | |
99ccb8ff | 777 | .enterprise_identifier = enterprise_identifier, |
e7d5fe17 AD |
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); |