]>
Commit | Line | Data |
---|---|---|
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 |
39 | typedef struct DHCP6Option { |
40 | be16_t code; | |
41 | be16_t len; | |
42 | uint8_t data[]; | |
c962cb68 | 43 | } _packed_ DHCP6Option; |
4903a73c | 44 | |
f12ed3bf PF |
45 | static 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 | ||
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 | ||
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 | ||
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) { | |
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 |
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 | ||
c6affce8 | 167 | |
c962cb68 TG |
168 | static 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 |
193 | int 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 | |
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) { | |
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 | ||
343 | error: | |
344 | *buf += *buflen; | |
345 | *buflen = 0; | |
346 | ||
347 | return r; | |
348 | } | |
b553817c PF |
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 | } | |
f96ccab7 | 367 | |
52efd56a | 368 | int 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 | ||
439 | fail: | |
440 | return r; | |
441 | } |