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