]>
Commit | Line | Data |
---|---|---|
74b2466e LP |
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 | ||
a2a416f7 LP |
22 | #include "af-list.h" |
23 | ||
74b2466e LP |
24 | #include "resolved-dns-query.h" |
25 | #include "resolved-dns-domain.h" | |
26 | ||
0c903ae7 LP |
27 | /* After how much time to repeat classic DNS requests */ |
28 | #define DNS_TRANSACTION_TIMEOUT_USEC (5 * USEC_PER_SEC) | |
29 | ||
30 | /* After how much time to repeat LLMNR requests, see RFC 4795 Section 7 */ | |
31 | #define LLMNR_TRANSACTION_TIMEOUT_USEC (1 * USEC_PER_SEC) | |
32 | ||
33 | /* How long to wait for the query in total */ | |
74b2466e | 34 | #define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC) |
0c903ae7 LP |
35 | |
36 | /* Maximum attempts to send DNS requests, across all DNS servers */ | |
37 | #define DNS_TRANSACTION_ATTEMPTS_MAX 8 | |
38 | ||
39 | /* Maximum attempts to send LLMNR requests, see RFC 4795 Section 2.7 */ | |
40 | #define LLMNR_TRANSACTION_ATTEMPTS_MAX 3 | |
41 | ||
8ba9fd9c | 42 | #define CNAME_MAX 8 |
39762fdf | 43 | #define QUERIES_MAX 2048 |
8ba9fd9c | 44 | |
0c903ae7 LP |
45 | #define TRANSACTION_TIMEOUT_USEC(p) ((t)->scope->protocol == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_TIMEOUT_USEC : DNS_TRANSACTION_TIMEOUT_USEC) |
46 | #define TRANSACTION_ATTEMPTS_MAX(p) ((t)->scope->protocol == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_ATTEMPTS_MAX : DNS_TRANSACTION_ATTEMPTS_MAX) | |
47 | ||
322345fd | 48 | static int dns_query_transaction_go(DnsQueryTransaction *t); |
74b2466e LP |
49 | |
50 | DnsQueryTransaction* dns_query_transaction_free(DnsQueryTransaction *t) { | |
faa133f3 LP |
51 | DnsQuery *q; |
52 | ||
74b2466e LP |
53 | if (!t) |
54 | return NULL; | |
55 | ||
56 | sd_event_source_unref(t->timeout_event_source); | |
ad867662 | 57 | |
faa133f3 | 58 | dns_question_unref(t->question); |
ad867662 LP |
59 | dns_packet_unref(t->sent); |
60 | dns_packet_unref(t->received); | |
faa133f3 | 61 | dns_answer_unref(t->cached); |
322345fd | 62 | |
623a4c97 | 63 | dns_stream_free(t->stream); |
74b2466e | 64 | |
faa133f3 LP |
65 | if (t->scope) { |
66 | LIST_REMOVE(transactions_by_scope, t->scope->transactions, t); | |
67 | ||
68 | if (t->id != 0) | |
69 | hashmap_remove(t->scope->manager->dns_query_transactions, UINT_TO_PTR(t->id)); | |
74b2466e LP |
70 | } |
71 | ||
faa133f3 LP |
72 | while ((q = set_steal_first(t->queries))) |
73 | set_remove(q->transactions, t); | |
74 | ||
75 | set_free(t->queries); | |
74b2466e LP |
76 | |
77 | free(t); | |
78 | return NULL; | |
79 | } | |
80 | ||
81 | DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQueryTransaction*, dns_query_transaction_free); | |
82 | ||
faa133f3 LP |
83 | static void dns_query_transaction_gc(DnsQueryTransaction *t) { |
84 | assert(t); | |
85 | ||
86 | if (t->block_gc > 0) | |
87 | return; | |
88 | ||
89 | if (set_isempty(t->queries)) | |
90 | dns_query_transaction_free(t); | |
91 | } | |
92 | ||
93 | static int dns_query_transaction_new(DnsQueryTransaction **ret, DnsScope *s, DnsQuestion *q) { | |
74b2466e LP |
94 | _cleanup_(dns_query_transaction_freep) DnsQueryTransaction *t = NULL; |
95 | int r; | |
96 | ||
faa133f3 | 97 | assert(ret); |
74b2466e | 98 | assert(s); |
faa133f3 | 99 | assert(q); |
74b2466e | 100 | |
faa133f3 | 101 | r = hashmap_ensure_allocated(&s->manager->dns_query_transactions, NULL, NULL); |
74b2466e LP |
102 | if (r < 0) |
103 | return r; | |
104 | ||
105 | t = new0(DnsQueryTransaction, 1); | |
106 | if (!t) | |
107 | return -ENOMEM; | |
108 | ||
faa133f3 | 109 | t->question = dns_question_ref(q); |
ad867662 | 110 | |
74b2466e LP |
111 | do |
112 | random_bytes(&t->id, sizeof(t->id)); | |
113 | while (t->id == 0 || | |
faa133f3 | 114 | hashmap_get(s->manager->dns_query_transactions, UINT_TO_PTR(t->id))); |
74b2466e | 115 | |
faa133f3 | 116 | r = hashmap_put(s->manager->dns_query_transactions, UINT_TO_PTR(t->id), t); |
74b2466e LP |
117 | if (r < 0) { |
118 | t->id = 0; | |
119 | return r; | |
120 | } | |
121 | ||
74b2466e LP |
122 | LIST_PREPEND(transactions_by_scope, s->transactions, t); |
123 | t->scope = s; | |
124 | ||
125 | if (ret) | |
126 | *ret = t; | |
127 | ||
128 | t = NULL; | |
129 | ||
130 | return 0; | |
131 | } | |
132 | ||
ad867662 LP |
133 | static void dns_query_transaction_stop(DnsQueryTransaction *t) { |
134 | assert(t); | |
135 | ||
136 | t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); | |
623a4c97 | 137 | t->stream = dns_stream_free(t->stream); |
ad867662 LP |
138 | } |
139 | ||
faa133f3 LP |
140 | void dns_query_transaction_complete(DnsQueryTransaction *t, DnsQueryState state) { |
141 | DnsQuery *q; | |
142 | Iterator i; | |
143 | ||
74b2466e | 144 | assert(t); |
322345fd LP |
145 | assert(!IN_SET(state, DNS_QUERY_NULL, DNS_QUERY_PENDING)); |
146 | assert(IN_SET(t->state, DNS_QUERY_NULL, DNS_QUERY_PENDING)); | |
74b2466e | 147 | |
322345fd LP |
148 | /* Note that this call might invalidate the query. Callers |
149 | * should hence not attempt to access the query or transaction | |
150 | * after calling this function. */ | |
74b2466e | 151 | |
a2a416f7 LP |
152 | log_debug("Transaction on scope %s on %s/%s now complete with %s", |
153 | dns_protocol_to_string(t->scope->protocol), | |
154 | t->scope->link ? t->scope->link->name : "*", | |
155 | t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family), | |
156 | dns_query_state_to_string(state)); | |
157 | ||
74b2466e LP |
158 | t->state = state; |
159 | ||
322345fd | 160 | dns_query_transaction_stop(t); |
faa133f3 LP |
161 | |
162 | /* Notify all queries that are interested, but make sure the | |
163 | * transaction isn't freed while we are still looking at it */ | |
164 | t->block_gc++; | |
165 | SET_FOREACH(q, t->queries, i) | |
166 | dns_query_ready(q); | |
167 | t->block_gc--; | |
168 | ||
169 | dns_query_transaction_gc(t); | |
ad867662 LP |
170 | } |
171 | ||
623a4c97 LP |
172 | static int on_stream_complete(DnsStream *s, int error) { |
173 | _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; | |
174 | DnsQueryTransaction *t; | |
ad867662 | 175 | |
623a4c97 LP |
176 | assert(s); |
177 | assert(s->transaction); | |
ad867662 | 178 | |
623a4c97 LP |
179 | /* Copy the data we care about out of the stream before we |
180 | * destroy it. */ | |
181 | t = s->transaction; | |
182 | p = dns_packet_ref(s->read_packet); | |
ad867662 | 183 | |
623a4c97 | 184 | t->stream = dns_stream_free(t->stream); |
ad867662 | 185 | |
623a4c97 LP |
186 | if (error != 0) { |
187 | dns_query_transaction_complete(t, DNS_QUERY_RESOURCES); | |
188 | return 0; | |
ad867662 LP |
189 | } |
190 | ||
b914e211 | 191 | t->block_gc++; |
623a4c97 | 192 | dns_query_transaction_process_reply(t, p); |
b914e211 LP |
193 | t->block_gc--; |
194 | ||
195 | /* If the response wasn't useful, then complete the transition now */ | |
196 | if (t->state == DNS_QUERY_PENDING) | |
197 | dns_query_transaction_complete(t, DNS_QUERY_INVALID_REPLY); | |
198 | ||
ad867662 LP |
199 | return 0; |
200 | } | |
201 | ||
322345fd | 202 | static int dns_query_transaction_open_tcp(DnsQueryTransaction *t) { |
623a4c97 | 203 | _cleanup_close_ int fd = -1; |
ad867662 LP |
204 | int r; |
205 | ||
206 | assert(t); | |
207 | ||
623a4c97 LP |
208 | if (t->stream) |
209 | return 0; | |
210 | ||
1716f6dc | 211 | if (t->scope->protocol == DNS_PROTOCOL_DNS) |
623a4c97 LP |
212 | fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53); |
213 | else if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { | |
1716f6dc | 214 | |
b914e211 LP |
215 | /* When we already received a query to this (but it was truncated), send to its sender address */ |
216 | if (t->received) | |
217 | fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port); | |
218 | else { | |
219 | union in_addr_union address; | |
220 | int family; | |
221 | ||
222 | /* Otherwise, try to talk to the owner of a | |
223 | * the IP address, in case this is a reverse | |
224 | * PTR lookup */ | |
225 | r = dns_question_extract_reverse_address(t->question, &family, &address); | |
226 | if (r < 0) | |
227 | return r; | |
228 | if (r == 0) | |
229 | return -EINVAL; | |
230 | ||
231 | fd = dns_scope_tcp_socket(t->scope, family, &address, 5355); | |
232 | } | |
623a4c97 LP |
233 | } else |
234 | return -EAFNOSUPPORT; | |
ad867662 | 235 | |
623a4c97 LP |
236 | if (fd < 0) |
237 | return fd; | |
ad867662 | 238 | |
623a4c97 LP |
239 | r = dns_stream_new(t->scope->manager, &t->stream, t->scope->protocol, fd); |
240 | if (r < 0) | |
241 | return r; | |
ad867662 | 242 | |
623a4c97 LP |
243 | fd = -1; |
244 | ||
245 | r = dns_stream_write_packet(t->stream, t->sent); | |
ad867662 | 246 | if (r < 0) { |
623a4c97 | 247 | t->stream = dns_stream_free(t->stream); |
ad867662 LP |
248 | return r; |
249 | } | |
74b2466e | 250 | |
623a4c97 LP |
251 | t->received = dns_packet_unref(t->received); |
252 | t->stream->complete = on_stream_complete; | |
b914e211 LP |
253 | t->stream->transaction = t; |
254 | ||
255 | /* The interface index is difficult to determine if we are | |
256 | * connecting to the local host, hence fill this in right away | |
257 | * instead of determining it from the socket */ | |
258 | if (t->scope->link) | |
259 | t->stream->ifindex = t->scope->link->ifindex; | |
623a4c97 | 260 | |
ad867662 | 261 | return 0; |
74b2466e LP |
262 | } |
263 | ||
faa133f3 | 264 | void dns_query_transaction_process_reply(DnsQueryTransaction *t, DnsPacket *p) { |
ad867662 LP |
265 | int r; |
266 | ||
74b2466e LP |
267 | assert(t); |
268 | assert(p); | |
322345fd | 269 | assert(t->state == DNS_QUERY_PENDING); |
74b2466e | 270 | |
322345fd LP |
271 | /* Note that this call might invalidate the query. Callers |
272 | * should hence not attempt to access the query or transaction | |
273 | * after calling this function. */ | |
ad867662 | 274 | |
623a4c97 LP |
275 | if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { |
276 | assert(t->scope->link); | |
277 | ||
278 | /* For LLMNR we will not accept any packets from other | |
279 | * interfaces */ | |
280 | ||
281 | if (p->ifindex != t->scope->link->ifindex) | |
282 | return; | |
283 | ||
284 | if (p->family != t->scope->family) | |
285 | return; | |
286 | ||
ea917db9 LP |
287 | /* Tentative replies shall be discarded, see RFC 4795, |
288 | * 2.1.1 */ | |
289 | ||
290 | if (DNS_PACKET_T(p)) | |
291 | return; | |
623a4c97 LP |
292 | } |
293 | ||
294 | if (t->scope->protocol == DNS_PROTOCOL_DNS) { | |
295 | ||
296 | /* For DNS we are fine with accepting packets on any | |
297 | * interface, but the source IP address must be one of | |
298 | * a valid DNS server */ | |
299 | ||
300 | if (!dns_scope_good_dns_server(t->scope, p->family, &p->sender)) | |
301 | return; | |
302 | ||
303 | if (p->sender_port != 53) | |
304 | return; | |
305 | } | |
306 | ||
ad867662 LP |
307 | if (t->received != p) { |
308 | dns_packet_unref(t->received); | |
309 | t->received = dns_packet_ref(p); | |
310 | } | |
311 | ||
623a4c97 | 312 | if (p->ipproto == IPPROTO_TCP) { |
ad867662 LP |
313 | if (DNS_PACKET_TC(p)) { |
314 | /* Truncated via TCP? Somebody must be fucking with us */ | |
322345fd | 315 | dns_query_transaction_complete(t, DNS_QUERY_INVALID_REPLY); |
ad867662 LP |
316 | return; |
317 | } | |
318 | ||
319 | if (DNS_PACKET_ID(p) != t->id) { | |
320 | /* Not the reply to our query? Somebody must be fucking with us */ | |
322345fd | 321 | dns_query_transaction_complete(t, DNS_QUERY_INVALID_REPLY); |
ad867662 LP |
322 | return; |
323 | } | |
324 | } | |
325 | ||
326 | if (DNS_PACKET_TC(p)) { | |
327 | /* Response was truncated, let's try again with good old TCP */ | |
322345fd | 328 | r = dns_query_transaction_open_tcp(t); |
8ba9fd9c LP |
329 | if (r == -ESRCH) { |
330 | /* No servers found? Damn! */ | |
322345fd | 331 | dns_query_transaction_complete(t, DNS_QUERY_NO_SERVERS); |
8ba9fd9c LP |
332 | return; |
333 | } | |
ad867662 | 334 | if (r < 0) { |
623a4c97 LP |
335 | /* On LLMNR, if we cannot connect to the host, |
336 | * we immediately give up */ | |
337 | if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { | |
338 | dns_query_transaction_complete(t, DNS_QUERY_RESOURCES); | |
339 | return; | |
340 | } | |
341 | ||
342 | /* On DNS, couldn't send? Try immediately again, with a new server */ | |
8ba9fd9c LP |
343 | dns_scope_next_dns_server(t->scope); |
344 | ||
322345fd | 345 | r = dns_query_transaction_go(t); |
8ba9fd9c | 346 | if (r < 0) { |
322345fd | 347 | dns_query_transaction_complete(t, DNS_QUERY_RESOURCES); |
8ba9fd9c LP |
348 | return; |
349 | } | |
350 | ||
ad867662 LP |
351 | return; |
352 | } | |
353 | } | |
74b2466e | 354 | |
322345fd | 355 | /* Parse and update the cache */ |
faa133f3 | 356 | r = dns_packet_extract(p); |
322345fd LP |
357 | if (r < 0) { |
358 | dns_query_transaction_complete(t, DNS_QUERY_INVALID_REPLY); | |
359 | return; | |
934e9b10 LP |
360 | } |
361 | ||
d2f47562 LP |
362 | /* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */ |
363 | dns_cache_put(&t->scope->cache, p->question, DNS_PACKET_RCODE(p), p->answer, DNS_PACKET_ANCOUNT(p), 0); | |
322345fd | 364 | |
b9d394ea | 365 | if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS) |
322345fd | 366 | dns_query_transaction_complete(t, DNS_QUERY_SUCCESS); |
b9d394ea | 367 | else |
322345fd | 368 | dns_query_transaction_complete(t, DNS_QUERY_FAILURE); |
74b2466e LP |
369 | } |
370 | ||
371 | static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { | |
372 | DnsQueryTransaction *t = userdata; | |
373 | int r; | |
374 | ||
375 | assert(s); | |
376 | assert(t); | |
377 | ||
378 | /* Timeout reached? Try again, with a new server */ | |
379 | dns_scope_next_dns_server(t->scope); | |
380 | ||
322345fd | 381 | r = dns_query_transaction_go(t); |
74b2466e | 382 | if (r < 0) |
322345fd | 383 | dns_query_transaction_complete(t, DNS_QUERY_RESOURCES); |
74b2466e LP |
384 | |
385 | return 0; | |
386 | } | |
387 | ||
ad867662 | 388 | static int dns_query_make_packet(DnsQueryTransaction *t) { |
74b2466e | 389 | _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; |
1716f6dc | 390 | unsigned n, added = 0; |
74b2466e LP |
391 | int r; |
392 | ||
393 | assert(t); | |
394 | ||
ad867662 | 395 | if (t->sent) |
74b2466e | 396 | return 0; |
74b2466e | 397 | |
1716f6dc | 398 | r = dns_packet_new_query(&p, t->scope->protocol, 0); |
74b2466e LP |
399 | if (r < 0) |
400 | return r; | |
401 | ||
faa133f3 LP |
402 | for (n = 0; n < t->question->n_keys; n++) { |
403 | r = dns_scope_good_key(t->scope, t->question->keys[n]); | |
1716f6dc LP |
404 | if (r < 0) |
405 | return r; | |
406 | if (r == 0) | |
407 | continue; | |
408 | ||
faa133f3 | 409 | r = dns_packet_append_key(p, t->question->keys[n], NULL); |
74b2466e LP |
410 | if (r < 0) |
411 | return r; | |
1716f6dc LP |
412 | |
413 | added++; | |
74b2466e LP |
414 | } |
415 | ||
1716f6dc LP |
416 | if (added <= 0) |
417 | return -EDOM; | |
418 | ||
419 | DNS_PACKET_HEADER(p)->qdcount = htobe16(added); | |
74b2466e LP |
420 | DNS_PACKET_HEADER(p)->id = t->id; |
421 | ||
ad867662 LP |
422 | t->sent = p; |
423 | p = NULL; | |
424 | ||
425 | return 0; | |
426 | } | |
427 | ||
322345fd | 428 | static int dns_query_transaction_go(DnsQueryTransaction *t) { |
0c903ae7 | 429 | bool had_stream; |
ad867662 LP |
430 | int r; |
431 | ||
432 | assert(t); | |
433 | ||
0c903ae7 LP |
434 | had_stream = !!t->stream; |
435 | ||
ad867662 LP |
436 | dns_query_transaction_stop(t); |
437 | ||
a2a416f7 LP |
438 | log_debug("Beginning transaction on scope %s on %s/%s", |
439 | dns_protocol_to_string(t->scope->protocol), | |
440 | t->scope->link ? t->scope->link->name : "*", | |
441 | t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family)); | |
442 | ||
0c903ae7 LP |
443 | if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t)) { |
444 | dns_query_transaction_complete(t, DNS_QUERY_ATTEMPTS_MAX); | |
445 | return 0; | |
446 | } | |
447 | ||
448 | if (t->scope->protocol == DNS_PROTOCOL_LLMNR && had_stream) { | |
449 | /* If we already tried via a stream, then we don't | |
450 | * retry on LLMNR. See RFC 4795, Section 2.7. */ | |
322345fd | 451 | dns_query_transaction_complete(t, DNS_QUERY_ATTEMPTS_MAX); |
ad867662 LP |
452 | return 0; |
453 | } | |
ad867662 | 454 | |
322345fd LP |
455 | t->n_attempts++; |
456 | t->received = dns_packet_unref(t->received); | |
faa133f3 | 457 | t->cached = dns_answer_unref(t->cached); |
7e8e0422 | 458 | t->cached_rcode = 0; |
322345fd LP |
459 | |
460 | /* First, let's try the cache */ | |
461 | dns_cache_prune(&t->scope->cache); | |
7e8e0422 | 462 | r = dns_cache_lookup(&t->scope->cache, t->question, &t->cached_rcode, &t->cached); |
ad867662 LP |
463 | if (r < 0) |
464 | return r; | |
322345fd | 465 | if (r > 0) { |
7e8e0422 LP |
466 | if (t->cached_rcode == DNS_RCODE_SUCCESS) |
467 | dns_query_transaction_complete(t, DNS_QUERY_SUCCESS); | |
468 | else | |
469 | dns_query_transaction_complete(t, DNS_QUERY_FAILURE); | |
322345fd LP |
470 | return 0; |
471 | } | |
ad867662 | 472 | |
322345fd LP |
473 | /* Otherwise, we need to ask the network */ |
474 | r = dns_query_make_packet(t); | |
1716f6dc LP |
475 | if (r == -EDOM) { |
476 | /* Not the right request to make on this network? | |
477 | * (i.e. an A request made on IPv6 or an AAAA request | |
478 | * made on IPv4, on LLMNR or mDNS.) */ | |
479 | dns_query_transaction_complete(t, DNS_QUERY_NO_SERVERS); | |
480 | return 0; | |
481 | } | |
322345fd LP |
482 | if (r < 0) |
483 | return r; | |
8ba9fd9c | 484 | |
b914e211 LP |
485 | if (t->scope->protocol == DNS_PROTOCOL_LLMNR && |
486 | (dns_question_endswith(t->question, "in-addr.arpa") > 0 || | |
487 | dns_question_endswith(t->question, "ip6.arpa") > 0)) { | |
488 | ||
489 | /* RFC 4795, Section 2.4. says reverse lookups shall | |
490 | * always be made via TCP on LLMNR */ | |
322345fd | 491 | r = dns_query_transaction_open_tcp(t); |
b914e211 LP |
492 | } else { |
493 | /* Try via UDP, and if that fails due to large size try via TCP */ | |
494 | r = dns_scope_send(t->scope, t->sent); | |
495 | if (r == -EMSGSIZE) | |
496 | r = dns_query_transaction_open_tcp(t); | |
497 | } | |
ad867662 | 498 | if (r == -ESRCH) { |
1716f6dc | 499 | /* No servers to send this to? */ |
322345fd | 500 | dns_query_transaction_complete(t, DNS_QUERY_NO_SERVERS); |
ad867662 LP |
501 | return 0; |
502 | } | |
74b2466e LP |
503 | if (r < 0) { |
504 | /* Couldn't send? Try immediately again, with a new server */ | |
505 | dns_scope_next_dns_server(t->scope); | |
506 | ||
322345fd | 507 | return dns_query_transaction_go(t); |
74b2466e LP |
508 | } |
509 | ||
0c903ae7 | 510 | r = sd_event_add_time(t->scope->manager->event, &t->timeout_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + TRANSACTION_TIMEOUT_USEC(t), 0, on_transaction_timeout, t); |
ad867662 LP |
511 | if (r < 0) |
512 | return r; | |
74b2466e | 513 | |
322345fd | 514 | t->state = DNS_QUERY_PENDING; |
ad867662 | 515 | return 1; |
74b2466e LP |
516 | } |
517 | ||
518 | DnsQuery *dns_query_free(DnsQuery *q) { | |
faa133f3 | 519 | DnsQueryTransaction *t; |
74b2466e LP |
520 | |
521 | if (!q) | |
522 | return NULL; | |
523 | ||
524 | sd_bus_message_unref(q->request); | |
322345fd | 525 | |
faa133f3 LP |
526 | dns_question_unref(q->question); |
527 | dns_answer_unref(q->answer); | |
322345fd | 528 | |
74b2466e LP |
529 | sd_event_source_unref(q->timeout_event_source); |
530 | ||
faa133f3 LP |
531 | while ((t = set_steal_first(q->transactions))) { |
532 | set_remove(t->queries, q); | |
533 | dns_query_transaction_gc(t); | |
534 | } | |
535 | ||
536 | set_free(q->transactions); | |
74b2466e | 537 | |
39762fdf | 538 | if (q->manager) { |
74b2466e | 539 | LIST_REMOVE(queries, q->manager->dns_queries, q); |
39762fdf LP |
540 | q->manager->n_dns_queries--; |
541 | } | |
74b2466e | 542 | |
74b2466e LP |
543 | free(q); |
544 | ||
545 | return NULL; | |
546 | } | |
547 | ||
faa133f3 | 548 | int dns_query_new(Manager *m, DnsQuery **ret, DnsQuestion *question) { |
74b2466e | 549 | _cleanup_(dns_query_freep) DnsQuery *q = NULL; |
faa133f3 LP |
550 | unsigned i; |
551 | int r; | |
74b2466e LP |
552 | |
553 | assert(m); | |
faa133f3 | 554 | assert(question); |
74b2466e | 555 | |
faa133f3 LP |
556 | r = dns_question_is_valid(question); |
557 | if (r < 0) | |
558 | return r; | |
74b2466e | 559 | |
39762fdf LP |
560 | if (m->n_dns_queries >= QUERIES_MAX) |
561 | return -EBUSY; | |
562 | ||
74b2466e LP |
563 | q = new0(DnsQuery, 1); |
564 | if (!q) | |
565 | return -ENOMEM; | |
566 | ||
faa133f3 | 567 | q->question = dns_question_ref(question); |
322345fd | 568 | |
faa133f3 | 569 | for (i = 0; i < question->n_keys; i++) { |
e4501ed4 LP |
570 | _cleanup_free_ char *p; |
571 | ||
572 | r = dns_resource_key_to_string(question->keys[i], &p); | |
573 | if (r < 0) | |
574 | return r; | |
575 | ||
576 | log_debug("Looking up RR for %s", p); | |
74b2466e LP |
577 | } |
578 | ||
579 | LIST_PREPEND(queries, m->dns_queries, q); | |
39762fdf | 580 | m->n_dns_queries++; |
74b2466e LP |
581 | q->manager = m; |
582 | ||
8ba9fd9c LP |
583 | if (ret) |
584 | *ret = q; | |
585 | q = NULL; | |
586 | ||
587 | return 0; | |
588 | } | |
589 | ||
590 | static void dns_query_stop(DnsQuery *q) { | |
faa133f3 LP |
591 | DnsQueryTransaction *t; |
592 | ||
8ba9fd9c LP |
593 | assert(q); |
594 | ||
595 | q->timeout_event_source = sd_event_source_unref(q->timeout_event_source); | |
596 | ||
faa133f3 LP |
597 | while ((t = set_steal_first(q->transactions))) { |
598 | set_remove(t->queries, q); | |
599 | dns_query_transaction_gc(t); | |
600 | } | |
8ba9fd9c LP |
601 | } |
602 | ||
322345fd | 603 | static void dns_query_complete(DnsQuery *q, DnsQueryState state) { |
8ba9fd9c | 604 | assert(q); |
322345fd LP |
605 | assert(!IN_SET(state, DNS_QUERY_NULL, DNS_QUERY_PENDING)); |
606 | assert(IN_SET(q->state, DNS_QUERY_NULL, DNS_QUERY_PENDING)); | |
8ba9fd9c | 607 | |
322345fd LP |
608 | /* Note that this call might invalidate the query. Callers |
609 | * should hence not attempt to access the query or transaction | |
610 | * after calling this function. */ | |
8ba9fd9c | 611 | |
8ba9fd9c LP |
612 | q->state = state; |
613 | ||
322345fd LP |
614 | dns_query_stop(q); |
615 | if (q->complete) | |
616 | q->complete(q); | |
8ba9fd9c LP |
617 | } |
618 | ||
619 | static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) { | |
620 | DnsQuery *q = userdata; | |
621 | ||
622 | assert(s); | |
623 | assert(q); | |
624 | ||
322345fd | 625 | dns_query_complete(q, DNS_QUERY_TIMEOUT); |
8ba9fd9c LP |
626 | return 0; |
627 | } | |
628 | ||
934e9b10 LP |
629 | static int dns_query_add_transaction(DnsQuery *q, DnsScope *s, DnsResourceKey *key) { |
630 | _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; | |
faa133f3 LP |
631 | DnsQueryTransaction *t; |
632 | int r; | |
633 | ||
634 | assert(q); | |
635 | ||
636 | r = set_ensure_allocated(&q->transactions, NULL, NULL); | |
637 | if (r < 0) | |
638 | return r; | |
639 | ||
934e9b10 LP |
640 | if (key) { |
641 | question = dns_question_new(1); | |
642 | if (!question) | |
643 | return -ENOMEM; | |
644 | ||
645 | r = dns_question_add(question, key); | |
646 | if (r < 0) | |
647 | return r; | |
648 | } else | |
649 | question = dns_question_ref(q->question); | |
650 | ||
faa133f3 | 651 | LIST_FOREACH(transactions_by_scope, t, s->transactions) |
934e9b10 | 652 | if (dns_question_is_superset(t->question, question)) |
faa133f3 LP |
653 | break; |
654 | ||
655 | if (!t) { | |
934e9b10 | 656 | r = dns_query_transaction_new(&t, s, question); |
faa133f3 LP |
657 | if (r < 0) |
658 | return r; | |
659 | } | |
660 | ||
661 | r = set_ensure_allocated(&t->queries, NULL, NULL); | |
662 | if (r < 0) | |
663 | goto fail; | |
664 | ||
665 | r = set_put(t->queries, q); | |
666 | if (r < 0) | |
667 | goto fail; | |
668 | ||
669 | r = set_put(q->transactions, t); | |
670 | if (r < 0) { | |
671 | set_remove(t->queries, q); | |
672 | goto fail; | |
673 | } | |
674 | ||
675 | return 0; | |
676 | ||
677 | fail: | |
678 | dns_query_transaction_gc(t); | |
679 | return r; | |
680 | } | |
681 | ||
934e9b10 LP |
682 | static int dns_query_add_transaction_split(DnsQuery *q, DnsScope *s) { |
683 | int r; | |
684 | ||
685 | assert(q); | |
686 | assert(s); | |
687 | ||
688 | if (s->protocol == DNS_PROTOCOL_MDNS) { | |
689 | r = dns_query_add_transaction(q, s, NULL); | |
690 | if (r < 0) | |
691 | return r; | |
692 | } else { | |
693 | unsigned i; | |
694 | ||
695 | /* On DNS and LLMNR we can only send a single | |
696 | * question per datagram, hence issue multiple | |
697 | * transactions. */ | |
698 | ||
699 | for (i = 0; i < q->question->n_keys; i++) { | |
700 | r = dns_query_add_transaction(q, s, q->question->keys[i]); | |
701 | if (r < 0) | |
702 | return r; | |
703 | } | |
704 | } | |
705 | ||
706 | return 0; | |
707 | } | |
708 | ||
322345fd | 709 | int dns_query_go(DnsQuery *q) { |
8ba9fd9c LP |
710 | DnsScopeMatch found = DNS_SCOPE_NO; |
711 | DnsScope *s, *first = NULL; | |
712 | DnsQueryTransaction *t; | |
faa133f3 LP |
713 | const char *name; |
714 | Iterator i; | |
8ba9fd9c LP |
715 | int r; |
716 | ||
717 | assert(q); | |
718 | ||
719 | if (q->state != DNS_QUERY_NULL) | |
720 | return 0; | |
721 | ||
faa133f3 LP |
722 | assert(q->question); |
723 | assert(q->question->n_keys > 0); | |
724 | ||
725 | name = DNS_RESOURCE_KEY_NAME(q->question->keys[0]); | |
8ba9fd9c LP |
726 | |
727 | LIST_FOREACH(scopes, s, q->manager->dns_scopes) { | |
74b2466e LP |
728 | DnsScopeMatch match; |
729 | ||
faa133f3 | 730 | match = dns_scope_good_domain(s, name); |
74b2466e LP |
731 | if (match < 0) |
732 | return match; | |
733 | ||
734 | if (match == DNS_SCOPE_NO) | |
735 | continue; | |
736 | ||
737 | found = match; | |
738 | ||
739 | if (match == DNS_SCOPE_YES) { | |
740 | first = s; | |
741 | break; | |
742 | } else { | |
743 | assert(match == DNS_SCOPE_MAYBE); | |
744 | ||
745 | if (!first) | |
746 | first = s; | |
747 | } | |
748 | } | |
749 | ||
750 | if (found == DNS_SCOPE_NO) | |
8ba9fd9c | 751 | return -ESRCH; |
74b2466e | 752 | |
934e9b10 | 753 | r = dns_query_add_transaction_split(q, first); |
74b2466e LP |
754 | if (r < 0) |
755 | return r; | |
756 | ||
74b2466e LP |
757 | LIST_FOREACH(scopes, s, first->scopes_next) { |
758 | DnsScopeMatch match; | |
759 | ||
faa133f3 | 760 | match = dns_scope_good_domain(s, name); |
74b2466e LP |
761 | if (match < 0) |
762 | return match; | |
763 | ||
764 | if (match != found) | |
765 | continue; | |
766 | ||
934e9b10 | 767 | r = dns_query_add_transaction_split(q, s); |
74b2466e LP |
768 | if (r < 0) |
769 | return r; | |
74b2466e LP |
770 | } |
771 | ||
faa133f3 LP |
772 | q->answer = dns_answer_unref(q->answer); |
773 | q->answer_ifindex = 0; | |
774 | q->answer_rcode = 0; | |
74b2466e LP |
775 | |
776 | 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); | |
777 | if (r < 0) | |
778 | goto fail; | |
779 | ||
322345fd | 780 | q->state = DNS_QUERY_PENDING; |
faa133f3 | 781 | q->block_ready++; |
74b2466e | 782 | |
faa133f3 LP |
783 | SET_FOREACH(t, q->transactions, i) { |
784 | if (t->state == DNS_QUERY_NULL) { | |
785 | r = dns_query_transaction_go(t); | |
786 | if (r < 0) | |
787 | goto fail; | |
788 | } | |
74b2466e LP |
789 | } |
790 | ||
faa133f3 LP |
791 | q->block_ready--; |
792 | dns_query_ready(q); | |
322345fd | 793 | |
8ba9fd9c | 794 | return 1; |
74b2466e LP |
795 | |
796 | fail: | |
8ba9fd9c | 797 | dns_query_stop(q); |
74b2466e LP |
798 | return r; |
799 | } | |
800 | ||
faa133f3 | 801 | void dns_query_ready(DnsQuery *q) { |
74b2466e | 802 | DnsQueryTransaction *t; |
ad867662 | 803 | DnsQueryState state = DNS_QUERY_NO_SERVERS; |
934e9b10 LP |
804 | _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; |
805 | int rcode = 0; | |
806 | DnsScope *scope = NULL; | |
e4501ed4 | 807 | bool pending = false; |
faa133f3 | 808 | Iterator i; |
74b2466e LP |
809 | |
810 | assert(q); | |
322345fd | 811 | assert(IN_SET(q->state, DNS_QUERY_NULL, DNS_QUERY_PENDING)); |
74b2466e | 812 | |
322345fd LP |
813 | /* Note that this call might invalidate the query. Callers |
814 | * should hence not attempt to access the query or transaction | |
faa133f3 | 815 | * after calling this function, unless the block_ready |
322345fd LP |
816 | * counter was explicitly bumped before doing so. */ |
817 | ||
faa133f3 | 818 | if (q->block_ready > 0) |
74b2466e LP |
819 | return; |
820 | ||
faa133f3 | 821 | SET_FOREACH(t, q->transactions, i) { |
74b2466e | 822 | |
934e9b10 LP |
823 | /* If we found a successful answer, ignore all answers from other scopes */ |
824 | if (state == DNS_QUERY_SUCCESS && t->scope != scope) | |
825 | continue; | |
826 | ||
e4501ed4 LP |
827 | /* One of the transactions is still going on, let's maybe wait for it */ |
828 | if (IN_SET(t->state, DNS_QUERY_PENDING, DNS_QUERY_NULL)) { | |
829 | pending = true; | |
830 | continue; | |
831 | } | |
74b2466e | 832 | |
322345fd LP |
833 | /* One of the transactions is successful, let's use |
834 | * it, and copy its data out */ | |
74b2466e | 835 | if (t->state == DNS_QUERY_SUCCESS) { |
934e9b10 LP |
836 | DnsAnswer *a; |
837 | ||
faa133f3 | 838 | if (t->received) { |
934e9b10 LP |
839 | rcode = DNS_PACKET_RCODE(t->received); |
840 | a = t->received->answer; | |
faa133f3 | 841 | } else { |
934e9b10 LP |
842 | rcode = t->cached_rcode; |
843 | a = t->cached; | |
faa133f3 | 844 | } |
322345fd | 845 | |
934e9b10 LP |
846 | if (state == DNS_QUERY_SUCCESS) { |
847 | DnsAnswer *merged; | |
848 | ||
849 | merged = dns_answer_merge(answer, a); | |
850 | if (!merged) { | |
851 | dns_query_complete(q, DNS_QUERY_RESOURCES); | |
852 | return; | |
853 | } | |
854 | ||
855 | dns_answer_unref(answer); | |
856 | answer = merged; | |
857 | } else { | |
858 | dns_answer_unref(answer); | |
859 | answer = dns_answer_ref(a); | |
860 | } | |
861 | ||
862 | scope = t->scope; | |
863 | state = DNS_QUERY_SUCCESS; | |
864 | continue; | |
74b2466e LP |
865 | } |
866 | ||
ad867662 LP |
867 | /* One of the transactions has failed, let's see |
868 | * whether we find anything better, but if not, return | |
934e9b10 LP |
869 | * its response data */ |
870 | if (state != DNS_QUERY_SUCCESS && t->state == DNS_QUERY_FAILURE) { | |
871 | DnsAnswer *a; | |
872 | ||
7e8e0422 | 873 | if (t->received) { |
934e9b10 LP |
874 | rcode = DNS_PACKET_RCODE(t->received); |
875 | a = t->received->answer; | |
7e8e0422 | 876 | } else { |
934e9b10 LP |
877 | rcode = t->cached_rcode; |
878 | a = t->cached; | |
7e8e0422 LP |
879 | } |
880 | ||
934e9b10 LP |
881 | dns_answer_unref(answer); |
882 | answer = dns_answer_ref(a); | |
883 | ||
884 | scope = t->scope; | |
74b2466e | 885 | state = DNS_QUERY_FAILURE; |
ad867662 LP |
886 | continue; |
887 | } | |
74b2466e | 888 | |
ad867662 | 889 | if (state == DNS_QUERY_NO_SERVERS && t->state != DNS_QUERY_NO_SERVERS) |
74b2466e LP |
890 | state = t->state; |
891 | } | |
892 | ||
e4501ed4 LP |
893 | if (pending) { |
894 | ||
895 | /* If so far we weren't successful, and there's | |
896 | * something still pending, then wait for it */ | |
897 | if (state != DNS_QUERY_SUCCESS) | |
898 | return; | |
899 | ||
900 | /* If we already were successful, then only wait for | |
901 | * other transactions on the same scope to finish. */ | |
902 | SET_FOREACH(t, q->transactions, i) { | |
903 | if (t->scope == scope && IN_SET(t->state, DNS_QUERY_PENDING, DNS_QUERY_NULL)) | |
904 | return; | |
905 | } | |
906 | } | |
907 | ||
934e9b10 LP |
908 | if (IN_SET(state, DNS_QUERY_SUCCESS, DNS_QUERY_FAILURE)) { |
909 | q->answer = dns_answer_ref(answer); | |
910 | q->answer_rcode = rcode; | |
911 | q->answer_ifindex = (scope && scope->link) ? scope->link->ifindex : 0; | |
faa133f3 | 912 | } |
74b2466e | 913 | |
322345fd | 914 | dns_query_complete(q, state); |
74b2466e | 915 | } |
8ba9fd9c | 916 | |
322345fd | 917 | int dns_query_cname_redirect(DnsQuery *q, const char *name) { |
faa133f3 LP |
918 | _cleanup_(dns_question_unrefp) DnsQuestion *nq = NULL; |
919 | int r; | |
8ba9fd9c LP |
920 | |
921 | assert(q); | |
922 | ||
faa133f3 | 923 | if (q->n_cname_redirects > CNAME_MAX) |
8ba9fd9c LP |
924 | return -ELOOP; |
925 | ||
faa133f3 LP |
926 | r = dns_question_cname_redirect(q->question, name, &nq); |
927 | if (r < 0) | |
928 | return r; | |
8ba9fd9c | 929 | |
faa133f3 LP |
930 | dns_question_unref(q->question); |
931 | q->question = nq; | |
932 | nq = NULL; | |
8ba9fd9c | 933 | |
faa133f3 | 934 | q->n_cname_redirects++; |
8ba9fd9c | 935 | |
322345fd LP |
936 | dns_query_stop(q); |
937 | q->state = DNS_QUERY_NULL; | |
938 | ||
8ba9fd9c LP |
939 | return 0; |
940 | } | |
a2a416f7 LP |
941 | |
942 | static const char* const dns_query_state_table[_DNS_QUERY_STATE_MAX] = { | |
943 | [DNS_QUERY_NULL] = "null", | |
944 | [DNS_QUERY_PENDING] = "pending", | |
945 | [DNS_QUERY_FAILURE] = "failure", | |
946 | [DNS_QUERY_SUCCESS] = "success", | |
947 | [DNS_QUERY_NO_SERVERS] = "no-servers", | |
948 | [DNS_QUERY_TIMEOUT] = "timeout", | |
949 | [DNS_QUERY_ATTEMPTS_MAX] = "attempts-max", | |
950 | [DNS_QUERY_INVALID_REPLY] = "invalid-reply", | |
951 | [DNS_QUERY_RESOURCES] = "resources", | |
952 | [DNS_QUERY_ABORTED] = "aborted", | |
953 | }; | |
954 | DEFINE_STRING_TABLE_LOOKUP(dns_query_state, DnsQueryState); |