1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "alloc-util.h"
6 #include "dns-domain.h"
9 #include "resolved-dns-answer.h"
10 #include "resolved-dns-packet.h"
11 #include "resolved-dns-rr.h"
12 #include "resolved-dns-scope.h"
13 #include "resolved-dns-transaction.h"
14 #include "resolved-dns-zone.h"
15 #include "resolved-dnssd.h"
16 #include "resolved-manager.h"
18 #include "string-util.h"
20 /* Never allow more than 1K entries */
23 void dns_zone_item_probe_stop(DnsZoneItem
*i
) {
27 if (!i
->probe_transaction
)
30 t
= TAKE_PTR(i
->probe_transaction
);
32 set_remove(t
->notify_zone_items
, i
);
33 set_remove(t
->notify_zone_items_done
, i
);
34 dns_transaction_gc(t
);
37 static DnsZoneItem
* dns_zone_item_free(DnsZoneItem
*i
) {
41 dns_zone_item_probe_stop(i
);
42 dns_resource_record_unref(i
->rr
);
46 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem
*, dns_zone_item_free
);
48 static void dns_zone_item_remove_and_free(DnsZone
*z
, DnsZoneItem
*i
) {
56 first
= hashmap_get(z
->by_key
, i
->rr
->key
);
57 LIST_REMOVE(by_key
, first
, i
);
59 assert_se(hashmap_replace(z
->by_key
, first
->rr
->key
, first
) >= 0);
61 hashmap_remove(z
->by_key
, i
->rr
->key
);
63 first
= hashmap_get(z
->by_name
, dns_resource_key_name(i
->rr
->key
));
64 LIST_REMOVE(by_name
, first
, i
);
66 assert_se(hashmap_replace(z
->by_name
, dns_resource_key_name(first
->rr
->key
), first
) >= 0);
68 hashmap_remove(z
->by_name
, dns_resource_key_name(i
->rr
->key
));
70 dns_zone_item_free(i
);
73 void dns_zone_flush(DnsZone
*z
) {
78 while ((i
= hashmap_first(z
->by_key
)))
79 dns_zone_item_remove_and_free(z
, i
);
81 assert(hashmap_isempty(z
->by_key
));
82 assert(hashmap_isempty(z
->by_name
));
84 z
->by_key
= hashmap_free(z
->by_key
);
85 z
->by_name
= hashmap_free(z
->by_name
);
88 DnsZoneItem
* dns_zone_get(DnsZone
*z
, DnsResourceRecord
*rr
) {
92 LIST_FOREACH(by_key
, i
, (DnsZoneItem
*) hashmap_get(z
->by_key
, rr
->key
))
93 if (dns_resource_record_equal(i
->rr
, rr
) > 0)
99 void dns_zone_remove_rr(DnsZone
*z
, DnsResourceRecord
*rr
) {
107 i
= dns_zone_get(z
, rr
);
109 dns_zone_item_remove_and_free(z
, i
);
112 int dns_zone_remove_rrs_by_key(DnsZone
*z
, DnsResourceKey
*key
) {
113 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
114 DnsResourceRecord
*rr
;
118 r
= dns_zone_lookup(z
, key
, 0, &answer
, &soa
, &tentative
);
122 DNS_ANSWER_FOREACH(rr
, answer
)
123 dns_zone_remove_rr(z
, rr
);
128 static int dns_zone_init(DnsZone
*z
) {
133 r
= hashmap_ensure_allocated(&z
->by_key
, &dns_resource_key_hash_ops
);
137 r
= hashmap_ensure_allocated(&z
->by_name
, &dns_name_hash_ops
);
144 static int dns_zone_link_item(DnsZone
*z
, DnsZoneItem
*i
) {
148 first
= hashmap_get(z
->by_key
, i
->rr
->key
);
150 LIST_PREPEND(by_key
, first
, i
);
151 assert_se(hashmap_replace(z
->by_key
, first
->rr
->key
, first
) >= 0);
153 r
= hashmap_put(z
->by_key
, i
->rr
->key
, i
);
158 first
= hashmap_get(z
->by_name
, dns_resource_key_name(i
->rr
->key
));
160 LIST_PREPEND(by_name
, first
, i
);
161 assert_se(hashmap_replace(z
->by_name
, dns_resource_key_name(first
->rr
->key
), first
) >= 0);
163 r
= hashmap_put(z
->by_name
, dns_resource_key_name(i
->rr
->key
), i
);
171 static int dns_zone_item_probe_start(DnsZoneItem
*i
) {
172 _cleanup_(dns_transaction_gcp
) DnsTransaction
*t
= NULL
;
177 if (i
->probe_transaction
)
180 t
= dns_scope_find_transaction(
182 &DNS_RESOURCE_KEY_CONST(i
->rr
->key
->class, DNS_TYPE_ANY
, dns_resource_key_name(i
->rr
->key
)),
183 SD_RESOLVED_NO_CACHE
|SD_RESOLVED_NO_ZONE
);
185 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*key
= NULL
;
187 key
= dns_resource_key_new(i
->rr
->key
->class, DNS_TYPE_ANY
, dns_resource_key_name(i
->rr
->key
));
191 r
= dns_transaction_new(&t
, i
->scope
, key
, NULL
, SD_RESOLVED_NO_CACHE
|SD_RESOLVED_NO_ZONE
);
196 r
= set_ensure_allocated(&t
->notify_zone_items_done
, NULL
);
200 r
= set_ensure_put(&t
->notify_zone_items
, NULL
, i
);
205 i
->probe_transaction
= TAKE_PTR(t
);
207 if (i
->probe_transaction
->state
== DNS_TRANSACTION_NULL
) {
209 r
= dns_transaction_go(i
->probe_transaction
);
213 dns_zone_item_probe_stop(i
);
218 dns_zone_item_notify(i
);
222 int dns_zone_put(DnsZone
*z
, DnsScope
*s
, DnsResourceRecord
*rr
, bool probe
) {
223 _cleanup_(dns_zone_item_freep
) DnsZoneItem
*i
= NULL
;
224 DnsZoneItem
*existing
;
231 if (dns_class_is_pseudo(rr
->key
->class))
233 if (dns_type_is_pseudo(rr
->key
->type
))
236 existing
= dns_zone_get(z
, rr
);
240 r
= dns_zone_init(z
);
244 i
= new(DnsZoneItem
, 1);
250 .rr
= dns_resource_record_ref(rr
),
251 .probing_enabled
= probe
,
254 r
= dns_zone_link_item(z
, i
);
259 bool established
= false;
261 /* Check if there's already an RR with the same name
262 * established. If so, it has been probed already, and
263 * we don't need to probe again. */
265 LIST_FOREACH_OTHERS(by_name
, j
, i
)
266 if (j
->state
== DNS_ZONE_ITEM_ESTABLISHED
)
270 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
272 i
->state
= DNS_ZONE_ITEM_PROBING
;
274 r
= dns_zone_item_probe_start(i
);
276 dns_zone_item_remove_and_free(z
, i
);
282 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
288 static int dns_zone_add_authenticated_answer(DnsAnswer
*a
, DnsZoneItem
*i
, int ifindex
) {
289 DnsAnswerFlags flags
;
291 /* From RFC 6762, Section 10.2
292 * "They (the rules about when to set the cache-flush bit) apply to
293 * startup announcements as described in Section 8.3, "Announcing",
294 * and to responses generated as a result of receiving query messages."
295 * So, set the cache-flush bit for mDNS answers except for DNS-SD
296 * service enumeration PTRs described in RFC 6763, Section 4.1. */
297 if (i
->scope
->protocol
== DNS_PROTOCOL_MDNS
&&
298 !dns_resource_key_is_dnssd_ptr(i
->rr
->key
))
299 flags
= DNS_ANSWER_AUTHENTICATED
|DNS_ANSWER_CACHE_FLUSH
;
301 flags
= DNS_ANSWER_AUTHENTICATED
;
303 return dns_answer_add(a
, i
->rr
, ifindex
, flags
, NULL
);
306 int dns_zone_lookup(DnsZone
*z
, DnsResourceKey
*key
, int ifindex
, DnsAnswer
**ret_answer
, DnsAnswer
**ret_soa
, bool *ret_tentative
) {
307 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
308 unsigned n_answer
= 0;
310 bool tentative
= true, need_soa
= false;
313 /* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the
314 * ifindex field in the answer with it */
320 /* First iteration, count what we have */
322 if (key
->type
== DNS_TYPE_ANY
|| key
->class == DNS_CLASS_ANY
) {
323 bool found
= false, added
= false;
326 /* If this is a generic match, then we have to
327 * go through the list by the name and look
328 * for everything manually */
330 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
331 LIST_FOREACH(by_name
, j
, first
) {
332 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
337 k
= dns_resource_key_match_rr(key
, j
->rr
, NULL
);
353 /* If this is a specific match, then look for
354 * the right key immediately */
356 first
= hashmap_get(z
->by_key
, key
);
357 LIST_FOREACH(by_key
, j
, first
) {
358 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
366 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
367 LIST_FOREACH(by_name
, j
, first
) {
368 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
377 if (n_answer
<= 0 && !need_soa
)
381 answer
= dns_answer_new(n_answer
);
387 soa
= dns_answer_new(1);
392 /* Second iteration, actually add the RRs to the answers */
393 if (key
->type
== DNS_TYPE_ANY
|| key
->class == DNS_CLASS_ANY
) {
394 bool found
= false, added
= false;
397 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
398 LIST_FOREACH(by_name
, j
, first
) {
399 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
404 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
407 k
= dns_resource_key_match_rr(key
, j
->rr
, NULL
);
411 r
= dns_zone_add_authenticated_answer(answer
, j
, ifindex
);
419 if (found
&& !added
) {
420 r
= dns_answer_add_soa(soa
, dns_resource_key_name(key
), LLMNR_DEFAULT_TTL
, ifindex
);
427 first
= hashmap_get(z
->by_key
, key
);
428 LIST_FOREACH(by_key
, j
, first
) {
429 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
434 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
437 r
= dns_zone_add_authenticated_answer(answer
, j
, ifindex
);
443 bool add_soa
= false;
445 first
= hashmap_get(z
->by_name
, dns_resource_key_name(key
));
446 LIST_FOREACH(by_name
, j
, first
) {
447 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
450 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
457 r
= dns_answer_add_soa(soa
, dns_resource_key_name(key
), LLMNR_DEFAULT_TTL
, ifindex
);
464 /* If the caller sets ret_tentative to NULL, then use this as
465 * indication to not return tentative entries */
467 if (!ret_tentative
&& tentative
)
470 *ret_answer
= TAKE_PTR(answer
);
473 *ret_soa
= TAKE_PTR(soa
);
476 *ret_tentative
= tentative
;
487 *ret_tentative
= false;
492 void dns_zone_item_conflict(DnsZoneItem
*i
) {
495 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_VERIFYING
, DNS_ZONE_ITEM_ESTABLISHED
))
498 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i
->rr
)));
500 dns_zone_item_probe_stop(i
);
502 /* Withdraw the conflict item */
503 i
->state
= DNS_ZONE_ITEM_WITHDRAWN
;
505 (void) dnssd_signal_conflict(i
->scope
->manager
, dns_resource_key_name(i
->rr
->key
));
507 /* Maybe change the hostname */
508 if (manager_is_own_hostname(i
->scope
->manager
, dns_resource_key_name(i
->rr
->key
)) > 0)
509 manager_next_hostname(i
->scope
->manager
);
512 void dns_zone_item_notify(DnsZoneItem
*i
) {
514 assert(i
->probe_transaction
);
516 if (i
->block_ready
> 0)
519 if (IN_SET(i
->probe_transaction
->state
, DNS_TRANSACTION_NULL
, DNS_TRANSACTION_PENDING
, DNS_TRANSACTION_VALIDATING
))
522 if (i
->probe_transaction
->state
== DNS_TRANSACTION_SUCCESS
) {
523 bool we_lost
= false;
525 /* The probe got a successful reply. If we so far
526 * weren't established we just give up.
528 * In LLMNR case if we already
529 * were established, and the peer has the
530 * lexicographically larger IP address we continue
533 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
)) {
534 log_debug("Got a successful probe for not yet established RR, we lost.");
536 } else if (i
->probe_transaction
->scope
->protocol
== DNS_PROTOCOL_LLMNR
) {
537 assert(i
->probe_transaction
->received
);
538 we_lost
= memcmp(&i
->probe_transaction
->received
->sender
, &i
->probe_transaction
->received
->destination
, FAMILY_ADDRESS_SIZE(i
->probe_transaction
->received
->family
)) < 0;
540 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
544 dns_zone_item_conflict(i
);
548 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
551 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i
->rr
)));
553 dns_zone_item_probe_stop(i
);
554 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
557 static int dns_zone_item_verify(DnsZoneItem
*i
) {
562 if (i
->state
!= DNS_ZONE_ITEM_ESTABLISHED
)
565 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i
->rr
)));
567 i
->state
= DNS_ZONE_ITEM_VERIFYING
;
568 r
= dns_zone_item_probe_start(i
);
570 log_error_errno(r
, "Failed to start probing for verifying RR: %m");
571 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
578 int dns_zone_check_conflicts(DnsZone
*zone
, DnsResourceRecord
*rr
) {
585 /* This checks whether a response RR we received from somebody
586 * else is one that we actually thought was uniquely ours. If
587 * so, we'll verify our RRs. */
589 /* No conflict if we don't have the name at all. */
590 first
= hashmap_get(zone
->by_name
, dns_resource_key_name(rr
->key
));
594 /* No conflict if we have the exact same RR */
595 if (dns_zone_get(zone
, rr
))
598 /* No conflict if it is DNS-SD RR used for service enumeration. */
599 if (dns_resource_key_is_dnssd_ptr(rr
->key
))
602 /* OK, somebody else has RRs for the same name. Yuck! Let's
603 * start probing again */
605 LIST_FOREACH(by_name
, i
, first
) {
606 if (dns_resource_record_equal(i
->rr
, rr
))
609 dns_zone_item_verify(i
);
616 int dns_zone_verify_conflicts(DnsZone
*zone
, DnsResourceKey
*key
) {
622 /* Somebody else notified us about a possible conflict. Let's
623 * verify if that's true. */
625 first
= hashmap_get(zone
->by_name
, dns_resource_key_name(key
));
629 LIST_FOREACH(by_name
, i
, first
) {
630 dns_zone_item_verify(i
);
637 void dns_zone_verify_all(DnsZone
*zone
) {
642 HASHMAP_FOREACH(i
, zone
->by_key
)
643 LIST_FOREACH(by_key
, j
, i
)
644 dns_zone_item_verify(j
);
647 void dns_zone_dump(DnsZone
*zone
, FILE *f
) {
656 HASHMAP_FOREACH(i
, zone
->by_key
)
657 LIST_FOREACH(by_key
, j
, i
) {
660 t
= dns_resource_record_to_string(j
->rr
);
672 bool dns_zone_is_empty(DnsZone
*zone
) {
676 return hashmap_isempty(zone
->by_key
);
679 bool dns_zone_contains_name(DnsZone
*z
, const char *name
) {
682 first
= hashmap_get(z
->by_name
, name
);
686 LIST_FOREACH(by_name
, i
, first
) {
687 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))