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