]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-answer.c
Merge pull request #11841 from keszybz/dns-packet-speedup
[thirdparty/systemd.git] / src / resolve / resolved-dns-answer.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include "alloc-util.h"
4 #include "dns-domain.h"
5 #include "resolved-dns-answer.h"
6 #include "resolved-dns-dnssec.h"
7 #include "string-util.h"
8
9 DnsAnswer *dns_answer_new(size_t n) {
10 DnsAnswer *a;
11
12 a = malloc0(offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * n);
13 if (!a)
14 return NULL;
15
16 a->n_ref = 1;
17 a->n_allocated = n;
18
19 return a;
20 }
21
22 static void dns_answer_flush(DnsAnswer *a) {
23 DnsResourceRecord *rr;
24
25 if (!a)
26 return;
27
28 DNS_ANSWER_FOREACH(rr, a)
29 dns_resource_record_unref(rr);
30
31 a->n_rrs = 0;
32 }
33
34 static DnsAnswer *dns_answer_free(DnsAnswer *a) {
35 assert(a);
36
37 dns_answer_flush(a);
38 return mfree(a);
39 }
40
41 DEFINE_TRIVIAL_REF_UNREF_FUNC(DnsAnswer, dns_answer, dns_answer_free);
42
43 static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) {
44 assert(rr);
45
46 if (!a)
47 return -ENOSPC;
48
49 if (a->n_rrs >= a->n_allocated)
50 return -ENOSPC;
51
52 a->items[a->n_rrs++] = (DnsAnswerItem) {
53 .rr = dns_resource_record_ref(rr),
54 .ifindex = ifindex,
55 .flags = flags,
56 };
57
58 return 1;
59 }
60
61 static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) {
62 DnsResourceRecord *rr;
63 DnsAnswerFlags flags;
64 int ifindex, r;
65
66 DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, source) {
67 r = dns_answer_add_raw(a, rr, ifindex, flags);
68 if (r < 0)
69 return r;
70 }
71
72 return 0;
73 }
74
75 int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) {
76 size_t i;
77 int r;
78
79 assert(rr);
80
81 if (!a)
82 return -ENOSPC;
83 if (a->n_ref > 1)
84 return -EBUSY;
85
86 for (i = 0; i < a->n_rrs; i++) {
87 if (a->items[i].ifindex != ifindex)
88 continue;
89
90 r = dns_resource_key_equal(a->items[i].rr->key, rr->key);
91 if (r < 0)
92 return r;
93 if (r == 0)
94 continue;
95
96 /* There's already an RR of the same RRset in place! Let's see if the TTLs more or less
97 * match. We don't really care if they match precisely, but we do care whether one is 0 and
98 * the other is not. See RFC 2181, Section 5.2. */
99 if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0))
100 return -EINVAL;
101
102 r = dns_resource_record_payload_equal(a->items[i].rr, rr);
103 if (r < 0)
104 return r;
105 if (r == 0)
106 continue;
107
108 /* Entry already exists, keep the entry with the higher RR. */
109 if (rr->ttl > a->items[i].rr->ttl) {
110 dns_resource_record_ref(rr);
111 dns_resource_record_unref(a->items[i].rr);
112 a->items[i].rr = rr;
113 }
114
115 a->items[i].flags |= flags;
116 return 0;
117 }
118
119 return dns_answer_add_raw(a, rr, ifindex, flags);
120 }
121
122 static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) {
123 DnsResourceRecord *rr;
124 DnsAnswerFlags flags;
125 int ifindex, r;
126
127 DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, b) {
128 r = dns_answer_add(a, rr, ifindex, flags);
129 if (r < 0)
130 return r;
131 }
132
133 return 0;
134 }
135
136 int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) {
137 int r;
138
139 assert(a);
140 assert(rr);
141
142 r = dns_answer_reserve_or_clone(a, 1);
143 if (r < 0)
144 return r;
145
146 return dns_answer_add(*a, rr, ifindex, flags);
147 }
148
149 int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex) {
150 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *soa = NULL;
151
152 soa = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_SOA, name);
153 if (!soa)
154 return -ENOMEM;
155
156 soa->ttl = ttl;
157
158 soa->soa.mname = strdup(name);
159 if (!soa->soa.mname)
160 return -ENOMEM;
161
162 soa->soa.rname = strappend("root.", name);
163 if (!soa->soa.rname)
164 return -ENOMEM;
165
166 soa->soa.serial = 1;
167 soa->soa.refresh = 1;
168 soa->soa.retry = 1;
169 soa->soa.expire = 1;
170 soa->soa.minimum = ttl;
171
172 return dns_answer_add(a, soa, ifindex, DNS_ANSWER_AUTHENTICATED);
173 }
174
175 int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) {
176 DnsAnswerFlags flags = 0, i_flags;
177 DnsResourceRecord *i;
178 bool found = false;
179 int r;
180
181 assert(key);
182
183 DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) {
184 r = dns_resource_key_match_rr(key, i, NULL);
185 if (r < 0)
186 return r;
187 if (r == 0)
188 continue;
189
190 if (!ret_flags)
191 return 1;
192
193 if (found)
194 flags &= i_flags;
195 else {
196 flags = i_flags;
197 found = true;
198 }
199 }
200
201 if (ret_flags)
202 *ret_flags = flags;
203
204 return found;
205 }
206
207 int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a) {
208 DnsResourceRecord *i;
209
210 DNS_ANSWER_FOREACH(i, a) {
211 if (IN_SET(i->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3))
212 return true;
213 }
214
215 return false;
216 }
217
218 int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone) {
219 DnsResourceRecord *rr;
220 int r;
221
222 /* Checks whether the specified answer contains at least one NSEC3 RR in the specified zone */
223
224 DNS_ANSWER_FOREACH(rr, answer) {
225 const char *p;
226
227 if (rr->key->type != DNS_TYPE_NSEC3)
228 continue;
229
230 p = dns_resource_key_name(rr->key);
231 r = dns_name_parent(&p);
232 if (r < 0)
233 return r;
234 if (r == 0)
235 continue;
236
237 r = dns_name_equal(p, zone);
238 if (r != 0)
239 return r;
240 }
241
242 return false;
243 }
244
245 int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) {
246 DnsResourceRecord *rr, *soa = NULL;
247 DnsAnswerFlags rr_flags, soa_flags = 0;
248 int r;
249
250 assert(key);
251
252 /* For a SOA record we can never find a matching SOA record */
253 if (key->type == DNS_TYPE_SOA)
254 return 0;
255
256 DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) {
257 r = dns_resource_key_match_soa(key, rr->key);
258 if (r < 0)
259 return r;
260 if (r > 0) {
261
262 if (soa) {
263 r = dns_name_endswith(dns_resource_key_name(rr->key), dns_resource_key_name(soa->key));
264 if (r < 0)
265 return r;
266 if (r > 0)
267 continue;
268 }
269
270 soa = rr;
271 soa_flags = rr_flags;
272 }
273 }
274
275 if (!soa)
276 return 0;
277
278 if (ret)
279 *ret = soa;
280 if (flags)
281 *flags = soa_flags;
282
283 return 1;
284 }
285
286 int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) {
287 DnsResourceRecord *rr;
288 DnsAnswerFlags rr_flags;
289 int r;
290
291 assert(key);
292
293 /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */
294 if (!dns_type_may_redirect(key->type))
295 return 0;
296
297 DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) {
298 r = dns_resource_key_match_cname_or_dname(key, rr->key, NULL);
299 if (r < 0)
300 return r;
301 if (r > 0) {
302 if (ret)
303 *ret = rr;
304 if (flags)
305 *flags = rr_flags;
306 return 1;
307 }
308 }
309
310 return 0;
311 }
312
313 int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret) {
314 _cleanup_(dns_answer_unrefp) DnsAnswer *k = NULL;
315 int r;
316
317 assert(ret);
318
319 if (dns_answer_size(a) <= 0) {
320 *ret = dns_answer_ref(b);
321 return 0;
322 }
323
324 if (dns_answer_size(b) <= 0) {
325 *ret = dns_answer_ref(a);
326 return 0;
327 }
328
329 k = dns_answer_new(a->n_rrs + b->n_rrs);
330 if (!k)
331 return -ENOMEM;
332
333 r = dns_answer_add_raw_all(k, a);
334 if (r < 0)
335 return r;
336
337 r = dns_answer_add_all(k, b);
338 if (r < 0)
339 return r;
340
341 *ret = TAKE_PTR(k);
342
343 return 0;
344 }
345
346 int dns_answer_extend(DnsAnswer **a, DnsAnswer *b) {
347 DnsAnswer *merged;
348 int r;
349
350 assert(a);
351
352 r = dns_answer_merge(*a, b, &merged);
353 if (r < 0)
354 return r;
355
356 dns_answer_unref(*a);
357 *a = merged;
358
359 return 0;
360 }
361
362 int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) {
363 bool found = false, other = false;
364 DnsResourceRecord *rr;
365 size_t i;
366 int r;
367
368 assert(a);
369 assert(key);
370
371 /* Remove all entries matching the specified key from *a */
372
373 DNS_ANSWER_FOREACH(rr, *a) {
374 r = dns_resource_key_equal(rr->key, key);
375 if (r < 0)
376 return r;
377 if (r > 0)
378 found = true;
379 else
380 other = true;
381
382 if (found && other)
383 break;
384 }
385
386 if (!found)
387 return 0;
388
389 if (!other) {
390 *a = dns_answer_unref(*a); /* Return NULL for the empty answer */
391 return 1;
392 }
393
394 if ((*a)->n_ref > 1) {
395 _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL;
396 DnsAnswerFlags flags;
397 int ifindex;
398
399 copy = dns_answer_new((*a)->n_rrs);
400 if (!copy)
401 return -ENOMEM;
402
403 DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) {
404 r = dns_resource_key_equal(rr->key, key);
405 if (r < 0)
406 return r;
407 if (r > 0)
408 continue;
409
410 r = dns_answer_add_raw(copy, rr, ifindex, flags);
411 if (r < 0)
412 return r;
413 }
414
415 dns_answer_unref(*a);
416 *a = TAKE_PTR(copy);
417
418 return 1;
419 }
420
421 /* Only a single reference, edit in-place */
422
423 i = 0;
424 for (;;) {
425 if (i >= (*a)->n_rrs)
426 break;
427
428 r = dns_resource_key_equal((*a)->items[i].rr->key, key);
429 if (r < 0)
430 return r;
431 if (r > 0) {
432 /* Kill this entry */
433
434 dns_resource_record_unref((*a)->items[i].rr);
435 memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1));
436 (*a)->n_rrs--;
437 continue;
438
439 } else
440 /* Keep this entry */
441 i++;
442 }
443
444 return 1;
445 }
446
447 int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) {
448 bool found = false, other = false;
449 DnsResourceRecord *rr;
450 size_t i;
451 int r;
452
453 assert(a);
454 assert(rm);
455
456 /* Remove all entries matching the specified RR from *a */
457
458 DNS_ANSWER_FOREACH(rr, *a) {
459 r = dns_resource_record_equal(rr, rm);
460 if (r < 0)
461 return r;
462 if (r > 0)
463 found = true;
464 else
465 other = true;
466
467 if (found && other)
468 break;
469 }
470
471 if (!found)
472 return 0;
473
474 if (!other) {
475 *a = dns_answer_unref(*a); /* Return NULL for the empty answer */
476 return 1;
477 }
478
479 if ((*a)->n_ref > 1) {
480 _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL;
481 DnsAnswerFlags flags;
482 int ifindex;
483
484 copy = dns_answer_new((*a)->n_rrs);
485 if (!copy)
486 return -ENOMEM;
487
488 DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) {
489 r = dns_resource_record_equal(rr, rm);
490 if (r < 0)
491 return r;
492 if (r > 0)
493 continue;
494
495 r = dns_answer_add_raw(copy, rr, ifindex, flags);
496 if (r < 0)
497 return r;
498 }
499
500 dns_answer_unref(*a);
501 *a = TAKE_PTR(copy);
502
503 return 1;
504 }
505
506 /* Only a single reference, edit in-place */
507
508 i = 0;
509 for (;;) {
510 if (i >= (*a)->n_rrs)
511 break;
512
513 r = dns_resource_record_equal((*a)->items[i].rr, rm);
514 if (r < 0)
515 return r;
516 if (r > 0) {
517 /* Kill this entry */
518
519 dns_resource_record_unref((*a)->items[i].rr);
520 memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1));
521 (*a)->n_rrs--;
522 continue;
523
524 } else
525 /* Keep this entry */
526 i++;
527 }
528
529 return 1;
530 }
531
532 int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags) {
533 DnsResourceRecord *rr_source;
534 int ifindex_source, r;
535 DnsAnswerFlags flags_source;
536
537 assert(a);
538 assert(key);
539
540 /* Copy all RRs matching the specified key from source into *a */
541
542 DNS_ANSWER_FOREACH_FULL(rr_source, ifindex_source, flags_source, source) {
543
544 r = dns_resource_key_equal(rr_source->key, key);
545 if (r < 0)
546 return r;
547 if (r == 0)
548 continue;
549
550 /* Make space for at least one entry */
551 r = dns_answer_reserve_or_clone(a, 1);
552 if (r < 0)
553 return r;
554
555 r = dns_answer_add(*a, rr_source, ifindex_source, flags_source|or_flags);
556 if (r < 0)
557 return r;
558 }
559
560 return 0;
561 }
562
563 int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags) {
564 int r;
565
566 assert(to);
567 assert(from);
568 assert(key);
569
570 r = dns_answer_copy_by_key(to, *from, key, or_flags);
571 if (r < 0)
572 return r;
573
574 return dns_answer_remove_by_key(from, key);
575 }
576
577 void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) {
578 DnsAnswerItem *items;
579 size_t i, start, end;
580
581 if (!a)
582 return;
583
584 if (a->n_rrs <= 1)
585 return;
586
587 start = 0;
588 end = a->n_rrs-1;
589
590 /* RFC 4795, Section 2.6 suggests we should order entries
591 * depending on whether the sender is a link-local address. */
592
593 items = newa(DnsAnswerItem, a->n_rrs);
594 for (i = 0; i < a->n_rrs; i++) {
595
596 if (a->items[i].rr->key->class == DNS_CLASS_IN &&
597 ((a->items[i].rr->key->type == DNS_TYPE_A && in_addr_is_link_local(AF_INET, (union in_addr_union*) &a->items[i].rr->a.in_addr) != prefer_link_local) ||
598 (a->items[i].rr->key->type == DNS_TYPE_AAAA && in_addr_is_link_local(AF_INET6, (union in_addr_union*) &a->items[i].rr->aaaa.in6_addr) != prefer_link_local)))
599 /* Order address records that are not preferred to the end of the array */
600 items[end--] = a->items[i];
601 else
602 /* Order all other records to the beginning of the array */
603 items[start++] = a->items[i];
604 }
605
606 assert(start == end+1);
607 memcpy(a->items, items, sizeof(DnsAnswerItem) * a->n_rrs);
608 }
609
610 int dns_answer_reserve(DnsAnswer **a, size_t n_free) {
611 DnsAnswer *n;
612
613 assert(a);
614
615 if (n_free <= 0)
616 return 0;
617
618 if (*a) {
619 size_t ns;
620
621 if ((*a)->n_ref > 1)
622 return -EBUSY;
623
624 ns = (*a)->n_rrs + n_free;
625
626 if ((*a)->n_allocated >= ns)
627 return 0;
628
629 /* Allocate more than we need */
630 ns *= 2;
631
632 n = realloc(*a, offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * ns);
633 if (!n)
634 return -ENOMEM;
635
636 n->n_allocated = ns;
637 } else {
638 n = dns_answer_new(n_free);
639 if (!n)
640 return -ENOMEM;
641 }
642
643 *a = n;
644 return 0;
645 }
646
647 int dns_answer_reserve_or_clone(DnsAnswer **a, size_t n_free) {
648 _cleanup_(dns_answer_unrefp) DnsAnswer *n = NULL;
649 int r;
650
651 assert(a);
652
653 /* Tries to extend the DnsAnswer object. And if that's not
654 * possible, since we are not the sole owner, then allocate a
655 * new, appropriately sized one. Either way, after this call
656 * the object will only have a single reference, and has room
657 * for at least the specified number of RRs. */
658
659 r = dns_answer_reserve(a, n_free);
660 if (r != -EBUSY)
661 return r;
662
663 assert(*a);
664
665 n = dns_answer_new(((*a)->n_rrs + n_free) * 2);
666 if (!n)
667 return -ENOMEM;
668
669 r = dns_answer_add_raw_all(n, *a);
670 if (r < 0)
671 return r;
672
673 dns_answer_unref(*a);
674 *a = TAKE_PTR(n);
675
676 return 0;
677 }
678
679 void dns_answer_dump(DnsAnswer *answer, FILE *f) {
680 DnsResourceRecord *rr;
681 DnsAnswerFlags flags;
682 int ifindex;
683
684 if (!f)
685 f = stdout;
686
687 DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) {
688 const char *t;
689
690 fputc('\t', f);
691
692 t = dns_resource_record_to_string(rr);
693 if (!t) {
694 log_oom();
695 continue;
696 }
697
698 fputs(t, f);
699
700 if (ifindex != 0 || flags & (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE|DNS_ANSWER_SHARED_OWNER))
701 fputs("\t;", f);
702
703 if (ifindex != 0)
704 printf(" ifindex=%i", ifindex);
705 if (flags & DNS_ANSWER_AUTHENTICATED)
706 fputs(" authenticated", f);
707 if (flags & DNS_ANSWER_CACHEABLE)
708 fputs(" cachable", f);
709 if (flags & DNS_ANSWER_SHARED_OWNER)
710 fputs(" shared-owner", f);
711
712 fputc('\n', f);
713 }
714 }
715
716 int dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname) {
717 DnsResourceRecord *rr;
718 int r;
719
720 assert(cname);
721
722 /* Checks whether the answer contains a DNAME record that indicates that the specified CNAME record is
723 * synthesized from it */
724
725 if (cname->key->type != DNS_TYPE_CNAME)
726 return 0;
727
728 DNS_ANSWER_FOREACH(rr, a) {
729 _cleanup_free_ char *n = NULL;
730
731 if (rr->key->type != DNS_TYPE_DNAME)
732 continue;
733 if (rr->key->class != cname->key->class)
734 continue;
735
736 r = dns_name_change_suffix(cname->cname.name, rr->dname.name, dns_resource_key_name(rr->key), &n);
737 if (r < 0)
738 return r;
739 if (r == 0)
740 continue;
741
742 r = dns_name_equal(n, dns_resource_key_name(cname->key));
743 if (r < 0)
744 return r;
745 if (r > 0)
746 return 1;
747 }
748
749 return 0;
750 }