resolved-dns-query.c revision edbcc1fdd94355c5cf22263ba2c1cfa4ec2eb010
/*-*- 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 "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_RESOURCES;
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_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;
}
static int SYNTHESIZE_IFINDEX(int ifindex) {
/* When the caller asked for resolving on a specific
* interface, we synthesize the answer for that
* interface. However, if nothing specific was claimed and we
* only return localhost RRs, we synthesize the answer for
* localhost. */
if (ifindex > 0)
return ifindex;
return LOOPBACK_IFINDEX;
}
/* Picks an address family depending on set flags. This is
* purely for synthesized answers, where the family we return
* for the reply should match what was requested in the
* question, even though we are synthesizing the answer
* here. */
if (!(flags & SD_RESOLVED_DNS)) {
if (flags & SD_RESOLVED_LLMNR_IPV4)
return AF_INET;
if (flags & SD_RESOLVED_LLMNR_IPV6)
return AF_INET6;
}
return AF_UNSPEC;
}
/* Similar as SYNTHESIZE_FAMILY() but does this for the
* protocol. If resolving via DNS was requested, we claim it
* was DNS. Similar, if nothing specific was
* requested. However, if only resolving via LLMNR was
* requested we return that. */
if (flags & SD_RESOLVED_DNS)
return DNS_PROTOCOL_DNS;
if (flags & SD_RESOLVED_LLMNR)
return DNS_PROTOCOL_LLMNR;
return DNS_PROTOCOL_DNS;
}
static int dns_type_to_af(uint16_t t) {
switch (t) {
case DNS_TYPE_A:
return AF_INET;
case DNS_TYPE_AAAA:
return AF_INET6;
case DNS_TYPE_ANY:
return AF_UNSPEC;
default:
return -EINVAL;
}
}
int r;
assert(q);
if (r < 0)
return r;
if (!rr)
return -ENOMEM;
if (r < 0)
return r;
}
if (!rr)
return -ENOMEM;
if (r < 0)
return r;
}
return 0;
}
static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex, DnsAnswerFlags flags) {
if (!rr)
return -ENOMEM;
return -ENOMEM;
}
int r;
assert(q);
if (r < 0)
return r;
r = answer_add_ptr(answer, DNS_RESOURCE_KEY_NAME(key), "localhost", SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
}
return 0;
}
static int answer_add_addresses_rr(
const char *name,
struct local_address *addresses,
unsigned n_addresses) {
unsigned j;
int r;
if (r < 0)
return r;
for (j = 0; j < n_addresses; j++) {
if (r < 0)
return r;
if (r < 0)
return r;
}
return 0;
}
static int answer_add_addresses_ptr(
const char *name,
struct local_address *addresses,
unsigned n_addresses,
unsigned j;
int r;
for (j = 0; j < n_addresses; j++) {
continue;
continue;
}
if (r < 0)
return r;
if (r < 0)
return r;
if (r < 0)
return r;
}
return 0;
}
static int synthesize_system_hostname_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) {
int n = 0, af;
assert(q);
if (af >= 0) {
if (n < 0)
return n;
if (n == 0) {
/* If we have no local addresses then use ::1
* and 127.0.0.2 as local ones. */
buffer[n++] = (struct local_address) {
};
buffer[n++] = (struct local_address) {
};
}
}
}
static int synthesize_system_hostname_ptr(DnsQuery *q, int af, const union in_addr_union *address, DnsAnswer **answer) {
int n, r;
assert(q);
/* Always map the IPv4 address 127.0.0.2 to the local
* hostname, in addition to "localhost": */
if (r < 0)
return r;
r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->llmnr_hostname, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->mdns_hostname, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
return 0;
}
if (n < 0)
return n;
if (r < 0)
return r;
}
int n = 0, af;
assert(q);
if (af >= 0) {
if (n < 0)
return n;
}
}
static int synthesize_gateway_ptr(DnsQuery *q, int af, const union in_addr_union *address, DnsAnswer **answer) {
int n;
assert(q);
if (n < 0)
return n;
}
int r;
assert(q);
/* Tries to synthesize localhost RR replies where appropriate */
return 0;
union in_addr_union address;
const char *name;
int af;
continue;
if (is_localhost(name)) {
if (r < 0)
return log_error_errno(r, "Failed to synthesize localhost RRs: %m");
if (r < 0)
return log_error_errno(r, "Failed to synthesize system hostname RRs: %m");
} else if (is_gateway_hostname(name)) {
if (r < 0)
return log_error_errno(r, "Failed to synthesize gateway RRs: %m");
} else if ((dns_name_endswith(name, "127.in-addr.arpa") > 0 && dns_name_equal(name, "2.0.0.127.in-addr.arpa") == 0) ||
dns_name_equal(name, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) {
if (r < 0)
return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m");
if (r < 0)
return log_error_errno(r, "Failed to synthesize system hostname PTR RR: %m");
if (r < 0)
return log_error_errno(r, "Failed to synthesize gateway hostname PTR RR: %m");
}
}
if (!answer)
return 0;
dns_answer_unref(q->answer);
return 1;
}
int dns_query_go(DnsQuery *q) {
int r;
assert(q);
if (q->state != DNS_TRANSACTION_NULL)
return 0;
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) {
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) {
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) {
return;
}
q->answer_rcode = t->answer_rcode;
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;
break;
}
}
if (state == DNS_TRANSACTION_SUCCESS) {
q->answer_dnssec_result = q->answer_authenticated ? dnssec_result_authenticated : dnssec_result_non_authenticated;
}
dns_query_complete(q, state);
}
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);
}