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