]>
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 | 43 | |
cf6c33bd YW |
44 | static 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 | ||
65 | int 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 |
83 | int 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 | 115 | int 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 |
187 | static 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 | ||
218 | int 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 | 271 | int 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 | 298 | int 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 | 330 | int 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 |
373 | static 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 |
398 | int 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 | 418 | int 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 | 428 | static 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 | 470 | static 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 | 512 | int 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, <_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, <_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 | |
680 | int 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 |
698 | static 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 | ||
761 | int 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 | ||
777 | int 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 | |
808 | static 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 | 816 | int 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 | ||
840 | DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option, sd_dhcp6_option, dhcp6_option_free); | |
841 | DEFINE_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); |