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