]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/dhcp6-option.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / libsystemd-network / dhcp6-option.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
f12ed3bf
PF
2/***
3 This file is part of systemd.
4
b553817c 5 Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
f12ed3bf
PF
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
f12ed3bf 21#include <errno.h>
cf0fbc49 22#include <netinet/in.h>
f12ed3bf
PF
23#include <string.h>
24
2c1ab8ca
BG
25#include "sd-dhcp6-client.h"
26
b5efdb8a 27#include "alloc-util.h"
f12ed3bf
PF
28#include "dhcp6-internal.h"
29#include "dhcp6-protocol.h"
f96ccab7 30#include "dns-domain.h"
b5efdb8a
LP
31#include "sparse-endian.h"
32#include "strv.h"
33#include "unaligned.h"
34#include "util.h"
f12ed3bf 35
f12ed3bf
PF
36#define DHCP6_OPTION_IA_NA_LEN 12
37#define DHCP6_OPTION_IA_TA_LEN 4
f12ed3bf 38
4903a73c
TG
39typedef struct DHCP6Option {
40 be16_t code;
41 be16_t len;
42 uint8_t data[];
c962cb68 43} _packed_ DHCP6Option;
4903a73c 44
f12ed3bf
PF
45static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
46 size_t optlen) {
c962cb68 47 DHCP6Option *option = (DHCP6Option*) *buf;
4903a73c 48
f12ed3bf
PF
49 assert_return(buf, -EINVAL);
50 assert_return(*buf, -EINVAL);
51 assert_return(buflen, -EINVAL);
52
4903a73c 53 if (optlen > 0xffff || *buflen < optlen + sizeof(DHCP6Option))
f12ed3bf
PF
54 return -ENOBUFS;
55
c962cb68
TG
56 option->code = htobe16(optcode);
57 option->len = htobe16(optlen);
f12ed3bf 58
4903a73c
TG
59 *buf += sizeof(DHCP6Option);
60 *buflen -= sizeof(DHCP6Option);
f12ed3bf
PF
61
62 return 0;
63}
64
65int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
66 size_t optlen, const void *optval) {
67 int r;
68
ed6ee219 69 assert_return(optval || optlen == 0, -EINVAL);
f12ed3bf
PF
70
71 r = option_append_hdr(buf, buflen, code, optlen);
72 if (r < 0)
73 return r;
74
75f32f04 75 memcpy_safe(*buf, optval, optlen);
f12ed3bf
PF
76
77 *buf += optlen;
78 *buflen -= optlen;
79
80 return 0;
81}
82
83int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
84 uint16_t len;
85 uint8_t *ia_hdr;
86 size_t ia_buflen, ia_addrlen = 0;
87 DHCP6Address *addr;
88 int r;
89
90 assert_return(buf && *buf && buflen && ia, -EINVAL);
91
92 switch (ia->type) {
2c1ab8ca 93 case SD_DHCP6_OPTION_IA_NA:
f12ed3bf
PF
94 len = DHCP6_OPTION_IA_NA_LEN;
95 break;
96
2c1ab8ca 97 case SD_DHCP6_OPTION_IA_TA:
f12ed3bf
PF
98 len = DHCP6_OPTION_IA_TA_LEN;
99 break;
100
101 default:
102 return -EINVAL;
103 }
104
105 if (*buflen < len)
106 return -ENOBUFS;
107
108 ia_hdr = *buf;
109 ia_buflen = *buflen;
110
4903a73c
TG
111 *buf += sizeof(DHCP6Option);
112 *buflen -= sizeof(DHCP6Option);
f12ed3bf
PF
113
114 memcpy(*buf, &ia->id, len);
115
116 *buf += len;
117 *buflen -= len;
118
119 LIST_FOREACH(addresses, addr, ia->addresses) {
2c1ab8ca 120 r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR,
ee3a5027 121 sizeof(addr->iaaddr));
f12ed3bf
PF
122 if (r < 0)
123 return r;
124
ee3a5027 125 memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
f12ed3bf 126
ee3a5027
PF
127 *buf += sizeof(addr->iaaddr);
128 *buflen -= sizeof(addr->iaaddr);
f12ed3bf 129
4903a73c 130 ia_addrlen += sizeof(DHCP6Option) + sizeof(addr->iaaddr);
f12ed3bf
PF
131 }
132
133 r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
134 if (r < 0)
135 return r;
136
137 return 0;
138}
139
8006aa32
SA
140int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
141 uint8_t buffer[1 + DNS_WIRE_FOMAT_HOSTNAME_MAX];
142 int r;
143
144 assert_return(buf && *buf && buflen && fqdn, -EINVAL);
145
146 buffer[0] = DHCP6_FQDN_FLAG_S; /* Request server to perform AAAA RR DNS updates */
147
148 /* Store domain name after flags field */
149 r = dns_name_to_wire_format(fqdn, buffer + 1, sizeof(buffer) - 1, false);
150 if (r <= 0)
151 return r;
152
153 /*
154 * According to RFC 4704, chapter 4.2 only add terminating zero-length
155 * label in case a FQDN is provided. Since dns_name_to_wire_format
156 * always adds terminating zero-length label remove if only a hostname
157 * is provided.
158 */
159 if (dns_name_is_single_label(fqdn))
160 r--;
161
162 r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_FQDN, 1 + r, buffer);
163
164 return r;
165}
166
c6affce8 167
c962cb68
TG
168static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
169 DHCP6Option *option = (DHCP6Option*) *buf;
c6affce8
PF
170 uint16_t len;
171
172 assert_return(buf, -EINVAL);
4903a73c 173 assert_return(optcode, -EINVAL);
c6affce8
PF
174 assert_return(optlen, -EINVAL);
175
4903a73c 176 if (*buflen < sizeof(DHCP6Option))
c6affce8
PF
177 return -ENOMSG;
178
c962cb68 179 len = be16toh(option->len);
c6affce8
PF
180
181 if (len > *buflen)
182 return -ENOMSG;
183
c962cb68 184 *optcode = be16toh(option->code);
c6affce8
PF
185 *optlen = len;
186
187 *buf += 4;
188 *buflen -= 4;
189
190 return 0;
191}
192
f12ed3bf
PF
193int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
194 size_t *optlen, uint8_t **optvalue) {
c6affce8 195 int r;
f12ed3bf 196
c6affce8 197 assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
f12ed3bf 198
c6affce8
PF
199 r = option_parse_hdr(buf, buflen, optcode, optlen);
200 if (r < 0)
201 return r;
f12ed3bf 202
c6affce8 203 if (*optlen > *buflen)
f12ed3bf
PF
204 return -ENOBUFS;
205
c6affce8
PF
206 *optvalue = *buf;
207 *buflen -= *optlen;
208 *buf += *optlen;
f12ed3bf
PF
209
210 return 0;
211}
c6affce8
PF
212
213int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype,
214 DHCP6IA *ia) {
215 int r;
216 uint16_t opt, status;
217 size_t optlen;
218 size_t iaaddr_offset;
219 DHCP6Address *addr;
220 uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0;
221
222 assert_return(ia, -EINVAL);
223 assert_return(!ia->addresses, -EINVAL);
224
225 switch (iatype) {
2c1ab8ca 226 case SD_DHCP6_OPTION_IA_NA:
c6affce8 227
4903a73c 228 if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) +
ee3a5027 229 sizeof(addr->iaaddr)) {
c6affce8
PF
230 r = -ENOBUFS;
231 goto error;
232 }
233
234 iaaddr_offset = DHCP6_OPTION_IA_NA_LEN;
235 memcpy(&ia->id, *buf, iaaddr_offset);
236
237 lt_t1 = be32toh(ia->lifetime_t1);
238 lt_t2 = be32toh(ia->lifetime_t2);
239
240 if (lt_t1 && lt_t2 && lt_t1 > lt_t2) {
241 log_dhcp6_client(client, "IA T1 %ds > T2 %ds",
242 lt_t1, lt_t2);
243 r = -EINVAL;
244 goto error;
245 }
246
247 break;
248
2c1ab8ca 249 case SD_DHCP6_OPTION_IA_TA:
4903a73c 250 if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) +
ee3a5027 251 sizeof(addr->iaaddr)) {
c6affce8
PF
252 r = -ENOBUFS;
253 goto error;
254 }
255
256 iaaddr_offset = DHCP6_OPTION_IA_TA_LEN;
257 memcpy(&ia->id, *buf, iaaddr_offset);
258
259 ia->lifetime_t1 = 0;
260 ia->lifetime_t2 = 0;
261
262 break;
263
264 default:
265 r = -ENOMSG;
266 goto error;
267 }
268
269 ia->type = iatype;
270
271 *buflen -= iaaddr_offset;
272 *buf += iaaddr_offset;
273
274 while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) {
275
276 switch (opt) {
2c1ab8ca 277 case SD_DHCP6_OPTION_IAADDR:
c6affce8
PF
278
279 addr = new0(DHCP6Address, 1);
280 if (!addr) {
281 r = -ENOMEM;
282 goto error;
283 }
284
285 LIST_INIT(addresses, addr);
286
ee3a5027 287 memcpy(&addr->iaaddr, *buf, sizeof(addr->iaaddr));
c6affce8 288
ee3a5027
PF
289 lt_valid = be32toh(addr->iaaddr.lifetime_valid);
290 lt_pref = be32toh(addr->iaaddr.lifetime_valid);
c6affce8
PF
291
292 if (!lt_valid || lt_pref > lt_valid) {
293 log_dhcp6_client(client, "IA preferred %ds > valid %ds",
294 lt_pref, lt_valid);
295 free(addr);
296 } else {
297 LIST_PREPEND(addresses, ia->addresses, addr);
298 if (lt_valid < lt_min)
299 lt_min = lt_valid;
300 }
301
302 break;
303
2c1ab8ca 304 case SD_DHCP6_OPTION_STATUS_CODE:
c6affce8
PF
305 if (optlen < sizeof(status))
306 break;
307
308 status = (*buf)[0] << 8 | (*buf)[1];
309 if (status) {
310 log_dhcp6_client(client, "IA status %d",
311 status);
312 r = -EINVAL;
313 goto error;
314 }
315
316 break;
317
318 default:
319 log_dhcp6_client(client, "Unknown IA option %d", opt);
320 break;
321 }
322
323 *buflen -= optlen;
324 *buf += optlen;
325 }
326
327 if (r == -ENOMSG)
328 r = 0;
329
330 if (!ia->lifetime_t1 && !ia->lifetime_t2) {
331 lt_t1 = lt_min / 2;
332 lt_t2 = lt_min / 10 * 8;
333 ia->lifetime_t1 = htobe32(lt_t1);
334 ia->lifetime_t2 = htobe32(lt_t2);
335
336 log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as both were zero",
337 lt_t1, lt_t2);
338 }
339
340 if (*buflen)
341 r = -ENOMSG;
342
343error:
344 *buf += *buflen;
345 *buflen = 0;
346
347 return r;
348}
b553817c
PF
349
350int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
351 struct in6_addr **addrs, size_t count,
352 size_t *allocated) {
353
354 if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0)
355 return -EINVAL;
356
357 if (!GREEDY_REALLOC(*addrs, *allocated,
358 count * sizeof(struct in6_addr) + optlen))
359 return -ENOMEM;
360
361 memcpy(*addrs + count, optval, optlen);
362
363 count += optlen / sizeof(struct in6_addr);
364
365 return count;
366}
f96ccab7 367
52efd56a 368int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char ***str_arr) {
f96ccab7 369 size_t pos = 0, idx = 0;
419eaa8f 370 _cleanup_strv_free_ char **names = NULL;
f96ccab7
PF
371 int r;
372
373 assert_return(optlen > 1, -ENODATA);
93f660da 374 assert_return(optval[optlen - 1] == '\0', -EINVAL);
f96ccab7
PF
375
376 while (pos < optlen) {
377 _cleanup_free_ char *ret = NULL;
378 size_t n = 0, allocated = 0;
379 bool first = true;
380
381 for (;;) {
382 uint8_t c;
383
384 c = optval[pos++];
385
386 if (c == 0)
387 /* End of name */
388 break;
389 else if (c <= 63) {
f96ccab7
PF
390 const char *label;
391
392 /* Literal label */
393 label = (const char *)&optval[pos];
394 pos += c;
395 if (pos > optlen)
396 return -EMSGSIZE;
397
422baca0 398 if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) {
f96ccab7
PF
399 r = -ENOMEM;
400 goto fail;
401 }
402
422baca0 403 if (first)
f96ccab7 404 first = false;
422baca0
LP
405 else
406 ret[n++] = '.';
407
408 r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
409 if (r < 0)
410 goto fail;
f96ccab7 411
f96ccab7
PF
412 n += r;
413 continue;
414 } else {
415 r = -EBADMSG;
416 goto fail;
417 }
418 }
419
420 if (!GREEDY_REALLOC(ret, allocated, n + 1)) {
421 r = -ENOMEM;
422 goto fail;
423 }
424
425 ret[n] = 0;
426
427 r = strv_extend(&names, ret);
428 if (r < 0)
429 goto fail;
430
f96ccab7
PF
431 idx++;
432 }
433
434 *str_arr = names;
435 names = NULL;
436
437 return idx;
438
439fail:
440 return r;
441}