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