/src/systemd/src/libsystemd-network/ndisc-option.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include <linux/ipv6.h> |
4 | | #include <netinet/icmp6.h> |
5 | | |
6 | | #include "sd-ndisc-protocol.h" |
7 | | |
8 | | #include "alloc-util.h" |
9 | | #include "dns-def.h" |
10 | | #include "dns-domain.h" |
11 | | #include "dns-packet.h" |
12 | | #include "dns-resolver-internal.h" |
13 | | #include "ether-addr-util.h" |
14 | | #include "hash-funcs.h" |
15 | | #include "hostname-util.h" |
16 | | #include "icmp6-packet.h" |
17 | | #include "icmp6-util.h" |
18 | | #include "in-addr-util.h" |
19 | | #include "iovec-util.h" |
20 | | #include "ndisc-option.h" |
21 | | #include "network-common.h" |
22 | | #include "set.h" |
23 | | #include "siphash24.h" |
24 | | #include "string-util.h" |
25 | | #include "strv.h" |
26 | | #include "unaligned.h" |
27 | | |
28 | | /* RFC does not say anything about the maximum number of options, but let's limit the number of options for |
29 | | * safety. Typically, the number of options in an ICMPv6 message should be only a few. */ |
30 | 66.2k | #define MAX_OPTIONS 128 |
31 | | |
32 | | int ndisc_option_parse( |
33 | | ICMP6Packet *p, |
34 | | size_t offset, |
35 | | uint8_t *ret_type, |
36 | | size_t *ret_len, |
37 | 88.8k | const uint8_t **ret_opt) { |
38 | | |
39 | 88.8k | assert(p); |
40 | | |
41 | 88.8k | if (offset == p->raw_size) |
42 | 0 | return -ESPIPE; /* end of the packet */ |
43 | | |
44 | 88.8k | if (offset > p->raw_size) |
45 | 0 | return -EBADMSG; |
46 | | |
47 | 88.8k | if (p->raw_size - offset < sizeof(struct nd_opt_hdr)) |
48 | 142 | return -EBADMSG; |
49 | | |
50 | 88.7k | assert_cc(alignof(struct nd_opt_hdr) == 1); |
51 | 88.7k | const struct nd_opt_hdr *hdr = (const struct nd_opt_hdr*) (p->raw_packet + offset); |
52 | 88.7k | if (hdr->nd_opt_len == 0) |
53 | 57 | return -EBADMSG; |
54 | | |
55 | 88.6k | size_t len = hdr->nd_opt_len * 8; |
56 | 88.6k | if (p->raw_size - offset < len) |
57 | 174 | return -EBADMSG; |
58 | | |
59 | 88.5k | if (ret_type) |
60 | 88.5k | *ret_type = hdr->nd_opt_type; |
61 | 88.5k | if (ret_len) |
62 | 88.5k | *ret_len = len; |
63 | 88.5k | if (ret_opt) |
64 | 88.5k | *ret_opt = p->raw_packet + offset; |
65 | | |
66 | 88.5k | return 0; |
67 | 88.6k | } |
68 | | |
69 | 75.4k | static sd_ndisc_option* ndisc_option_new(uint8_t type, size_t offset) { |
70 | 75.4k | sd_ndisc_option *p = new0(sd_ndisc_option, 1); /* use new0() here to make the fuzzers silent. */ |
71 | 75.4k | if (!p) |
72 | 0 | return NULL; |
73 | | |
74 | | /* As the same reason in the above, do not use the structured initializer here. */ |
75 | 75.4k | p->type = type; |
76 | 75.4k | p->offset = offset; |
77 | | |
78 | 75.4k | return p; |
79 | 75.4k | } |
80 | | |
81 | 0 | static void ndisc_raw_done(sd_ndisc_raw *raw) { |
82 | 0 | if (!raw) |
83 | 0 | return; |
84 | | |
85 | 0 | free(raw->bytes); |
86 | 0 | } |
87 | | |
88 | 1.06k | static void ndisc_rdnss_done(sd_ndisc_rdnss *rdnss) { |
89 | 1.06k | if (!rdnss) |
90 | 0 | return; |
91 | | |
92 | 1.06k | free(rdnss->addresses); |
93 | 1.06k | } |
94 | | |
95 | 4.57k | static void ndisc_dnssl_done(sd_ndisc_dnssl *dnssl) { |
96 | 4.57k | if (!dnssl) |
97 | 0 | return; |
98 | | |
99 | 4.57k | strv_free(dnssl->domains); |
100 | 4.57k | } |
101 | | |
102 | 1.23k | static void ndisc_dnr_done(sd_ndisc_dnr *dnr) { |
103 | 1.23k | if (!dnr) |
104 | 0 | return; |
105 | | |
106 | 1.23k | sd_dns_resolver_unref(dnr->resolver); |
107 | 1.23k | } |
108 | | |
109 | 75.4k | sd_ndisc_option* ndisc_option_free(sd_ndisc_option *option) { |
110 | 75.4k | if (!option) |
111 | 0 | return NULL; |
112 | | |
113 | 75.4k | switch (option->type) { |
114 | 0 | case 0: |
115 | 0 | ndisc_raw_done(&option->raw); |
116 | 0 | break; |
117 | | |
118 | 1.06k | case SD_NDISC_OPTION_RDNSS: |
119 | 1.06k | ndisc_rdnss_done(&option->rdnss); |
120 | 1.06k | break; |
121 | | |
122 | 4.57k | case SD_NDISC_OPTION_DNSSL: |
123 | 4.57k | ndisc_dnssl_done(&option->dnssl); |
124 | 4.57k | break; |
125 | | |
126 | 782 | case SD_NDISC_OPTION_CAPTIVE_PORTAL: |
127 | 782 | free(option->captive_portal); |
128 | 782 | break; |
129 | | |
130 | 1.23k | case SD_NDISC_OPTION_ENCRYPTED_DNS: |
131 | 1.23k | ndisc_dnr_done(&option->encrypted_dns); |
132 | 1.23k | break; |
133 | 75.4k | } |
134 | | |
135 | 75.4k | return mfree(option); |
136 | 75.4k | } |
137 | | |
138 | 193k | static int ndisc_option_compare_func(const sd_ndisc_option *x, const sd_ndisc_option *y) { |
139 | 193k | int r; |
140 | | |
141 | 193k | assert(x); |
142 | 193k | assert(y); |
143 | | |
144 | 193k | r = CMP(x->type, y->type); |
145 | 193k | if (r != 0) |
146 | 56.2k | return r; |
147 | | |
148 | 136k | switch (x->type) { |
149 | 0 | case 0: |
150 | 0 | return memcmp_nn(x->raw.bytes, x->raw.length, y->raw.bytes, y->raw.length); |
151 | | |
152 | 7.01k | case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: |
153 | 7.57k | case SD_NDISC_OPTION_TARGET_LL_ADDRESS: |
154 | 7.72k | case SD_NDISC_OPTION_REDIRECTED_HEADER: |
155 | 8.19k | case SD_NDISC_OPTION_MTU: |
156 | 9.24k | case SD_NDISC_OPTION_HOME_AGENT: |
157 | 9.80k | case SD_NDISC_OPTION_FLAGS_EXTENSION: |
158 | 10.2k | case SD_NDISC_OPTION_CAPTIVE_PORTAL: |
159 | | /* These options cannot be specified multiple times. */ |
160 | 10.2k | return 0; |
161 | | |
162 | 4.36k | case SD_NDISC_OPTION_PREFIX_INFORMATION: |
163 | | /* Should not specify the same prefix multiple times. */ |
164 | 4.36k | r = CMP(x->prefix.prefixlen, y->prefix.prefixlen); |
165 | 4.36k | if (r != 0) |
166 | 2.05k | return r; |
167 | | |
168 | 2.31k | return memcmp(&x->prefix.address, &y->prefix.address, sizeof(struct in6_addr)); |
169 | | |
170 | 3.78k | case SD_NDISC_OPTION_ROUTE_INFORMATION: |
171 | 3.78k | r = CMP(x->route.prefixlen, y->route.prefixlen); |
172 | 3.78k | if (r != 0) |
173 | 1.77k | return r; |
174 | | |
175 | 2.01k | return memcmp(&x->route.address, &y->route.address, sizeof(struct in6_addr)); |
176 | | |
177 | 4.06k | case SD_NDISC_OPTION_PREF64: |
178 | 4.06k | r = CMP(x->prefix64.prefixlen, y->prefix64.prefixlen); |
179 | 4.06k | if (r != 0) |
180 | 2.18k | return r; |
181 | | |
182 | 1.87k | return memcmp(&x->prefix64.prefix, &y->prefix64.prefix, sizeof(struct in6_addr)); |
183 | | |
184 | 114k | default: |
185 | | /* DNSSL, RDNSS, and other unsupported options can be specified multiple times. */ |
186 | 114k | return trivial_compare_func(x, y); |
187 | 136k | } |
188 | 136k | } |
189 | | |
190 | 179k | static void ndisc_option_hash_func(const sd_ndisc_option *option, struct siphash *state) { |
191 | 179k | assert(option); |
192 | 179k | assert(state); |
193 | | |
194 | 179k | siphash24_compress_typesafe(option->type, state); |
195 | | |
196 | 179k | switch (option->type) { |
197 | 0 | case 0: |
198 | 0 | siphash24_compress(option->raw.bytes, option->raw.length, state); |
199 | 0 | break; |
200 | | |
201 | 18.7k | case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: |
202 | 20.8k | case SD_NDISC_OPTION_TARGET_LL_ADDRESS: |
203 | 21.2k | case SD_NDISC_OPTION_REDIRECTED_HEADER: |
204 | 24.5k | case SD_NDISC_OPTION_MTU: |
205 | 27.9k | case SD_NDISC_OPTION_HOME_AGENT: |
206 | 30.4k | case SD_NDISC_OPTION_FLAGS_EXTENSION: |
207 | 32.2k | case SD_NDISC_OPTION_CAPTIVE_PORTAL: |
208 | 32.2k | break; |
209 | | |
210 | 6.52k | case SD_NDISC_OPTION_PREFIX_INFORMATION: |
211 | 6.52k | siphash24_compress_typesafe(option->prefix.prefixlen, state); |
212 | 6.52k | siphash24_compress_typesafe(option->prefix.address, state); |
213 | 6.52k | break; |
214 | | |
215 | 7.74k | case SD_NDISC_OPTION_ROUTE_INFORMATION: |
216 | 7.74k | siphash24_compress_typesafe(option->route.prefixlen, state); |
217 | 7.74k | siphash24_compress_typesafe(option->route.address, state); |
218 | 7.74k | break; |
219 | | |
220 | 6.89k | case SD_NDISC_OPTION_PREF64: |
221 | 6.89k | siphash24_compress_typesafe(option->prefix64.prefixlen, state); |
222 | 6.89k | siphash24_compress_typesafe(option->prefix64.prefix, state); |
223 | 6.89k | break; |
224 | | |
225 | 126k | default: |
226 | 126k | trivial_hash_func(option, state); |
227 | 179k | } |
228 | 179k | } |
229 | | |
230 | | DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( |
231 | | ndisc_option_hash_ops, |
232 | | sd_ndisc_option, |
233 | | ndisc_option_hash_func, |
234 | | ndisc_option_compare_func, |
235 | | ndisc_option_free); |
236 | | |
237 | 66.2k | static int ndisc_option_consume(Set **options, sd_ndisc_option *p) { |
238 | 66.2k | assert(options); |
239 | 66.2k | assert(p); |
240 | | |
241 | 66.2k | if (set_size(*options) >= MAX_OPTIONS) { |
242 | 1.68k | ndisc_option_free(p); |
243 | 1.68k | return -ETOOMANYREFS; /* recognizable error code */ |
244 | 1.68k | } |
245 | | |
246 | 64.6k | return set_ensure_consume(options, &ndisc_option_hash_ops, p); |
247 | 66.2k | } |
248 | | |
249 | 0 | static int ndisc_option_build_raw(const sd_ndisc_option *option, uint8_t **ret) { |
250 | 0 | assert(option); |
251 | 0 | assert(option->type == 0); |
252 | 0 | assert(ret); |
253 | |
|
254 | 0 | _cleanup_free_ uint8_t *buf = newdup(uint8_t, option->raw.bytes, option->raw.length); |
255 | 0 | if (!buf) |
256 | 0 | return -ENOMEM; |
257 | | |
258 | 0 | *ret = TAKE_PTR(buf); |
259 | 0 | return 0; |
260 | 0 | } |
261 | | |
262 | 12.7k | int ndisc_option_add_link_layer_address(Set **options, uint8_t type, size_t offset, const struct ether_addr *mac) { |
263 | 12.7k | assert(options); |
264 | 12.7k | assert(IN_SET(type, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS)); |
265 | | |
266 | 12.7k | if (!mac || ether_addr_is_null(mac)) { |
267 | 0 | ndisc_option_remove_by_type(*options, type); |
268 | 0 | return 0; |
269 | 0 | } |
270 | | |
271 | 12.7k | sd_ndisc_option *p = ndisc_option_get_by_type(*options, type); |
272 | 12.7k | if (p) { |
273 | | /* offset == 0 means that we are now building a packet to be sent, and in that case we allow |
274 | | * to override the option we previously set. |
275 | | * offset != 0 means that we are now parsing a received packet, and we refuse to override |
276 | | * conflicting options. */ |
277 | 3.53k | if (offset != 0) |
278 | 3.53k | return -EEXIST; |
279 | | |
280 | 0 | p->mac = *mac; |
281 | 0 | return 0; |
282 | 3.53k | } |
283 | | |
284 | 9.20k | p = ndisc_option_new(type, offset); |
285 | 9.20k | if (!p) |
286 | 0 | return -ENOMEM; |
287 | | |
288 | 9.20k | p->mac = *mac; |
289 | | |
290 | 9.20k | return set_ensure_consume(options, &ndisc_option_hash_ops, p); |
291 | 9.20k | } |
292 | | |
293 | 5.94k | static int ndisc_option_parse_link_layer_address(Set **options, size_t offset, size_t len, const uint8_t *opt) { |
294 | 5.94k | assert(options); |
295 | 5.94k | assert(opt); |
296 | | |
297 | 5.94k | if (len != sizeof(struct ether_addr) + 2) |
298 | 201 | return -EBADMSG; |
299 | | |
300 | 5.74k | if (!IN_SET(opt[0], SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS)) |
301 | 0 | return -EBADMSG; |
302 | | |
303 | 5.74k | struct ether_addr mac; |
304 | 5.74k | memcpy(&mac, opt + 2, sizeof(struct ether_addr)); |
305 | | |
306 | 5.74k | if (ether_addr_is_null(&mac)) |
307 | 472 | return -EBADMSG; |
308 | | |
309 | 5.27k | return ndisc_option_add_link_layer_address(options, opt[0], offset, &mac); |
310 | 5.74k | } |
311 | | |
312 | 7.16k | static int ndisc_option_build_link_layer_address(const sd_ndisc_option *option, uint8_t **ret) { |
313 | 7.16k | assert(option); |
314 | 7.16k | assert(IN_SET(option->type, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS)); |
315 | 7.16k | assert(ret); |
316 | | |
317 | 7.16k | assert_cc(2 + sizeof(struct ether_addr) == 8); |
318 | | |
319 | 7.16k | _cleanup_free_ uint8_t *buf = new(uint8_t, 2 + sizeof(struct ether_addr)); |
320 | 7.16k | if (!buf) |
321 | 0 | return -ENOMEM; |
322 | | |
323 | 7.16k | buf[0] = option->type; |
324 | 7.16k | buf[1] = 1; |
325 | 7.16k | memcpy(buf + 2, &option->mac, sizeof(struct ether_addr)); |
326 | | |
327 | 7.16k | *ret = TAKE_PTR(buf); |
328 | 7.16k | return 0; |
329 | 7.16k | } |
330 | | |
331 | | int ndisc_option_add_prefix_internal( |
332 | | Set **options, |
333 | | size_t offset, |
334 | | uint8_t flags, |
335 | | uint8_t prefixlen, |
336 | | const struct in6_addr *address, |
337 | | usec_t valid_lifetime, |
338 | | usec_t preferred_lifetime, |
339 | | usec_t valid_until, |
340 | 3.26k | usec_t preferred_until) { |
341 | | |
342 | 3.26k | assert(options); |
343 | 3.26k | assert(address); |
344 | | |
345 | 3.26k | if (prefixlen > 128) |
346 | 66 | return -EINVAL; |
347 | | |
348 | 3.20k | struct in6_addr addr = *address; |
349 | 3.20k | in6_addr_mask(&addr, prefixlen); |
350 | | |
351 | | /* RFC 4861 and 4862 only state that link-local prefix should be ignored. |
352 | | * But here we also ignore null and multicast addresses. */ |
353 | 3.20k | if (in6_addr_is_link_local(&addr) || in6_addr_is_null(&addr) || in6_addr_is_multicast(&addr)) |
354 | 220 | return -EINVAL; |
355 | | |
356 | 2.98k | if (preferred_lifetime > valid_lifetime) |
357 | 159 | return -EINVAL; |
358 | | |
359 | 2.82k | if (preferred_until > valid_until) |
360 | 0 | return -EINVAL; |
361 | | |
362 | 2.82k | sd_ndisc_option *p = ndisc_option_get( |
363 | 2.82k | *options, |
364 | 2.82k | &(const sd_ndisc_option) { |
365 | 2.82k | .type = SD_NDISC_OPTION_PREFIX_INFORMATION, |
366 | 2.82k | .prefix.prefixlen = prefixlen, |
367 | 2.82k | .prefix.address = addr, |
368 | 2.82k | }); |
369 | 2.82k | if (p) { |
370 | 167 | if (offset != 0) |
371 | 167 | return -EEXIST; |
372 | | |
373 | 0 | p->prefix.flags = flags; |
374 | 0 | p->prefix.valid_lifetime = valid_lifetime; |
375 | 0 | p->prefix.preferred_lifetime = preferred_lifetime; |
376 | 0 | p->prefix.valid_until = valid_until; |
377 | 0 | p->prefix.preferred_until = preferred_until; |
378 | 0 | return 0; |
379 | 167 | } |
380 | | |
381 | 2.65k | p = ndisc_option_new(SD_NDISC_OPTION_PREFIX_INFORMATION, offset); |
382 | 2.65k | if (!p) |
383 | 0 | return -ENOMEM; |
384 | | |
385 | 2.65k | p->prefix = (sd_ndisc_prefix) { |
386 | 2.65k | .flags = flags, |
387 | 2.65k | .prefixlen = prefixlen, |
388 | 2.65k | .address = addr, |
389 | 2.65k | .valid_lifetime = valid_lifetime, |
390 | 2.65k | .preferred_lifetime = preferred_lifetime, |
391 | 2.65k | .valid_until = valid_until, |
392 | 2.65k | .preferred_until = preferred_until, |
393 | 2.65k | }; |
394 | | |
395 | 2.65k | return ndisc_option_consume(options, p); |
396 | 2.65k | } |
397 | | |
398 | 3.47k | static int ndisc_option_parse_prefix(Set **options, size_t offset, size_t len, const uint8_t *opt) { |
399 | 3.47k | const struct nd_opt_prefix_info *pi = (const struct nd_opt_prefix_info*) ASSERT_PTR(opt); |
400 | | |
401 | 3.47k | assert(options); |
402 | | |
403 | 3.47k | if (len != sizeof(struct nd_opt_prefix_info)) |
404 | 210 | return -EBADMSG; |
405 | | |
406 | 3.26k | if (pi->nd_opt_pi_type != SD_NDISC_OPTION_PREFIX_INFORMATION) |
407 | 0 | return -EBADMSG; |
408 | | |
409 | 3.26k | usec_t valid = be32_sec_to_usec(pi->nd_opt_pi_valid_time, /* max_as_infinity= */ true); |
410 | 3.26k | usec_t pref = be32_sec_to_usec(pi->nd_opt_pi_preferred_time, /* max_as_infinity= */ true); |
411 | | |
412 | | /* We only support 64 bits interface identifier for addrconf. */ |
413 | 3.26k | uint8_t flags = pi->nd_opt_pi_flags_reserved; |
414 | 3.26k | if (FLAGS_SET(flags, ND_OPT_PI_FLAG_AUTO) && pi->nd_opt_pi_prefix_len != 64) |
415 | 1.22k | flags &= ~ND_OPT_PI_FLAG_AUTO; |
416 | | |
417 | 3.26k | return ndisc_option_add_prefix(options, offset, flags, |
418 | 3.26k | pi->nd_opt_pi_prefix_len, &pi->nd_opt_pi_prefix, |
419 | 3.26k | valid, pref); |
420 | 3.26k | } |
421 | | |
422 | 843 | static int ndisc_option_build_prefix(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) { |
423 | 843 | assert(option); |
424 | 843 | assert(option->type == SD_NDISC_OPTION_PREFIX_INFORMATION); |
425 | 843 | assert(ret); |
426 | | |
427 | 843 | assert_cc(sizeof(struct nd_opt_prefix_info) % 8 == 0); |
428 | | |
429 | 843 | _cleanup_free_ struct nd_opt_prefix_info *buf = new(struct nd_opt_prefix_info, 1); |
430 | 843 | if (!buf) |
431 | 0 | return -ENOMEM; |
432 | | |
433 | 843 | usec_t valid = MIN(option->prefix.valid_lifetime, |
434 | 843 | usec_sub_unsigned(option->prefix.valid_until, timestamp)); |
435 | 843 | usec_t pref = MIN3(valid, |
436 | 843 | option->prefix.preferred_lifetime, |
437 | 843 | usec_sub_unsigned(option->prefix.preferred_until, timestamp)); |
438 | | |
439 | 843 | *buf = (struct nd_opt_prefix_info) { |
440 | 843 | .nd_opt_pi_type = SD_NDISC_OPTION_PREFIX_INFORMATION, |
441 | 843 | .nd_opt_pi_len = sizeof(struct nd_opt_prefix_info) / 8, |
442 | 843 | .nd_opt_pi_prefix_len = option->prefix.prefixlen, |
443 | 843 | .nd_opt_pi_flags_reserved = option->prefix.flags, |
444 | 843 | .nd_opt_pi_valid_time = usec_to_be32_sec(valid), |
445 | 843 | .nd_opt_pi_preferred_time = usec_to_be32_sec(pref), |
446 | 843 | .nd_opt_pi_prefix = option->prefix.address, |
447 | 843 | }; |
448 | | |
449 | 843 | *ret = (uint8_t*) TAKE_PTR(buf); |
450 | 843 | return 0; |
451 | 843 | } |
452 | | |
453 | 295 | int ndisc_option_add_redirected_header(Set **options, size_t offset, const struct ip6_hdr *hdr) { |
454 | 295 | assert(options); |
455 | | |
456 | 295 | if (!hdr) { |
457 | 0 | ndisc_option_remove_by_type(*options, SD_NDISC_OPTION_REDIRECTED_HEADER); |
458 | 0 | return 0; |
459 | 0 | } |
460 | | |
461 | 295 | sd_ndisc_option *p = ndisc_option_get_by_type(*options, SD_NDISC_OPTION_REDIRECTED_HEADER); |
462 | 295 | if (p) { |
463 | 146 | if (offset != 0) |
464 | 146 | return -EEXIST; |
465 | | |
466 | 0 | memcpy(&p->hdr, hdr, sizeof(struct ip6_hdr)); |
467 | 0 | return 0; |
468 | 146 | } |
469 | | |
470 | 149 | p = ndisc_option_new(SD_NDISC_OPTION_REDIRECTED_HEADER, offset); |
471 | 149 | if (!p) |
472 | 0 | return -ENOMEM; |
473 | | |
474 | | /* For safety, here we copy only IPv6 header. */ |
475 | 149 | memcpy(&p->hdr, hdr, sizeof(struct ip6_hdr)); |
476 | | |
477 | 149 | return ndisc_option_consume(options, p); |
478 | 149 | } |
479 | | |
480 | 501 | static int ndisc_option_parse_redirected_header(Set **options, size_t offset, size_t len, const uint8_t *opt) { |
481 | 501 | assert(options); |
482 | 501 | assert(opt); |
483 | | |
484 | 501 | if (len < sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr)) |
485 | 206 | return -EBADMSG; |
486 | | |
487 | 295 | if (opt[0] != SD_NDISC_OPTION_REDIRECTED_HEADER) |
488 | 0 | return -EBADMSG; |
489 | | |
490 | 295 | return ndisc_option_add_redirected_header(options, offset, (const struct ip6_hdr*) (opt + sizeof(struct nd_opt_rd_hdr))); |
491 | 295 | } |
492 | | |
493 | 26 | static int ndisc_option_build_redirected_header(const sd_ndisc_option *option, uint8_t **ret) { |
494 | 26 | assert(option); |
495 | 26 | assert(option->type == SD_NDISC_OPTION_REDIRECTED_HEADER); |
496 | 26 | assert(ret); |
497 | | |
498 | 26 | assert_cc((sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr)) % 8 == 0); |
499 | | |
500 | 26 | size_t len = DIV_ROUND_UP(sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr), 8); |
501 | | |
502 | 26 | _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8); |
503 | 26 | if (!buf) |
504 | 0 | return -ENOMEM; |
505 | | |
506 | 26 | uint8_t *p; |
507 | 26 | p = mempcpy(buf, |
508 | 26 | &(const struct nd_opt_rd_hdr) { |
509 | 26 | .nd_opt_rh_type = SD_NDISC_OPTION_REDIRECTED_HEADER, |
510 | 26 | .nd_opt_rh_len = len, |
511 | 26 | }, |
512 | 26 | sizeof(struct nd_opt_rd_hdr)); |
513 | 26 | memcpy(p, &option->hdr, sizeof(struct ip6_hdr)); |
514 | | |
515 | 26 | *ret = TAKE_PTR(buf); |
516 | 26 | return 0; |
517 | 26 | } |
518 | | |
519 | 1.75k | int ndisc_option_add_mtu(Set **options, size_t offset, uint32_t mtu) { |
520 | 1.75k | assert(options); |
521 | | |
522 | 1.75k | if (mtu < IPV6_MIN_MTU) |
523 | 220 | return -EINVAL; |
524 | | |
525 | 1.53k | sd_ndisc_option *p = ndisc_option_get_by_type(*options, SD_NDISC_OPTION_MTU); |
526 | 1.53k | if (p) { |
527 | 474 | if (offset != 0) |
528 | 474 | return -EEXIST; |
529 | | |
530 | 0 | p->mtu = mtu; |
531 | 0 | return 0; |
532 | 474 | } |
533 | | |
534 | 1.05k | p = ndisc_option_new(SD_NDISC_OPTION_MTU, offset); |
535 | 1.05k | if (!p) |
536 | 0 | return -ENOMEM; |
537 | | |
538 | 1.05k | p->mtu = mtu; |
539 | | |
540 | 1.05k | return ndisc_option_consume(options, p); |
541 | 1.05k | } |
542 | | |
543 | 1.94k | static int ndisc_option_parse_mtu(Set **options, size_t offset, size_t len, const uint8_t *opt) { |
544 | 1.94k | const struct nd_opt_mtu *pm = (const struct nd_opt_mtu*) ASSERT_PTR(opt); |
545 | | |
546 | 1.94k | assert(options); |
547 | | |
548 | 1.94k | if (len != sizeof(struct nd_opt_mtu)) |
549 | 194 | return -EBADMSG; |
550 | | |
551 | 1.75k | if (pm->nd_opt_mtu_type != SD_NDISC_OPTION_MTU) |
552 | 0 | return -EBADMSG; |
553 | | |
554 | 1.75k | return ndisc_option_add_mtu(options, offset, be32toh(pm->nd_opt_mtu_mtu)); |
555 | 1.75k | } |
556 | | |
557 | 273 | static int ndisc_option_build_mtu(const sd_ndisc_option *option, uint8_t **ret) { |
558 | 273 | assert(option); |
559 | 273 | assert(option->type == SD_NDISC_OPTION_MTU); |
560 | 273 | assert(ret); |
561 | | |
562 | 273 | assert_cc(sizeof(struct nd_opt_mtu) % 8 == 0); |
563 | | |
564 | 273 | _cleanup_free_ struct nd_opt_mtu *buf = new(struct nd_opt_mtu, 1); |
565 | 273 | if (!buf) |
566 | 0 | return -ENOMEM; |
567 | | |
568 | 273 | *buf = (struct nd_opt_mtu) { |
569 | 273 | .nd_opt_mtu_type = SD_NDISC_OPTION_MTU, |
570 | 273 | .nd_opt_mtu_len = sizeof(struct nd_opt_mtu) / 8, |
571 | 273 | .nd_opt_mtu_mtu = htobe32(option->mtu), |
572 | 273 | }; |
573 | | |
574 | 273 | *ret = (uint8_t*) TAKE_PTR(buf); |
575 | 273 | return 0; |
576 | 273 | } |
577 | | |
578 | | int ndisc_option_add_home_agent_internal( |
579 | | Set **options, |
580 | | size_t offset, |
581 | | uint16_t preference, |
582 | | usec_t lifetime, |
583 | 2.17k | usec_t valid_until) { |
584 | | |
585 | 2.17k | assert(options); |
586 | | |
587 | 2.17k | if (lifetime > UINT16_MAX * USEC_PER_SEC) |
588 | 0 | return -EINVAL; |
589 | | |
590 | 2.17k | sd_ndisc_option *p = ndisc_option_get_by_type(*options, SD_NDISC_OPTION_HOME_AGENT); |
591 | 2.17k | if (p) { |
592 | 1.05k | if (offset != 0) |
593 | 1.05k | return -EEXIST; |
594 | | |
595 | 0 | p->home_agent = (sd_ndisc_home_agent) { |
596 | 0 | .preference = preference, |
597 | 0 | .lifetime = lifetime, |
598 | 0 | .valid_until = valid_until, |
599 | 0 | }; |
600 | 0 | return 0; |
601 | 1.05k | } |
602 | | |
603 | 1.12k | p = ndisc_option_new(SD_NDISC_OPTION_HOME_AGENT, offset); |
604 | 1.12k | if (!p) |
605 | 0 | return -ENOMEM; |
606 | | |
607 | 1.12k | p->home_agent = (sd_ndisc_home_agent) { |
608 | 1.12k | .preference = preference, |
609 | 1.12k | .lifetime = lifetime, |
610 | 1.12k | .valid_until = valid_until, |
611 | 1.12k | }; |
612 | | |
613 | 1.12k | return ndisc_option_consume(options, p); |
614 | 1.12k | } |
615 | | |
616 | 2.37k | static int ndisc_option_parse_home_agent(Set **options, size_t offset, size_t len, const uint8_t *opt) { |
617 | 2.37k | const struct nd_opt_home_agent_info *p = (const struct nd_opt_home_agent_info*) ASSERT_PTR(opt); |
618 | | |
619 | 2.37k | assert(options); |
620 | | |
621 | 2.37k | if (len != sizeof(struct nd_opt_home_agent_info)) |
622 | 198 | return -EBADMSG; |
623 | | |
624 | 2.17k | if (p->nd_opt_home_agent_info_type != SD_NDISC_OPTION_HOME_AGENT) |
625 | 0 | return -EBADMSG; |
626 | | |
627 | 2.17k | return ndisc_option_add_home_agent( |
628 | 2.17k | options, offset, |
629 | 2.17k | be16toh(p->nd_opt_home_agent_info_preference), |
630 | 2.17k | be16_sec_to_usec(p->nd_opt_home_agent_info_lifetime, /* max_as_infinity= */ false)); |
631 | 2.17k | } |
632 | | |
633 | 304 | static int ndisc_option_build_home_agent(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) { |
634 | 304 | assert(option); |
635 | 304 | assert(option->type == SD_NDISC_OPTION_HOME_AGENT); |
636 | 304 | assert(ret); |
637 | | |
638 | 304 | assert_cc(sizeof(struct nd_opt_home_agent_info) % 8 == 0); |
639 | | |
640 | 304 | usec_t lifetime = MIN(option->home_agent.lifetime, |
641 | 304 | usec_sub_unsigned(option->home_agent.valid_until, timestamp)); |
642 | | |
643 | 304 | _cleanup_free_ struct nd_opt_home_agent_info *buf = new(struct nd_opt_home_agent_info, 1); |
644 | 304 | if (!buf) |
645 | 0 | return -ENOMEM; |
646 | | |
647 | 304 | *buf = (struct nd_opt_home_agent_info) { |
648 | 304 | .nd_opt_home_agent_info_type = SD_NDISC_OPTION_HOME_AGENT, |
649 | 304 | .nd_opt_home_agent_info_len = sizeof(struct nd_opt_home_agent_info) / 8, |
650 | 304 | .nd_opt_home_agent_info_preference = htobe16(option->home_agent.preference), |
651 | 304 | .nd_opt_home_agent_info_lifetime = usec_to_be16_sec(lifetime), |
652 | 304 | }; |
653 | | |
654 | 304 | *ret = (uint8_t*) TAKE_PTR(buf); |
655 | 304 | return 0; |
656 | 304 | } |
657 | | |
658 | | int ndisc_option_add_route_internal( |
659 | | Set **options, |
660 | | size_t offset, |
661 | | uint8_t preference, |
662 | | uint8_t prefixlen, |
663 | | const struct in6_addr *prefix, |
664 | | usec_t lifetime, |
665 | 4.11k | usec_t valid_until) { |
666 | | |
667 | 4.11k | assert(options); |
668 | 4.11k | assert(prefix); |
669 | | |
670 | 4.11k | if (prefixlen > 128) |
671 | 0 | return -EINVAL; |
672 | | |
673 | | /* RFC 4191 section 2.3 |
674 | | * Prf (Route Preference) |
675 | | * 2-bit signed integer. The Route Preference indicates whether to prefer the router associated with |
676 | | * this prefix over others, when multiple identical prefixes (for different routers) have been |
677 | | * received. If the Reserved (10) value is received, the Route Information Option MUST be ignored. */ |
678 | 4.11k | if (!IN_SET(preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH)) |
679 | 304 | return -EINVAL; |
680 | | |
681 | 3.81k | struct in6_addr addr = *prefix; |
682 | 3.81k | in6_addr_mask(&addr, prefixlen); |
683 | | |
684 | 3.81k | sd_ndisc_option *p = ndisc_option_get( |
685 | 3.81k | *options, |
686 | 3.81k | &(const sd_ndisc_option) { |
687 | 3.81k | .type = SD_NDISC_OPTION_ROUTE_INFORMATION, |
688 | 3.81k | .route.prefixlen = prefixlen, |
689 | 3.81k | .route.address = addr, |
690 | 3.81k | }); |
691 | 3.81k | if (p) { |
692 | 863 | if (offset != 0) |
693 | 863 | return -EEXIST; |
694 | | |
695 | 0 | p->route.preference = preference; |
696 | 0 | p->route.lifetime = lifetime; |
697 | 0 | p->route.valid_until = valid_until; |
698 | 0 | return 0; |
699 | 863 | } |
700 | | |
701 | 2.95k | p = ndisc_option_new(SD_NDISC_OPTION_ROUTE_INFORMATION, offset); |
702 | 2.95k | if (!p) |
703 | 0 | return -ENOMEM; |
704 | | |
705 | 2.95k | p->route = (sd_ndisc_route) { |
706 | 2.95k | .preference = preference, |
707 | 2.95k | .prefixlen = prefixlen, |
708 | 2.95k | .address = addr, |
709 | 2.95k | .lifetime = lifetime, |
710 | 2.95k | .valid_until = valid_until, |
711 | 2.95k | }; |
712 | | |
713 | 2.95k | return ndisc_option_consume(options, p); |
714 | 2.95k | } |
715 | | |
716 | 4.61k | static int ndisc_option_parse_route(Set **options, size_t offset, size_t len, const uint8_t *opt) { |
717 | 4.61k | assert(options); |
718 | 4.61k | assert(opt); |
719 | | |
720 | 4.61k | if (!IN_SET(len, 1*8, 2*8, 3*8)) |
721 | 75 | return -EBADMSG; |
722 | | |
723 | 4.53k | if (opt[0] != SD_NDISC_OPTION_ROUTE_INFORMATION) |
724 | 0 | return -EBADMSG; |
725 | | |
726 | 4.53k | uint8_t prefixlen = opt[2]; |
727 | 4.53k | if (prefixlen > 128) |
728 | 200 | return -EBADMSG; |
729 | | |
730 | 4.33k | if (len < (size_t) (DIV_ROUND_UP(prefixlen, 64) + 1) * 8) |
731 | 220 | return -EBADMSG; |
732 | | |
733 | 4.11k | uint8_t preference = (opt[3] >> 3) & 0x03; |
734 | 4.11k | usec_t lifetime = unaligned_be32_sec_to_usec(opt + 4, /* max_as_infinity= */ true); |
735 | | |
736 | 4.11k | struct in6_addr prefix; |
737 | 4.11k | memcpy_safe(&prefix, opt + 8, len - 8); |
738 | 4.11k | in6_addr_mask(&prefix, prefixlen); |
739 | | |
740 | 4.11k | return ndisc_option_add_route(options, offset, preference, prefixlen, &prefix, lifetime); |
741 | 4.33k | } |
742 | | |
743 | 905 | static int ndisc_option_build_route(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) { |
744 | 905 | assert(option); |
745 | 905 | assert(option->type == SD_NDISC_OPTION_ROUTE_INFORMATION); |
746 | 905 | assert(option->route.prefixlen <= 128); |
747 | 905 | assert(ret); |
748 | | |
749 | 905 | size_t len = 1 + DIV_ROUND_UP(option->route.prefixlen, 64); |
750 | 905 | be32_t lifetime = usec_to_be32_sec(MIN(option->route.lifetime, |
751 | 905 | usec_sub_unsigned(option->route.valid_until, timestamp))); |
752 | | |
753 | 905 | _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8); |
754 | 905 | if (!buf) |
755 | 0 | return -ENOMEM; |
756 | | |
757 | 905 | buf[0] = SD_NDISC_OPTION_ROUTE_INFORMATION; |
758 | 905 | buf[1] = len; |
759 | 905 | buf[2] = option->route.prefixlen; |
760 | 905 | buf[3] = option->route.preference << 3; |
761 | 905 | memcpy(buf + 4, &lifetime, sizeof(be32_t)); |
762 | 905 | memcpy_safe(buf + 8, &option->route.address, (len - 1) * 8); |
763 | | |
764 | 905 | *ret = TAKE_PTR(buf); |
765 | 905 | return 0; |
766 | 905 | } |
767 | | |
768 | | int ndisc_option_add_rdnss_internal( |
769 | | Set **options, |
770 | | size_t offset, |
771 | | size_t n_addresses, |
772 | | const struct in6_addr *addresses, |
773 | | usec_t lifetime, |
774 | 1.06k | usec_t valid_until) { |
775 | | |
776 | 1.06k | assert(options); |
777 | 1.06k | assert(addresses); |
778 | | |
779 | 1.06k | if (n_addresses == 0) |
780 | 0 | return -EINVAL; |
781 | | |
782 | 1.06k | _cleanup_free_ struct in6_addr *addrs = newdup(struct in6_addr, addresses, n_addresses); |
783 | 1.06k | if (!addrs) |
784 | 0 | return -ENOMEM; |
785 | | |
786 | 1.06k | sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_RDNSS, offset); |
787 | 1.06k | if (!p) |
788 | 0 | return -ENOMEM; |
789 | | |
790 | 1.06k | p->rdnss = (sd_ndisc_rdnss) { |
791 | 1.06k | .n_addresses = n_addresses, |
792 | 1.06k | .addresses = TAKE_PTR(addrs), |
793 | 1.06k | .lifetime = lifetime, |
794 | 1.06k | .valid_until = valid_until, |
795 | 1.06k | }; |
796 | | |
797 | 1.06k | return ndisc_option_consume(options, p); |
798 | 1.06k | } |
799 | | |
800 | 1.33k | static int ndisc_option_parse_rdnss(Set **options, size_t offset, size_t len, const uint8_t *opt) { |
801 | 1.33k | assert(options); |
802 | 1.33k | assert(opt); |
803 | | |
804 | 1.33k | if (len < 8 + sizeof(struct in6_addr) || (len % sizeof(struct in6_addr)) != 8) |
805 | 276 | return -EBADMSG; |
806 | | |
807 | 1.06k | if (opt[0] != SD_NDISC_OPTION_RDNSS) |
808 | 0 | return -EBADMSG; |
809 | | |
810 | 1.06k | usec_t lifetime = unaligned_be32_sec_to_usec(opt + 4, /* max_as_infinity= */ true); |
811 | 1.06k | size_t n_addrs = len / sizeof(struct in6_addr); |
812 | | |
813 | 1.06k | return ndisc_option_add_rdnss(options, offset, n_addrs, (const struct in6_addr*) (opt + 8), lifetime); |
814 | 1.06k | } |
815 | | |
816 | 331 | static int ndisc_option_build_rdnss(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) { |
817 | 331 | assert(option); |
818 | 331 | assert(option->type == SD_NDISC_OPTION_RDNSS); |
819 | 331 | assert(ret); |
820 | | |
821 | 331 | size_t len = option->rdnss.n_addresses * 2 + 1; |
822 | 331 | be32_t lifetime = usec_to_be32_sec(MIN(option->rdnss.lifetime, |
823 | 331 | usec_sub_unsigned(option->rdnss.valid_until, timestamp))); |
824 | | |
825 | 331 | _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8); |
826 | 331 | if (!buf) |
827 | 0 | return -ENOMEM; |
828 | | |
829 | 331 | buf[0] = SD_NDISC_OPTION_RDNSS; |
830 | 331 | buf[1] = len; |
831 | 331 | buf[2] = 0; |
832 | 331 | buf[3] = 0; |
833 | 331 | memcpy(buf + 4, &lifetime, sizeof(be32_t)); |
834 | 331 | memcpy(buf + 8, option->rdnss.addresses, sizeof(struct in6_addr) * option->rdnss.n_addresses); |
835 | | |
836 | 331 | *ret = TAKE_PTR(buf); |
837 | 331 | return 0; |
838 | 331 | } |
839 | | |
840 | 1.57k | int ndisc_option_add_flags_extension(Set **options, size_t offset, uint64_t flags) { |
841 | 1.57k | assert(options); |
842 | | |
843 | 1.57k | if ((flags & UINT64_C(0x00ffffffffffff00)) != flags) |
844 | 0 | return -EINVAL; |
845 | | |
846 | 1.57k | sd_ndisc_option *p = ndisc_option_get_by_type(*options, SD_NDISC_OPTION_FLAGS_EXTENSION); |
847 | 1.57k | if (p) { |
848 | 560 | if (offset != 0) |
849 | 560 | return -EEXIST; |
850 | | |
851 | 0 | p->extended_flags = flags; |
852 | 0 | return 0; |
853 | 560 | } |
854 | | |
855 | 1.01k | p = ndisc_option_new(SD_NDISC_OPTION_FLAGS_EXTENSION, offset); |
856 | 1.01k | if (!p) |
857 | 0 | return -ENOMEM; |
858 | | |
859 | 1.01k | p->extended_flags = flags; |
860 | | |
861 | 1.01k | return ndisc_option_consume(options, p); |
862 | 1.01k | } |
863 | | |
864 | 1.77k | static int ndisc_option_parse_flags_extension(Set **options, size_t offset, size_t len, const uint8_t *opt) { |
865 | 1.77k | assert(options); |
866 | 1.77k | assert(opt); |
867 | | |
868 | 1.77k | if (len != 8) |
869 | 206 | return -EBADMSG; |
870 | | |
871 | 1.57k | if (opt[0] != SD_NDISC_OPTION_FLAGS_EXTENSION) |
872 | 0 | return -EBADMSG; |
873 | | |
874 | 1.57k | uint64_t flags = (unaligned_read_be64(opt) & UINT64_C(0xffffffffffff0000)) >> 8; |
875 | 1.57k | return ndisc_option_add_flags_extension(options, offset, flags); |
876 | 1.57k | } |
877 | | |
878 | 225 | static int ndisc_option_build_flags_extension(const sd_ndisc_option *option, uint8_t **ret) { |
879 | 225 | assert(option); |
880 | 225 | assert(option->type == SD_NDISC_OPTION_FLAGS_EXTENSION); |
881 | 225 | assert(ret); |
882 | | |
883 | 225 | _cleanup_free_ uint8_t *buf = new(uint8_t, 8); |
884 | 225 | if (!buf) |
885 | 0 | return 0; |
886 | | |
887 | 225 | unaligned_write_be64(buf, (option->extended_flags & UINT64_C(0x00ffffffffffff00)) << 8); |
888 | 225 | buf[0] = SD_NDISC_OPTION_FLAGS_EXTENSION; |
889 | 225 | buf[1] = 1; |
890 | | |
891 | 225 | *ret = TAKE_PTR(buf); |
892 | 225 | return 0; |
893 | 225 | } |
894 | | |
895 | | int ndisc_option_add_dnssl_internal( |
896 | | Set **options, |
897 | | size_t offset, char * |
898 | | const *domains, |
899 | | usec_t lifetime, |
900 | 4.78k | usec_t valid_until) { |
901 | | |
902 | 4.78k | int r; |
903 | | |
904 | 4.78k | assert(options); |
905 | | |
906 | 4.78k | if (strv_isempty(domains)) |
907 | 210 | return -EINVAL; |
908 | | |
909 | 12.2k | STRV_FOREACH(s, domains) { |
910 | 12.2k | r = dns_name_is_valid(*s); |
911 | 12.2k | if (r < 0) |
912 | 0 | return r; |
913 | | |
914 | 12.2k | if (is_localhost(*s) || dns_name_is_root(*s)) |
915 | 0 | return -EINVAL; |
916 | 12.2k | } |
917 | | |
918 | 4.57k | _cleanup_strv_free_ char **copy = strv_copy(domains); |
919 | 4.57k | if (!copy) |
920 | 0 | return -ENOMEM; |
921 | | |
922 | 4.57k | sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_DNSSL, offset); |
923 | 4.57k | if (!p) |
924 | 0 | return -ENOMEM; |
925 | | |
926 | 4.57k | p->dnssl = (sd_ndisc_dnssl) { |
927 | 4.57k | .domains = TAKE_PTR(copy), |
928 | 4.57k | .lifetime = lifetime, |
929 | 4.57k | .valid_until = valid_until, |
930 | 4.57k | }; |
931 | | |
932 | 4.57k | return ndisc_option_consume(options, p); |
933 | 4.57k | } |
934 | | |
935 | 5.56k | static int ndisc_option_parse_dnssl(Set **options, size_t offset, size_t len, const uint8_t *opt) { |
936 | 5.56k | int r; |
937 | | |
938 | 5.56k | assert(options); |
939 | 5.56k | assert(opt); |
940 | | |
941 | 5.56k | if (len < 2*8) |
942 | 78 | return -EBADMSG; |
943 | | |
944 | 5.48k | if (opt[0] != SD_NDISC_OPTION_DNSSL) |
945 | 0 | return -EBADMSG; |
946 | | |
947 | 5.48k | usec_t lifetime = unaligned_be32_sec_to_usec(opt + 4, /* max_as_infinity= */ true); |
948 | | |
949 | 5.48k | _cleanup_strv_free_ char **l = NULL; |
950 | 5.48k | _cleanup_free_ char *e = NULL; |
951 | 5.48k | size_t n = 0; |
952 | 113k | for (size_t c, pos = 8; pos < len; pos += c) { |
953 | | |
954 | 108k | c = opt[pos]; |
955 | 108k | pos++; |
956 | | |
957 | 108k | if (c == 0) { |
958 | | /* Found NUL termination */ |
959 | | |
960 | 87.7k | if (n > 0) { |
961 | 13.2k | _cleanup_free_ char *normalized = NULL; |
962 | | |
963 | 13.2k | e[n] = 0; |
964 | 13.2k | r = dns_name_normalize(e, 0, &normalized); |
965 | 13.2k | if (r < 0) |
966 | 46 | return r; |
967 | | |
968 | | /* Ignore the root domain name or "localhost" and friends */ |
969 | 13.2k | if (!is_localhost(normalized) && !dns_name_is_root(normalized)) { |
970 | 12.7k | r = strv_consume(&l, TAKE_PTR(normalized)); |
971 | 12.7k | if (r < 0) |
972 | 0 | return r; |
973 | 12.7k | } |
974 | 13.2k | } |
975 | | |
976 | 87.6k | n = 0; |
977 | 87.6k | continue; |
978 | 87.7k | } |
979 | | |
980 | | /* Check for compression (which is not allowed) */ |
981 | 21.1k | if (c > 63) |
982 | 281 | return -EBADMSG; |
983 | | |
984 | 20.8k | if (pos + c >= len) |
985 | 376 | return -EBADMSG; |
986 | | |
987 | 20.4k | if (!GREEDY_REALLOC(e, n + (n != 0) + DNS_LABEL_ESCAPED_MAX + 1U)) |
988 | 0 | return -ENOMEM; |
989 | | |
990 | 20.4k | if (n != 0) |
991 | 6.98k | e[n++] = '.'; |
992 | | |
993 | 20.4k | r = dns_label_escape((const char*) (opt + pos), c, e + n, DNS_LABEL_ESCAPED_MAX); |
994 | 20.4k | if (r < 0) |
995 | 0 | return r; |
996 | | |
997 | 20.4k | n += r; |
998 | 20.4k | } |
999 | | |
1000 | 4.78k | if (n > 0) /* Not properly NUL terminated */ |
1001 | 0 | return -EBADMSG; |
1002 | | |
1003 | 4.78k | return ndisc_option_add_dnssl(options, offset, l, lifetime); |
1004 | 4.78k | } |
1005 | | |
1006 | 1.48k | static int ndisc_option_build_dnssl(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) { |
1007 | 1.48k | int r; |
1008 | | |
1009 | 1.48k | assert(option); |
1010 | 1.48k | assert(option->type == SD_NDISC_OPTION_DNSSL); |
1011 | 1.48k | assert(ret); |
1012 | | |
1013 | 1.48k | size_t len = 8; |
1014 | 1.48k | STRV_FOREACH(s, option->dnssl.domains) |
1015 | 4.14k | len += strlen(*s) + 2; |
1016 | 1.48k | len = DIV_ROUND_UP(len, 8); |
1017 | | |
1018 | 1.48k | be32_t lifetime = usec_to_be32_sec(MIN(option->dnssl.lifetime, |
1019 | 1.48k | usec_sub_unsigned(option->dnssl.valid_until, timestamp))); |
1020 | | |
1021 | 1.48k | _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8); |
1022 | 1.48k | if (!buf) |
1023 | 0 | return -ENOMEM; |
1024 | | |
1025 | 1.48k | buf[0] = SD_NDISC_OPTION_DNSSL; |
1026 | 1.48k | buf[1] = len; |
1027 | 1.48k | buf[2] = 0; |
1028 | 1.48k | buf[3] = 0; |
1029 | 1.48k | memcpy(buf + 4, &lifetime, sizeof(be32_t)); |
1030 | | |
1031 | 1.48k | size_t remaining = len * 8 - 8; |
1032 | 1.48k | uint8_t *p = buf + 8; |
1033 | | |
1034 | 4.14k | STRV_FOREACH(s, option->dnssl.domains) { |
1035 | 4.14k | r = dns_name_to_wire_format(*s, p, remaining, /* canonical= */ false); |
1036 | 4.14k | if (r < 0) |
1037 | 0 | return r; |
1038 | | |
1039 | 4.14k | assert(remaining >= (size_t) r); |
1040 | 4.14k | p += r; |
1041 | 4.14k | remaining -= r; |
1042 | 4.14k | } |
1043 | | |
1044 | 1.48k | memzero(p, remaining); |
1045 | | |
1046 | 1.48k | *ret = TAKE_PTR(buf); |
1047 | 1.48k | return 0; |
1048 | 1.48k | } |
1049 | | |
1050 | 1.60k | int ndisc_option_add_captive_portal(Set **options, size_t offset, const char *portal) { |
1051 | 1.60k | assert(options); |
1052 | | |
1053 | 1.60k | if (isempty(portal)) |
1054 | 0 | return -EINVAL; |
1055 | | |
1056 | 1.60k | if (!in_charset(portal, URI_VALID)) |
1057 | 420 | return -EINVAL; |
1058 | | |
1059 | 1.18k | sd_ndisc_option *p = ndisc_option_get_by_type(*options, SD_NDISC_OPTION_CAPTIVE_PORTAL); |
1060 | 1.18k | if (p) { |
1061 | 398 | if (offset != 0) |
1062 | 398 | return -EEXIST; |
1063 | | |
1064 | 0 | return free_and_strdup(&p->captive_portal, portal); |
1065 | 398 | } |
1066 | | |
1067 | 782 | _cleanup_free_ char *copy = strdup(portal); |
1068 | 782 | if (!copy) |
1069 | 0 | return -ENOMEM; |
1070 | | |
1071 | 782 | p = ndisc_option_new(SD_NDISC_OPTION_CAPTIVE_PORTAL, offset); |
1072 | 782 | if (!p) |
1073 | 0 | return -ENOMEM; |
1074 | | |
1075 | 782 | p->captive_portal = TAKE_PTR(copy); |
1076 | | |
1077 | 782 | return ndisc_option_consume(options, p); |
1078 | 782 | } |
1079 | | |
1080 | 1.87k | static int ndisc_option_parse_captive_portal(Set **options, size_t offset, size_t len, const uint8_t *opt) { |
1081 | 1.87k | assert(options); |
1082 | 1.87k | assert(opt); |
1083 | | |
1084 | 1.87k | if (len < 8) |
1085 | 0 | return -EBADMSG; |
1086 | | |
1087 | 1.87k | if (opt[0] != SD_NDISC_OPTION_CAPTIVE_PORTAL) |
1088 | 0 | return -EBADMSG; |
1089 | | |
1090 | 1.87k | _cleanup_free_ char *portal = memdup_suffix0(opt + 2, len - 2); |
1091 | 1.87k | if (!portal) |
1092 | 0 | return -ENOMEM; |
1093 | | |
1094 | 1.87k | size_t size = strlen(portal); |
1095 | 1.87k | if (size == 0) |
1096 | 186 | return -EBADMSG; |
1097 | | |
1098 | | /* Check that the message is not truncated by an embedded NUL. |
1099 | | * NUL padding to a multiple of 8 is expected. */ |
1100 | 1.69k | if (DIV_ROUND_UP(size + 2, 8) * 8 != len && DIV_ROUND_UP(size + 3, 8) * 8 != len) |
1101 | 92 | return -EBADMSG; |
1102 | | |
1103 | 1.60k | return ndisc_option_add_captive_portal(options, offset, portal); |
1104 | 1.69k | } |
1105 | | |
1106 | 193 | static int ndisc_option_build_captive_portal(const sd_ndisc_option *option, uint8_t **ret) { |
1107 | 193 | assert(option); |
1108 | 193 | assert(option->type == SD_NDISC_OPTION_CAPTIVE_PORTAL); |
1109 | 193 | assert(ret); |
1110 | | |
1111 | 193 | size_t len_portal = strlen(option->captive_portal); |
1112 | 193 | size_t len = DIV_ROUND_UP(len_portal + 1 + 2, 8); |
1113 | | |
1114 | 193 | _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8); |
1115 | 193 | if (!buf) |
1116 | 0 | return -ENOMEM; |
1117 | | |
1118 | 193 | buf[0] = SD_NDISC_OPTION_CAPTIVE_PORTAL; |
1119 | 193 | buf[1] = len; |
1120 | | |
1121 | 193 | uint8_t *p = mempcpy(buf + 2, option->captive_portal, len_portal); |
1122 | 193 | size_t remaining = len * 8 - 2 - len_portal; |
1123 | | |
1124 | 193 | memzero(p, remaining); |
1125 | | |
1126 | 193 | *ret = TAKE_PTR(buf); |
1127 | 193 | return 0; |
1128 | 193 | } |
1129 | | |
1130 | | static const uint8_t prefix_length_code_to_prefix_length[_PREFIX_LENGTH_CODE_MAX] = { |
1131 | | [PREFIX_LENGTH_CODE_96] = 96, |
1132 | | [PREFIX_LENGTH_CODE_64] = 64, |
1133 | | [PREFIX_LENGTH_CODE_56] = 56, |
1134 | | [PREFIX_LENGTH_CODE_48] = 48, |
1135 | | [PREFIX_LENGTH_CODE_40] = 40, |
1136 | | [PREFIX_LENGTH_CODE_32] = 32, |
1137 | | }; |
1138 | | |
1139 | 3.48k | int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret) { |
1140 | 13.4k | for (size_t i = 0; i < ELEMENTSOF(prefix_length_code_to_prefix_length); i++) |
1141 | 13.4k | if (prefix_length_code_to_prefix_length[i] == prefixlen) { |
1142 | 3.48k | if (ret) |
1143 | 777 | *ret = i; |
1144 | 3.48k | return 0; |
1145 | 3.48k | } |
1146 | | |
1147 | 0 | return -EINVAL; |
1148 | 3.48k | } |
1149 | | |
1150 | 2.90k | static int pref64_lifetime_and_plc_parse(uint16_t lifetime_and_plc, uint8_t *ret_prefixlen, usec_t *ret_lifetime) { |
1151 | 2.90k | uint16_t plc = lifetime_and_plc & PREF64_PLC_MASK; |
1152 | 2.90k | if (plc >= _PREFIX_LENGTH_CODE_MAX) |
1153 | 196 | return -EINVAL; |
1154 | | |
1155 | 2.70k | if (ret_prefixlen) |
1156 | 2.70k | *ret_prefixlen = prefix_length_code_to_prefix_length[plc]; |
1157 | 2.70k | if (ret_lifetime) |
1158 | 2.70k | *ret_lifetime = (lifetime_and_plc & PREF64_SCALED_LIFETIME_MASK) * USEC_PER_SEC; |
1159 | 2.70k | return 0; |
1160 | 2.90k | } |
1161 | | |
1162 | | int ndisc_option_add_prefix64_internal( |
1163 | | Set **options, |
1164 | | size_t offset, |
1165 | | uint8_t prefixlen, |
1166 | | const struct in6_addr *prefix, |
1167 | | usec_t lifetime, |
1168 | 2.70k | usec_t valid_until) { |
1169 | | |
1170 | 2.70k | int r; |
1171 | | |
1172 | 2.70k | assert(options); |
1173 | 2.70k | assert(prefix); |
1174 | | |
1175 | 2.70k | r = pref64_prefix_length_to_plc(prefixlen, NULL); |
1176 | 2.70k | if (r < 0) |
1177 | 0 | return r; |
1178 | | |
1179 | 2.70k | if (lifetime > PREF64_MAX_LIFETIME_USEC) |
1180 | 0 | return -EINVAL; |
1181 | | |
1182 | 2.70k | struct in6_addr addr = *prefix; |
1183 | 2.70k | in6_addr_mask(&addr, prefixlen); |
1184 | | |
1185 | 2.70k | sd_ndisc_option *p = ndisc_option_get( |
1186 | 2.70k | *options, |
1187 | 2.70k | &(const sd_ndisc_option) { |
1188 | 2.70k | .type = SD_NDISC_OPTION_PREF64, |
1189 | 2.70k | .prefix64.prefixlen = prefixlen, |
1190 | 2.70k | .prefix64.prefix = addr, |
1191 | 2.70k | }); |
1192 | 2.70k | if (p) { |
1193 | 276 | if (offset != 0) |
1194 | 276 | return -EEXIST; |
1195 | | |
1196 | 0 | p->prefix64.lifetime = lifetime; |
1197 | 0 | p->prefix64.valid_until = valid_until; |
1198 | 0 | return 0; |
1199 | 276 | } |
1200 | | |
1201 | 2.43k | p = ndisc_option_new(SD_NDISC_OPTION_PREF64, offset); |
1202 | 2.43k | if (!p) |
1203 | 0 | return -ENOMEM; |
1204 | | |
1205 | 2.43k | p->prefix64 = (sd_ndisc_prefix64) { |
1206 | 2.43k | .prefixlen = prefixlen, |
1207 | 2.43k | .prefix = addr, |
1208 | 2.43k | .lifetime = lifetime, |
1209 | 2.43k | .valid_until = valid_until, |
1210 | 2.43k | }; |
1211 | | |
1212 | 2.43k | return ndisc_option_consume(options, p); |
1213 | 2.43k | } |
1214 | | |
1215 | 3.32k | static int ndisc_option_parse_prefix64(Set **options, size_t offset, size_t len, const uint8_t *opt) { |
1216 | 3.32k | int r; |
1217 | | |
1218 | 3.32k | assert(options); |
1219 | 3.32k | assert(opt); |
1220 | | |
1221 | 3.32k | if (len != 2*8) |
1222 | 417 | return -EBADMSG; |
1223 | | |
1224 | 2.90k | if (opt[0] != SD_NDISC_OPTION_PREF64) |
1225 | 0 | return -EBADMSG; |
1226 | | |
1227 | 2.90k | uint8_t prefixlen; |
1228 | 2.90k | usec_t lifetime; |
1229 | 2.90k | r = pref64_lifetime_and_plc_parse(unaligned_read_be16(opt + 2), &prefixlen, &lifetime); |
1230 | 2.90k | if (r < 0) |
1231 | 196 | return r; |
1232 | | |
1233 | 2.70k | struct in6_addr prefix; |
1234 | 2.70k | memcpy(&prefix, opt + 4, len - 4); |
1235 | 2.70k | in6_addr_mask(&prefix, prefixlen); |
1236 | | |
1237 | 2.70k | return ndisc_option_add_prefix64(options, offset, prefixlen, &prefix, lifetime); |
1238 | 2.90k | } |
1239 | | |
1240 | 777 | static int ndisc_option_build_prefix64(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) { |
1241 | 777 | int r; |
1242 | | |
1243 | 777 | assert(option); |
1244 | 777 | assert(option->type == SD_NDISC_OPTION_PREF64); |
1245 | 777 | assert(ret); |
1246 | | |
1247 | 777 | uint8_t code; |
1248 | 777 | r = pref64_prefix_length_to_plc(option->prefix64.prefixlen, &code); |
1249 | 777 | if (r < 0) |
1250 | 0 | return r; |
1251 | | |
1252 | 777 | uint16_t lifetime = (uint16_t) DIV_ROUND_UP(MIN(option->prefix64.lifetime, |
1253 | 777 | usec_sub_unsigned(option->prefix64.valid_until, timestamp)), |
1254 | 777 | USEC_PER_SEC) & PREF64_SCALED_LIFETIME_MASK; |
1255 | | |
1256 | 777 | _cleanup_free_ uint8_t *buf = new(uint8_t, 2 * 8); |
1257 | 777 | if (!buf) |
1258 | 0 | return -ENOMEM; |
1259 | | |
1260 | 777 | buf[0] = SD_NDISC_OPTION_PREF64; |
1261 | 777 | buf[1] = 2; |
1262 | 777 | unaligned_write_be16(buf + 2, lifetime | code); |
1263 | 777 | memcpy(buf + 4, &option->prefix64.prefix, 12); |
1264 | | |
1265 | 777 | *ret = TAKE_PTR(buf); |
1266 | 777 | return 0; |
1267 | 777 | } |
1268 | | |
1269 | | int ndisc_option_add_encrypted_dns_internal( |
1270 | | Set **options, |
1271 | | size_t offset, |
1272 | | sd_dns_resolver *res, |
1273 | | usec_t lifetime, |
1274 | 1.23k | usec_t valid_until) { |
1275 | 1.23k | assert(options); |
1276 | | |
1277 | 1.23k | sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_ENCRYPTED_DNS, offset); |
1278 | 1.23k | if (!p) |
1279 | 0 | return -ENOMEM; |
1280 | | |
1281 | 1.23k | p->encrypted_dns = (sd_ndisc_dnr) { |
1282 | 1.23k | .resolver = res, |
1283 | 1.23k | .lifetime = lifetime, |
1284 | 1.23k | .valid_until = valid_until, |
1285 | 1.23k | }; |
1286 | | |
1287 | 1.23k | return ndisc_option_consume(options, p); |
1288 | 1.23k | } |
1289 | | |
1290 | 7.30k | static int ndisc_get_dns_name(const uint8_t *optval, size_t optlen, char **ret) { |
1291 | 7.30k | _cleanup_free_ char *name = NULL; |
1292 | 7.30k | int r; |
1293 | | |
1294 | 7.30k | assert(optval || optlen == 0); |
1295 | 7.30k | assert(ret); |
1296 | | |
1297 | 7.30k | r = dns_name_from_wire_format(&optval, &optlen, &name); |
1298 | 7.30k | if (r < 0) |
1299 | 461 | return r; |
1300 | 6.84k | if (r == 0 || optlen != 0) |
1301 | 418 | return -EBADMSG; |
1302 | | |
1303 | 6.42k | *ret = TAKE_PTR(name); |
1304 | 6.42k | return r; |
1305 | 6.84k | } |
1306 | | |
1307 | 7.96k | static int ndisc_option_parse_encrypted_dns(Set **options, size_t offset, size_t len, const uint8_t *opt) { |
1308 | 7.96k | int r; |
1309 | | |
1310 | 7.96k | assert(options); |
1311 | 7.96k | assert(opt); |
1312 | 7.96k | _cleanup_(sd_dns_resolver_done) sd_dns_resolver res = {}; |
1313 | 7.96k | usec_t lifetime; |
1314 | 7.96k | size_t ilen; |
1315 | | |
1316 | | /* Every field up to and including adn must be present */ |
1317 | 7.96k | if (len < 2*8) |
1318 | 213 | return -EBADMSG; |
1319 | | |
1320 | 7.75k | if (opt[0] != SD_NDISC_OPTION_ENCRYPTED_DNS) |
1321 | 0 | return -EBADMSG; |
1322 | | |
1323 | 7.75k | size_t off = 2; |
1324 | | |
1325 | | /* Priority */ |
1326 | 7.75k | res.priority = unaligned_read_be16(opt + off); |
1327 | | /* Alias mode is not allowed */ |
1328 | 7.75k | if (res.priority == 0) |
1329 | 194 | return -EBADMSG; |
1330 | 7.55k | off += sizeof(uint16_t); |
1331 | | |
1332 | | /* Lifetime */ |
1333 | 7.55k | lifetime = unaligned_be32_sec_to_usec(opt + off, /* max_as_infinity= */ true); |
1334 | 7.55k | off += sizeof(uint32_t); |
1335 | | |
1336 | | /* adn field (length + dns-name) */ |
1337 | 7.55k | ilen = unaligned_read_be16(opt + off); |
1338 | 7.55k | off += sizeof(uint16_t); |
1339 | 7.55k | if (off + ilen > len) |
1340 | 255 | return -EBADMSG; |
1341 | | |
1342 | 7.30k | r = ndisc_get_dns_name(opt + off, ilen, &res.auth_name); |
1343 | 7.30k | if (r < 0) |
1344 | 879 | return r; |
1345 | 6.42k | r = dns_name_is_valid_ldh(res.auth_name); |
1346 | 6.42k | if (r < 0) |
1347 | 0 | return r; |
1348 | 6.42k | if (!r) |
1349 | 915 | return -EBADMSG; |
1350 | 5.50k | if (dns_name_is_root(res.auth_name)) |
1351 | 0 | return -EBADMSG; |
1352 | 5.50k | off += ilen; |
1353 | | |
1354 | | /* This is the last field in adn-only mode, sans padding */ |
1355 | 5.50k | if (8 * DIV_ROUND_UP(off, 8) == len && memeqzero(opt + off, len - off)) |
1356 | 469 | return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Received ADN-only encrypted DNS option, ignoring."); |
1357 | | |
1358 | | /* Fields following the variable (octets) length adn field are no longer certain to be aligned. */ |
1359 | | |
1360 | | /* addrs (length + packed struct in6_addr) */ |
1361 | 5.03k | if (off + sizeof(uint16_t) > len) |
1362 | 354 | return -EBADMSG; |
1363 | 4.68k | ilen = unaligned_read_be16(opt + off); |
1364 | 4.68k | off += sizeof(uint16_t); |
1365 | 4.68k | if (off + ilen > len || ilen % (sizeof(struct in6_addr)) != 0) |
1366 | 431 | return -EBADMSG; |
1367 | | |
1368 | 4.25k | size_t n_addrs = ilen / (sizeof(struct in6_addr)); |
1369 | 4.25k | if (n_addrs == 0) |
1370 | 198 | return -EBADMSG; |
1371 | 4.05k | res.addrs = new(union in_addr_union, n_addrs); |
1372 | 4.05k | if (!res.addrs) |
1373 | 0 | return -ENOMEM; |
1374 | | |
1375 | 9.53k | for (size_t i = 0; i < n_addrs; i++) { |
1376 | 5.58k | union in_addr_union addr; |
1377 | 5.58k | memcpy(&addr.in6, opt + off, sizeof(struct in6_addr)); |
1378 | 5.58k | if (in_addr_is_multicast(AF_INET6, &addr) || |
1379 | 5.50k | in_addr_is_localhost(AF_INET6, &addr)) |
1380 | 105 | return -EBADMSG; |
1381 | 5.47k | res.addrs[i] = addr; |
1382 | 5.47k | off += sizeof(struct in6_addr); |
1383 | 5.47k | } |
1384 | 3.95k | res.n_addrs = n_addrs; |
1385 | 3.95k | res.family = AF_INET6; |
1386 | | |
1387 | | /* SvcParam field. (length + SvcParams) */ |
1388 | 3.95k | if (off + sizeof(uint16_t) > len) |
1389 | 86 | return -EBADMSG; |
1390 | 3.86k | ilen = unaligned_read_be16(opt + off); |
1391 | 3.86k | off += sizeof(uint16_t); |
1392 | 3.86k | if (off + ilen > len) |
1393 | 221 | return -EBADMSG; |
1394 | | |
1395 | 3.64k | r = dnr_parse_svc_params(opt + off, ilen, &res); |
1396 | 3.64k | if (r < 0) |
1397 | 2.04k | return r; |
1398 | 1.59k | if (r == 0) /* This indicates a valid message we don't support */ |
1399 | 217 | return -EOPNOTSUPP; |
1400 | 1.38k | off += ilen; |
1401 | | |
1402 | | /* the remaining padding bytes must be zeroed */ |
1403 | 1.38k | if (len - off >= 8 || !memeqzero(opt + off, len - off)) |
1404 | 146 | return -EBADMSG; |
1405 | | |
1406 | 1.23k | sd_dns_resolver *new_res = new(sd_dns_resolver, 1); |
1407 | 1.23k | if (!new_res) |
1408 | 0 | return -ENOMEM; |
1409 | | |
1410 | 1.23k | *new_res = TAKE_STRUCT(res); |
1411 | | |
1412 | 1.23k | return ndisc_option_add_encrypted_dns(options, offset, new_res, lifetime); |
1413 | 1.23k | } |
1414 | | |
1415 | 566 | static int ndisc_option_build_encrypted_dns(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) { |
1416 | 566 | int r; |
1417 | | |
1418 | 566 | assert(option); |
1419 | 566 | assert(option->type == SD_NDISC_OPTION_ENCRYPTED_DNS); |
1420 | 566 | assert(ret); |
1421 | | |
1422 | 566 | size_t off, len, ilen, plen, poff; |
1423 | | |
1424 | | /* Everything up to adn field is required, so we need at least 2*8 bytes */ |
1425 | 566 | _cleanup_free_ uint8_t *buf = new(uint8_t, 2 * 8); |
1426 | 566 | if (!buf) |
1427 | 0 | return -ENOMEM; |
1428 | | |
1429 | 566 | _cleanup_strv_free_ char **alpns = NULL; |
1430 | 566 | const sd_dns_resolver *res = option->encrypted_dns.resolver; |
1431 | 566 | be32_t lifetime = usec_to_be32_sec(MIN(option->encrypted_dns.lifetime, |
1432 | 566 | usec_sub_unsigned(option->encrypted_dns.valid_until, timestamp))); |
1433 | | |
1434 | | /* Type (Length field filled in last) */ |
1435 | 566 | buf[0] = option->type; |
1436 | | |
1437 | | /* Priority */ |
1438 | 566 | off = 2; |
1439 | 566 | unaligned_write_be16(buf + off, res->priority); |
1440 | 566 | off += sizeof(be16_t); |
1441 | | |
1442 | | /* Lifetime */ |
1443 | 566 | memcpy(buf + off, &lifetime, sizeof(be32_t)); |
1444 | 566 | off += sizeof(be32_t); |
1445 | | |
1446 | | /* ADN */ |
1447 | | //FIXME can the wire format be longer than this? |
1448 | 566 | ilen = strlen(res->auth_name) + 2; |
1449 | | |
1450 | | /* From now on, there isn't guaranteed to be enough space to put each field */ |
1451 | 566 | if (!GREEDY_REALLOC(buf, off + sizeof(uint16_t) + ilen)) |
1452 | 0 | return -ENOMEM; |
1453 | | |
1454 | 566 | r = dns_name_to_wire_format(res->auth_name, buf + off + sizeof(uint16_t), ilen, /* canonical= */ false); |
1455 | 566 | if (r < 0) |
1456 | 0 | return r; |
1457 | 566 | unaligned_write_be16(buf + off, (uint16_t) r); |
1458 | 566 | off += sizeof(uint16_t) + r; |
1459 | | |
1460 | | /* ADN-only mode */ |
1461 | 566 | if (res->n_addrs == 0) |
1462 | 0 | goto padding; |
1463 | | |
1464 | | /* addrs */ |
1465 | 566 | if (size_multiply_overflow(sizeof(struct in6_addr), res->n_addrs)) |
1466 | 0 | return -ENOMEM; |
1467 | | |
1468 | 566 | ilen = res->n_addrs * sizeof(struct in6_addr); |
1469 | 566 | if (!GREEDY_REALLOC(buf, off + sizeof(uint16_t) + ilen)) |
1470 | 0 | return -ENOMEM; |
1471 | | |
1472 | 566 | unaligned_write_be16(buf + off, ilen); |
1473 | 566 | off += sizeof(uint16_t); |
1474 | | |
1475 | 771 | FOREACH_ARRAY(addr, res->addrs, res->n_addrs) { |
1476 | 771 | memcpy(buf + off, &addr->in6, sizeof(struct in6_addr)); |
1477 | 771 | off += sizeof(struct in6_addr); |
1478 | 771 | } |
1479 | | |
1480 | | /* SvcParam, MUST appear in order */ |
1481 | 566 | poff = off + sizeof(uint16_t); |
1482 | | |
1483 | | /* ALPN */ |
1484 | 566 | dns_resolver_transports_to_strv(res->transports, &alpns); |
1485 | | |
1486 | | /* res needs to have at least one valid transport */ |
1487 | 566 | if (strv_isempty(alpns)) |
1488 | 0 | return -EINVAL; |
1489 | | |
1490 | 566 | plen = 0; |
1491 | 566 | STRV_FOREACH(alpn, alpns) |
1492 | 782 | plen += sizeof(uint8_t) + strlen(*alpn); |
1493 | | |
1494 | 566 | if (!GREEDY_REALLOC(buf, poff + 2 * sizeof(uint16_t) + plen)) |
1495 | 0 | return -ENOMEM; |
1496 | | |
1497 | 566 | unaligned_write_be16(buf + poff, (uint16_t) DNS_SVC_PARAM_KEY_ALPN); |
1498 | 566 | poff += sizeof(uint16_t); |
1499 | 566 | unaligned_write_be16(buf + poff, plen); |
1500 | 566 | poff += sizeof(uint16_t); |
1501 | | |
1502 | 782 | STRV_FOREACH(alpn, alpns) { |
1503 | 782 | size_t alen = strlen(*alpn); |
1504 | 782 | buf[poff++] = alen; |
1505 | 782 | memcpy(buf + poff, *alpn, alen); |
1506 | 782 | poff += alen; |
1507 | 782 | } |
1508 | | |
1509 | | /* port */ |
1510 | 566 | if (res->port > 0) { |
1511 | 35 | plen = 2; |
1512 | 35 | if (!GREEDY_REALLOC(buf, poff + 2 * sizeof(uint16_t) + plen)) |
1513 | 0 | return -ENOMEM; |
1514 | | |
1515 | 35 | unaligned_write_be16(buf + poff, (uint16_t) DNS_SVC_PARAM_KEY_PORT); |
1516 | 35 | poff += sizeof(uint16_t); |
1517 | 35 | unaligned_write_be16(buf + poff, plen); |
1518 | 35 | poff += sizeof(uint16_t); |
1519 | 35 | unaligned_write_be16(buf + poff, res->port); |
1520 | 35 | poff += sizeof(uint16_t); |
1521 | 35 | } |
1522 | | |
1523 | | /* dohpath */ |
1524 | 566 | if (res->dohpath) { |
1525 | 94 | plen = strlen(res->dohpath); |
1526 | 94 | if (!GREEDY_REALLOC(buf, poff + 2 * sizeof(uint16_t) + plen)) |
1527 | 0 | return -ENOMEM; |
1528 | | |
1529 | 94 | unaligned_write_be16(buf + poff, (uint16_t) DNS_SVC_PARAM_KEY_DOHPATH); |
1530 | 94 | poff += sizeof(uint16_t); |
1531 | 94 | unaligned_write_be16(buf + poff, plen); |
1532 | 94 | poff += sizeof(uint16_t); |
1533 | 94 | memcpy(buf + poff, res->dohpath, plen); |
1534 | 94 | poff += plen; |
1535 | 94 | } |
1536 | | |
1537 | 566 | unaligned_write_be16(buf + off, LESS_BY(poff, off)); |
1538 | 566 | off = poff; |
1539 | | |
1540 | 566 | padding: |
1541 | 566 | len = DIV_ROUND_UP(off, 8); |
1542 | 566 | if (!GREEDY_REALLOC(buf, 8*len)) |
1543 | 0 | return -ENOMEM; |
1544 | 566 | memzero(buf + off, 8*len - off); |
1545 | | |
1546 | 566 | buf[1] = len; |
1547 | 566 | *ret = TAKE_PTR(buf); |
1548 | 566 | return 0; |
1549 | 566 | } |
1550 | | |
1551 | 47.2k | static int ndisc_option_parse_default(Set **options, size_t offset, size_t len, const uint8_t *opt) { |
1552 | 47.2k | assert(options); |
1553 | 47.2k | assert(opt); |
1554 | 47.2k | assert(len > 0); |
1555 | | |
1556 | 47.2k | sd_ndisc_option *p = ndisc_option_new(opt[0], offset); |
1557 | 47.2k | if (!p) |
1558 | 0 | return -ENOMEM; |
1559 | | |
1560 | 47.2k | return ndisc_option_consume(options, p); |
1561 | 47.2k | } |
1562 | | |
1563 | 20.1k | static int ndisc_header_size(uint8_t icmp6_type) { |
1564 | 20.1k | switch (icmp6_type) { |
1565 | 11.8k | case ND_ROUTER_SOLICIT: |
1566 | 11.8k | return sizeof(struct nd_router_solicit); |
1567 | 7.11k | case ND_ROUTER_ADVERT: |
1568 | 7.11k | return sizeof(struct nd_router_advert); |
1569 | 508 | case ND_NEIGHBOR_SOLICIT: |
1570 | 508 | return sizeof(struct nd_neighbor_solicit); |
1571 | 386 | case ND_NEIGHBOR_ADVERT: |
1572 | 386 | return sizeof(struct nd_neighbor_advert); |
1573 | 272 | case ND_REDIRECT: |
1574 | 272 | return sizeof(struct nd_redirect); |
1575 | 18 | default: |
1576 | 18 | return -EINVAL; |
1577 | 20.1k | } |
1578 | 20.1k | } |
1579 | | |
1580 | 10.1k | int ndisc_parse_options(ICMP6Packet *packet, Set **ret_options) { |
1581 | 10.1k | _cleanup_set_free_ Set *options = NULL; |
1582 | 10.1k | int r; |
1583 | | |
1584 | 10.1k | assert(packet); |
1585 | 10.1k | assert(ret_options); |
1586 | | |
1587 | 10.1k | r = icmp6_packet_get_type(packet); |
1588 | 10.1k | if (r < 0) |
1589 | 0 | return r; |
1590 | | |
1591 | 10.1k | r = ndisc_header_size(r); |
1592 | 10.1k | if (r < 0) |
1593 | 18 | return -EBADMSG; |
1594 | 10.1k | size_t header_size = r; |
1595 | | |
1596 | 10.1k | if (packet->raw_size < header_size) |
1597 | 32 | return -EBADMSG; |
1598 | | |
1599 | 98.5k | for (size_t length, offset = header_size; offset < packet->raw_size; offset += length) { |
1600 | 88.8k | uint8_t type; |
1601 | 88.8k | const uint8_t *opt; |
1602 | | |
1603 | 88.8k | r = ndisc_option_parse(packet, offset, &type, &length, &opt); |
1604 | 88.8k | if (r < 0) |
1605 | 373 | return log_debug_errno(r, "Failed to parse NDisc option header: %m"); |
1606 | | |
1607 | 88.5k | switch (type) { |
1608 | 542 | case 0: |
1609 | 542 | r = -EBADMSG; |
1610 | 542 | break; |
1611 | | |
1612 | 4.45k | case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: |
1613 | 5.94k | case SD_NDISC_OPTION_TARGET_LL_ADDRESS: |
1614 | 5.94k | r = ndisc_option_parse_link_layer_address(&options, offset, length, opt); |
1615 | 5.94k | break; |
1616 | | |
1617 | 3.47k | case SD_NDISC_OPTION_PREFIX_INFORMATION: |
1618 | 3.47k | r = ndisc_option_parse_prefix(&options, offset, length, opt); |
1619 | 3.47k | break; |
1620 | | |
1621 | 501 | case SD_NDISC_OPTION_REDIRECTED_HEADER: |
1622 | 501 | r = ndisc_option_parse_redirected_header(&options, offset, length, opt); |
1623 | 501 | break; |
1624 | | |
1625 | 1.94k | case SD_NDISC_OPTION_MTU: |
1626 | 1.94k | r = ndisc_option_parse_mtu(&options, offset, length, opt); |
1627 | 1.94k | break; |
1628 | | |
1629 | 2.37k | case SD_NDISC_OPTION_HOME_AGENT: |
1630 | 2.37k | r = ndisc_option_parse_home_agent(&options, offset, length, opt); |
1631 | 2.37k | break; |
1632 | | |
1633 | 4.61k | case SD_NDISC_OPTION_ROUTE_INFORMATION: |
1634 | 4.61k | r = ndisc_option_parse_route(&options, offset, length, opt); |
1635 | 4.61k | break; |
1636 | | |
1637 | 1.33k | case SD_NDISC_OPTION_RDNSS: |
1638 | 1.33k | r = ndisc_option_parse_rdnss(&options, offset, length, opt); |
1639 | 1.33k | break; |
1640 | | |
1641 | 1.77k | case SD_NDISC_OPTION_FLAGS_EXTENSION: |
1642 | 1.77k | r = ndisc_option_parse_flags_extension(&options, offset, length, opt); |
1643 | 1.77k | break; |
1644 | | |
1645 | 5.56k | case SD_NDISC_OPTION_DNSSL: |
1646 | 5.56k | r = ndisc_option_parse_dnssl(&options, offset, length, opt); |
1647 | 5.56k | break; |
1648 | | |
1649 | 1.87k | case SD_NDISC_OPTION_CAPTIVE_PORTAL: |
1650 | 1.87k | r = ndisc_option_parse_captive_portal(&options, offset, length, opt); |
1651 | 1.87k | break; |
1652 | | |
1653 | 3.32k | case SD_NDISC_OPTION_PREF64: |
1654 | 3.32k | r = ndisc_option_parse_prefix64(&options, offset, length, opt); |
1655 | 3.32k | break; |
1656 | | |
1657 | 7.96k | case SD_NDISC_OPTION_ENCRYPTED_DNS: |
1658 | 7.96k | r = ndisc_option_parse_encrypted_dns(&options, offset, length, opt); |
1659 | 7.96k | break; |
1660 | | |
1661 | 47.2k | default: |
1662 | 47.2k | r = ndisc_option_parse_default(&options, offset, length, opt); |
1663 | 88.5k | } |
1664 | 88.5k | if (r == -ENOMEM) |
1665 | 0 | return log_oom_debug(); |
1666 | 88.5k | if (r < 0) |
1667 | 88.5k | log_debug_errno(r, "Failed to parse NDisc option %u, ignoring: %m", type); |
1668 | 88.5k | } |
1669 | | |
1670 | 9.71k | *ret_options = TAKE_PTR(options); |
1671 | 9.71k | return 0; |
1672 | 10.0k | } |
1673 | | |
1674 | 35.4k | sd_ndisc_option* ndisc_option_get(Set *options, const sd_ndisc_option *p) { |
1675 | 35.4k | return set_get(options, ASSERT_PTR(p)); |
1676 | 35.4k | } |
1677 | | |
1678 | 26.1k | sd_ndisc_option* ndisc_option_get_by_type(Set *options, uint8_t type) { |
1679 | 26.1k | return ndisc_option_get(options, &(const sd_ndisc_option) { .type = type }); |
1680 | 26.1k | } |
1681 | | |
1682 | 6.61k | int ndisc_option_get_mac(Set *options, uint8_t type, struct ether_addr *ret) { |
1683 | 6.61k | assert(IN_SET(type, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS)); |
1684 | | |
1685 | 6.61k | sd_ndisc_option *p = ndisc_option_get_by_type(options, type); |
1686 | 6.61k | if (!p) |
1687 | 2.57k | return -ENODATA; |
1688 | | |
1689 | 4.03k | if (ret) |
1690 | 3.73k | *ret = p->mac; |
1691 | 4.03k | return 0; |
1692 | 6.61k | } |
1693 | | |
1694 | 0 | void ndisc_option_remove(Set *options, const sd_ndisc_option *p) { |
1695 | 0 | ndisc_option_free(set_remove(options, ASSERT_PTR(p))); |
1696 | 0 | } |
1697 | | |
1698 | 9.96k | int ndisc_send(int fd, const struct in6_addr *dst, const struct icmp6_hdr *hdr, Set *options, usec_t timestamp) { |
1699 | 9.96k | int r; |
1700 | | |
1701 | 9.96k | assert(fd >= 0); |
1702 | 9.96k | assert(dst); |
1703 | 9.96k | assert(hdr); |
1704 | | |
1705 | 9.96k | size_t n; |
1706 | 9.96k | _cleanup_free_ sd_ndisc_option **list = NULL; |
1707 | 9.96k | r = set_dump_sorted(options, (void***) &list, &n); |
1708 | 9.96k | if (r < 0) |
1709 | 0 | return r; |
1710 | | |
1711 | 9.96k | struct iovec *iov = NULL; |
1712 | 9.96k | size_t n_iov = 0; |
1713 | 9.96k | CLEANUP_ARRAY(iov, n_iov, iovec_array_free); |
1714 | | |
1715 | 9.96k | iov = new(struct iovec, 1 + n); |
1716 | 9.96k | if (!iov) |
1717 | 0 | return -ENOMEM; |
1718 | | |
1719 | 9.96k | r = ndisc_header_size(hdr->icmp6_type); |
1720 | 9.96k | if (r < 0) |
1721 | 0 | return r; |
1722 | 9.96k | size_t hdr_size = r; |
1723 | | |
1724 | 9.96k | _cleanup_free_ uint8_t *copy = newdup(uint8_t, hdr, hdr_size); |
1725 | 9.96k | if (!copy) |
1726 | 0 | return -ENOMEM; |
1727 | | |
1728 | 9.96k | iov[n_iov++] = IOVEC_MAKE(TAKE_PTR(copy), hdr_size); |
1729 | | |
1730 | 35.4k | FOREACH_ARRAY(p, list, n) { |
1731 | 35.4k | _cleanup_free_ uint8_t *buf = NULL; |
1732 | 35.4k | sd_ndisc_option *option = *p; |
1733 | | |
1734 | 35.4k | switch (option->type) { |
1735 | 0 | case 0: |
1736 | 0 | r = ndisc_option_build_raw(option, &buf); |
1737 | 0 | break; |
1738 | | |
1739 | 6.94k | case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: |
1740 | 7.16k | case SD_NDISC_OPTION_TARGET_LL_ADDRESS: |
1741 | 7.16k | r = ndisc_option_build_link_layer_address(option, &buf); |
1742 | 7.16k | break; |
1743 | | |
1744 | 843 | case SD_NDISC_OPTION_PREFIX_INFORMATION: |
1745 | 843 | r = ndisc_option_build_prefix(option, timestamp, &buf); |
1746 | 843 | break; |
1747 | | |
1748 | 26 | case SD_NDISC_OPTION_REDIRECTED_HEADER: |
1749 | 26 | r = ndisc_option_build_redirected_header(option, &buf); |
1750 | 26 | break; |
1751 | | |
1752 | 273 | case SD_NDISC_OPTION_MTU: |
1753 | 273 | r = ndisc_option_build_mtu(option, &buf); |
1754 | 273 | break; |
1755 | | |
1756 | 304 | case SD_NDISC_OPTION_HOME_AGENT: |
1757 | 304 | r = ndisc_option_build_home_agent(option, timestamp, &buf); |
1758 | 304 | break; |
1759 | | |
1760 | 905 | case SD_NDISC_OPTION_ROUTE_INFORMATION: |
1761 | 905 | r = ndisc_option_build_route(option, timestamp, &buf); |
1762 | 905 | break; |
1763 | | |
1764 | 331 | case SD_NDISC_OPTION_RDNSS: |
1765 | 331 | r = ndisc_option_build_rdnss(option, timestamp, &buf); |
1766 | 331 | break; |
1767 | | |
1768 | 225 | case SD_NDISC_OPTION_FLAGS_EXTENSION: |
1769 | 225 | r = ndisc_option_build_flags_extension(option, &buf); |
1770 | 225 | break; |
1771 | | |
1772 | 1.48k | case SD_NDISC_OPTION_DNSSL: |
1773 | 1.48k | r = ndisc_option_build_dnssl(option, timestamp, &buf); |
1774 | 1.48k | break; |
1775 | | |
1776 | 193 | case SD_NDISC_OPTION_CAPTIVE_PORTAL: |
1777 | 193 | r = ndisc_option_build_captive_portal(option, &buf); |
1778 | 193 | break; |
1779 | | |
1780 | 777 | case SD_NDISC_OPTION_PREF64: |
1781 | 777 | r = ndisc_option_build_prefix64(option, timestamp, &buf); |
1782 | 777 | break; |
1783 | | |
1784 | 566 | case SD_NDISC_OPTION_ENCRYPTED_DNS: |
1785 | 566 | r = ndisc_option_build_encrypted_dns(option, timestamp, &buf); |
1786 | 566 | break; |
1787 | | |
1788 | 22.3k | default: |
1789 | 22.3k | continue; |
1790 | 35.4k | } |
1791 | 13.0k | if (r == -ENOMEM) |
1792 | 0 | return log_oom_debug(); |
1793 | 13.0k | if (r < 0) |
1794 | 13.0k | log_debug_errno(r, "Failed to build NDisc option %u, ignoring: %m", option->type); |
1795 | | |
1796 | 13.0k | iov[n_iov++] = IOVEC_MAKE(buf, buf[1] * 8); |
1797 | 13.0k | TAKE_PTR(buf); |
1798 | 13.0k | } |
1799 | | |
1800 | 9.96k | return icmp6_send(fd, dst, iov, n_iov); |
1801 | 9.96k | } |