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