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