]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-query.c
resolved: implement LLMNR uniqueness verification
[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
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 }