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 "dns-domain.h"
24 #include "resolved-dns-packet.h"
25 #include "resolved-dns-zone.h"
26 #include "string-util.h"
28 /* Never allow more than 1K entries */
31 void dns_zone_item_probe_stop(DnsZoneItem
*i
) {
35 if (!i
->probe_transaction
)
38 t
= i
->probe_transaction
;
39 i
->probe_transaction
= NULL
;
41 set_remove(t
->zone_items
, i
);
42 dns_transaction_gc(t
);
45 static void dns_zone_item_free(DnsZoneItem
*i
) {
49 dns_zone_item_probe_stop(i
);
50 dns_resource_record_unref(i
->rr
);
55 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem
*, dns_zone_item_free
);
57 static void dns_zone_item_remove_and_free(DnsZone
*z
, DnsZoneItem
*i
) {
65 first
= hashmap_get(z
->by_key
, i
->rr
->key
);
66 LIST_REMOVE(by_key
, first
, i
);
68 assert_se(hashmap_replace(z
->by_key
, first
->rr
->key
, first
) >= 0);
70 hashmap_remove(z
->by_key
, i
->rr
->key
);
72 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
));
73 LIST_REMOVE(by_name
, first
, i
);
75 assert_se(hashmap_replace(z
->by_name
, DNS_RESOURCE_KEY_NAME(first
->rr
->key
), first
) >= 0);
77 hashmap_remove(z
->by_name
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
));
79 dns_zone_item_free(i
);
82 void dns_zone_flush(DnsZone
*z
) {
87 while ((i
= hashmap_first(z
->by_key
)))
88 dns_zone_item_remove_and_free(z
, i
);
90 assert(hashmap_size(z
->by_key
) == 0);
91 assert(hashmap_size(z
->by_name
) == 0);
93 z
->by_key
= hashmap_free(z
->by_key
);
94 z
->by_name
= hashmap_free(z
->by_name
);
97 static DnsZoneItem
* dns_zone_get(DnsZone
*z
, DnsResourceRecord
*rr
) {
103 LIST_FOREACH(by_key
, i
, hashmap_get(z
->by_key
, rr
->key
))
104 if (dns_resource_record_equal(i
->rr
, rr
) > 0)
110 void dns_zone_remove_rr(DnsZone
*z
, DnsResourceRecord
*rr
) {
116 i
= dns_zone_get(z
, rr
);
118 dns_zone_item_remove_and_free(z
, i
);
121 static int dns_zone_init(DnsZone
*z
) {
126 r
= hashmap_ensure_allocated(&z
->by_key
, &dns_resource_key_hash_ops
);
130 r
= hashmap_ensure_allocated(&z
->by_name
, &dns_name_hash_ops
);
137 static int dns_zone_link_item(DnsZone
*z
, DnsZoneItem
*i
) {
141 first
= hashmap_get(z
->by_key
, i
->rr
->key
);
143 LIST_PREPEND(by_key
, first
, i
);
144 assert_se(hashmap_replace(z
->by_key
, first
->rr
->key
, first
) >= 0);
146 r
= hashmap_put(z
->by_key
, i
->rr
->key
, i
);
151 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
));
153 LIST_PREPEND(by_name
, first
, i
);
154 assert_se(hashmap_replace(z
->by_name
, DNS_RESOURCE_KEY_NAME(first
->rr
->key
), first
) >= 0);
156 r
= hashmap_put(z
->by_name
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
), i
);
164 static int dns_zone_item_probe_start(DnsZoneItem
*i
) {
165 _cleanup_(dns_resource_key_unrefp
) DnsResourceKey
*key
= NULL
;
171 if (i
->probe_transaction
)
174 key
= dns_resource_key_new(i
->rr
->key
->class, DNS_TYPE_ANY
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
));
178 t
= dns_scope_find_transaction(i
->scope
, key
, false);
180 r
= dns_transaction_new(&t
, i
->scope
, key
);
185 r
= set_ensure_allocated(&t
->zone_items
, NULL
);
189 r
= set_put(t
->zone_items
, i
);
193 i
->probe_transaction
= t
;
195 if (t
->state
== DNS_TRANSACTION_NULL
) {
198 r
= dns_transaction_go(t
);
202 dns_zone_item_probe_stop(i
);
207 dns_zone_item_ready(i
);
211 dns_transaction_gc(t
);
215 int dns_zone_put(DnsZone
*z
, DnsScope
*s
, DnsResourceRecord
*rr
, bool probe
) {
216 _cleanup_(dns_zone_item_freep
) DnsZoneItem
*i
= NULL
;
217 DnsZoneItem
*existing
;
224 if (rr
->key
->class == DNS_CLASS_ANY
)
226 if (rr
->key
->type
== DNS_TYPE_ANY
)
229 existing
= dns_zone_get(z
, rr
);
233 r
= dns_zone_init(z
);
237 i
= new0(DnsZoneItem
, 1);
242 i
->rr
= dns_resource_record_ref(rr
);
243 i
->probing_enabled
= probe
;
245 r
= dns_zone_link_item(z
, i
);
250 DnsZoneItem
*first
, *j
;
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 ned to probe again. */
257 LIST_FIND_HEAD(by_name
, i
, first
);
258 LIST_FOREACH(by_name
, j
, first
) {
262 if (j
->state
== DNS_ZONE_ITEM_ESTABLISHED
)
267 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
269 i
->state
= DNS_ZONE_ITEM_PROBING
;
271 r
= dns_zone_item_probe_start(i
);
273 dns_zone_item_remove_and_free(z
, i
);
279 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
285 int dns_zone_lookup(DnsZone
*z
, DnsQuestion
*q
, DnsAnswer
**ret_answer
, DnsAnswer
**ret_soa
, bool *ret_tentative
) {
286 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
287 unsigned i
, n_answer
= 0, n_soa
= 0;
288 bool tentative
= true;
296 if (q
->n_keys
<= 0) {
301 *ret_tentative
= false;
306 /* First iteration, count what we have */
307 for (i
= 0; i
< q
->n_keys
; i
++) {
308 DnsZoneItem
*j
, *first
;
310 if (q
->keys
[i
]->type
== DNS_TYPE_ANY
||
311 q
->keys
[i
]->class == DNS_CLASS_ANY
) {
312 bool found
= false, added
= false;
315 /* If this is a generic match, then we have to
316 * go through the list by the name and look
317 * for everything manually */
319 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
320 LIST_FOREACH(by_name
, j
, first
) {
321 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
326 k
= dns_resource_key_match_rr(q
->keys
[i
], j
->rr
);
342 /* If this is a specific match, then look for
343 * the right key immediately */
345 first
= hashmap_get(z
->by_key
, q
->keys
[i
]);
346 LIST_FOREACH(by_key
, j
, first
) {
347 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
355 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
356 LIST_FOREACH(by_name
, j
, first
) {
357 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
367 if (n_answer
<= 0 && n_soa
<= 0) {
372 *ret_tentative
= false;
378 answer
= dns_answer_new(n_answer
);
384 soa
= dns_answer_new(n_soa
);
389 /* Second iteration, actually add the RRs to the answers */
390 for (i
= 0; i
< q
->n_keys
; i
++) {
391 DnsZoneItem
*j
, *first
;
393 if (q
->keys
[i
]->type
== DNS_TYPE_ANY
||
394 q
->keys
[i
]->class == DNS_CLASS_ANY
) {
395 bool found
= false, added
= false;
398 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
399 LIST_FOREACH(by_name
, j
, first
) {
400 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
405 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
408 k
= dns_resource_key_match_rr(q
->keys
[i
], j
->rr
);
412 r
= dns_answer_add(answer
, j
->rr
, 0);
420 if (found
&& !added
) {
421 r
= dns_answer_add_soa(soa
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]), LLMNR_DEFAULT_TTL
);
428 first
= hashmap_get(z
->by_key
, q
->keys
[i
]);
429 LIST_FOREACH(by_key
, j
, first
) {
430 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
435 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
438 r
= dns_answer_add(answer
, j
->rr
, 0);
444 bool add_soa
= false;
446 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
447 LIST_FOREACH(by_name
, j
, first
) {
448 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
451 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
458 r
= dns_answer_add_soa(soa
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]), LLMNR_DEFAULT_TTL
);
466 *ret_answer
= answer
;
473 *ret_tentative
= tentative
;
478 void dns_zone_item_conflict(DnsZoneItem
*i
) {
479 _cleanup_free_
char *pretty
= NULL
;
483 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_VERIFYING
, DNS_ZONE_ITEM_ESTABLISHED
))
486 dns_resource_record_to_string(i
->rr
, &pretty
);
487 log_info("Detected conflict on %s", strna(pretty
));
489 dns_zone_item_probe_stop(i
);
491 /* Withdraw the conflict item */
492 i
->state
= DNS_ZONE_ITEM_WITHDRAWN
;
494 /* Maybe change the hostname */
495 if (manager_is_own_hostname(i
->scope
->manager
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
)) > 0)
496 manager_next_hostname(i
->scope
->manager
);
499 void dns_zone_item_ready(DnsZoneItem
*i
) {
500 _cleanup_free_
char *pretty
= NULL
;
503 assert(i
->probe_transaction
);
505 if (i
->block_ready
> 0)
508 if (IN_SET(i
->probe_transaction
->state
, DNS_TRANSACTION_NULL
, DNS_TRANSACTION_PENDING
))
511 if (i
->probe_transaction
->state
== DNS_TRANSACTION_SUCCESS
) {
512 bool we_lost
= false;
514 /* The probe got a successful reply. If we so far
515 * weren't established we just give up. If we already
516 * were established, and the peer has the
517 * lexicographically larger IP address we continue
520 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
)) {
521 log_debug("Got a successful probe for not yet established RR, we lost.");
524 assert(i
->probe_transaction
->received
);
525 we_lost
= memcmp(&i
->probe_transaction
->received
->sender
, &i
->probe_transaction
->received
->destination
, FAMILY_ADDRESS_SIZE(i
->probe_transaction
->received
->family
)) < 0;
527 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
531 dns_zone_item_conflict(i
);
535 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
538 dns_resource_record_to_string(i
->rr
, &pretty
);
539 log_debug("Record %s successfully probed.", strna(pretty
));
541 dns_zone_item_probe_stop(i
);
542 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
545 static int dns_zone_item_verify(DnsZoneItem
*i
) {
546 _cleanup_free_
char *pretty
= NULL
;
551 if (i
->state
!= DNS_ZONE_ITEM_ESTABLISHED
)
554 dns_resource_record_to_string(i
->rr
, &pretty
);
555 log_debug("Verifying RR %s", strna(pretty
));
557 i
->state
= DNS_ZONE_ITEM_VERIFYING
;
558 r
= dns_zone_item_probe_start(i
);
560 log_error_errno(r
, "Failed to start probing for verifying RR: %m");
561 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
568 int dns_zone_check_conflicts(DnsZone
*zone
, DnsResourceRecord
*rr
) {
569 DnsZoneItem
*i
, *first
;
575 /* This checks whether a response RR we received from somebody
576 * else is one that we actually thought was uniquely ours. If
577 * so, we'll verify our RRs. */
579 /* No conflict if we don't have the name at all. */
580 first
= hashmap_get(zone
->by_name
, DNS_RESOURCE_KEY_NAME(rr
->key
));
584 /* No conflict if we have the exact same RR */
585 if (dns_zone_get(zone
, rr
))
588 /* OK, somebody else has RRs for the same name. Yuck! Let's
589 * start probing again */
591 LIST_FOREACH(by_name
, i
, first
) {
592 if (dns_resource_record_equal(i
->rr
, rr
))
595 dns_zone_item_verify(i
);
602 int dns_zone_verify_conflicts(DnsZone
*zone
, DnsResourceKey
*key
) {
603 DnsZoneItem
*i
, *first
;
608 /* Somebody else notified us about a possible conflict. Let's
609 * verify if that's true. */
611 first
= hashmap_get(zone
->by_name
, DNS_RESOURCE_KEY_NAME(key
));
615 LIST_FOREACH(by_name
, i
, first
) {
616 dns_zone_item_verify(i
);
623 void dns_zone_verify_all(DnsZone
*zone
) {
629 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
632 LIST_FOREACH(by_key
, j
, i
)
633 dns_zone_item_verify(j
);
637 void dns_zone_dump(DnsZone
*zone
, FILE *f
) {
648 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
651 LIST_FOREACH(by_key
, j
, i
) {
652 _cleanup_free_
char *t
= NULL
;
654 r
= dns_resource_record_to_string(j
->rr
, &t
);
667 bool dns_zone_is_empty(DnsZone
*zone
) {
671 return hashmap_isempty(zone
->by_key
);