Systemd/src/resolve/resolved-dns-zone.c
Lennart Poettering 0c69794138 tree-wide: remove Lennart's copyright lines
These lines are generally out-of-date, incomplete and unnecessary. With
SPDX and git repository much more accurate and fine grained information
about licensing and authorship is available, hence let's drop the
per-file copyright notice. Of course, removing copyright lines of others
is problematic, hence this commit only removes my own lines and leaves
all others untouched. It might be nicer if sooner or later those could
go away too, making git the only and accurate source of authorship
information.
2018-06-14 10:20:20 +02:00

705 lines
21 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#include "alloc-util.h"
#include "dns-domain.h"
#include "list.h"
#include "resolved-dns-packet.h"
#include "resolved-dns-zone.h"
#include "resolved-dnssd.h"
#include "string-util.h"
/* Never allow more than 1K entries */
#define ZONE_MAX 1024
void dns_zone_item_probe_stop(DnsZoneItem *i) {
DnsTransaction *t;
assert(i);
if (!i->probe_transaction)
return;
t = TAKE_PTR(i->probe_transaction);
set_remove(t->notify_zone_items, i);
set_remove(t->notify_zone_items_done, i);
dns_transaction_gc(t);
}
static void dns_zone_item_free(DnsZoneItem *i) {
if (!i)
return;
dns_zone_item_probe_stop(i);
dns_resource_record_unref(i->rr);
free(i);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) {
DnsZoneItem *first;
assert(z);
if (!i)
return;
first = hashmap_get(z->by_key, i->rr->key);
LIST_REMOVE(by_key, first, i);
if (first)
assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
else
hashmap_remove(z->by_key, i->rr->key);
first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
LIST_REMOVE(by_name, first, i);
if (first)
assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
else
hashmap_remove(z->by_name, dns_resource_key_name(i->rr->key));
dns_zone_item_free(i);
}
void dns_zone_flush(DnsZone *z) {
DnsZoneItem *i;
assert(z);
while ((i = hashmap_first(z->by_key)))
dns_zone_item_remove_and_free(z, i);
assert(hashmap_size(z->by_key) == 0);
assert(hashmap_size(z->by_name) == 0);
z->by_key = hashmap_free(z->by_key);
z->by_name = hashmap_free(z->by_name);
}
DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
DnsZoneItem *i;
assert(z);
assert(rr);
LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key))
if (dns_resource_record_equal(i->rr, rr) > 0)
return i;
return NULL;
}
void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) {
DnsZoneItem *i;
assert(z);
assert(rr);
i = dns_zone_get(z, rr);
if (i)
dns_zone_item_remove_and_free(z, i);
}
int dns_zone_remove_rrs_by_key(DnsZone *z, DnsResourceKey *key) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
DnsResourceRecord *rr;
bool tentative;
int r;
r = dns_zone_lookup(z, key, 0, &answer, &soa, &tentative);
if (r < 0)
return r;
DNS_ANSWER_FOREACH(rr, answer)
dns_zone_remove_rr(z, rr);
return 0;
}
static int dns_zone_init(DnsZone *z) {
int r;
assert(z);
r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops);
if (r < 0)
return r;
r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops);
if (r < 0)
return r;
return 0;
}
static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
DnsZoneItem *first;
int r;
first = hashmap_get(z->by_key, i->rr->key);
if (first) {
LIST_PREPEND(by_key, first, i);
assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
} else {
r = hashmap_put(z->by_key, i->rr->key, i);
if (r < 0)
return r;
}
first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
if (first) {
LIST_PREPEND(by_name, first, i);
assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
} else {
r = hashmap_put(z->by_name, dns_resource_key_name(i->rr->key), i);
if (r < 0)
return r;
}
return 0;
}
static int dns_zone_item_probe_start(DnsZoneItem *i) {
DnsTransaction *t;
int r;
assert(i);
if (i->probe_transaction)
return 0;
t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)), false);
if (!t) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key));
if (!key)
return -ENOMEM;
r = dns_transaction_new(&t, i->scope, key);
if (r < 0)
return r;
}
r = set_ensure_allocated(&t->notify_zone_items, NULL);
if (r < 0)
goto gc;
r = set_ensure_allocated(&t->notify_zone_items_done, NULL);
if (r < 0)
goto gc;
r = set_put(t->notify_zone_items, i);
if (r < 0)
goto gc;
i->probe_transaction = t;
t->probing = true;
if (t->state == DNS_TRANSACTION_NULL) {
i->block_ready++;
r = dns_transaction_go(t);
i->block_ready--;
if (r < 0) {
dns_zone_item_probe_stop(i);
return r;
}
}
dns_zone_item_notify(i);
return 0;
gc:
dns_transaction_gc(t);
return r;
}
int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
_cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
DnsZoneItem *existing;
int r;
assert(z);
assert(s);
assert(rr);
if (dns_class_is_pseudo(rr->key->class))
return -EINVAL;
if (dns_type_is_pseudo(rr->key->type))
return -EINVAL;
existing = dns_zone_get(z, rr);
if (existing)
return 0;
r = dns_zone_init(z);
if (r < 0)
return r;
i = new0(DnsZoneItem, 1);
if (!i)
return -ENOMEM;
i->scope = s;
i->rr = dns_resource_record_ref(rr);
i->probing_enabled = probe;
r = dns_zone_link_item(z, i);
if (r < 0)
return r;
if (probe) {
DnsZoneItem *first, *j;
bool established = false;
/* Check if there's already an RR with the same name
* established. If so, it has been probed already, and
* we don't ned to probe again. */
LIST_FIND_HEAD(by_name, i, first);
LIST_FOREACH(by_name, j, first) {
if (i == j)
continue;
if (j->state == DNS_ZONE_ITEM_ESTABLISHED)
established = true;
}
if (established)
i->state = DNS_ZONE_ITEM_ESTABLISHED;
else {
i->state = DNS_ZONE_ITEM_PROBING;
r = dns_zone_item_probe_start(i);
if (r < 0) {
dns_zone_item_remove_and_free(z, i);
i = NULL;
return r;
}
}
} else
i->state = DNS_ZONE_ITEM_ESTABLISHED;
i = NULL;
return 0;
}
static int dns_zone_add_authenticated_answer(DnsAnswer *a, DnsZoneItem *i, int ifindex) {
DnsAnswerFlags flags;
/* From RFC 6762, Section 10.2
* "They (the rules about when to set the cache-flush bit) apply to
* startup announcements as described in Section 8.3, "Announcing",
* and to responses generated as a result of receiving query messages."
* So, set the cache-flush bit for mDNS answers except for DNS-SD
* service enumeration PTRs described in RFC 6763, Section 4.1. */
if (i->scope->protocol == DNS_PROTOCOL_MDNS &&
!dns_resource_key_is_dnssd_ptr(i->rr->key))
flags = DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHE_FLUSH;
else
flags = DNS_ANSWER_AUTHENTICATED;
return dns_answer_add(a, i->rr, ifindex, flags);
}
int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
unsigned n_answer = 0;
DnsZoneItem *j, *first;
bool tentative = true, need_soa = false;
int r;
/* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the
* ifindex field in the answer with it */
assert(z);
assert(key);
assert(ret_answer);
/* First iteration, count what we have */
if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
bool found = false, added = false;
int k;
/* If this is a generic match, then we have to
* go through the list by the name and look
* for everything manually */
first = hashmap_get(z->by_name, dns_resource_key_name(key));
LIST_FOREACH(by_name, j, first) {
if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
continue;
found = true;
k = dns_resource_key_match_rr(key, j->rr, NULL);
if (k < 0)
return k;
if (k > 0) {
n_answer++;
added = true;
}
}
if (found && !added)
need_soa = true;
} else {
bool found = false;
/* If this is a specific match, then look for
* the right key immediately */
first = hashmap_get(z->by_key, key);
LIST_FOREACH(by_key, j, first) {
if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
continue;
found = true;
n_answer++;
}
if (!found) {
first = hashmap_get(z->by_name, dns_resource_key_name(key));
LIST_FOREACH(by_name, j, first) {
if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
continue;
need_soa = true;
break;
}
}
}
if (n_answer <= 0 && !need_soa)
goto return_empty;
if (n_answer > 0) {
answer = dns_answer_new(n_answer);
if (!answer)
return -ENOMEM;
}
if (need_soa) {
soa = dns_answer_new(1);
if (!soa)
return -ENOMEM;
}
/* Second iteration, actually add the RRs to the answers */
if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
bool found = false, added = false;
int k;
first = hashmap_get(z->by_name, dns_resource_key_name(key));
LIST_FOREACH(by_name, j, first) {
if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
continue;
found = true;
if (j->state != DNS_ZONE_ITEM_PROBING)
tentative = false;
k = dns_resource_key_match_rr(key, j->rr, NULL);
if (k < 0)
return k;
if (k > 0) {
r = dns_zone_add_authenticated_answer(answer, j, ifindex);
if (r < 0)
return r;
added = true;
}
}
if (found && !added) {
r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
if (r < 0)
return r;
}
} else {
bool found = false;
first = hashmap_get(z->by_key, key);
LIST_FOREACH(by_key, j, first) {
if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
continue;
found = true;
if (j->state != DNS_ZONE_ITEM_PROBING)
tentative = false;
r = dns_zone_add_authenticated_answer(answer, j, ifindex);
if (r < 0)
return r;
}
if (!found) {
bool add_soa = false;
first = hashmap_get(z->by_name, dns_resource_key_name(key));
LIST_FOREACH(by_name, j, first) {
if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
continue;
if (j->state != DNS_ZONE_ITEM_PROBING)
tentative = false;
add_soa = true;
}
if (add_soa) {
r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
if (r < 0)
return r;
}
}
}
/* If the caller sets ret_tentative to NULL, then use this as
* indication to not return tentative entries */
if (!ret_tentative && tentative)
goto return_empty;
*ret_answer = TAKE_PTR(answer);
if (ret_soa)
*ret_soa = TAKE_PTR(soa);
if (ret_tentative)
*ret_tentative = tentative;
return 1;
return_empty:
*ret_answer = NULL;
if (ret_soa)
*ret_soa = NULL;
if (ret_tentative)
*ret_tentative = false;
return 0;
}
void dns_zone_item_conflict(DnsZoneItem *i) {
assert(i);
if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
return;
log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr)));
dns_zone_item_probe_stop(i);
/* Withdraw the conflict item */
i->state = DNS_ZONE_ITEM_WITHDRAWN;
dnssd_signal_conflict(i->scope->manager, dns_resource_key_name(i->rr->key));
/* Maybe change the hostname */
if (manager_is_own_hostname(i->scope->manager, dns_resource_key_name(i->rr->key)) > 0)
manager_next_hostname(i->scope->manager);
}
void dns_zone_item_notify(DnsZoneItem *i) {
assert(i);
assert(i->probe_transaction);
if (i->block_ready > 0)
return;
if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING))
return;
if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
bool we_lost = false;
/* The probe got a successful reply. If we so far
* weren't established we just give up.
*
* In LLMNR case if we already
* were established, and the peer has the
* lexicographically larger IP address we continue
* and defend it. */
if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
log_debug("Got a successful probe for not yet established RR, we lost.");
we_lost = true;
} else if (i->probe_transaction->scope->protocol == DNS_PROTOCOL_LLMNR) {
assert(i->probe_transaction->received);
we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
if (we_lost)
log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
}
if (we_lost) {
dns_zone_item_conflict(i);
return;
}
log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
}
log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr)));
dns_zone_item_probe_stop(i);
i->state = DNS_ZONE_ITEM_ESTABLISHED;
}
static int dns_zone_item_verify(DnsZoneItem *i) {
int r;
assert(i);
if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
return 0;
log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr)));
i->state = DNS_ZONE_ITEM_VERIFYING;
r = dns_zone_item_probe_start(i);
if (r < 0) {
log_error_errno(r, "Failed to start probing for verifying RR: %m");
i->state = DNS_ZONE_ITEM_ESTABLISHED;
return r;
}
return 0;
}
int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
DnsZoneItem *i, *first;
int c = 0;
assert(zone);
assert(rr);
/* This checks whether a response RR we received from somebody
* else is one that we actually thought was uniquely ours. If
* so, we'll verify our RRs. */
/* No conflict if we don't have the name at all. */
first = hashmap_get(zone->by_name, dns_resource_key_name(rr->key));
if (!first)
return 0;
/* No conflict if we have the exact same RR */
if (dns_zone_get(zone, rr))
return 0;
/* No conflict if it is DNS-SD RR used for service enumeration. */
if (dns_resource_key_is_dnssd_ptr(rr->key))
return 0;
/* OK, somebody else has RRs for the same name. Yuck! Let's
* start probing again */
LIST_FOREACH(by_name, i, first) {
if (dns_resource_record_equal(i->rr, rr))
continue;
dns_zone_item_verify(i);
c++;
}
return c;
}
int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
DnsZoneItem *i, *first;
int c = 0;
assert(zone);
/* Somebody else notified us about a possible conflict. Let's
* verify if that's true. */
first = hashmap_get(zone->by_name, dns_resource_key_name(key));
if (!first)
return 0;
LIST_FOREACH(by_name, i, first) {
dns_zone_item_verify(i);
c++;
}
return c;
}
void dns_zone_verify_all(DnsZone *zone) {
DnsZoneItem *i;
Iterator iterator;
assert(zone);
HASHMAP_FOREACH(i, zone->by_key, iterator) {
DnsZoneItem *j;
LIST_FOREACH(by_key, j, i)
dns_zone_item_verify(j);
}
}
void dns_zone_dump(DnsZone *zone, FILE *f) {
Iterator iterator;
DnsZoneItem *i;
if (!zone)
return;
if (!f)
f = stdout;
HASHMAP_FOREACH(i, zone->by_key, iterator) {
DnsZoneItem *j;
LIST_FOREACH(by_key, j, i) {
const char *t;
t = dns_resource_record_to_string(j->rr);
if (!t) {
log_oom();
continue;
}
fputc('\t', f);
fputs(t, f);
fputc('\n', f);
}
}
}
bool dns_zone_is_empty(DnsZone *zone) {
if (!zone)
return true;
return hashmap_isempty(zone->by_key);
}
bool dns_zone_contains_name(DnsZone *z, const char *name) {
DnsZoneItem *i, *first;
first = hashmap_get(z->by_name, name);
if (!first)
return false;
LIST_FOREACH(by_name, i, first) {
if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
continue;
return true;
}
return false;
}