resolved-dns-query.c revision 322345fdb9865ef2477fba8e4bdde0e1183ef505
/*-*- 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 "resolved-dns-query.h"
#include "resolved-dns-domain.h"
#define ATTEMPTS_MAX 8
#define CNAME_MAX 8
#define QUERIES_MAX 2048
static int dns_query_transaction_go(DnsQueryTransaction *t);
if (!t)
return NULL;
dns_packet_unref(t->sent);
dns_packet_unref(t->received);
safe_close(t->tcp_fd);
if (t->query) {
}
if (t->scope)
free(t);
return NULL;
}
int r;
assert(q);
assert(s);
if (r < 0)
return r;
if (!t)
return -ENOMEM;
t->tcp_fd = -1;
do
while (t->id == 0 ||
if (r < 0) {
t->id = 0;
return r;
}
t->query = q;
t->scope = s;
if (ret)
*ret = t;
t = NULL;
return 0;
}
static void dns_query_transaction_stop(DnsQueryTransaction *t) {
assert(t);
}
assert(t);
/* 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_finish(t->query);
}
DnsQueryTransaction *t = userdata;
int r;
assert(t);
if (ss < 0) {
return -errno;
}
} else
t->tcp_written += ss;
/* Are we done? If so, disable the event source for EPOLLOUT */
r = sd_event_source_set_io_events(s, EPOLLIN);
if (r < 0) {
return r;
}
}
}
if (t->tcp_read < sizeof(t->tcp_read_size)) {
if (ss < 0) {
return -errno;
}
} else if (ss == 0) {
return -EIO;
} else
}
if (t->tcp_read >= sizeof(t->tcp_read_size)) {
return -EBADMSG;
}
if (!t->received) {
if (r < 0) {
return r;
}
}
if (ss < 0) {
return -errno;
}
} else if (ss == 0) {
return -EIO;
} else
}
dns_query_transaction_reply(t, t->received);
return 0;
}
}
}
return 0;
}
static int dns_query_transaction_open_tcp(DnsQueryTransaction *t) {
int r;
assert(t);
if (t->tcp_fd >= 0)
return 0;
t->tcp_written = 0;
t->tcp_read = 0;
if (t->tcp_fd < 0)
return t->tcp_fd;
r = sd_event_add_io(t->query->manager->event, &t->tcp_event_source, t->tcp_fd, EPOLLIN|EPOLLOUT, on_tcp_ready, t);
if (r < 0) {
return r;
}
return 0;
}
int r;
assert(t);
assert(p);
/* Note that this call might invalidate the query. Callers
* should hence not attempt to access the query or transaction
* after calling this function. */
if (t->received != p) {
dns_packet_unref(t->received);
t->received = dns_packet_ref(p);
}
if (t->tcp_fd >= 0) {
if (DNS_PACKET_TC(p)) {
/* Truncated via TCP? Somebody must be fucking with us */
return;
}
if (DNS_PACKET_ID(p) != t->id) {
/* Not the reply to our query? Somebody must be fucking with us */
return;
}
}
if (DNS_PACKET_TC(p)) {
/* Response was truncated, let's try again with good old TCP */
r = dns_query_transaction_open_tcp(t);
if (r == -ESRCH) {
/* No servers found? Damn! */
return;
}
if (r < 0) {
/* Couldn't send? Try immediately again, with a new server */
r = dns_query_transaction_go(t);
if (r < 0) {
return;
}
return;
}
}
/* Parse and update the cache */
r = dns_packet_extract_rrs(p);
if (r < 0) {
return;
} else if (r > 0)
if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS)
else
}
DnsQueryTransaction *t = userdata;
int r;
assert(s);
assert(t);
/* Timeout reached? Try again, with a new server */
r = dns_query_transaction_go(t);
if (r < 0)
return 0;
}
static int dns_query_make_packet(DnsQueryTransaction *t) {
unsigned n;
int r;
assert(t);
if (t->sent)
return 0;
r = dns_packet_new_query(&p, 0);
if (r < 0)
return r;
if (r < 0)
return r;
}
t->sent = p;
p = NULL;
return 0;
}
static int dns_query_transaction_go(DnsQueryTransaction *t) {
int r;
assert(t);
if (t->n_attempts >= ATTEMPTS_MAX) {
return 0;
}
t->n_attempts++;
t->n_cached_rrs = 0;
/* First, let's try the cache */
if (r < 0)
return r;
if (r > 0) {
t->n_cached_rrs = r;
return 0;
}
/* Otherwise, we need to ask the network */
r = dns_query_make_packet(t);
if (r < 0)
return r;
/* Try via UDP, and if that fails due to large size try via TCP */
if (r == -EMSGSIZE)
r = dns_query_transaction_open_tcp(t);
if (r == -ESRCH) {
return 0;
}
if (r < 0) {
/* Couldn't send? Try immediately again, with a new server */
return dns_query_transaction_go(t);
}
r = sd_event_add_time(t->query->manager->event, &t->timeout_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + TRANSACTION_TIMEOUT_USEC, 0, on_transaction_timeout, t);
if (r < 0)
return r;
t->state = DNS_QUERY_PENDING;
return 1;
}
unsigned n;
if (!q)
return NULL;
dns_packet_unref(q->received);
while (q->transactions)
if (q->manager) {
q->manager->n_dns_queries--;
}
for (n = 0; n < q->n_keys; n++)
free(q);
return NULL;
}
assert(m);
return -EINVAL;
if (m->n_dns_queries >= QUERIES_MAX)
return -EBUSY;
if (!q)
return -ENOMEM;
if (!q->keys)
return -ENOMEM;
return -ENOMEM;
if (!name)
return -EINVAL;
log_debug("Looking up RR for %s %s %s",
}
m->n_dns_queries++;
q->manager = m;
if (ret)
*ret = q;
q = NULL;
return 0;
}
static void dns_query_stop(DnsQuery *q) {
assert(q);
while (q->transactions)
}
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 dns_query_go(DnsQuery *q) {
int r;
assert(q);
if (q->state != DNS_QUERY_NULL)
return 0;
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)
return -ESRCH;
if (r < 0)
return r;
if (match < 0)
return match;
continue;
r = dns_query_transaction_new(q, NULL, s);
if (r < 0)
return r;
}
r = sd_event_add_time(q->manager->event, &q->timeout_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + QUERY_TIMEOUT_USEC, 0, on_query_timeout, q);
if (r < 0)
goto fail;
q->state = DNS_QUERY_PENDING;
q->block_finish++;
r = dns_query_transaction_go(t);
if (r < 0)
goto fail;
}
q->block_finish--;
dns_query_finish(q);
return 1;
fail:
dns_query_stop(q);
return r;
}
void dns_query_finish(DnsQuery *q) {
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_finish
* counter was explicitly bumped before doing so. */
if (q->block_finish > 0)
return;
/* One of the transactions is still going on, let's wait for it */
return;
/* One of the transactions is successful, let's use
* it, and copy its data out */
if (t->state == DNS_QUERY_SUCCESS) {
/* We simply steal the cached RRs array */
q->cached_rrs = t->cached_rrs;
q->n_cached_rrs = t->n_cached_rrs;
t->cached_rrs = NULL;
t->n_cached_rrs = 0;
return;
}
/* One of the transactions has failed, let's see
* whether we find anything better, but if not, return
* its response packet */
if (t->state == DNS_QUERY_FAILURE) {
continue;
}
}
if (state == DNS_QUERY_FAILURE)
dns_query_complete(q, state);
}
unsigned i;
assert(q);
return -ELOOP;
if (!keys)
return -ENOMEM;
for (i = 0; i < q->n_keys; i++) {
for (; i > 0; i--)
return -ENOMEM;
}
}
for (i = 0; i < q->n_keys; i++)
q->n_cname++;
dns_query_stop(q);
q->state = DNS_QUERY_NULL;
return 0;
}
unsigned i;
int r;
assert(q);
for (i = 0; i < q->n_keys; i++) {
continue;
continue;
if (r != 0)
return r;
}
return 0;
}
unsigned i;
int r;
assert(q);
for (i = 0; i < q->n_keys; i++) {
continue;
continue;
if (r != 0)
return r;
}
return 0;
}
int r;
assert(q);
return -EBUSY;
if (q->received) {
r = dns_packet_extract_rrs(q->received);
if (r < 0)
return r;
if (r == 0) {
return r;
}
return r;
}
if (q->cached_rrs) {
*rrs = q->cached_rrs;
return q->n_cached_rrs;
}
return -ESRCH;
}
int dns_query_get_rcode(DnsQuery *q) {
assert(q);
return -EBUSY;
if (!q->received)
return -ESRCH;
return DNS_PACKET_RCODE(q->received);
}
int dns_query_get_ifindex(DnsQuery *q) {
assert(q);
return -EBUSY;
if (!q->received)
return -ESRCH;
}