]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-zone.c
Merge pull request #7588 from poettering/resolve-route-tweak
[thirdparty/systemd.git] / src / resolve / resolved-dns-zone.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2014 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include "alloc-util.h"
22 #include "dns-domain.h"
23 #include "list.h"
24 #include "resolved-dns-packet.h"
25 #include "resolved-dns-zone.h"
26 #include "resolved-dnssd.h"
27 #include "string-util.h"
28
29 /* Never allow more than 1K entries */
30 #define ZONE_MAX 1024
31
32 void dns_zone_item_probe_stop(DnsZoneItem *i) {
33 DnsTransaction *t;
34 assert(i);
35
36 if (!i->probe_transaction)
37 return;
38
39 t = i->probe_transaction;
40 i->probe_transaction = NULL;
41
42 set_remove(t->notify_zone_items, i);
43 set_remove(t->notify_zone_items_done, i);
44 dns_transaction_gc(t);
45 }
46
47 static void dns_zone_item_free(DnsZoneItem *i) {
48 if (!i)
49 return;
50
51 dns_zone_item_probe_stop(i);
52 dns_resource_record_unref(i->rr);
53
54 free(i);
55 }
56
57 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
58
59 static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) {
60 DnsZoneItem *first;
61
62 assert(z);
63
64 if (!i)
65 return;
66
67 first = hashmap_get(z->by_key, i->rr->key);
68 LIST_REMOVE(by_key, first, i);
69 if (first)
70 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
71 else
72 hashmap_remove(z->by_key, i->rr->key);
73
74 first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
75 LIST_REMOVE(by_name, first, i);
76 if (first)
77 assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
78 else
79 hashmap_remove(z->by_name, dns_resource_key_name(i->rr->key));
80
81 dns_zone_item_free(i);
82 }
83
84 void dns_zone_flush(DnsZone *z) {
85 DnsZoneItem *i;
86
87 assert(z);
88
89 while ((i = hashmap_first(z->by_key)))
90 dns_zone_item_remove_and_free(z, i);
91
92 assert(hashmap_size(z->by_key) == 0);
93 assert(hashmap_size(z->by_name) == 0);
94
95 z->by_key = hashmap_free(z->by_key);
96 z->by_name = hashmap_free(z->by_name);
97 }
98
99 DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
100 DnsZoneItem *i;
101
102 assert(z);
103 assert(rr);
104
105 LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key))
106 if (dns_resource_record_equal(i->rr, rr) > 0)
107 return i;
108
109 return NULL;
110 }
111
112 void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) {
113 DnsZoneItem *i;
114
115 assert(z);
116 assert(rr);
117
118 i = dns_zone_get(z, rr);
119 if (i)
120 dns_zone_item_remove_and_free(z, i);
121 }
122
123 int dns_zone_remove_rrs_by_key(DnsZone *z, DnsResourceKey *key) {
124 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
125 DnsResourceRecord *rr;
126 bool tentative;
127 int r;
128
129 r = dns_zone_lookup(z, key, 0, &answer, &soa, &tentative);
130 if (r < 0)
131 return r;
132
133 DNS_ANSWER_FOREACH(rr, answer)
134 dns_zone_remove_rr(z, rr);
135
136 return 0;
137 }
138
139 static int dns_zone_init(DnsZone *z) {
140 int r;
141
142 assert(z);
143
144 r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops);
145 if (r < 0)
146 return r;
147
148 r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops);
149 if (r < 0)
150 return r;
151
152 return 0;
153 }
154
155 static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
156 DnsZoneItem *first;
157 int r;
158
159 first = hashmap_get(z->by_key, i->rr->key);
160 if (first) {
161 LIST_PREPEND(by_key, first, i);
162 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
163 } else {
164 r = hashmap_put(z->by_key, i->rr->key, i);
165 if (r < 0)
166 return r;
167 }
168
169 first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
170 if (first) {
171 LIST_PREPEND(by_name, first, i);
172 assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
173 } else {
174 r = hashmap_put(z->by_name, dns_resource_key_name(i->rr->key), i);
175 if (r < 0)
176 return r;
177 }
178
179 return 0;
180 }
181
182 static int dns_zone_item_probe_start(DnsZoneItem *i) {
183 DnsTransaction *t;
184 int r;
185
186 assert(i);
187
188 if (i->probe_transaction)
189 return 0;
190
191 t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)), false);
192 if (!t) {
193 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
194
195 key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key));
196 if (!key)
197 return -ENOMEM;
198
199 r = dns_transaction_new(&t, i->scope, key);
200 if (r < 0)
201 return r;
202 }
203
204 r = set_ensure_allocated(&t->notify_zone_items, NULL);
205 if (r < 0)
206 goto gc;
207
208 r = set_ensure_allocated(&t->notify_zone_items_done, NULL);
209 if (r < 0)
210 goto gc;
211
212 r = set_put(t->notify_zone_items, i);
213 if (r < 0)
214 goto gc;
215
216 i->probe_transaction = t;
217 t->probing = true;
218
219 if (t->state == DNS_TRANSACTION_NULL) {
220
221 i->block_ready++;
222 r = dns_transaction_go(t);
223 i->block_ready--;
224
225 if (r < 0) {
226 dns_zone_item_probe_stop(i);
227 return r;
228 }
229 }
230
231 dns_zone_item_notify(i);
232 return 0;
233
234 gc:
235 dns_transaction_gc(t);
236 return r;
237 }
238
239 int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
240 _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
241 DnsZoneItem *existing;
242 int r;
243
244 assert(z);
245 assert(s);
246 assert(rr);
247
248 if (dns_class_is_pseudo(rr->key->class))
249 return -EINVAL;
250 if (dns_type_is_pseudo(rr->key->type))
251 return -EINVAL;
252
253 existing = dns_zone_get(z, rr);
254 if (existing)
255 return 0;
256
257 r = dns_zone_init(z);
258 if (r < 0)
259 return r;
260
261 i = new0(DnsZoneItem, 1);
262 if (!i)
263 return -ENOMEM;
264
265 i->scope = s;
266 i->rr = dns_resource_record_ref(rr);
267 i->probing_enabled = probe;
268
269 r = dns_zone_link_item(z, i);
270 if (r < 0)
271 return r;
272
273 if (probe) {
274 DnsZoneItem *first, *j;
275 bool established = false;
276
277 /* Check if there's already an RR with the same name
278 * established. If so, it has been probed already, and
279 * we don't ned to probe again. */
280
281 LIST_FIND_HEAD(by_name, i, first);
282 LIST_FOREACH(by_name, j, first) {
283 if (i == j)
284 continue;
285
286 if (j->state == DNS_ZONE_ITEM_ESTABLISHED)
287 established = true;
288 }
289
290 if (established)
291 i->state = DNS_ZONE_ITEM_ESTABLISHED;
292 else {
293 i->state = DNS_ZONE_ITEM_PROBING;
294
295 r = dns_zone_item_probe_start(i);
296 if (r < 0) {
297 dns_zone_item_remove_and_free(z, i);
298 i = NULL;
299 return r;
300 }
301 }
302 } else
303 i->state = DNS_ZONE_ITEM_ESTABLISHED;
304
305 i = NULL;
306 return 0;
307 }
308
309 static int dns_zone_add_authenticated_answer(DnsAnswer *a, DnsZoneItem *i, int ifindex) {
310 DnsAnswerFlags flags;
311
312 /* From RFC 6762, Section 10.2
313 * "They (the rules about when to set the cache-flush bit) apply to
314 * startup announcements as described in Section 8.3, "Announcing",
315 * and to responses generated as a result of receiving query messages."
316 * So, set the cache-flush bit for mDNS answers except for DNS-SD
317 * service enumeration PTRs described in RFC 6763, Section 4.1. */
318 if (i->scope->protocol == DNS_PROTOCOL_MDNS &&
319 !dns_resource_key_is_dnssd_ptr(i->rr->key))
320 flags = DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHE_FLUSH;
321 else
322 flags = DNS_ANSWER_AUTHENTICATED;
323
324 return dns_answer_add(a, i->rr, ifindex, flags);
325 }
326
327 int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
328 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
329 unsigned n_answer = 0;
330 DnsZoneItem *j, *first;
331 bool tentative = true, need_soa = false;
332 int r;
333
334 /* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the
335 * ifindex field in the answer with it */
336
337 assert(z);
338 assert(key);
339 assert(ret_answer);
340
341 /* First iteration, count what we have */
342
343 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
344 bool found = false, added = false;
345 int k;
346
347 /* If this is a generic match, then we have to
348 * go through the list by the name and look
349 * for everything manually */
350
351 first = hashmap_get(z->by_name, dns_resource_key_name(key));
352 LIST_FOREACH(by_name, j, first) {
353 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
354 continue;
355
356 found = true;
357
358 k = dns_resource_key_match_rr(key, j->rr, NULL);
359 if (k < 0)
360 return k;
361 if (k > 0) {
362 n_answer++;
363 added = true;
364 }
365
366 }
367
368 if (found && !added)
369 need_soa = true;
370
371 } else {
372 bool found = false;
373
374 /* If this is a specific match, then look for
375 * the right key immediately */
376
377 first = hashmap_get(z->by_key, key);
378 LIST_FOREACH(by_key, j, first) {
379 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
380 continue;
381
382 found = true;
383 n_answer++;
384 }
385
386 if (!found) {
387 first = hashmap_get(z->by_name, dns_resource_key_name(key));
388 LIST_FOREACH(by_name, j, first) {
389 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
390 continue;
391
392 need_soa = true;
393 break;
394 }
395 }
396 }
397
398 if (n_answer <= 0 && !need_soa)
399 goto return_empty;
400
401 if (n_answer > 0) {
402 answer = dns_answer_new(n_answer);
403 if (!answer)
404 return -ENOMEM;
405 }
406
407 if (need_soa) {
408 soa = dns_answer_new(1);
409 if (!soa)
410 return -ENOMEM;
411 }
412
413 /* Second iteration, actually add the RRs to the answers */
414 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
415 bool found = false, added = false;
416 int k;
417
418 first = hashmap_get(z->by_name, dns_resource_key_name(key));
419 LIST_FOREACH(by_name, j, first) {
420 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
421 continue;
422
423 found = true;
424
425 if (j->state != DNS_ZONE_ITEM_PROBING)
426 tentative = false;
427
428 k = dns_resource_key_match_rr(key, j->rr, NULL);
429 if (k < 0)
430 return k;
431 if (k > 0) {
432 r = dns_zone_add_authenticated_answer(answer, j, ifindex);
433 if (r < 0)
434 return r;
435
436 added = true;
437 }
438 }
439
440 if (found && !added) {
441 r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
442 if (r < 0)
443 return r;
444 }
445 } else {
446 bool found = false;
447
448 first = hashmap_get(z->by_key, key);
449 LIST_FOREACH(by_key, j, first) {
450 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
451 continue;
452
453 found = true;
454
455 if (j->state != DNS_ZONE_ITEM_PROBING)
456 tentative = false;
457
458 r = dns_zone_add_authenticated_answer(answer, j, ifindex);
459 if (r < 0)
460 return r;
461 }
462
463 if (!found) {
464 bool add_soa = false;
465
466 first = hashmap_get(z->by_name, dns_resource_key_name(key));
467 LIST_FOREACH(by_name, j, first) {
468 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
469 continue;
470
471 if (j->state != DNS_ZONE_ITEM_PROBING)
472 tentative = false;
473
474 add_soa = true;
475 }
476
477 if (add_soa) {
478 r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
479 if (r < 0)
480 return r;
481 }
482 }
483 }
484
485 /* If the caller sets ret_tentative to NULL, then use this as
486 * indication to not return tentative entries */
487
488 if (!ret_tentative && tentative)
489 goto return_empty;
490
491 *ret_answer = answer;
492 answer = NULL;
493
494 if (ret_soa) {
495 *ret_soa = soa;
496 soa = NULL;
497 }
498
499 if (ret_tentative)
500 *ret_tentative = tentative;
501
502 return 1;
503
504 return_empty:
505 *ret_answer = NULL;
506
507 if (ret_soa)
508 *ret_soa = NULL;
509
510 if (ret_tentative)
511 *ret_tentative = false;
512
513 return 0;
514 }
515
516 void dns_zone_item_conflict(DnsZoneItem *i) {
517 assert(i);
518
519 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
520 return;
521
522 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr)));
523
524 dns_zone_item_probe_stop(i);
525
526 /* Withdraw the conflict item */
527 i->state = DNS_ZONE_ITEM_WITHDRAWN;
528
529 dnssd_signal_conflict(i->scope->manager, dns_resource_key_name(i->rr->key));
530
531 /* Maybe change the hostname */
532 if (manager_is_own_hostname(i->scope->manager, dns_resource_key_name(i->rr->key)) > 0)
533 manager_next_hostname(i->scope->manager);
534 }
535
536 void dns_zone_item_notify(DnsZoneItem *i) {
537 assert(i);
538 assert(i->probe_transaction);
539
540 if (i->block_ready > 0)
541 return;
542
543 if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING))
544 return;
545
546 if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
547 bool we_lost = false;
548
549 /* The probe got a successful reply. If we so far
550 * weren't established we just give up.
551 *
552 * In LLMNR case if we already
553 * were established, and the peer has the
554 * lexicographically larger IP address we continue
555 * and defend it. */
556
557 if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
558 log_debug("Got a successful probe for not yet established RR, we lost.");
559 we_lost = true;
560 } else if (i->probe_transaction->scope->protocol == DNS_PROTOCOL_LLMNR) {
561 assert(i->probe_transaction->received);
562 we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
563 if (we_lost)
564 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
565 }
566
567 if (we_lost) {
568 dns_zone_item_conflict(i);
569 return;
570 }
571
572 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
573 }
574
575 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr)));
576
577 dns_zone_item_probe_stop(i);
578 i->state = DNS_ZONE_ITEM_ESTABLISHED;
579 }
580
581 static int dns_zone_item_verify(DnsZoneItem *i) {
582 int r;
583
584 assert(i);
585
586 if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
587 return 0;
588
589 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr)));
590
591 i->state = DNS_ZONE_ITEM_VERIFYING;
592 r = dns_zone_item_probe_start(i);
593 if (r < 0) {
594 log_error_errno(r, "Failed to start probing for verifying RR: %m");
595 i->state = DNS_ZONE_ITEM_ESTABLISHED;
596 return r;
597 }
598
599 return 0;
600 }
601
602 int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
603 DnsZoneItem *i, *first;
604 int c = 0;
605
606 assert(zone);
607 assert(rr);
608
609 /* This checks whether a response RR we received from somebody
610 * else is one that we actually thought was uniquely ours. If
611 * so, we'll verify our RRs. */
612
613 /* No conflict if we don't have the name at all. */
614 first = hashmap_get(zone->by_name, dns_resource_key_name(rr->key));
615 if (!first)
616 return 0;
617
618 /* No conflict if we have the exact same RR */
619 if (dns_zone_get(zone, rr))
620 return 0;
621
622 /* No conflict if it is DNS-SD RR used for service enumeration. */
623 if (dns_resource_key_is_dnssd_ptr(rr->key))
624 return 0;
625
626 /* OK, somebody else has RRs for the same name. Yuck! Let's
627 * start probing again */
628
629 LIST_FOREACH(by_name, i, first) {
630 if (dns_resource_record_equal(i->rr, rr))
631 continue;
632
633 dns_zone_item_verify(i);
634 c++;
635 }
636
637 return c;
638 }
639
640 int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
641 DnsZoneItem *i, *first;
642 int c = 0;
643
644 assert(zone);
645
646 /* Somebody else notified us about a possible conflict. Let's
647 * verify if that's true. */
648
649 first = hashmap_get(zone->by_name, dns_resource_key_name(key));
650 if (!first)
651 return 0;
652
653 LIST_FOREACH(by_name, i, first) {
654 dns_zone_item_verify(i);
655 c++;
656 }
657
658 return c;
659 }
660
661 void dns_zone_verify_all(DnsZone *zone) {
662 DnsZoneItem *i;
663 Iterator iterator;
664
665 assert(zone);
666
667 HASHMAP_FOREACH(i, zone->by_key, iterator) {
668 DnsZoneItem *j;
669
670 LIST_FOREACH(by_key, j, i)
671 dns_zone_item_verify(j);
672 }
673 }
674
675 void dns_zone_dump(DnsZone *zone, FILE *f) {
676 Iterator iterator;
677 DnsZoneItem *i;
678
679 if (!zone)
680 return;
681
682 if (!f)
683 f = stdout;
684
685 HASHMAP_FOREACH(i, zone->by_key, iterator) {
686 DnsZoneItem *j;
687
688 LIST_FOREACH(by_key, j, i) {
689 const char *t;
690
691 t = dns_resource_record_to_string(j->rr);
692 if (!t) {
693 log_oom();
694 continue;
695 }
696
697 fputc('\t', f);
698 fputs(t, f);
699 fputc('\n', f);
700 }
701 }
702 }
703
704 bool dns_zone_is_empty(DnsZone *zone) {
705 if (!zone)
706 return true;
707
708 return hashmap_isempty(zone->by_key);
709 }
710
711 bool dns_zone_contains_name(DnsZone *z, const char *name) {
712 DnsZoneItem *i, *first;
713
714 first = hashmap_get(z->by_name, name);
715 if (!first)
716 return false;
717
718 LIST_FOREACH(by_name, i, first) {
719 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
720 continue;
721
722 return true;
723 }
724
725 return false;
726 }