1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include "alloc-util.h"
23 #include "dns-domain.h"
25 #include "resolved-dns-packet.h"
26 #include "resolved-dns-zone.h"
27 #include "string-util.h"
29 /* Never allow more than 1K entries */
32 void dns_zone_item_probe_stop(DnsZoneItem
*i
) {
36 if (!i
->probe_transaction
)
39 t
= i
->probe_transaction
;
40 i
->probe_transaction
= NULL
;
42 set_remove(t
->zone_items
, i
);
43 dns_transaction_gc(t
);
46 static void dns_zone_item_free(DnsZoneItem
*i
) {
50 dns_zone_item_probe_stop(i
);
51 dns_resource_record_unref(i
->rr
);
56 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem
*, dns_zone_item_free
);
58 static void dns_zone_item_remove_and_free(DnsZone
*z
, DnsZoneItem
*i
) {
66 first
= hashmap_get(z
->by_key
, i
->rr
->key
);
67 LIST_REMOVE(by_key
, first
, i
);
69 assert_se(hashmap_replace(z
->by_key
, first
->rr
->key
, first
) >= 0);
71 hashmap_remove(z
->by_key
, i
->rr
->key
);
73 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
));
74 LIST_REMOVE(by_name
, first
, i
);
76 assert_se(hashmap_replace(z
->by_name
, DNS_RESOURCE_KEY_NAME(first
->rr
->key
), first
) >= 0);
78 hashmap_remove(z
->by_name
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
));
80 dns_zone_item_free(i
);
83 void dns_zone_flush(DnsZone
*z
) {
88 while ((i
= hashmap_first(z
->by_key
)))
89 dns_zone_item_remove_and_free(z
, i
);
91 assert(hashmap_size(z
->by_key
) == 0);
92 assert(hashmap_size(z
->by_name
) == 0);
94 z
->by_key
= hashmap_free(z
->by_key
);
95 z
->by_name
= hashmap_free(z
->by_name
);
98 static DnsZoneItem
* dns_zone_get(DnsZone
*z
, DnsResourceRecord
*rr
) {
104 LIST_FOREACH(by_key
, i
, hashmap_get(z
->by_key
, rr
->key
))
105 if (dns_resource_record_equal(i
->rr
, rr
) > 0)
111 void dns_zone_remove_rr(DnsZone
*z
, DnsResourceRecord
*rr
) {
117 i
= dns_zone_get(z
, rr
);
119 dns_zone_item_remove_and_free(z
, i
);
122 static int dns_zone_init(DnsZone
*z
) {
127 r
= hashmap_ensure_allocated(&z
->by_key
, &dns_resource_key_hash_ops
);
131 r
= hashmap_ensure_allocated(&z
->by_name
, &dns_name_hash_ops
);
138 static int dns_zone_link_item(DnsZone
*z
, DnsZoneItem
*i
) {
142 first
= hashmap_get(z
->by_key
, i
->rr
->key
);
144 LIST_PREPEND(by_key
, first
, i
);
145 assert_se(hashmap_replace(z
->by_key
, first
->rr
->key
, first
) >= 0);
147 r
= hashmap_put(z
->by_key
, i
->rr
->key
, i
);
152 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
));
154 LIST_PREPEND(by_name
, first
, i
);
155 assert_se(hashmap_replace(z
->by_name
, DNS_RESOURCE_KEY_NAME(first
->rr
->key
), first
) >= 0);
157 r
= hashmap_put(z
->by_name
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
), i
);
165 static int dns_zone_item_probe_start(DnsZoneItem
*i
) {
166 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*key
= NULL
;
172 if (i
->probe_transaction
)
175 key
= dns_resource_key_new(i
->rr
->key
->class, DNS_TYPE_ANY
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
));
179 t
= dns_scope_find_transaction(i
->scope
, key
, false);
181 r
= dns_transaction_new(&t
, i
->scope
, key
);
186 r
= set_ensure_allocated(&t
->zone_items
, NULL
);
190 r
= set_put(t
->zone_items
, i
);
194 i
->probe_transaction
= t
;
196 if (t
->state
== DNS_TRANSACTION_NULL
) {
199 r
= dns_transaction_go(t
);
203 dns_zone_item_probe_stop(i
);
208 dns_zone_item_ready(i
);
212 dns_transaction_gc(t
);
216 int dns_zone_put(DnsZone
*z
, DnsScope
*s
, DnsResourceRecord
*rr
, bool probe
) {
217 _cleanup_(dns_zone_item_freep
) DnsZoneItem
*i
= NULL
;
218 DnsZoneItem
*existing
;
225 if (rr
->key
->class == DNS_CLASS_ANY
)
227 if (rr
->key
->type
== DNS_TYPE_ANY
)
230 existing
= dns_zone_get(z
, rr
);
234 r
= dns_zone_init(z
);
238 i
= new0(DnsZoneItem
, 1);
243 i
->rr
= dns_resource_record_ref(rr
);
244 i
->probing_enabled
= probe
;
246 r
= dns_zone_link_item(z
, i
);
251 DnsZoneItem
*first
, *j
;
252 bool established
= false;
254 /* Check if there's already an RR with the same name
255 * established. If so, it has been probed already, and
256 * we don't ned to probe again. */
258 LIST_FIND_HEAD(by_name
, i
, first
);
259 LIST_FOREACH(by_name
, j
, first
) {
263 if (j
->state
== DNS_ZONE_ITEM_ESTABLISHED
)
268 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
270 i
->state
= DNS_ZONE_ITEM_PROBING
;
272 r
= dns_zone_item_probe_start(i
);
274 dns_zone_item_remove_and_free(z
, i
);
280 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
286 int dns_zone_lookup(DnsZone
*z
, DnsResourceKey
*key
, DnsAnswer
**ret_answer
, DnsAnswer
**ret_soa
, bool *ret_tentative
) {
287 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
288 unsigned n_answer
= 0;
289 DnsZoneItem
*j
, *first
;
290 bool tentative
= true, need_soa
= false;
297 /* First iteration, count what we have */
299 if (key
->type
== DNS_TYPE_ANY
|| key
->class == DNS_CLASS_ANY
) {
300 bool found
= false, added
= false;
303 /* If this is a generic match, then we have to
304 * go through the list by the name and look
305 * for everything manually */
307 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(key
));
308 LIST_FOREACH(by_name
, j
, first
) {
309 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
314 k
= dns_resource_key_match_rr(key
, j
->rr
, NULL
);
330 /* If this is a specific match, then look for
331 * the right key immediately */
333 first
= hashmap_get(z
->by_key
, key
);
334 LIST_FOREACH(by_key
, j
, first
) {
335 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
343 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(key
));
344 LIST_FOREACH(by_name
, j
, first
) {
345 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
354 if (n_answer
<= 0 && !need_soa
)
358 answer
= dns_answer_new(n_answer
);
364 soa
= dns_answer_new(1);
369 /* Second iteration, actually add the RRs to the answers */
370 if (key
->type
== DNS_TYPE_ANY
|| key
->class == DNS_CLASS_ANY
) {
371 bool found
= false, added
= false;
374 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(key
));
375 LIST_FOREACH(by_name
, j
, first
) {
376 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
381 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
384 k
= dns_resource_key_match_rr(key
, j
->rr
, NULL
);
388 r
= dns_answer_add(answer
, j
->rr
, 0);
396 if (found
&& !added
) {
397 r
= dns_answer_add_soa(soa
, DNS_RESOURCE_KEY_NAME(key
), LLMNR_DEFAULT_TTL
);
404 first
= hashmap_get(z
->by_key
, key
);
405 LIST_FOREACH(by_key
, j
, first
) {
406 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
411 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
414 r
= dns_answer_add(answer
, j
->rr
, 0);
420 bool add_soa
= false;
422 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(key
));
423 LIST_FOREACH(by_name
, j
, first
) {
424 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
427 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
434 r
= dns_answer_add_soa(soa
, DNS_RESOURCE_KEY_NAME(key
), LLMNR_DEFAULT_TTL
);
441 /* If the caller sets ret_tentative to NULL, then use this as
442 * indication to not return tentative entries */
444 if (!ret_tentative
&& tentative
)
447 *ret_answer
= answer
;
456 *ret_tentative
= tentative
;
467 *ret_tentative
= false;
472 void dns_zone_item_conflict(DnsZoneItem
*i
) {
473 _cleanup_free_
char *pretty
= NULL
;
477 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_VERIFYING
, DNS_ZONE_ITEM_ESTABLISHED
))
480 dns_resource_record_to_string(i
->rr
, &pretty
);
481 log_info("Detected conflict on %s", strna(pretty
));
483 dns_zone_item_probe_stop(i
);
485 /* Withdraw the conflict item */
486 i
->state
= DNS_ZONE_ITEM_WITHDRAWN
;
488 /* Maybe change the hostname */
489 if (manager_is_own_hostname(i
->scope
->manager
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
)) > 0)
490 manager_next_hostname(i
->scope
->manager
);
493 void dns_zone_item_ready(DnsZoneItem
*i
) {
494 _cleanup_free_
char *pretty
= NULL
;
497 assert(i
->probe_transaction
);
499 if (i
->block_ready
> 0)
502 if (IN_SET(i
->probe_transaction
->state
, DNS_TRANSACTION_NULL
, DNS_TRANSACTION_PENDING
))
505 if (i
->probe_transaction
->state
== DNS_TRANSACTION_SUCCESS
) {
506 bool we_lost
= false;
508 /* The probe got a successful reply. If we so far
509 * weren't established we just give up. If we already
510 * were established, and the peer has the
511 * lexicographically larger IP address we continue
514 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
)) {
515 log_debug("Got a successful probe for not yet established RR, we lost.");
518 assert(i
->probe_transaction
->received
);
519 we_lost
= memcmp(&i
->probe_transaction
->received
->sender
, &i
->probe_transaction
->received
->destination
, FAMILY_ADDRESS_SIZE(i
->probe_transaction
->received
->family
)) < 0;
521 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
525 dns_zone_item_conflict(i
);
529 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
532 dns_resource_record_to_string(i
->rr
, &pretty
);
533 log_debug("Record %s successfully probed.", strna(pretty
));
535 dns_zone_item_probe_stop(i
);
536 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
539 static int dns_zone_item_verify(DnsZoneItem
*i
) {
540 _cleanup_free_
char *pretty
= NULL
;
545 if (i
->state
!= DNS_ZONE_ITEM_ESTABLISHED
)
548 dns_resource_record_to_string(i
->rr
, &pretty
);
549 log_debug("Verifying RR %s", strna(pretty
));
551 i
->state
= DNS_ZONE_ITEM_VERIFYING
;
552 r
= dns_zone_item_probe_start(i
);
554 log_error_errno(r
, "Failed to start probing for verifying RR: %m");
555 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
562 int dns_zone_check_conflicts(DnsZone
*zone
, DnsResourceRecord
*rr
) {
563 DnsZoneItem
*i
, *first
;
569 /* This checks whether a response RR we received from somebody
570 * else is one that we actually thought was uniquely ours. If
571 * so, we'll verify our RRs. */
573 /* No conflict if we don't have the name at all. */
574 first
= hashmap_get(zone
->by_name
, DNS_RESOURCE_KEY_NAME(rr
->key
));
578 /* No conflict if we have the exact same RR */
579 if (dns_zone_get(zone
, rr
))
582 /* OK, somebody else has RRs for the same name. Yuck! Let's
583 * start probing again */
585 LIST_FOREACH(by_name
, i
, first
) {
586 if (dns_resource_record_equal(i
->rr
, rr
))
589 dns_zone_item_verify(i
);
596 int dns_zone_verify_conflicts(DnsZone
*zone
, DnsResourceKey
*key
) {
597 DnsZoneItem
*i
, *first
;
602 /* Somebody else notified us about a possible conflict. Let's
603 * verify if that's true. */
605 first
= hashmap_get(zone
->by_name
, DNS_RESOURCE_KEY_NAME(key
));
609 LIST_FOREACH(by_name
, i
, first
) {
610 dns_zone_item_verify(i
);
617 void dns_zone_verify_all(DnsZone
*zone
) {
623 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
626 LIST_FOREACH(by_key
, j
, i
)
627 dns_zone_item_verify(j
);
631 void dns_zone_dump(DnsZone
*zone
, FILE *f
) {
642 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
645 LIST_FOREACH(by_key
, j
, i
) {
646 _cleanup_free_
char *t
= NULL
;
648 r
= dns_resource_record_to_string(j
->rr
, &t
);
661 bool dns_zone_is_empty(DnsZone
*zone
) {
665 return hashmap_isempty(zone
->by_key
);