resolved-dns-query.c revision 7cc6ed7ba6c667caef9a92ba4d59e1ecdc3af8ff
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2014 Lennart Poettering
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "alloc-util.h"
#include "dns-domain.h"
#include "hostname-util.h"
#include "local-addresses.h"
#include "resolved-dns-query.h"
#include "resolved-dns-synthesize.h"
#include "resolved-etc-hosts.h"
#include "string-util.h"
/* How long to wait for the query in total */
#define CNAME_MAX 8
#define QUERIES_MAX 2048
#define AUXILIARY_QUERIES_MAX 64
assert(q);
assert(s);
if (!c)
return -ENOMEM;
c->query = q;
c->scope = s;
*ret = c;
return 0;
}
static void dns_query_candidate_stop(DnsQueryCandidate *c) {
DnsTransaction *t;
assert(c);
while ((t = set_steal_first(c->transactions))) {
set_remove(t->notify_query_candidates, c);
}
}
if (!c)
return NULL;
set_free(c->transactions);
if (c->query)
if (c->scope)
free(c);
return NULL;
}
static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) {
assert(c);
if (!next) /* We hit the end of the list */
return 0;
} else {
if (!next) /* OK, there's nothing. */
return 0;
}
return 1;
}
DnsTransaction *t;
int r;
assert(c);
if (!t) {
if (r < 0)
return r;
} else {
if (set_contains(c->transactions, t))
return 0;
}
if (r < 0)
goto gc;
if (r < 0)
goto gc;
r = set_put(t->notify_query_candidates, c);
if (r < 0)
goto gc;
r = set_put(c->transactions, t);
if (r < 0) {
(void) set_remove(t->notify_query_candidates, c);
goto gc;
}
return 1;
gc:
return r;
}
static int dns_query_candidate_go(DnsQueryCandidate *c) {
DnsTransaction *t;
Iterator i;
int r;
assert(c);
/* Start the transactions that are not started yet */
SET_FOREACH(t, c->transactions, i) {
if (t->state != DNS_TRANSACTION_NULL)
continue;
r = dns_transaction_go(t);
if (r < 0)
return r;
}
return 0;
}
DnsTransaction *t;
Iterator i;
assert(c);
if (c->error_code != 0)
return DNS_TRANSACTION_ERRNO;
SET_FOREACH(t, c->transactions, i) {
switch (t->state) {
case DNS_TRANSACTION_NULL:
/* If there's a NULL transaction pending, then
* this means not all transactions where
* started yet, and we were called from within
* the stackframe that is supposed to start
* remaining transactions. In this case,
* simply claim the candidate is pending. */
case DNS_TRANSACTION_PENDING:
/* If there's one transaction currently in
* VALIDATING state, then this means there's
* also one in PENDING state, hence we can
* return PENDING immediately. */
return DNS_TRANSACTION_PENDING;
case DNS_TRANSACTION_SUCCESS:
break;
default:
if (state != DNS_TRANSACTION_SUCCESS)
break;
}
}
return state;
}
static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) {
int n = 0, r;
assert(c);
/* Create one transaction per question key */
if (c->search_domain) {
if (r < 0)
goto fail;
}
if (r < 0)
goto fail;
n++;
}
return n;
fail:
return r;
}
void dns_query_candidate_notify(DnsQueryCandidate *c) {
int r;
assert(c);
if (DNS_TRANSACTION_IS_LIVE(state))
return;
if (r < 0)
goto fail;
if (r > 0) {
/* OK, there's another search domain to try, let's do so. */
if (r < 0)
goto fail;
if (r > 0) {
/* New transactions where queued. Start them and wait */
r = dns_query_candidate_go(c);
if (r < 0)
goto fail;
return;
}
}
}
dns_query_ready(c->query);
return;
fail:
log_warning_errno(r, "Failed to follow search domains: %m");
c->error_code = r;
dns_query_ready(c->query);
}
static void dns_query_stop(DnsQuery *q) {
assert(q);
}
static void dns_query_free_candidates(DnsQuery *q) {
assert(q);
while (q->candidates)
}
static void dns_query_reset_answer(DnsQuery *q) {
assert(q);
q->answer_rcode = 0;
q->answer_errno = 0;
q->answer_authenticated = false;
q->answer_family = AF_UNSPEC;
}
if (!q)
return NULL;
while (q->auxiliary_queries)
if (q->auxiliary_for) {
q->auxiliary_for->n_auxiliary_queries--;
}
if (q->manager) {
q->manager->n_dns_queries--;
}
free(q);
return NULL;
}
int dns_query_new(
Manager *m,
bool good = false;
int r;
assert(m);
if (dns_question_size(question_utf8) > 0) {
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
good = true;
}
/* If the IDNA and UTF8 questions are the same, merge their references */
if (r < 0)
return r;
if (r > 0)
else {
if (dns_question_size(question_idna) > 0) {
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
good = true;
}
}
if (!good) /* don't allow empty queries */
return -EINVAL;
if (m->n_dns_queries >= QUERIES_MAX)
return -EBUSY;
if (!q)
return -ENOMEM;
q->answer_family = AF_UNSPEC;
/* First dump UTF8 question */
_cleanup_free_ char *p = NULL;
r = dns_resource_key_to_string(key, &p);
if (r < 0)
return r;
}
/* And then dump the IDNA question, but only what hasn't been dumped already through the UTF8 question. */
_cleanup_free_ char *p = NULL;
if (r < 0)
return r;
if (r > 0)
continue;
r = dns_resource_key_to_string(key, &p);
if (r < 0)
return r;
}
m->n_dns_queries++;
q->manager = m;
if (ret)
*ret = q;
q = NULL;
return 0;
}
assert(q);
/* Ensure that that the query is not auxiliary yet, and
* nothing else is auxiliary to it either */
assert(!q->auxiliary_for);
assert(!q->auxiliary_queries);
/* Ensure that the unit we shall be made auxiliary for isn't
* auxiliary itself */
return -EAGAIN;
q->auxiliary_for = auxiliary_for;
return 0;
}
assert(q);
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
* after calling this function. */
dns_query_stop(q);
if (q->complete)
q->complete(q);
}
assert(s);
assert(q);
return 0;
}
int r;
assert(q);
assert(s);
r = dns_query_candidate_new(&c, q, s);
if (r < 0)
return r;
/* If this a single-label domain on DNS, we might append a suitable search domain first. */
if ((q->flags & SD_RESOLVED_NO_SEARCH) == 0) {
if (r < 0)
goto fail;
if (r > 0) {
/* OK, we need a search domain now. Let's find one for this scope */
if (r <= 0) /* if there's no search domain, then we won't add any transaction. */
goto fail;
}
}
if (r < 0)
goto fail;
return 0;
fail:
return r;
}
int r;
assert(q);
/* Tries to synthesize localhost RR replies (and others) where appropriate. Note that this is done *after* the
* the normal lookup finished. The data from the network hence takes precedence over the data we
* synthesize. (But note that many scopes refuse to resolve certain domain names) */
return 0;
q->manager,
q->question_utf8,
q->ifindex,
&answer);
if (r <= 0)
return r;
q->answer_authenticated = true;
return 1;
}
static int dns_query_try_etc_hosts(DnsQuery *q) {
int r;
assert(q);
/* Looks in /etc/hosts for matching entries. Note that this is done *before* the normal lookup is done. The
q->manager,
q->question_utf8,
&answer);
if (r <= 0)
return r;
q->answer_authenticated = true;
return 1;
}
int dns_query_go(DnsQuery *q) {
int r;
assert(q);
if (q->state != DNS_TRANSACTION_NULL)
return 0;
r = dns_query_try_etc_hosts(q);
if (r < 0)
return r;
if (r > 0) {
return 1;
}
const char *name;
if (!name)
continue;
if (match < 0)
return match;
if (match == DNS_SCOPE_NO)
continue;
if (match == DNS_SCOPE_YES) {
first = s;
break;
} else {
if (!first)
first = s;
}
}
if (found == DNS_SCOPE_NO) {
r = dns_query_synthesize_reply(q, &state);
if (r < 0)
return r;
dns_query_complete(q, state);
return 1;
}
r = dns_query_add_candidate(q, first);
if (r < 0)
goto fail;
const char *name;
if (!name)
continue;
if (match < 0)
goto fail;
continue;
r = dns_query_add_candidate(q, s);
if (r < 0)
goto fail;
}
q->answer_rcode = 0;
q->answer_family = AF_UNSPEC;
r = sd_event_add_time(
&q->timeout_event_source,
on_query_timeout, q);
if (r < 0)
goto fail;
q->state = DNS_TRANSACTION_PENDING;
q->block_ready++;
/* Start the transactions */
r = dns_query_candidate_go(c);
if (r < 0) {
q->block_ready--;
goto fail;
}
}
q->block_ready--;
dns_query_ready(q);
return 1;
fail:
dns_query_stop(q);
return r;
}
bool has_authenticated = false, has_non_authenticated = false;
DnssecResult dnssec_result_authenticated = _DNSSEC_RESULT_INVALID, dnssec_result_non_authenticated = _DNSSEC_RESULT_INVALID;
DnsTransaction *t;
Iterator i;
int r;
assert(q);
if (!c) {
r = dns_query_synthesize_reply(q, &state);
if (r < 0)
goto fail;
dns_query_complete(q, state);
return;
}
SET_FOREACH(t, c->transactions, i) {
switch (t->state) {
case DNS_TRANSACTION_SUCCESS: {
/* We found a successfuly reply, merge it into the answer */
if (r < 0)
goto fail;
q->answer_rcode = t->answer_rcode;
q->answer_errno = 0;
if (t->answer_authenticated) {
has_authenticated = true;
} else {
has_non_authenticated = true;
}
break;
}
case DNS_TRANSACTION_NULL:
case DNS_TRANSACTION_PENDING:
case DNS_TRANSACTION_ABORTED:
/* Ignore transactions that didn't complete */
continue;
default:
/* Any kind of failure? Store the data away,
* if there's nothing stored yet. */
if (state == DNS_TRANSACTION_SUCCESS)
continue;
q->answer_rcode = t->answer_rcode;
q->answer_dnssec_result = t->answer_dnssec_result;
q->answer_errno = t->answer_errno;
break;
}
}
if (state == DNS_TRANSACTION_SUCCESS) {
q->answer_dnssec_result = q->answer_authenticated ? dnssec_result_authenticated : dnssec_result_non_authenticated;
}
r = dns_query_synthesize_reply(q, &state);
if (r < 0)
goto fail;
dns_query_complete(q, state);
return;
fail:
q->answer_errno = -r;
}
void dns_query_ready(DnsQuery *q) {
bool pending = false;
assert(q);
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
* after calling this function, unless the block_ready
* counter was explicitly bumped before doing so. */
if (q->block_ready > 0)
return;
switch (state) {
case DNS_TRANSACTION_SUCCESS:
/* One of the candidates is successful,
* let's use it, and copy its data out */
dns_query_accept(q, c);
return;
case DNS_TRANSACTION_NULL:
case DNS_TRANSACTION_PENDING:
/* One of the candidates is still going on,
* let's maybe wait for it */
pending = true;
break;
default:
/* Any kind of failure */
bad = c;
break;
}
}
if (pending)
return;
dns_query_accept(q, bad);
}
int r, k;
assert(q);
q->n_cname_redirects ++;
if (q->n_cname_redirects > CNAME_MAX)
return -ELOOP;
if (r < 0)
return r;
else if (r > 0)
log_debug("Following CNAME/DNAME %s → %s.", dns_question_first_name(q->question_idna), dns_question_first_name(nq_idna));
if (k < 0)
return r;
if (k > 0) {
/* Same question? Shortcut new question generation */
k = r;
} else {
if (k < 0)
return k;
else if (k > 0)
log_debug("Following UTF8 CNAME/DNAME %s → %s.", dns_question_first_name(q->question_utf8), dns_question_first_name(nq_utf8));
}
if (r == 0 && k == 0) /* No actual cname happened? */
return -ELOOP;
q->question_idna = nq_idna;
q->question_utf8 = nq_utf8;
q->state = DNS_TRANSACTION_NULL;
/* Turn off searching for the new name */
q->flags |= SD_RESOLVED_NO_SEARCH;
return 0;
}
int dns_query_process_cname(DnsQuery *q) {
int r;
assert(q);
return DNS_QUERY_NOMATCH;
if (r < 0)
return r;
if (r > 0)
return DNS_QUERY_MATCH; /* The answer matches directly, no need to follow cnames */
r = dns_question_matches_cname_or_dname(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
if (r < 0)
return r;
if (r > 0 && !cname)
}
if (!cname)
return DNS_QUERY_NOMATCH; /* No match and no cname to follow */
if (q->flags & SD_RESOLVED_NO_CNAME)
return -ELOOP;
/* OK, let's actually follow the CNAME */
r = dns_query_cname_redirect(q, cname);
if (r < 0)
return r;
/* Let's see if the answer can already answer the new
* redirected question */
r = dns_query_process_cname(q);
if (r != DNS_QUERY_NOMATCH)
return r;
/* OK, it cannot, let's begin with the new query */
r = dns_query_go(q);
if (r < 0)
return r;
return DNS_QUERY_RESTARTED; /* We restarted the query for a new cname */
}
assert(t);
assert(q);
log_debug("Client of active query vanished, aborting query.");
return 0;
}
int r;
assert(q);
assert(m);
if (!q->bus_track) {
if (r < 0)
return r;
}
r = sd_bus_track_add_sender(q->bus_track, m);
if (r < 0)
return r;
return 0;
}
assert(q);
switch (protocol) {
case DNS_PROTOCOL_DNS:
return q->question_idna;
case DNS_PROTOCOL_MDNS:
case DNS_PROTOCOL_LLMNR:
return q->question_utf8;
default:
return NULL;
}
}
const char *dns_query_string(DnsQuery *q) {
const char *name;
int r;
/* Returns a somewhat useful human-readable lookup key string for this query */
if (q->request_address_string)
return q->request_address_string;
if (q->request_address_valid) {
if (r >= 0)
return q->request_address_string;
}
if (name)
return name;
return dns_question_first_name(q->question_idna);
}