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/>.
23 #include "resolved-dns-query.h"
25 /* How long to wait for the query in total */
26 #define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC)
29 #define QUERIES_MAX 2048
31 static void dns_query_stop(DnsQuery *q) {
36 q->timeout_event_source = sd_event_source_unref(q->timeout_event_source);
38 while ((t = set_steal_first(q->transactions))) {
39 set_remove(t->queries, q);
40 dns_transaction_gc(t);
44 DnsQuery *dns_query_free(DnsQuery *q) {
49 set_free(q->transactions);
51 dns_question_unref(q->question);
52 dns_answer_unref(q->answer);
54 sd_bus_message_unref(q->request);
55 sd_bus_track_unref(q->bus_track);
58 LIST_REMOVE(queries, q->manager->dns_queries, q);
59 q->manager->n_dns_queries--;
67 int dns_query_new(Manager *m, DnsQuery **ret, DnsQuestion *question, int ifindex, uint64_t flags) {
68 _cleanup_(dns_query_freep) DnsQuery *q = NULL;
75 r = dns_question_is_valid(question);
79 if (m->n_dns_queries >= QUERIES_MAX)
82 q = new0(DnsQuery, 1);
86 q->question = dns_question_ref(question);
90 for (i = 0; i < question->n_keys; i++) {
91 _cleanup_free_ char *p;
93 r = dns_resource_key_to_string(question->keys[i], &p);
97 log_debug("Looking up RR for %s", p);
100 LIST_PREPEND(queries, m->dns_queries, q);
111 static void dns_query_complete(DnsQuery *q, DnsTransactionState state) {
113 assert(!IN_SET(state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
114 assert(IN_SET(q->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
116 /* Note that this call might invalidate the query. Callers
117 * should hence not attempt to access the query or transaction
118 * after calling this function. */
127 static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) {
128 DnsQuery *q = userdata;
133 dns_query_complete(q, DNS_TRANSACTION_TIMEOUT);
137 static int dns_query_add_transaction(DnsQuery *q, DnsScope *s, DnsResourceKey *key) {
138 _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
145 r = set_ensure_allocated(&q->transactions, NULL);
150 question = dns_question_new(1);
154 r = dns_question_add(question, key);
158 question = dns_question_ref(q->question);
160 t = dns_scope_find_transaction(s, question, true);
162 r = dns_transaction_new(&t, s, question);
167 r = set_ensure_allocated(&t->queries, NULL);
171 r = set_put(t->queries, q);
175 r = set_put(q->transactions, t);
177 set_remove(t->queries, q);
184 dns_transaction_gc(t);
188 static int dns_query_add_transaction_split(DnsQuery *q, DnsScope *s) {
194 if (s->protocol == DNS_PROTOCOL_MDNS) {
195 r = dns_query_add_transaction(q, s, NULL);
201 /* On DNS and LLMNR we can only send a single
202 * question per datagram, hence issue multiple
205 for (i = 0; i < q->question->n_keys; i++) {
206 r = dns_query_add_transaction(q, s, q->question->keys[i]);
215 int dns_query_go(DnsQuery *q) {
216 DnsScopeMatch found = DNS_SCOPE_NO;
217 DnsScope *s, *first = NULL;
225 if (q->state != DNS_TRANSACTION_NULL)
229 assert(q->question->n_keys > 0);
231 name = DNS_RESOURCE_KEY_NAME(q->question->keys[0]);
233 LIST_FOREACH(scopes, s, q->manager->dns_scopes) {
236 match = dns_scope_good_domain(s, q->ifindex, q->flags, name);
240 if (match == DNS_SCOPE_NO)
245 if (match == DNS_SCOPE_YES) {
249 assert(match == DNS_SCOPE_MAYBE);
256 if (found == DNS_SCOPE_NO)
259 r = dns_query_add_transaction_split(q, first);
263 LIST_FOREACH(scopes, s, first->scopes_next) {
266 match = dns_scope_good_domain(s, q->ifindex, q->flags, name);
273 r = dns_query_add_transaction_split(q, s);
278 q->answer = dns_answer_unref(q->answer);
279 q->answer_ifindex = 0;
281 q->answer_family = AF_UNSPEC;
282 q->answer_protocol = _DNS_PROTOCOL_INVALID;
284 r = sd_event_add_time(
286 &q->timeout_event_source,
287 clock_boottime_or_monotonic(),
288 now(clock_boottime_or_monotonic()) + QUERY_TIMEOUT_USEC, 0,
289 on_query_timeout, q);
293 q->state = DNS_TRANSACTION_PENDING;
296 /* Start the transactions that are not started yet */
297 SET_FOREACH(t, q->transactions, i) {
298 if (t->state != DNS_TRANSACTION_NULL)
301 r = dns_transaction_go(t);
316 void dns_query_ready(DnsQuery *q) {
318 DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS;
319 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
321 DnsScope *scope = NULL;
322 bool pending = false;
326 assert(IN_SET(q->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
328 /* Note that this call might invalidate the query. Callers
329 * should hence not attempt to access the query or transaction
330 * after calling this function, unless the block_ready
331 * counter was explicitly bumped before doing so. */
333 if (q->block_ready > 0)
336 SET_FOREACH(t, q->transactions, i) {
338 /* If we found a successful answer, ignore all answers from other scopes */
339 if (state == DNS_TRANSACTION_SUCCESS && t->scope != scope)
342 /* One of the transactions is still going on, let's maybe wait for it */
343 if (IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_NULL)) {
348 /* One of the transactions is successful, let's use
349 * it, and copy its data out */
350 if (t->state == DNS_TRANSACTION_SUCCESS) {
354 rcode = DNS_PACKET_RCODE(t->received);
355 a = t->received->answer;
357 rcode = t->cached_rcode;
361 if (state == DNS_TRANSACTION_SUCCESS) {
364 merged = dns_answer_merge(answer, a);
366 dns_query_complete(q, DNS_TRANSACTION_RESOURCES);
370 dns_answer_unref(answer);
373 dns_answer_unref(answer);
374 answer = dns_answer_ref(a);
378 state = DNS_TRANSACTION_SUCCESS;
382 /* One of the transactions has failed, let's see
383 * whether we find anything better, but if not, return
384 * its response data */
385 if (state != DNS_TRANSACTION_SUCCESS && t->state == DNS_TRANSACTION_FAILURE) {
389 rcode = DNS_PACKET_RCODE(t->received);
390 a = t->received->answer;
392 rcode = t->cached_rcode;
396 dns_answer_unref(answer);
397 answer = dns_answer_ref(a);
400 state = DNS_TRANSACTION_FAILURE;
404 if (state == DNS_TRANSACTION_NO_SERVERS && t->state != DNS_TRANSACTION_NO_SERVERS)
410 /* If so far we weren't successful, and there's
411 * something still pending, then wait for it */
412 if (state != DNS_TRANSACTION_SUCCESS)
415 /* If we already were successful, then only wait for
416 * other transactions on the same scope to finish. */
417 SET_FOREACH(t, q->transactions, i) {
418 if (t->scope == scope && IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_NULL))
423 if (IN_SET(state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_FAILURE)) {
424 q->answer = dns_answer_ref(answer);
425 q->answer_rcode = rcode;
426 q->answer_ifindex = (scope && scope->link) ? scope->link->ifindex : 0;
427 q->answer_protocol = scope ? scope->protocol : _DNS_PROTOCOL_INVALID;
428 q->answer_family = scope ? scope->family : AF_UNSPEC;
431 dns_query_complete(q, state);
434 int dns_query_cname_redirect(DnsQuery *q, const char *name) {
435 _cleanup_(dns_question_unrefp) DnsQuestion *nq = NULL;
440 if (q->n_cname_redirects > CNAME_MAX)
443 r = dns_question_cname_redirect(q->question, name, &nq);
447 dns_question_unref(q->question);
451 q->n_cname_redirects++;
454 q->state = DNS_TRANSACTION_NULL;
459 static int on_bus_track(sd_bus_track *t, void *userdata) {
460 DnsQuery *q = userdata;
465 log_debug("Client of active query vanished, aborting query.");
466 dns_query_complete(q, DNS_TRANSACTION_ABORTED);
470 int dns_query_bus_track(DnsQuery *q, sd_bus *bus, sd_bus_message *m) {
477 r = sd_bus_track_new(bus, &q->bus_track, on_bus_track, q);
482 r = sd_bus_track_add_sender(q->bus_track, m);