Coverage Report

Created: 2025-06-13 06:25

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