]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-query.c
resolved: rr - print formated timestamps in RRSIG
[thirdparty/systemd.git] / src / resolve / resolved-dns-query.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2014 Lennart Poettering
7
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.
12
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.
17
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/>.
20 ***/
21
22
23 #include "resolved-dns-query.h"
24
25 /* How long to wait for the query in total */
26 #define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC)
27
28 #define CNAME_MAX 8
29 #define QUERIES_MAX 2048
30
31 static void dns_query_stop(DnsQuery *q) {
32 DnsTransaction *t;
33
34 assert(q);
35
36 q->timeout_event_source = sd_event_source_unref(q->timeout_event_source);
37
38 while ((t = set_steal_first(q->transactions))) {
39 set_remove(t->queries, q);
40 dns_transaction_gc(t);
41 }
42 }
43
44 DnsQuery *dns_query_free(DnsQuery *q) {
45 if (!q)
46 return NULL;
47
48 dns_query_stop(q);
49 set_free(q->transactions);
50
51 dns_question_unref(q->question);
52 dns_answer_unref(q->answer);
53
54 sd_bus_message_unref(q->request);
55 sd_bus_track_unref(q->bus_track);
56
57 if (q->manager) {
58 LIST_REMOVE(queries, q->manager->dns_queries, q);
59 q->manager->n_dns_queries--;
60 }
61
62 free(q);
63
64 return NULL;
65 }
66
67 int dns_query_new(Manager *m, DnsQuery **ret, DnsQuestion *question, int ifindex, uint64_t flags) {
68 _cleanup_(dns_query_freep) DnsQuery *q = NULL;
69 unsigned i;
70 int r;
71
72 assert(m);
73 assert(question);
74
75 r = dns_question_is_valid(question);
76 if (r < 0)
77 return r;
78
79 if (m->n_dns_queries >= QUERIES_MAX)
80 return -EBUSY;
81
82 q = new0(DnsQuery, 1);
83 if (!q)
84 return -ENOMEM;
85
86 q->question = dns_question_ref(question);
87 q->ifindex = ifindex;
88 q->flags = flags;
89
90 for (i = 0; i < question->n_keys; i++) {
91 _cleanup_free_ char *p;
92
93 r = dns_resource_key_to_string(question->keys[i], &p);
94 if (r < 0)
95 return r;
96
97 log_debug("Looking up RR for %s", p);
98 }
99
100 LIST_PREPEND(queries, m->dns_queries, q);
101 m->n_dns_queries++;
102 q->manager = m;
103
104 if (ret)
105 *ret = q;
106 q = NULL;
107
108 return 0;
109 }
110
111 static void dns_query_complete(DnsQuery *q, DnsTransactionState state) {
112 assert(q);
113 assert(!IN_SET(state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
114 assert(IN_SET(q->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
115
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. */
119
120 q->state = state;
121
122 dns_query_stop(q);
123 if (q->complete)
124 q->complete(q);
125 }
126
127 static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) {
128 DnsQuery *q = userdata;
129
130 assert(s);
131 assert(q);
132
133 dns_query_complete(q, DNS_TRANSACTION_TIMEOUT);
134 return 0;
135 }
136
137 static int dns_query_add_transaction(DnsQuery *q, DnsScope *s, DnsResourceKey *key) {
138 _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
139 DnsTransaction *t;
140 int r;
141
142 assert(q);
143 assert(s);
144
145 r = set_ensure_allocated(&q->transactions, NULL);
146 if (r < 0)
147 return r;
148
149 if (key) {
150 question = dns_question_new(1);
151 if (!question)
152 return -ENOMEM;
153
154 r = dns_question_add(question, key);
155 if (r < 0)
156 return r;
157 } else
158 question = dns_question_ref(q->question);
159
160 t = dns_scope_find_transaction(s, question, true);
161 if (!t) {
162 r = dns_transaction_new(&t, s, question);
163 if (r < 0)
164 return r;
165 }
166
167 r = set_ensure_allocated(&t->queries, NULL);
168 if (r < 0)
169 goto gc;
170
171 r = set_put(t->queries, q);
172 if (r < 0)
173 goto gc;
174
175 r = set_put(q->transactions, t);
176 if (r < 0) {
177 set_remove(t->queries, q);
178 goto gc;
179 }
180
181 return 0;
182
183 gc:
184 dns_transaction_gc(t);
185 return r;
186 }
187
188 static int dns_query_add_transaction_split(DnsQuery *q, DnsScope *s) {
189 int r;
190
191 assert(q);
192 assert(s);
193
194 if (s->protocol == DNS_PROTOCOL_MDNS) {
195 r = dns_query_add_transaction(q, s, NULL);
196 if (r < 0)
197 return r;
198 } else {
199 unsigned i;
200
201 /* On DNS and LLMNR we can only send a single
202 * question per datagram, hence issue multiple
203 * transactions. */
204
205 for (i = 0; i < q->question->n_keys; i++) {
206 r = dns_query_add_transaction(q, s, q->question->keys[i]);
207 if (r < 0)
208 return r;
209 }
210 }
211
212 return 0;
213 }
214
215 int dns_query_go(DnsQuery *q) {
216 DnsScopeMatch found = DNS_SCOPE_NO;
217 DnsScope *s, *first = NULL;
218 DnsTransaction *t;
219 const char *name;
220 Iterator i;
221 int r;
222
223 assert(q);
224
225 if (q->state != DNS_TRANSACTION_NULL)
226 return 0;
227
228 assert(q->question);
229 assert(q->question->n_keys > 0);
230
231 name = DNS_RESOURCE_KEY_NAME(q->question->keys[0]);
232
233 LIST_FOREACH(scopes, s, q->manager->dns_scopes) {
234 DnsScopeMatch match;
235
236 match = dns_scope_good_domain(s, q->ifindex, q->flags, name);
237 if (match < 0)
238 return match;
239
240 if (match == DNS_SCOPE_NO)
241 continue;
242
243 found = match;
244
245 if (match == DNS_SCOPE_YES) {
246 first = s;
247 break;
248 } else {
249 assert(match == DNS_SCOPE_MAYBE);
250
251 if (!first)
252 first = s;
253 }
254 }
255
256 if (found == DNS_SCOPE_NO)
257 return -ESRCH;
258
259 r = dns_query_add_transaction_split(q, first);
260 if (r < 0)
261 goto fail;
262
263 LIST_FOREACH(scopes, s, first->scopes_next) {
264 DnsScopeMatch match;
265
266 match = dns_scope_good_domain(s, q->ifindex, q->flags, name);
267 if (match < 0)
268 goto fail;
269
270 if (match != found)
271 continue;
272
273 r = dns_query_add_transaction_split(q, s);
274 if (r < 0)
275 goto fail;
276 }
277
278 q->answer = dns_answer_unref(q->answer);
279 q->answer_ifindex = 0;
280 q->answer_rcode = 0;
281 q->answer_family = AF_UNSPEC;
282 q->answer_protocol = _DNS_PROTOCOL_INVALID;
283
284 r = sd_event_add_time(
285 q->manager->event,
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);
290 if (r < 0)
291 goto fail;
292
293 q->state = DNS_TRANSACTION_PENDING;
294 q->block_ready++;
295
296 /* Start the transactions that are not started yet */
297 SET_FOREACH(t, q->transactions, i) {
298 if (t->state != DNS_TRANSACTION_NULL)
299 continue;
300
301 r = dns_transaction_go(t);
302 if (r < 0)
303 goto fail;
304 }
305
306 q->block_ready--;
307 dns_query_ready(q);
308
309 return 1;
310
311 fail:
312 dns_query_stop(q);
313 return r;
314 }
315
316 void dns_query_ready(DnsQuery *q) {
317 DnsTransaction *t;
318 DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS;
319 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
320 int rcode = 0;
321 DnsScope *scope = NULL;
322 bool pending = false;
323 Iterator i;
324
325 assert(q);
326 assert(IN_SET(q->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
327
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. */
332
333 if (q->block_ready > 0)
334 return;
335
336 SET_FOREACH(t, q->transactions, i) {
337
338 /* If we found a successful answer, ignore all answers from other scopes */
339 if (state == DNS_TRANSACTION_SUCCESS && t->scope != scope)
340 continue;
341
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)) {
344 pending = true;
345 continue;
346 }
347
348 /* One of the transactions is successful, let's use
349 * it, and copy its data out */
350 if (t->state == DNS_TRANSACTION_SUCCESS) {
351 DnsAnswer *a;
352
353 if (t->received) {
354 rcode = DNS_PACKET_RCODE(t->received);
355 a = t->received->answer;
356 } else {
357 rcode = t->cached_rcode;
358 a = t->cached;
359 }
360
361 if (state == DNS_TRANSACTION_SUCCESS) {
362 DnsAnswer *merged;
363
364 merged = dns_answer_merge(answer, a);
365 if (!merged) {
366 dns_query_complete(q, DNS_TRANSACTION_RESOURCES);
367 return;
368 }
369
370 dns_answer_unref(answer);
371 answer = merged;
372 } else {
373 dns_answer_unref(answer);
374 answer = dns_answer_ref(a);
375 }
376
377 scope = t->scope;
378 state = DNS_TRANSACTION_SUCCESS;
379 continue;
380 }
381
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) {
386 DnsAnswer *a;
387
388 if (t->received) {
389 rcode = DNS_PACKET_RCODE(t->received);
390 a = t->received->answer;
391 } else {
392 rcode = t->cached_rcode;
393 a = t->cached;
394 }
395
396 dns_answer_unref(answer);
397 answer = dns_answer_ref(a);
398
399 scope = t->scope;
400 state = DNS_TRANSACTION_FAILURE;
401 continue;
402 }
403
404 if (state == DNS_TRANSACTION_NO_SERVERS && t->state != DNS_TRANSACTION_NO_SERVERS)
405 state = t->state;
406 }
407
408 if (pending) {
409
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)
413 return;
414
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))
419 return;
420 }
421 }
422
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;
429 }
430
431 dns_query_complete(q, state);
432 }
433
434 int dns_query_cname_redirect(DnsQuery *q, const char *name) {
435 _cleanup_(dns_question_unrefp) DnsQuestion *nq = NULL;
436 int r;
437
438 assert(q);
439
440 if (q->n_cname_redirects > CNAME_MAX)
441 return -ELOOP;
442
443 r = dns_question_cname_redirect(q->question, name, &nq);
444 if (r < 0)
445 return r;
446
447 dns_question_unref(q->question);
448 q->question = nq;
449 nq = NULL;
450
451 q->n_cname_redirects++;
452
453 dns_query_stop(q);
454 q->state = DNS_TRANSACTION_NULL;
455
456 return 0;
457 }
458
459 static int on_bus_track(sd_bus_track *t, void *userdata) {
460 DnsQuery *q = userdata;
461
462 assert(t);
463 assert(q);
464
465 log_debug("Client of active query vanished, aborting query.");
466 dns_query_complete(q, DNS_TRANSACTION_ABORTED);
467 return 0;
468 }
469
470 int dns_query_bus_track(DnsQuery *q, sd_bus_message *m) {
471 int r;
472
473 assert(q);
474 assert(m);
475
476 if (!q->bus_track) {
477 r = sd_bus_track_new(sd_bus_message_get_bus(m), &q->bus_track, on_bus_track, q);
478 if (r < 0)
479 return r;
480 }
481
482 r = sd_bus_track_add_sender(q->bus_track, m);
483 if (r < 0)
484 return r;
485
486 return 0;
487 }