1:255.13-alt1
[systemd_ALT.git] / src / resolve / resolved-dns-zone.c
blobf533f972fc955418177718f2c01264bd62638b7e
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include "alloc-util.h"
4 #include "dns-domain.h"
5 #include "list.h"
6 #include "resolved-dns-packet.h"
7 #include "resolved-dns-zone.h"
8 #include "resolved-dnssd.h"
9 #include "resolved-manager.h"
10 #include "string-util.h"
12 /* Never allow more than 1K entries */
13 #define ZONE_MAX 1024
15 void dns_zone_item_probe_stop(DnsZoneItem *i) {
16 DnsTransaction *t;
17 assert(i);
19 if (!i->probe_transaction)
20 return;
22 t = TAKE_PTR(i->probe_transaction);
24 set_remove(t->notify_zone_items, i);
25 set_remove(t->notify_zone_items_done, i);
26 dns_transaction_gc(t);
29 static DnsZoneItem* dns_zone_item_free(DnsZoneItem *i) {
30 if (!i)
31 return NULL;
33 dns_zone_item_probe_stop(i);
34 dns_resource_record_unref(i->rr);
36 return mfree(i);
38 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
40 static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) {
41 DnsZoneItem *first;
43 assert(z);
45 if (!i)
46 return;
48 first = hashmap_get(z->by_key, i->rr->key);
49 LIST_REMOVE(by_key, first, i);
50 if (first)
51 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
52 else
53 hashmap_remove(z->by_key, i->rr->key);
55 first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
56 LIST_REMOVE(by_name, first, i);
57 if (first)
58 assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
59 else
60 hashmap_remove(z->by_name, dns_resource_key_name(i->rr->key));
62 dns_zone_item_free(i);
65 void dns_zone_flush(DnsZone *z) {
66 DnsZoneItem *i;
68 assert(z);
70 while ((i = hashmap_first(z->by_key)))
71 dns_zone_item_remove_and_free(z, i);
73 assert(hashmap_size(z->by_key) == 0);
74 assert(hashmap_size(z->by_name) == 0);
76 z->by_key = hashmap_free(z->by_key);
77 z->by_name = hashmap_free(z->by_name);
80 DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
81 assert(z);
82 assert(rr);
84 LIST_FOREACH(by_key, i, (DnsZoneItem*) hashmap_get(z->by_key, rr->key))
85 if (dns_resource_record_equal(i->rr, rr) > 0)
86 return i;
88 return NULL;
91 void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) {
92 DnsZoneItem *i;
94 assert(z);
96 if (!rr)
97 return;
99 i = dns_zone_get(z, rr);
100 if (i)
101 dns_zone_item_remove_and_free(z, i);
104 int dns_zone_remove_rrs_by_key(DnsZone *z, DnsResourceKey *key) {
105 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
106 DnsResourceRecord *rr;
107 bool tentative;
108 int r;
110 r = dns_zone_lookup(z, key, 0, &answer, &soa, &tentative);
111 if (r < 0)
112 return r;
114 DNS_ANSWER_FOREACH(rr, answer)
115 dns_zone_remove_rr(z, rr);
117 return 0;
120 static int dns_zone_init(DnsZone *z) {
121 int r;
123 assert(z);
125 r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops);
126 if (r < 0)
127 return r;
129 r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops);
130 if (r < 0)
131 return r;
133 return 0;
136 static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
137 DnsZoneItem *first;
138 int r;
140 first = hashmap_get(z->by_key, i->rr->key);
141 if (first) {
142 LIST_PREPEND(by_key, first, i);
143 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
144 } else {
145 r = hashmap_put(z->by_key, i->rr->key, i);
146 if (r < 0)
147 return r;
150 first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
151 if (first) {
152 LIST_PREPEND(by_name, first, i);
153 assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
154 } else {
155 r = hashmap_put(z->by_name, dns_resource_key_name(i->rr->key), i);
156 if (r < 0)
157 return r;
160 return 0;
163 static int dns_zone_item_probe_start(DnsZoneItem *i) {
164 _cleanup_(dns_transaction_gcp) DnsTransaction *t = NULL;
165 int r;
167 assert(i);
169 if (i->probe_transaction)
170 return 0;
172 t = dns_scope_find_transaction(
173 i->scope,
174 &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)),
175 SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE);
176 if (!t) {
177 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
179 key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key));
180 if (!key)
181 return -ENOMEM;
183 r = dns_transaction_new(&t, i->scope, key, NULL, SD_RESOLVED_NO_CACHE|SD_RESOLVED_NO_ZONE);
184 if (r < 0)
185 return r;
188 r = set_ensure_allocated(&t->notify_zone_items_done, NULL);
189 if (r < 0)
190 return r;
192 r = set_ensure_put(&t->notify_zone_items, NULL, i);
193 if (r < 0)
194 return r;
196 t->probing = true;
197 i->probe_transaction = TAKE_PTR(t);
199 if (i->probe_transaction->state == DNS_TRANSACTION_NULL) {
200 i->block_ready++;
201 r = dns_transaction_go(i->probe_transaction);
202 i->block_ready--;
204 if (r < 0) {
205 dns_zone_item_probe_stop(i);
206 return r;
210 dns_zone_item_notify(i);
211 return 0;
214 int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
215 _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
216 DnsZoneItem *existing;
217 int r;
219 assert(z);
220 assert(s);
221 assert(rr);
223 if (dns_class_is_pseudo(rr->key->class))
224 return -EINVAL;
225 if (dns_type_is_pseudo(rr->key->type))
226 return -EINVAL;
228 existing = dns_zone_get(z, rr);
229 if (existing)
230 return 0;
232 r = dns_zone_init(z);
233 if (r < 0)
234 return r;
236 i = new(DnsZoneItem, 1);
237 if (!i)
238 return -ENOMEM;
240 *i = (DnsZoneItem) {
241 .scope = s,
242 .rr = dns_resource_record_ref(rr),
243 .probing_enabled = probe,
246 r = dns_zone_link_item(z, i);
247 if (r < 0)
248 return r;
250 if (probe) {
251 bool established = false;
253 /* Check if there's already an RR with the same name
254 * established. If so, it has been probed already, and
255 * we don't need to probe again. */
257 LIST_FOREACH_OTHERS(by_name, j, i)
258 if (j->state == DNS_ZONE_ITEM_ESTABLISHED)
259 established = true;
261 if (established)
262 i->state = DNS_ZONE_ITEM_ESTABLISHED;
263 else {
264 i->state = DNS_ZONE_ITEM_PROBING;
266 r = dns_zone_item_probe_start(i);
267 if (r < 0) {
268 dns_zone_item_remove_and_free(z, i);
269 i = NULL;
270 return r;
273 } else
274 i->state = DNS_ZONE_ITEM_ESTABLISHED;
276 i = NULL;
277 return 0;
280 static int dns_zone_add_authenticated_answer(DnsAnswer *a, DnsZoneItem *i, int ifindex) {
281 DnsAnswerFlags flags;
283 /* From RFC 6762, Section 10.2
284 * "They (the rules about when to set the cache-flush bit) apply to
285 * startup announcements as described in Section 8.3, "Announcing",
286 * and to responses generated as a result of receiving query messages."
287 * So, set the cache-flush bit for mDNS answers except for DNS-SD
288 * service enumeration PTRs described in RFC 6763, Section 4.1. */
289 if (i->scope->protocol == DNS_PROTOCOL_MDNS &&
290 !dns_resource_key_is_dnssd_ptr(i->rr->key))
291 flags = DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHE_FLUSH;
292 else
293 flags = DNS_ANSWER_AUTHENTICATED;
295 return dns_answer_add(a, i->rr, ifindex, flags, NULL);
298 int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
299 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
300 unsigned n_answer = 0;
301 DnsZoneItem *first;
302 bool tentative = true, need_soa = false;
303 int r;
305 /* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the
306 * ifindex field in the answer with it */
308 assert(z);
309 assert(key);
310 assert(ret_answer);
312 /* First iteration, count what we have */
314 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
315 bool found = false, added = false;
316 int k;
318 /* If this is a generic match, then we have to
319 * go through the list by the name and look
320 * for everything manually */
322 first = hashmap_get(z->by_name, dns_resource_key_name(key));
323 LIST_FOREACH(by_name, j, first) {
324 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
325 continue;
327 found = true;
329 k = dns_resource_key_match_rr(key, j->rr, NULL);
330 if (k < 0)
331 return k;
332 if (k > 0) {
333 n_answer++;
334 added = true;
339 if (found && !added)
340 need_soa = true;
342 } else {
343 bool found = false;
345 /* If this is a specific match, then look for
346 * the right key immediately */
348 first = hashmap_get(z->by_key, key);
349 LIST_FOREACH(by_key, j, first) {
350 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
351 continue;
353 found = true;
354 n_answer++;
357 if (!found) {
358 first = hashmap_get(z->by_name, dns_resource_key_name(key));
359 LIST_FOREACH(by_name, j, first) {
360 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
361 continue;
363 need_soa = true;
364 break;
369 if (n_answer <= 0 && !need_soa)
370 goto return_empty;
372 if (n_answer > 0) {
373 answer = dns_answer_new(n_answer);
374 if (!answer)
375 return -ENOMEM;
378 if (need_soa) {
379 soa = dns_answer_new(1);
380 if (!soa)
381 return -ENOMEM;
384 /* Second iteration, actually add the RRs to the answers */
385 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
386 bool found = false, added = false;
387 int k;
389 first = hashmap_get(z->by_name, dns_resource_key_name(key));
390 LIST_FOREACH(by_name, j, first) {
391 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
392 continue;
394 found = true;
396 if (j->state != DNS_ZONE_ITEM_PROBING)
397 tentative = false;
399 k = dns_resource_key_match_rr(key, j->rr, NULL);
400 if (k < 0)
401 return k;
402 if (k > 0) {
403 r = dns_zone_add_authenticated_answer(answer, j, ifindex);
404 if (r < 0)
405 return r;
407 added = true;
411 if (found && !added) {
412 r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
413 if (r < 0)
414 return r;
416 } else {
417 bool found = false;
419 first = hashmap_get(z->by_key, key);
420 LIST_FOREACH(by_key, j, first) {
421 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
422 continue;
424 found = true;
426 if (j->state != DNS_ZONE_ITEM_PROBING)
427 tentative = false;
429 r = dns_zone_add_authenticated_answer(answer, j, ifindex);
430 if (r < 0)
431 return r;
434 if (!found) {
435 bool add_soa = false;
437 first = hashmap_get(z->by_name, dns_resource_key_name(key));
438 LIST_FOREACH(by_name, j, first) {
439 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
440 continue;
442 if (j->state != DNS_ZONE_ITEM_PROBING)
443 tentative = false;
445 add_soa = true;
448 if (add_soa) {
449 r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
450 if (r < 0)
451 return r;
456 /* If the caller sets ret_tentative to NULL, then use this as
457 * indication to not return tentative entries */
459 if (!ret_tentative && tentative)
460 goto return_empty;
462 *ret_answer = TAKE_PTR(answer);
464 if (ret_soa)
465 *ret_soa = TAKE_PTR(soa);
467 if (ret_tentative)
468 *ret_tentative = tentative;
470 return 1;
472 return_empty:
473 *ret_answer = NULL;
475 if (ret_soa)
476 *ret_soa = NULL;
478 if (ret_tentative)
479 *ret_tentative = false;
481 return 0;
484 void dns_zone_item_conflict(DnsZoneItem *i) {
485 assert(i);
487 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
488 return;
490 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr)));
492 dns_zone_item_probe_stop(i);
494 /* Withdraw the conflict item */
495 i->state = DNS_ZONE_ITEM_WITHDRAWN;
497 (void) dnssd_signal_conflict(i->scope->manager, dns_resource_key_name(i->rr->key));
499 /* Maybe change the hostname */
500 if (manager_is_own_hostname(i->scope->manager, dns_resource_key_name(i->rr->key)) > 0)
501 manager_next_hostname(i->scope->manager);
504 void dns_zone_item_notify(DnsZoneItem *i) {
505 assert(i);
506 assert(i->probe_transaction);
508 if (i->block_ready > 0)
509 return;
511 if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING))
512 return;
514 if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
515 bool we_lost = false;
517 /* The probe got a successful reply. If we so far
518 * weren't established we just give up.
520 * In LLMNR case if we already
521 * were established, and the peer has the
522 * lexicographically larger IP address we continue
523 * and defend it. */
525 if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
526 log_debug("Got a successful probe for not yet established RR, we lost.");
527 we_lost = true;
528 } else if (i->probe_transaction->scope->protocol == DNS_PROTOCOL_LLMNR) {
529 assert(i->probe_transaction->received);
530 we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
531 if (we_lost)
532 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
535 if (we_lost) {
536 dns_zone_item_conflict(i);
537 return;
540 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
543 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr)));
545 dns_zone_item_probe_stop(i);
546 i->state = DNS_ZONE_ITEM_ESTABLISHED;
549 static int dns_zone_item_verify(DnsZoneItem *i) {
550 int r;
552 assert(i);
554 if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
555 return 0;
557 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr)));
559 i->state = DNS_ZONE_ITEM_VERIFYING;
560 r = dns_zone_item_probe_start(i);
561 if (r < 0) {
562 log_error_errno(r, "Failed to start probing for verifying RR: %m");
563 i->state = DNS_ZONE_ITEM_ESTABLISHED;
564 return r;
567 return 0;
570 int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
571 DnsZoneItem *first;
572 int c = 0;
574 assert(zone);
575 assert(rr);
577 /* This checks whether a response RR we received from somebody
578 * else is one that we actually thought was uniquely ours. If
579 * so, we'll verify our RRs. */
581 /* No conflict if we don't have the name at all. */
582 first = hashmap_get(zone->by_name, dns_resource_key_name(rr->key));
583 if (!first)
584 return 0;
586 /* No conflict if we have the exact same RR */
587 if (dns_zone_get(zone, rr))
588 return 0;
590 /* No conflict if it is DNS-SD RR used for service enumeration. */
591 if (dns_resource_key_is_dnssd_ptr(rr->key))
592 return 0;
594 /* OK, somebody else has RRs for the same name. Yuck! Let's
595 * start probing again */
597 LIST_FOREACH(by_name, i, first) {
598 if (dns_resource_record_equal(i->rr, rr))
599 continue;
601 dns_zone_item_verify(i);
602 c++;
605 return c;
608 int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
609 DnsZoneItem *first;
610 int c = 0;
612 assert(zone);
614 /* Somebody else notified us about a possible conflict. Let's
615 * verify if that's true. */
617 first = hashmap_get(zone->by_name, dns_resource_key_name(key));
618 if (!first)
619 return 0;
621 LIST_FOREACH(by_name, i, first) {
622 dns_zone_item_verify(i);
623 c++;
626 return c;
629 void dns_zone_verify_all(DnsZone *zone) {
630 DnsZoneItem *i;
632 assert(zone);
634 HASHMAP_FOREACH(i, zone->by_key)
635 LIST_FOREACH(by_key, j, i)
636 dns_zone_item_verify(j);
639 void dns_zone_dump(DnsZone *zone, FILE *f) {
640 DnsZoneItem *i;
642 if (!zone)
643 return;
645 if (!f)
646 f = stdout;
648 HASHMAP_FOREACH(i, zone->by_key)
649 LIST_FOREACH(by_key, j, i) {
650 const char *t;
652 t = dns_resource_record_to_string(j->rr);
653 if (!t) {
654 log_oom();
655 continue;
658 fputc('\t', f);
659 fputs(t, f);
660 fputc('\n', f);
664 bool dns_zone_is_empty(DnsZone *zone) {
665 if (!zone)
666 return true;
668 return hashmap_isempty(zone->by_key);
671 bool dns_zone_contains_name(DnsZone *z, const char *name) {
672 DnsZoneItem *first;
674 first = hashmap_get(z->by_name, name);
675 if (!first)
676 return false;
678 LIST_FOREACH(by_name, i, first) {
679 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
680 continue;
682 return true;
685 return false;