chiark / gitweb /
resolved: implement LLMNR uniqueness verification
[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(q->manager->event, &q->timeout_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + QUERY_TIMEOUT_USEC, 0, on_query_timeout, q);
282         if (r < 0)
283                 goto fail;
284
285         q->state = DNS_TRANSACTION_PENDING;
286         q->block_ready++;
287
288         /* Start the transactions that are not started yet */
289         SET_FOREACH(t, q->transactions, i) {
290                 if (t->state != DNS_TRANSACTION_NULL)
291                         continue;
292
293                 r = dns_transaction_go(t);
294                 if (r < 0)
295                         goto fail;
296         }
297
298         q->block_ready--;
299         dns_query_ready(q);
300
301         return 1;
302
303 fail:
304         dns_query_stop(q);
305         return r;
306 }
307
308 void dns_query_ready(DnsQuery *q) {
309         DnsTransaction *t;
310         DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS;
311         _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
312         int rcode = 0;
313         DnsScope *scope = NULL;
314         bool pending = false;
315         Iterator i;
316
317         assert(q);
318         assert(IN_SET(q->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING));
319
320         /* Note that this call might invalidate the query. Callers
321          * should hence not attempt to access the query or transaction
322          * after calling this function, unless the block_ready
323          * counter was explicitly bumped before doing so. */
324
325         if (q->block_ready > 0)
326                 return;
327
328         SET_FOREACH(t, q->transactions, i) {
329
330                 /* If we found a successful answer, ignore all answers from other scopes */
331                 if (state == DNS_TRANSACTION_SUCCESS && t->scope != scope)
332                         continue;
333
334                 /* One of the transactions is still going on, let's maybe wait for it */
335                 if (IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_NULL)) {
336                         pending = true;
337                         continue;
338                 }
339
340                 /* One of the transactions is successful, let's use
341                  * it, and copy its data out */
342                 if (t->state == DNS_TRANSACTION_SUCCESS) {
343                         DnsAnswer *a;
344
345                         if (t->received) {
346                                 rcode = DNS_PACKET_RCODE(t->received);
347                                 a = t->received->answer;
348                         } else {
349                                 rcode = t->cached_rcode;
350                                 a = t->cached;
351                         }
352
353                         if (state == DNS_TRANSACTION_SUCCESS) {
354                                 DnsAnswer *merged;
355
356                                 merged = dns_answer_merge(answer, a);
357                                 if (!merged) {
358                                         dns_query_complete(q, DNS_TRANSACTION_RESOURCES);
359                                         return;
360                                 }
361
362                                 dns_answer_unref(answer);
363                                 answer = merged;
364                         } else {
365                                 dns_answer_unref(answer);
366                                 answer = dns_answer_ref(a);
367                         }
368
369                         scope = t->scope;
370                         state = DNS_TRANSACTION_SUCCESS;
371                         continue;
372                 }
373
374                 /* One of the transactions has failed, let's see
375                  * whether we find anything better, but if not, return
376                  * its response data */
377                 if (state != DNS_TRANSACTION_SUCCESS && t->state == DNS_TRANSACTION_FAILURE) {
378                         DnsAnswer *a;
379
380                         if (t->received) {
381                                 rcode = DNS_PACKET_RCODE(t->received);
382                                 a = t->received->answer;
383                         } else {
384                                 rcode = t->cached_rcode;
385                                 a = t->cached;
386                         }
387
388                         dns_answer_unref(answer);
389                         answer = dns_answer_ref(a);
390
391                         scope = t->scope;
392                         state = DNS_TRANSACTION_FAILURE;
393                         continue;
394                 }
395
396                 if (state == DNS_TRANSACTION_NO_SERVERS && t->state != DNS_TRANSACTION_NO_SERVERS)
397                         state = t->state;
398         }
399
400         if (pending) {
401
402                 /* If so far we weren't successful, and there's
403                  * something still pending, then wait for it */
404                 if (state != DNS_TRANSACTION_SUCCESS)
405                         return;
406
407                 /* If we already were successful, then only wait for
408                  * other transactions on the same scope to finish. */
409                 SET_FOREACH(t, q->transactions, i) {
410                         if (t->scope == scope && IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_NULL))
411                                 return;
412                 }
413         }
414
415         if (IN_SET(state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_FAILURE)) {
416                 q->answer = dns_answer_ref(answer);
417                 q->answer_rcode = rcode;
418                 q->answer_ifindex = (scope && scope->link) ? scope->link->ifindex : 0;
419         }
420
421         dns_query_complete(q, state);
422 }
423
424 int dns_query_cname_redirect(DnsQuery *q, const char *name) {
425         _cleanup_(dns_question_unrefp) DnsQuestion *nq = NULL;
426         int r;
427
428         assert(q);
429
430         if (q->n_cname_redirects > CNAME_MAX)
431                 return -ELOOP;
432
433         r = dns_question_cname_redirect(q->question, name, &nq);
434         if (r < 0)
435                 return r;
436
437         dns_question_unref(q->question);
438         q->question = nq;
439         nq = NULL;
440
441         q->n_cname_redirects++;
442
443         dns_query_stop(q);
444         q->state = DNS_TRANSACTION_NULL;
445
446         return 0;
447 }