]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/dhcp6-option.c
sd-dhcp6-client: store PD prefix hint in ia_pd
[thirdparty/systemd.git] / src / libsystemd-network / dhcp6-option.c
CommitLineData
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
fea8c180
YW
27bool dhcp6_option_can_request(uint16_t option) {
28 /* See Client ORO field in
29 * https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-2 */
30
31 switch (option) {
32 case SD_DHCP6_OPTION_CLIENTID:
33 case SD_DHCP6_OPTION_SERVERID:
34 case SD_DHCP6_OPTION_IA_NA:
35 case SD_DHCP6_OPTION_IA_TA:
36 case SD_DHCP6_OPTION_IAADDR:
37 case SD_DHCP6_OPTION_ORO:
38 case SD_DHCP6_OPTION_PREFERENCE:
39 case SD_DHCP6_OPTION_ELAPSED_TIME:
40 case SD_DHCP6_OPTION_RELAY_MSG:
41 case SD_DHCP6_OPTION_AUTH:
42 case SD_DHCP6_OPTION_UNICAST:
43 case SD_DHCP6_OPTION_STATUS_CODE:
44 case SD_DHCP6_OPTION_RAPID_COMMIT:
45 case SD_DHCP6_OPTION_USER_CLASS:
46 case SD_DHCP6_OPTION_VENDOR_CLASS:
47 return false;
48 case SD_DHCP6_OPTION_VENDOR_OPTS:
49 return true;
50 case SD_DHCP6_OPTION_INTERFACE_ID:
51 case SD_DHCP6_OPTION_RECONF_MSG:
52 case SD_DHCP6_OPTION_RECONF_ACCEPT:
53 return false;
54 case SD_DHCP6_OPTION_SIP_SERVER_DOMAIN_NAME:
55 case SD_DHCP6_OPTION_SIP_SERVER_ADDRESS:
56 case SD_DHCP6_OPTION_DNS_SERVERS:
57 case SD_DHCP6_OPTION_DOMAIN_LIST:
58 return true;
59 case SD_DHCP6_OPTION_IA_PD:
60 case SD_DHCP6_OPTION_IA_PD_PREFIX:
61 return false;
62 case SD_DHCP6_OPTION_NIS_SERVERS:
63 case SD_DHCP6_OPTION_NISP_SERVERS:
64 case SD_DHCP6_OPTION_NIS_DOMAIN_NAME:
65 case SD_DHCP6_OPTION_NISP_DOMAIN_NAME:
66 case SD_DHCP6_OPTION_SNTP_SERVERS:
67 case SD_DHCP6_OPTION_INFORMATION_REFRESH_TIME:
68 case SD_DHCP6_OPTION_BCMCS_SERVER_D:
69 case SD_DHCP6_OPTION_BCMCS_SERVER_A:
70 case SD_DHCP6_OPTION_GEOCONF_CIVIC:
71 return true;
72 case SD_DHCP6_OPTION_REMOTE_ID:
73 case SD_DHCP6_OPTION_SUBSCRIBER_ID:
74 return false;
75 case SD_DHCP6_OPTION_CLIENT_FQDN:
76 case SD_DHCP6_OPTION_PANA_AGENT:
77 case SD_DHCP6_OPTION_NEW_POSIX_TIMEZONE:
78 case SD_DHCP6_OPTION_NEW_TZDB_TIMEZONE:
79 return true;
80 case SD_DHCP6_OPTION_ERO:
81 case SD_DHCP6_OPTION_LQ_QUERY:
82 case SD_DHCP6_OPTION_CLIENT_DATA:
83 case SD_DHCP6_OPTION_CLT_TIME:
84 case SD_DHCP6_OPTION_LQ_RELAY_DATA:
85 case SD_DHCP6_OPTION_LQ_CLIENT_LINK:
86 return false;
87 case SD_DHCP6_OPTION_MIP6_HNIDF:
88 case SD_DHCP6_OPTION_MIP6_VDINF:
89 case SD_DHCP6_OPTION_V6_LOST:
90 case SD_DHCP6_OPTION_CAPWAP_AC_V6:
91 return true;
92 case SD_DHCP6_OPTION_RELAY_ID:
93 return false;
94 case SD_DHCP6_OPTION_IPV6_ADDRESS_MOS:
95 case SD_DHCP6_OPTION_IPV6_FQDN_MOS:
96 case SD_DHCP6_OPTION_NTP_SERVER:
97 case SD_DHCP6_OPTION_V6_ACCESS_DOMAIN:
98 case SD_DHCP6_OPTION_SIP_UA_CS_LIST:
99 case SD_DHCP6_OPTION_BOOTFILE_URL:
100 case SD_DHCP6_OPTION_BOOTFILE_PARAM:
101 return true;
102 case SD_DHCP6_OPTION_CLIENT_ARCH_TYPE:
103 return false;
104 case SD_DHCP6_OPTION_NII:
105 case SD_DHCP6_OPTION_GEOLOCATION:
106 case SD_DHCP6_OPTION_AFTR_NAME:
107 case SD_DHCP6_OPTION_ERP_LOCAL_DOMAIN_NAME:
108 return true;
109 case SD_DHCP6_OPTION_RSOO:
110 return false;
111 case SD_DHCP6_OPTION_PD_EXCLUDE:
112 return true;
113 case SD_DHCP6_OPTION_VSS:
114 return false;
115 case SD_DHCP6_OPTION_MIP6_IDINF:
116 case SD_DHCP6_OPTION_MIP6_UDINF:
117 case SD_DHCP6_OPTION_MIP6_HNP:
118 case SD_DHCP6_OPTION_MIP6_HAA:
119 case SD_DHCP6_OPTION_MIP6_HAF:
120 case SD_DHCP6_OPTION_RDNSS_SELECTION:
121 case SD_DHCP6_OPTION_KRB_PRINCIPAL_NAME:
122 case SD_DHCP6_OPTION_KRB_REALM_NAME:
123 case SD_DHCP6_OPTION_KRB_DEFAULT_REALM_NAME:
124 case SD_DHCP6_OPTION_KRB_KDC:
125 return true;
126 case SD_DHCP6_OPTION_CLIENT_LINKLAYER_ADDR:
127 case SD_DHCP6_OPTION_LINK_ADDRESS:
128 case SD_DHCP6_OPTION_RADIUS:
129 return false;
130 case SD_DHCP6_OPTION_SOL_MAX_RT:
131 case SD_DHCP6_OPTION_INF_MAX_RT:
132 case SD_DHCP6_OPTION_ADDRSEL:
133 case SD_DHCP6_OPTION_ADDRSEL_TABLE:
134 case SD_DHCP6_OPTION_V6_PCP_SERVER:
135 return true;
136 case SD_DHCP6_OPTION_DHCPV4_MSG:
137 return false;
138 case SD_DHCP6_OPTION_DHCP4_O_DHCP6_SERVER:
139 return true;
140 case SD_DHCP6_OPTION_S46_RULE:
141 return false;
142 case SD_DHCP6_OPTION_S46_BR:
143 return true;
144 case SD_DHCP6_OPTION_S46_DMR:
145 case SD_DHCP6_OPTION_S46_V4V6BIND:
146 case SD_DHCP6_OPTION_S46_PORTPARAMS:
147 return false;
148 case SD_DHCP6_OPTION_S46_CONT_MAPE:
149 case SD_DHCP6_OPTION_S46_CONT_MAPT:
150 case SD_DHCP6_OPTION_S46_CONT_LW:
151 case SD_DHCP6_OPTION_4RD:
152 case SD_DHCP6_OPTION_4RD_MAP_RULE:
153 case SD_DHCP6_OPTION_4RD_NON_MAP_RULE:
154 return true;
155 case SD_DHCP6_OPTION_LQ_BASE_TIME:
156 case SD_DHCP6_OPTION_LQ_START_TIME:
157 case SD_DHCP6_OPTION_LQ_END_TIME:
158 return false;
159 case SD_DHCP6_OPTION_CAPTIVE_PORTAL:
160 case SD_DHCP6_OPTION_MPL_PARAMETERS:
161 return true;
162 case SD_DHCP6_OPTION_ANI_ATT:
163 case SD_DHCP6_OPTION_ANI_NETWORK_NAME:
164 case SD_DHCP6_OPTION_ANI_AP_NAME:
165 case SD_DHCP6_OPTION_ANI_AP_BSSID:
166 case SD_DHCP6_OPTION_ANI_OPERATOR_ID:
167 case SD_DHCP6_OPTION_ANI_OPERATOR_REALM:
168 return false;
169 case SD_DHCP6_OPTION_S46_PRIORITY:
170 return true;
171 case SD_DHCP6_OPTION_MUD_URL_V6:
172 return false;
173 case SD_DHCP6_OPTION_V6_PREFIX64:
174 return true;
175 case SD_DHCP6_OPTION_F_BINDING_STATUS:
176 case SD_DHCP6_OPTION_F_CONNECT_FLAGS:
177 case SD_DHCP6_OPTION_F_DNS_REMOVAL_INFO:
178 case SD_DHCP6_OPTION_F_DNS_HOST_NAME:
179 case SD_DHCP6_OPTION_F_DNS_ZONE_NAME:
180 case SD_DHCP6_OPTION_F_DNS_FLAGS:
181 case SD_DHCP6_OPTION_F_EXPIRATION_TIME:
182 case SD_DHCP6_OPTION_F_MAX_UNACKED_BNDUPD:
183 case SD_DHCP6_OPTION_F_MCLT:
184 case SD_DHCP6_OPTION_F_PARTNER_LIFETIME:
185 case SD_DHCP6_OPTION_F_PARTNER_LIFETIME_SENT:
186 case SD_DHCP6_OPTION_F_PARTNER_DOWN_TIME:
187 case SD_DHCP6_OPTION_F_PARTNER_RAW_CLT_TIME:
188 case SD_DHCP6_OPTION_F_PROTOCOL_VERSION:
189 case SD_DHCP6_OPTION_F_KEEPALIVE_TIME:
190 case SD_DHCP6_OPTION_F_RECONFIGURE_DATA:
191 case SD_DHCP6_OPTION_F_RELATIONSHIP_NAME:
192 case SD_DHCP6_OPTION_F_SERVER_FLAGS:
193 case SD_DHCP6_OPTION_F_SERVER_STATE:
194 case SD_DHCP6_OPTION_F_START_TIME_OF_STATE:
195 case SD_DHCP6_OPTION_F_STATE_EXPIRATION_TIME:
196 case SD_DHCP6_OPTION_RELAY_PORT:
197 return false;
198 case SD_DHCP6_OPTION_V6_SZTP_REDIRECT:
199 case SD_DHCP6_OPTION_S46_BIND_IPV6_PREFIX:
200 return true;
201 case SD_DHCP6_OPTION_IA_LL:
202 case SD_DHCP6_OPTION_LLADDR:
203 case SD_DHCP6_OPTION_SLAP_QUAD:
204 return false;
205 case SD_DHCP6_OPTION_V6_DOTS_RI:
206 case SD_DHCP6_OPTION_V6_DOTS_ADDRESS:
207 case SD_DHCP6_OPTION_IPV6_ADDRESS_ANDSF:
208 return true;
209 default:
210 return false;
211 }
212}
213
cf6c33bd
YW
214static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode, size_t optlen) {
215 DHCP6Option *option;
4903a73c 216
f12ed3bf
PF
217 assert_return(buf, -EINVAL);
218 assert_return(*buf, -EINVAL);
219 assert_return(buflen, -EINVAL);
220
cf6c33bd
YW
221 option = (DHCP6Option*) *buf;
222
20b55f85 223 if (optlen > 0xffff || *buflen < optlen + offsetof(DHCP6Option, data))
f12ed3bf
PF
224 return -ENOBUFS;
225
c962cb68
TG
226 option->code = htobe16(optcode);
227 option->len = htobe16(optlen);
f12ed3bf 228
20b55f85
LP
229 *buf += offsetof(DHCP6Option, data);
230 *buflen -= offsetof(DHCP6Option, data);
f12ed3bf
PF
231
232 return 0;
233}
234
235int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
236 size_t optlen, const void *optval) {
237 int r;
238
ed6ee219 239 assert_return(optval || optlen == 0, -EINVAL);
f12ed3bf
PF
240
241 r = option_append_hdr(buf, buflen, code, optlen);
242 if (r < 0)
243 return r;
244
75f32f04 245 memcpy_safe(*buf, optval, optlen);
f12ed3bf
PF
246
247 *buf += optlen;
248 *buflen -= optlen;
249
250 return 0;
251}
252
99ccb8ff
SS
253int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options) {
254 sd_dhcp6_option *options;
99ccb8ff
SS
255 int r;
256
257 assert(buf);
258 assert(*buf);
259 assert(buflen);
260 assert(vendor_options);
261
90e74a66 262 ORDERED_HASHMAP_FOREACH(options, vendor_options) {
99ccb8ff
SS
263 _cleanup_free_ uint8_t *p = NULL;
264 size_t total;
265
266 total = 4 + 2 + 2 + options->length;
267
268 p = malloc(total);
269 if (!p)
270 return -ENOMEM;
271
272 unaligned_write_be32(p, options->enterprise_identifier);
273 unaligned_write_be16(p + 4, options->option);
274 unaligned_write_be16(p + 6, options->length);
275 memcpy(p + 8, options->data, options->length);
276
277 r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_OPTS, total, p);
278 if (r < 0)
279 return r;
280 }
281
282 return 0;
283}
284
e0a18b74 285int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) {
e7613578 286 size_t ia_buflen, ia_addrlen = 0;
4b0f2717 287 struct ia_header header;
f12ed3bf 288 DHCP6Address *addr;
e7613578
YW
289 uint8_t *ia_hdr;
290 uint16_t len;
f12ed3bf
PF
291 int r;
292
3c290c03
LP
293 assert_return(buf, -EINVAL);
294 assert_return(*buf, -EINVAL);
295 assert_return(buflen, -EINVAL);
296 assert_return(ia, -EINVAL);
f12ed3bf 297
e7613578
YW
298 /* client should not send set T1 and T2. See, RFC 8415, and issue #18090. */
299
f12ed3bf 300 switch (ia->type) {
2c1ab8ca 301 case SD_DHCP6_OPTION_IA_NA:
4b0f2717
YW
302 len = sizeof(struct ia_header);
303 header = (struct ia_header) {
304 .id = ia->header.id,
e7613578 305 };
f12ed3bf
PF
306 break;
307
2c1ab8ca 308 case SD_DHCP6_OPTION_IA_TA:
4b0f2717
YW
309 len = sizeof(be32_t); /* IA_TA does not have lifetime. */
310 header = (struct ia_header) {
311 .id = ia->header.id,
e7613578 312 };
f12ed3bf
PF
313 break;
314
315 default:
6fcf356b 316 assert_not_reached();
f12ed3bf
PF
317 }
318
4dac5eab 319 if (*buflen < offsetof(DHCP6Option, data) + len)
f12ed3bf
PF
320 return -ENOBUFS;
321
322 ia_hdr = *buf;
323 ia_buflen = *buflen;
324
20b55f85
LP
325 *buf += offsetof(DHCP6Option, data);
326 *buflen -= offsetof(DHCP6Option, data);
f12ed3bf 327
4b0f2717 328 memcpy(*buf, &header, len);
f12ed3bf
PF
329
330 *buf += len;
331 *buflen -= len;
332
333 LIST_FOREACH(addresses, addr, ia->addresses) {
e7613578
YW
334 struct iaaddr a = {
335 .address = addr->iaaddr.address,
336 };
337
338 r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR, sizeof(struct iaaddr));
f12ed3bf
PF
339 if (r < 0)
340 return r;
341
e7613578 342 memcpy(*buf, &a, sizeof(struct iaaddr));
f12ed3bf 343
e7613578
YW
344 *buf += sizeof(struct iaaddr);
345 *buflen -= sizeof(struct iaaddr);
f12ed3bf 346
e7613578 347 ia_addrlen += offsetof(DHCP6Option, data) + sizeof(struct iaaddr);
f12ed3bf
PF
348 }
349
e7613578 350 return option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
f12ed3bf
PF
351}
352
73b49d43
YW
353static int option_append_pd_prefix(uint8_t **buf, size_t *buflen, const DHCP6Address *prefix) {
354 struct iapdprefix p;
355 int r;
356
357 assert(buf);
358 assert(*buf);
359 assert(buflen);
360 assert(prefix);
361
362 if (prefix->iapdprefix.prefixlen == 0)
363 return -EINVAL;
364
365 /* Do not append T1 and T2. */
366
367 p = (struct iapdprefix) {
368 .prefixlen = prefix->iapdprefix.prefixlen,
369 .address = prefix->iapdprefix.address,
370 };
371
372 r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IA_PD_PREFIX, sizeof(struct iapdprefix));
373 if (r < 0)
374 return r;
375
376 memcpy(*buf, &p, sizeof(struct iapdprefix));
377
378 *buf += sizeof(struct iapdprefix);
379 *buflen -= sizeof(struct iapdprefix);
380
381 return offsetof(DHCP6Option, data) + sizeof(struct iapdprefix);
382}
383
877bfc78 384int dhcp6_option_append_pd(uint8_t **buf, size_t *buflen, const DHCP6IA *pd) {
4b0f2717 385 struct ia_header header;
73b49d43
YW
386 size_t len, pd_buflen;
387 uint8_t *pd_hdr;
388 int r;
389
390 assert_return(buf, -EINVAL);
391 assert_return(*buf, -EINVAL);
392 assert_return(buflen, -EINVAL);
393 assert_return(pd, -EINVAL);
394 assert_return(pd->type == SD_DHCP6_OPTION_IA_PD, -EINVAL);
395
396 /* Do not set T1 and T2. */
4b0f2717
YW
397 len = sizeof(struct ia_header);
398 header = (struct ia_header) {
399 .id = pd->header.id,
73b49d43 400 };
73b49d43
YW
401
402 if (*buflen < offsetof(DHCP6Option, data) + len)
403 return -ENOBUFS;
404
405 pd_hdr = *buf;
406 pd_buflen = *buflen;
407
408 /* The header will be written at the end of this function. */
409 *buf += offsetof(DHCP6Option, data);
410 *buflen -= offsetof(DHCP6Option, data);
411
4b0f2717
YW
412 memcpy(*buf, &header, len);
413 *buf += len;
414 *buflen -= len;
73b49d43
YW
415
416 DHCP6Address *prefix;
417 LIST_FOREACH(addresses, prefix, pd->addresses) {
418 r = option_append_pd_prefix(buf, buflen, prefix);
419 if (r < 0)
73b49d43
YW
420 return r;
421
422 len += r;
423 }
424
425 return option_append_hdr(&pd_hdr, &pd_buflen, pd->type, len);
426}
427
8006aa32 428int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
5e55cde9 429 uint8_t buffer[1 + DNS_WIRE_FORMAT_HOSTNAME_MAX];
8006aa32
SA
430 int r;
431
432 assert_return(buf && *buf && buflen && fqdn, -EINVAL);
433
434 buffer[0] = DHCP6_FQDN_FLAG_S; /* Request server to perform AAAA RR DNS updates */
435
436 /* Store domain name after flags field */
437 r = dns_name_to_wire_format(fqdn, buffer + 1, sizeof(buffer) - 1, false);
438 if (r <= 0)
439 return r;
440
441 /*
442 * According to RFC 4704, chapter 4.2 only add terminating zero-length
443 * label in case a FQDN is provided. Since dns_name_to_wire_format
444 * always adds terminating zero-length label remove if only a hostname
445 * is provided.
446 */
447 if (dns_name_is_single_label(fqdn))
448 r--;
449
f5e3619b 450 r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_CLIENT_FQDN, 1 + r, buffer);
8006aa32
SA
451
452 return r;
453}
454
5a99444e 455int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char * const *user_class) {
33923925
SS
456 _cleanup_free_ uint8_t *p = NULL;
457 size_t total = 0, offset = 0;
5a99444e 458 char * const *s;
33923925 459
5a99444e
YW
460 assert(buf);
461 assert(*buf);
462 assert(buflen);
463 assert(!strv_isempty(user_class));
33923925
SS
464
465 STRV_FOREACH(s, user_class) {
466 size_t len = strlen(*s);
467 uint8_t *q;
468
5a99444e
YW
469 if (len > 0xffff || len == 0)
470 return -EINVAL;
33923925
SS
471 q = realloc(p, total + len + 2);
472 if (!q)
473 return -ENOMEM;
474
475 p = q;
476
477 unaligned_write_be16(&p[offset], len);
478 memcpy(&p[offset + 2], *s, len);
479
480 offset += 2 + len;
481 total += 2 + len;
482 }
483
484 return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_USER_CLASS, total, p);
485}
486
019951ec 487int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char * const *vendor_class) {
73c8ced7
SS
488 _cleanup_free_ uint8_t *p = NULL;
489 uint32_t enterprise_identifier;
490 size_t total, offset;
019951ec 491 char * const *s;
73c8ced7
SS
492
493 assert(buf);
494 assert(*buf);
495 assert(buflen);
019951ec 496 assert(!strv_isempty(vendor_class));
73c8ced7
SS
497
498 enterprise_identifier = htobe32(SYSTEMD_PEN);
499
500 p = memdup(&enterprise_identifier, sizeof(enterprise_identifier));
501 if (!p)
502 return -ENOMEM;
503
504 total = sizeof(enterprise_identifier);
505 offset = total;
506
507 STRV_FOREACH(s, vendor_class) {
508 size_t len = strlen(*s);
509 uint8_t *q;
510
019951ec
YW
511 if (len > UINT16_MAX || len == 0)
512 return -EINVAL;
513
73c8ced7
SS
514 q = realloc(p, total + len + 2);
515 if (!q)
516 return -ENOMEM;
517
518 p = q;
519
520 unaligned_write_be16(&p[offset], len);
521 memcpy(&p[offset + 2], *s, len);
522
523 offset += 2 + len;
524 total += 2 + len;
525 }
526
527 return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_CLASS, total, p);
528}
529
b89a3758
YW
530int dhcp6_option_parse(
531 const uint8_t *buf,
532 size_t buflen,
533 size_t *offset,
534 uint16_t *ret_option_code,
535 size_t *ret_option_data_len,
536 const uint8_t **ret_option_data) {
c6affce8 537
b89a3758
YW
538 const DHCP6Option *option;
539 size_t len;
c6affce8 540
b89a3758
YW
541 assert(buf);
542 assert(offset);
543 assert(ret_option_code);
544 assert(ret_option_data_len);
545 assert(ret_option_data);
c6affce8 546
b89a3758
YW
547 if (buflen < offsetof(DHCP6Option, data))
548 return -EBADMSG;
f12ed3bf 549
b89a3758
YW
550 if (*offset >= buflen - offsetof(DHCP6Option, data))
551 return -EBADMSG;
f12ed3bf 552
b89a3758
YW
553 option = (const DHCP6Option*) (buf + *offset);
554 len = be16toh(option->len);
f12ed3bf 555
b89a3758
YW
556 if (len > buflen - offsetof(DHCP6Option, data) - *offset)
557 return -EBADMSG;
f12ed3bf 558
b89a3758
YW
559 *offset += offsetof(DHCP6Option, data) + len;
560 *ret_option_code = be16toh(option->code);
561 *ret_option_data_len = len;
562 *ret_option_data = option->data;
f12ed3bf
PF
563
564 return 0;
565}
c6affce8 566
4af39cb8
YW
567int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message) {
568 assert(data);
c6b4f32a 569
4af39cb8
YW
570 if (data_len < sizeof(uint16_t))
571 return -EBADMSG;
572
573 if (ret_status_message) {
574 char *msg;
c6b4f32a 575
4af39cb8
YW
576 /* The status message MUST NOT be null-terminated. See section 21.13 of RFC8415.
577 * Let's escape unsafe characters for safety. */
578 msg = cescape_length((const char*) (data + sizeof(uint16_t)), data_len - sizeof(uint16_t));
579 if (!msg)
580 return -ENOMEM;
581
582 *ret_status_message = msg;
583 }
584
585 return unaligned_read_be16(data);
586}
587
588static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t *buf, size_t buflen) {
589 int r;
590
4b0f2717 591 assert(buf || buflen == 0);
4af39cb8
YW
592
593 for(size_t offset = 0; offset < buflen;) {
594 const uint8_t *data;
595 size_t data_len;
596 uint16_t code;
597
598 r = dhcp6_option_parse(buf, buflen, &offset, &code, &data_len, &data);
599 if (r < 0)
600 return r;
601
602 switch(code) {
603 case SD_DHCP6_OPTION_STATUS_CODE: {
604 _cleanup_free_ char *msg = NULL;
605
606 r = dhcp6_option_parse_status(data, data_len, &msg);
607 if (r == -ENOMEM)
608 return r;
4b0f2717 609 if (r > 0)
4af39cb8
YW
610 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
611 "Received an IA address or PD prefix option with non-zero status: %s%s%s",
612 strempty(msg), isempty(msg) ? "" : ": ",
613 dhcp6_message_status_to_string(r));
4b0f2717
YW
614 if (r < 0)
615 /* Let's log but ignore the invalid status option. */
616 log_dhcp6_client_errno(client, r,
617 "Received an IA address or PD prefix option with an invalid status sub option, ignoring: %m");
4af39cb8
YW
618 break;
619 }
620 default:
621 log_dhcp6_client(client, "Received an unknown sub option %u in IA address or PD prefix, ignoring.", code);
622 }
623 }
624
625 return 0;
c6b4f32a
PF
626}
627
4b0f2717
YW
628static int dhcp6_option_parse_ia_address(sd_dhcp6_client *client, DHCP6IA *ia, const uint8_t *data, size_t len) {
629 _cleanup_free_ DHCP6Address *a = NULL;
0dfe2a4b
PF
630 uint32_t lt_valid, lt_pref;
631 int r;
632
4b0f2717
YW
633 assert(ia);
634 assert(data || len == 0);
635
636 if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA))
637 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
638 "Received an IA address sub-option in an invalid option, ignoring.");
0dfe2a4b 639
8a895550
YW
640 if (len < sizeof(struct iaaddr))
641 return -EBADMSG;
0dfe2a4b 642
4b0f2717
YW
643 a = new(DHCP6Address, 1);
644 if (!a)
645 return -ENOMEM;
646
647 memcpy(&a->iaaddr, data, sizeof(struct iaaddr));
648
649 lt_valid = be32toh(a->iaaddr.lifetime_valid);
650 lt_pref = be32toh(a->iaaddr.lifetime_preferred);
8a895550
YW
651
652 if (lt_valid == 0)
653 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
654 "Received an IA address with zero valid lifetime, ignoring.");
655 if (lt_pref > lt_valid)
35388783 656 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
8a895550
YW
657 "Received an IA address with preferred lifetime %"PRIu32
658 " larger than valid lifetime %"PRIu32", ignoring.",
35388783 659 lt_pref, lt_valid);
0dfe2a4b 660
8a895550
YW
661 if (len > sizeof(struct iaaddr)) {
662 r = dhcp6_option_parse_ia_options(client, data + sizeof(struct iaaddr), len - sizeof(struct iaaddr));
1e84213a
YW
663 if (r < 0)
664 return r;
0dfe2a4b
PF
665 }
666
4b0f2717 667 LIST_PREPEND(addresses, ia->addresses, TAKE_PTR(a));
0dfe2a4b
PF
668 return 0;
669}
670
4b0f2717
YW
671static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, DHCP6IA *ia, const uint8_t *data, size_t len) {
672 _cleanup_free_ DHCP6Address *a = NULL;
f8ad4dd4
PF
673 uint32_t lt_valid, lt_pref;
674 int r;
675
4b0f2717
YW
676 assert(ia);
677 assert(data || len == 0);
678
679 if (ia->type != SD_DHCP6_OPTION_IA_PD)
680 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
681 "Received an PD prefix sub-option in an invalid option, ignoring");
682
8a895550 683 if (len < sizeof(struct iapdprefix))
4b0f2717
YW
684 return -EBADMSG;
685
686 a = new(DHCP6Address, 1);
687 if (!a)
688 return -ENOMEM;
f8ad4dd4 689
4b0f2717
YW
690 memcpy(&a->iapdprefix, data, sizeof(struct iapdprefix));
691
692 lt_valid = be32toh(a->iapdprefix.lifetime_valid);
693 lt_pref = be32toh(a->iapdprefix.lifetime_preferred);
f8ad4dd4 694
8a895550 695 if (lt_valid == 0)
35388783 696 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
8a895550
YW
697 "Received a PD prefix with zero valid lifetime, ignoring.");
698 if (lt_pref > lt_valid)
699 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
700 "Received a PD prefix with preferred lifetime %"PRIu32
701 " larger than valid lifetime %"PRIu32", ignoring.",
35388783 702 lt_pref, lt_valid);
f8ad4dd4 703
8a895550
YW
704 if (len > sizeof(struct iapdprefix)) {
705 r = dhcp6_option_parse_ia_options(client, data + sizeof(struct iapdprefix), len - sizeof(struct iapdprefix));
1e84213a
YW
706 if (r < 0)
707 return r;
f8ad4dd4
PF
708 }
709
4b0f2717 710 LIST_PREPEND(addresses, ia->addresses, TAKE_PTR(a));
f8ad4dd4
PF
711 return 0;
712}
713
469fd57f
YW
714int dhcp6_option_parse_ia(
715 sd_dhcp6_client *client,
469fd57f 716 be32_t iaid,
8a895550
YW
717 uint16_t option_code,
718 size_t option_data_len,
719 const uint8_t *option_data,
e5b0b87f 720 DHCP6IA **ret) {
8a895550 721
e5b0b87f 722 _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL;
8a895550 723 uint32_t lt_t1, lt_t2, lt_min = UINT32_MAX;
4b0f2717 724 size_t header_len;
4af39cb8 725 int r;
c6affce8 726
8a895550 727 assert(IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA, SD_DHCP6_OPTION_IA_PD));
4b0f2717 728 assert(option_data || option_data_len == 0);
8a895550 729 assert(ret);
c6affce8 730
8a895550
YW
731 /* This will return the following:
732 * -ENOMEM: memory allocation error,
733 * -ENOANO: unmatching IAID,
734 * -EINVAL: non-zero status code, or invalid lifetime,
735 * -EBADMSG: invalid message format,
736 * -ENODATA: no valid address or PD prefix,
737 * 0: success. */
3bc424a3 738
8a895550 739 switch (option_code) {
2c1ab8ca 740 case SD_DHCP6_OPTION_IA_NA:
f8ad4dd4 741 case SD_DHCP6_OPTION_IA_PD:
4b0f2717 742 header_len = sizeof(struct ia_header);
c6affce8
PF
743 break;
744
2c1ab8ca 745 case SD_DHCP6_OPTION_IA_TA:
4b0f2717 746 header_len = sizeof(be32_t); /* IA_TA does not have lifetime. */
c6affce8
PF
747 break;
748
749 default:
8a895550 750 assert_not_reached();
c6affce8
PF
751 }
752
4b0f2717
YW
753 if (option_data_len < header_len)
754 return -EBADMSG;
755
e5b0b87f
YW
756 ia = new(DHCP6IA, 1);
757 if (!ia)
758 return -ENOMEM;
759
760 *ia = (DHCP6IA) {
761 .type = option_code,
762 };
763 memcpy(&ia->header, option_data, header_len);
4b0f2717 764
8a895550
YW
765 /* According to RFC8415, IAs which do not match the client's IAID should be ignored,
766 * but not necessary to ignore or refuse the whole message. */
e5b0b87f 767 if (ia->header.id != iaid)
8a895550
YW
768 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENOANO),
769 "Received an IA option with a different IAID "
770 "from the one chosen by the client, ignoring.");
3bc424a3 771
4b0f2717 772 /* It is not necessary to check if the lifetime_t2 is zero here, as in that case it will be updated later. */
e5b0b87f
YW
773 lt_t1 = be32toh(ia->header.lifetime_t1);
774 lt_t2 = be32toh(ia->header.lifetime_t2);
4b0f2717 775
8a895550
YW
776 if (lt_t1 > lt_t2)
777 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
778 "Received an IA option with T1 %"PRIu32"sec > T2 %"PRIu32"sec, ignoring.",
779 lt_t1, lt_t2);
4b0f2717
YW
780 if (lt_t1 == 0 && lt_t2 > 0)
781 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
782 "Received an IA option with zero T1 and non-zero T2 (%"PRIu32"sec), ignoring.",
783 lt_t2);
c6affce8 784
4b0f2717 785 for (size_t offset = header_len; offset < option_data_len;) {
8a895550
YW
786 const uint8_t *subdata;
787 size_t subdata_len;
788 uint16_t subopt;
c6affce8 789
8a895550
YW
790 r = dhcp6_option_parse(option_data, option_data_len, &offset, &subopt, &subdata_len, &subdata);
791 if (r < 0)
792 return r;
c6affce8 793
8a895550
YW
794 switch (subopt) {
795 case SD_DHCP6_OPTION_IAADDR: {
e5b0b87f 796 r = dhcp6_option_parse_ia_address(client, ia, subdata, subdata_len);
8a895550 797 if (r == -ENOMEM)
aae1fa5c 798 return r;
8a895550 799 if (r < 0)
4b0f2717 800 /* Ignore non-critical errors in the sub-option. */
8a895550 801 continue;
c6affce8 802
e5b0b87f 803 lt_min = MIN(lt_min, be32toh(ia->addresses->iaaddr.lifetime_valid));
c6affce8 804 break;
8a895550
YW
805 }
806 case SD_DHCP6_OPTION_IA_PD_PREFIX: {
e5b0b87f 807 r = dhcp6_option_parse_ia_pdprefix(client, ia, subdata, subdata_len);
8a895550 808 if (r == -ENOMEM)
aae1fa5c 809 return r;
8a895550 810 if (r < 0)
4b0f2717 811 /* Ignore non-critical errors in the sub-option. */
8a895550 812 continue;
f8ad4dd4 813
e5b0b87f 814 lt_min = MIN(lt_min, be32toh(ia->addresses->iapdprefix.lifetime_valid));
f8ad4dd4 815 break;
8a895550 816 }
4af39cb8
YW
817 case SD_DHCP6_OPTION_STATUS_CODE: {
818 _cleanup_free_ char *msg = NULL;
5c95a913 819
8a895550
YW
820 r = dhcp6_option_parse_status(subdata, subdata_len, &msg);
821 if (r == -ENOMEM)
4af39cb8 822 return r;
4b0f2717 823 if (r > 0)
8a895550
YW
824 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
825 "Received an IA option with non-zero status: %s%s%s",
826 strempty(msg), isempty(msg) ? "" : ": ",
827 dhcp6_message_status_to_string(r));
4b0f2717
YW
828 if (r < 0)
829 log_dhcp6_client_errno(client, r,
830 "Received an IA option with an invalid status sub option, ignoring: %m");
c6affce8 831 break;
4af39cb8 832 }
c6affce8 833 default:
8a895550 834 log_dhcp6_client(client, "Received an IA option with an unknown sub-option %u, ignoring", subopt);
c6affce8 835 }
8a895550 836 }
c6affce8 837
e5b0b87f 838 if (!ia->addresses)
8a895550
YW
839 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(ENODATA),
840 "Received an IA option without valid IA addresses or PD prefixes, ignoring.");
841
4b0f2717
YW
842 if (lt_t2 == 0 && IN_SET(option_code, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_PD)) {
843
844 /* addresses or prefixes with zero valid lifetime are already ignored. */
845 assert(lt_min != UINT32_MAX);
846
8a895550
YW
847 lt_t1 = lt_min / 2;
848 lt_t2 = lt_min / 10 * 8;
849
e5b0b87f
YW
850 ia->header.lifetime_t1 = htobe32(lt_t1);
851 ia->header.lifetime_t1 = htobe32(lt_t2);
4b0f2717 852
8a895550
YW
853 log_dhcp6_client(client, "Received an IA option with both T1 and T2 equal to zero. "
854 "Adjusting them based on the minimum valid lifetime of IA addresses or PD prefixes: "
855 "T1=%"PRIu32"sec, T2=%"PRIu32"sec", lt_t1, lt_t2);
c6affce8
PF
856 }
857
e5b0b87f 858 *ret = TAKE_PTR(ia);
8a895550 859 return 0;
c6affce8 860}
b553817c 861
ad3c8420
YW
862int dhcp6_option_parse_addresses(
863 const uint8_t *optval,
864 size_t optlen,
865 struct in6_addr **addrs,
866 size_t *count) {
867
868 assert(optval);
869 assert(addrs);
870 assert(count);
b553817c
PF
871
872 if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0)
ad3c8420 873 return -EBADMSG;
b553817c 874
ad3c8420 875 if (!GREEDY_REALLOC(*addrs, *count + optlen / sizeof(struct in6_addr)))
b553817c
PF
876 return -ENOMEM;
877
ad3c8420
YW
878 memcpy(*addrs + *count, optval, optlen);
879 *count += optlen / sizeof(struct in6_addr);
b553817c 880
ad3c8420 881 return 0;
b553817c 882}
f96ccab7 883
b27dcf08
YW
884static int parse_domain(const uint8_t **data, size_t *len, char **ret) {
885 _cleanup_free_ char *domain = NULL;
886 const uint8_t *optval;
887 size_t optlen, n = 0;
f96ccab7
PF
888 int r;
889
b27dcf08
YW
890 assert(data);
891 assert(*data);
892 assert(len);
893 assert(ret);
894
895 optval = *data;
896 optlen = *len;
897
af710b53
BG
898 if (optlen <= 1)
899 return -ENODATA;
f96ccab7 900
c43eea9f
BG
901 for (;;) {
902 const char *label;
903 uint8_t c;
f96ccab7 904
c43eea9f
BG
905 if (optlen == 0)
906 break;
f96ccab7 907
c43eea9f
BG
908 c = *optval;
909 optval++;
910 optlen--;
3c72b6ed 911
c43eea9f
BG
912 if (c == 0)
913 /* End label */
914 break;
915 if (c > 63)
916 return -EBADMSG;
917 if (c > optlen)
918 return -EMSGSIZE;
3c72b6ed 919
c43eea9f 920 /* Literal label */
b27dcf08 921 label = (const char*) optval;
c43eea9f
BG
922 optval += c;
923 optlen -= c;
3c72b6ed 924
b27dcf08 925 if (!GREEDY_REALLOC(domain, n + (n != 0) + DNS_LABEL_ESCAPED_MAX))
c43eea9f 926 return -ENOMEM;
3c72b6ed 927
b27dcf08
YW
928 if (n != 0)
929 domain[n++] = '.';
f96ccab7 930
b27dcf08 931 r = dns_label_escape(label, c, domain + n, DNS_LABEL_ESCAPED_MAX);
c43eea9f
BG
932 if (r < 0)
933 return r;
f96ccab7 934
c43eea9f
BG
935 n += r;
936 }
3c72b6ed 937
b27dcf08
YW
938 if (n > 0) {
939 if (!GREEDY_REALLOC(domain, n + 1))
3c72b6ed 940 return -ENOMEM;
b27dcf08
YW
941
942 domain[n] = '\0';
c43eea9f
BG
943 }
944
b27dcf08 945 *ret = TAKE_PTR(domain);
c43eea9f
BG
946 *data = optval;
947 *len = optlen;
948
949 return n;
950}
951
b27dcf08 952int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **ret) {
c43eea9f
BG
953 _cleanup_free_ char *domain = NULL;
954 int r;
955
b27dcf08
YW
956 assert(optval);
957 assert(ret);
958
c43eea9f
BG
959 r = parse_domain(&optval, &optlen, &domain);
960 if (r < 0)
961 return r;
962 if (r == 0)
963 return -ENODATA;
964 if (optlen != 0)
965 return -EINVAL;
966
b27dcf08 967 *ret = TAKE_PTR(domain);
c43eea9f
BG
968 return 0;
969}
970
b27dcf08 971int dhcp6_option_parse_domainname_list(const uint8_t *optval, size_t optlen, char ***ret) {
c43eea9f 972 _cleanup_strv_free_ char **names = NULL;
edeee50b 973 int r;
b27dcf08
YW
974
975 assert(optval);
976 assert(ret);
c43eea9f
BG
977
978 if (optlen <= 1)
979 return -ENODATA;
980 if (optval[optlen - 1] != '\0')
981 return -EINVAL;
982
983 while (optlen > 0) {
b27dcf08 984 _cleanup_free_ char *name = NULL;
c43eea9f 985
b27dcf08 986 r = parse_domain(&optval, &optlen, &name);
c43eea9f
BG
987 if (r < 0)
988 return r;
989 if (r == 0)
990 continue;
f96ccab7 991
b27dcf08 992 r = strv_consume(&names, TAKE_PTR(name));
f96ccab7 993 if (r < 0)
3c72b6ed 994 return r;
f96ccab7
PF
995 }
996
b27dcf08 997 *ret = TAKE_PTR(names);
edeee50b 998 return 0;
f96ccab7 999}
e7d5fe17
AD
1000
1001static sd_dhcp6_option* dhcp6_option_free(sd_dhcp6_option *i) {
1002 if (!i)
1003 return NULL;
1004
1005 free(i->data);
1006 return mfree(i);
1007}
1008
99ccb8ff 1009int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret) {
e7d5fe17
AD
1010 assert_return(ret, -EINVAL);
1011 assert_return(length == 0 || data, -EINVAL);
1012
1013 _cleanup_free_ void *q = memdup(data, length);
1014 if (!q)
1015 return -ENOMEM;
1016
1017 sd_dhcp6_option *p = new(sd_dhcp6_option, 1);
1018 if (!p)
1019 return -ENOMEM;
1020
1021 *p = (sd_dhcp6_option) {
1022 .n_ref = 1,
1023 .option = option,
99ccb8ff 1024 .enterprise_identifier = enterprise_identifier,
e7d5fe17
AD
1025 .length = length,
1026 .data = TAKE_PTR(q),
1027 };
1028
1029 *ret = p;
1030 return 0;
1031}
1032
1033DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option, sd_dhcp6_option, dhcp6_option_free);
1034DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
1035 dhcp6_option_hash_ops,
1036 void,
1037 trivial_hash_func,
1038 trivial_compare_func,
1039 sd_dhcp6_option,
1040 sd_dhcp6_option_unref);