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
, DnsQuestion
*q
, DnsAnswer
**ret_answer
, DnsAnswer
**ret_soa
, bool *ret_tentative
) {
287 _cleanup_(dns_answer_unrefp
) DnsAnswer
*answer
= NULL
, *soa
= NULL
;
288 unsigned i
, n_answer
= 0, n_soa
= 0;
289 bool tentative
= true;
297 if (q
->n_keys
<= 0) {
302 *ret_tentative
= false;
307 /* First iteration, count what we have */
308 for (i
= 0; i
< q
->n_keys
; i
++) {
309 DnsZoneItem
*j
, *first
;
311 if (q
->keys
[i
]->type
== DNS_TYPE_ANY
||
312 q
->keys
[i
]->class == DNS_CLASS_ANY
) {
313 bool found
= false, added
= false;
316 /* If this is a generic match, then we have to
317 * go through the list by the name and look
318 * for everything manually */
320 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
321 LIST_FOREACH(by_name
, j
, first
) {
322 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
327 k
= dns_resource_key_match_rr(q
->keys
[i
], j
->rr
);
343 /* If this is a specific match, then look for
344 * the right key immediately */
346 first
= hashmap_get(z
->by_key
, q
->keys
[i
]);
347 LIST_FOREACH(by_key
, j
, first
) {
348 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
356 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
357 LIST_FOREACH(by_name
, j
, first
) {
358 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
368 if (n_answer
<= 0 && n_soa
<= 0) {
373 *ret_tentative
= false;
379 answer
= dns_answer_new(n_answer
);
385 soa
= dns_answer_new(n_soa
);
390 /* Second iteration, actually add the RRs to the answers */
391 for (i
= 0; i
< q
->n_keys
; i
++) {
392 DnsZoneItem
*j
, *first
;
394 if (q
->keys
[i
]->type
== DNS_TYPE_ANY
||
395 q
->keys
[i
]->class == DNS_CLASS_ANY
) {
396 bool found
= false, added
= false;
399 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
400 LIST_FOREACH(by_name
, j
, first
) {
401 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
406 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
409 k
= dns_resource_key_match_rr(q
->keys
[i
], j
->rr
);
413 r
= dns_answer_add(answer
, j
->rr
, 0);
421 if (found
&& !added
) {
422 r
= dns_answer_add_soa(soa
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]), LLMNR_DEFAULT_TTL
);
429 first
= hashmap_get(z
->by_key
, q
->keys
[i
]);
430 LIST_FOREACH(by_key
, j
, first
) {
431 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
436 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
439 r
= dns_answer_add(answer
, j
->rr
, 0);
445 bool add_soa
= false;
447 first
= hashmap_get(z
->by_name
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]));
448 LIST_FOREACH(by_name
, j
, first
) {
449 if (!IN_SET(j
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
))
452 if (j
->state
!= DNS_ZONE_ITEM_PROBING
)
459 r
= dns_answer_add_soa(soa
, DNS_RESOURCE_KEY_NAME(q
->keys
[i
]), LLMNR_DEFAULT_TTL
);
467 *ret_answer
= answer
;
474 *ret_tentative
= tentative
;
479 void dns_zone_item_conflict(DnsZoneItem
*i
) {
480 _cleanup_free_
char *pretty
= NULL
;
484 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_PROBING
, DNS_ZONE_ITEM_VERIFYING
, DNS_ZONE_ITEM_ESTABLISHED
))
487 dns_resource_record_to_string(i
->rr
, &pretty
);
488 log_info("Detected conflict on %s", strna(pretty
));
490 dns_zone_item_probe_stop(i
);
492 /* Withdraw the conflict item */
493 i
->state
= DNS_ZONE_ITEM_WITHDRAWN
;
495 /* Maybe change the hostname */
496 if (manager_is_own_hostname(i
->scope
->manager
, DNS_RESOURCE_KEY_NAME(i
->rr
->key
)) > 0)
497 manager_next_hostname(i
->scope
->manager
);
500 void dns_zone_item_ready(DnsZoneItem
*i
) {
501 _cleanup_free_
char *pretty
= NULL
;
504 assert(i
->probe_transaction
);
506 if (i
->block_ready
> 0)
509 if (IN_SET(i
->probe_transaction
->state
, DNS_TRANSACTION_NULL
, DNS_TRANSACTION_PENDING
))
512 if (i
->probe_transaction
->state
== DNS_TRANSACTION_SUCCESS
) {
513 bool we_lost
= false;
515 /* The probe got a successful reply. If we so far
516 * weren't established we just give up. If we already
517 * were established, and the peer has the
518 * lexicographically larger IP address we continue
521 if (!IN_SET(i
->state
, DNS_ZONE_ITEM_ESTABLISHED
, DNS_ZONE_ITEM_VERIFYING
)) {
522 log_debug("Got a successful probe for not yet established RR, we lost.");
525 assert(i
->probe_transaction
->received
);
526 we_lost
= memcmp(&i
->probe_transaction
->received
->sender
, &i
->probe_transaction
->received
->destination
, FAMILY_ADDRESS_SIZE(i
->probe_transaction
->received
->family
)) < 0;
528 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
532 dns_zone_item_conflict(i
);
536 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
539 dns_resource_record_to_string(i
->rr
, &pretty
);
540 log_debug("Record %s successfully probed.", strna(pretty
));
542 dns_zone_item_probe_stop(i
);
543 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
546 static int dns_zone_item_verify(DnsZoneItem
*i
) {
547 _cleanup_free_
char *pretty
= NULL
;
552 if (i
->state
!= DNS_ZONE_ITEM_ESTABLISHED
)
555 dns_resource_record_to_string(i
->rr
, &pretty
);
556 log_debug("Verifying RR %s", strna(pretty
));
558 i
->state
= DNS_ZONE_ITEM_VERIFYING
;
559 r
= dns_zone_item_probe_start(i
);
561 log_error_errno(r
, "Failed to start probing for verifying RR: %m");
562 i
->state
= DNS_ZONE_ITEM_ESTABLISHED
;
569 int dns_zone_check_conflicts(DnsZone
*zone
, DnsResourceRecord
*rr
) {
570 DnsZoneItem
*i
, *first
;
576 /* This checks whether a response RR we received from somebody
577 * else is one that we actually thought was uniquely ours. If
578 * so, we'll verify our RRs. */
580 /* No conflict if we don't have the name at all. */
581 first
= hashmap_get(zone
->by_name
, DNS_RESOURCE_KEY_NAME(rr
->key
));
585 /* No conflict if we have the exact same RR */
586 if (dns_zone_get(zone
, rr
))
589 /* OK, somebody else has RRs for the same name. Yuck! Let's
590 * start probing again */
592 LIST_FOREACH(by_name
, i
, first
) {
593 if (dns_resource_record_equal(i
->rr
, rr
))
596 dns_zone_item_verify(i
);
603 int dns_zone_verify_conflicts(DnsZone
*zone
, DnsResourceKey
*key
) {
604 DnsZoneItem
*i
, *first
;
609 /* Somebody else notified us about a possible conflict. Let's
610 * verify if that's true. */
612 first
= hashmap_get(zone
->by_name
, DNS_RESOURCE_KEY_NAME(key
));
616 LIST_FOREACH(by_name
, i
, first
) {
617 dns_zone_item_verify(i
);
624 void dns_zone_verify_all(DnsZone
*zone
) {
630 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
633 LIST_FOREACH(by_key
, j
, i
)
634 dns_zone_item_verify(j
);
638 void dns_zone_dump(DnsZone
*zone
, FILE *f
) {
649 HASHMAP_FOREACH(i
, zone
->by_key
, iterator
) {
652 LIST_FOREACH(by_key
, j
, i
) {
653 _cleanup_free_
char *t
= NULL
;
655 r
= dns_resource_record_to_string(j
->rr
, &t
);
668 bool dns_zone_is_empty(DnsZone
*zone
) {
672 return hashmap_isempty(zone
->by_key
);