]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-query.c
resolve-host: make arg_type an int
[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(
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 }