]> git.ipfire.org Git - thirdparty/systemd.git/blob - 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
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
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
21 #include <errno.h>
22 #include <netinet/in.h>
23 #include <string.h>
24
25 #include "sd-dhcp6-client.h"
26
27 #include "alloc-util.h"
28 #include "dhcp6-internal.h"
29 #include "dhcp6-protocol.h"
30 #include "dns-domain.h"
31 #include "sparse-endian.h"
32 #include "strv.h"
33 #include "unaligned.h"
34 #include "util.h"
35
36 #define DHCP6_OPTION_IA_NA_LEN 12
37 #define DHCP6_OPTION_IA_TA_LEN 4
38
39 typedef struct DHCP6Option {
40 be16_t code;
41 be16_t len;
42 uint8_t data[];
43 } _packed_ DHCP6Option;
44
45 static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
46 size_t optlen) {
47 DHCP6Option *option = (DHCP6Option*) *buf;
48
49 assert_return(buf, -EINVAL);
50 assert_return(*buf, -EINVAL);
51 assert_return(buflen, -EINVAL);
52
53 if (optlen > 0xffff || *buflen < optlen + sizeof(DHCP6Option))
54 return -ENOBUFS;
55
56 option->code = htobe16(optcode);
57 option->len = htobe16(optlen);
58
59 *buf += sizeof(DHCP6Option);
60 *buflen -= sizeof(DHCP6Option);
61
62 return 0;
63 }
64
65 int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
66 size_t optlen, const void *optval) {
67 int r;
68
69 assert_return(optval || optlen == 0, -EINVAL);
70
71 r = option_append_hdr(buf, buflen, code, optlen);
72 if (r < 0)
73 return r;
74
75 memcpy_safe(*buf, optval, optlen);
76
77 *buf += optlen;
78 *buflen -= optlen;
79
80 return 0;
81 }
82
83 int 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) {
93 case SD_DHCP6_OPTION_IA_NA:
94 len = DHCP6_OPTION_IA_NA_LEN;
95 break;
96
97 case SD_DHCP6_OPTION_IA_TA:
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
111 *buf += sizeof(DHCP6Option);
112 *buflen -= sizeof(DHCP6Option);
113
114 memcpy(*buf, &ia->id, len);
115
116 *buf += len;
117 *buflen -= len;
118
119 LIST_FOREACH(addresses, addr, ia->addresses) {
120 r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR,
121 sizeof(addr->iaaddr));
122 if (r < 0)
123 return r;
124
125 memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr));
126
127 *buf += sizeof(addr->iaaddr);
128 *buflen -= sizeof(addr->iaaddr);
129
130 ia_addrlen += sizeof(DHCP6Option) + sizeof(addr->iaaddr);
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
140 int 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
167
168 static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) {
169 DHCP6Option *option = (DHCP6Option*) *buf;
170 uint16_t len;
171
172 assert_return(buf, -EINVAL);
173 assert_return(optcode, -EINVAL);
174 assert_return(optlen, -EINVAL);
175
176 if (*buflen < sizeof(DHCP6Option))
177 return -ENOMSG;
178
179 len = be16toh(option->len);
180
181 if (len > *buflen)
182 return -ENOMSG;
183
184 *optcode = be16toh(option->code);
185 *optlen = len;
186
187 *buf += 4;
188 *buflen -= 4;
189
190 return 0;
191 }
192
193 int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
194 size_t *optlen, uint8_t **optvalue) {
195 int r;
196
197 assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
198
199 r = option_parse_hdr(buf, buflen, optcode, optlen);
200 if (r < 0)
201 return r;
202
203 if (*optlen > *buflen)
204 return -ENOBUFS;
205
206 *optvalue = *buf;
207 *buflen -= *optlen;
208 *buf += *optlen;
209
210 return 0;
211 }
212
213 int 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) {
226 case SD_DHCP6_OPTION_IA_NA:
227
228 if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) +
229 sizeof(addr->iaaddr)) {
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
249 case SD_DHCP6_OPTION_IA_TA:
250 if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) +
251 sizeof(addr->iaaddr)) {
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) {
277 case SD_DHCP6_OPTION_IAADDR:
278
279 addr = new0(DHCP6Address, 1);
280 if (!addr) {
281 r = -ENOMEM;
282 goto error;
283 }
284
285 LIST_INIT(addresses, addr);
286
287 memcpy(&addr->iaaddr, *buf, sizeof(addr->iaaddr));
288
289 lt_valid = be32toh(addr->iaaddr.lifetime_valid);
290 lt_pref = be32toh(addr->iaaddr.lifetime_valid);
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
304 case SD_DHCP6_OPTION_STATUS_CODE:
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
343 error:
344 *buf += *buflen;
345 *buflen = 0;
346
347 return r;
348 }
349
350 int 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 }
367
368 int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char ***str_arr) {
369 size_t pos = 0, idx = 0;
370 _cleanup_strv_free_ char **names = NULL;
371 int r;
372
373 assert_return(optlen > 1, -ENODATA);
374 assert_return(optval[optlen - 1] == '\0', -EINVAL);
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) {
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
398 if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) {
399 r = -ENOMEM;
400 goto fail;
401 }
402
403 if (first)
404 first = false;
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;
411
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
431 idx++;
432 }
433
434 *str_arr = names;
435 names = NULL;
436
437 return idx;
438
439 fail:
440 return r;
441 }