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