]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
74b2466e | 2 | |
b5efdb8a | 3 | #include "alloc-util.h" |
2a1037af | 4 | #include "dns-domain.h" |
011696f7 | 5 | #include "dns-type.h" |
b5efdb8a | 6 | #include "hostname-util.h" |
78c6a153 | 7 | #include "local-addresses.h" |
74b2466e | 8 | #include "resolved-dns-query.h" |
839a4a20 | 9 | #include "resolved-dns-synthesize.h" |
dd0bc0f1 | 10 | #include "resolved-etc-hosts.h" |
23b298bc | 11 | #include "string-util.h" |
74b2466e | 12 | |
39762fdf | 13 | #define QUERIES_MAX 2048 |
45ec7efb | 14 | #define AUXILIARY_QUERIES_MAX 64 |
8ba9fd9c | 15 | |
801ad6a6 LP |
16 | static int dns_query_candidate_new(DnsQueryCandidate **ret, DnsQuery *q, DnsScope *s) { |
17 | DnsQueryCandidate *c; | |
74b2466e | 18 | |
801ad6a6 | 19 | assert(ret); |
faa133f3 | 20 | assert(q); |
801ad6a6 | 21 | assert(s); |
74b2466e | 22 | |
1ed31408 | 23 | c = new(DnsQueryCandidate, 1); |
801ad6a6 LP |
24 | if (!c) |
25 | return -ENOMEM; | |
26 | ||
1ed31408 | 27 | *c = (DnsQueryCandidate) { |
0e0fd08f | 28 | .n_ref = 1, |
1ed31408 LP |
29 | .query = q, |
30 | .scope = s, | |
31 | }; | |
801ad6a6 LP |
32 | |
33 | LIST_PREPEND(candidates_by_query, q->candidates, c); | |
34 | LIST_PREPEND(candidates_by_scope, s->query_candidates, c); | |
35 | ||
36 | *ret = c; | |
37 | return 0; | |
38 | } | |
39 | ||
40 | static void dns_query_candidate_stop(DnsQueryCandidate *c) { | |
41 | DnsTransaction *t; | |
74b2466e | 42 | |
801ad6a6 LP |
43 | assert(c); |
44 | ||
45 | while ((t = set_steal_first(c->transactions))) { | |
547973de | 46 | set_remove(t->notify_query_candidates, c); |
35aa04e9 | 47 | set_remove(t->notify_query_candidates_done, c); |
ec2c5e43 | 48 | dns_transaction_gc(t); |
74b2466e | 49 | } |
74b2466e LP |
50 | } |
51 | ||
0e0fd08f | 52 | static DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c) { |
801ad6a6 LP |
53 | if (!c) |
54 | return NULL; | |
55 | ||
56 | dns_query_candidate_stop(c); | |
57 | ||
58 | set_free(c->transactions); | |
59 | dns_search_domain_unref(c->search_domain); | |
60 | ||
61 | if (c->query) | |
62 | LIST_REMOVE(candidates_by_query, c->query->candidates, c); | |
63 | ||
64 | if (c->scope) | |
65 | LIST_REMOVE(candidates_by_scope, c->scope->query_candidates, c); | |
66 | ||
6b430fdb | 67 | return mfree(c); |
801ad6a6 LP |
68 | } |
69 | ||
0e0fd08f ZJS |
70 | DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(DnsQueryCandidate, dns_query_candidate, dns_query_candidate_free); |
71 | ||
801ad6a6 | 72 | static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) { |
c805014a | 73 | DnsSearchDomain *next; |
801ad6a6 LP |
74 | |
75 | assert(c); | |
76 | ||
ad44b56b | 77 | if (c->search_domain && c->search_domain->linked) |
801ad6a6 | 78 | next = c->search_domain->domains_next; |
ad44b56b LP |
79 | else |
80 | next = dns_scope_get_search_domains(c->scope); | |
801ad6a6 | 81 | |
ad44b56b | 82 | for (;;) { |
6627b7e2 LP |
83 | if (!next) /* We hit the end of the list */ |
84 | return 0; | |
801ad6a6 | 85 | |
ad44b56b LP |
86 | if (!next->route_only) |
87 | break; | |
801ad6a6 | 88 | |
ad44b56b LP |
89 | /* Skip over route-only domains */ |
90 | next = next->domains_next; | |
801ad6a6 LP |
91 | } |
92 | ||
93 | dns_search_domain_unref(c->search_domain); | |
94 | c->search_domain = dns_search_domain_ref(next); | |
6627b7e2 | 95 | |
801ad6a6 LP |
96 | return 1; |
97 | } | |
98 | ||
775ae354 LP |
99 | static int dns_query_candidate_add_transaction( |
100 | DnsQueryCandidate *c, | |
101 | DnsResourceKey *key, | |
102 | DnsPacket *bypass) { | |
103 | ||
29bd6012 | 104 | _cleanup_(dns_transaction_gcp) DnsTransaction *t = NULL; |
801ad6a6 LP |
105 | int r; |
106 | ||
107 | assert(c); | |
801ad6a6 | 108 | |
775ae354 LP |
109 | if (key) { |
110 | /* Regular lookup with a resource key */ | |
111 | assert(!bypass); | |
112 | ||
113 | t = dns_scope_find_transaction(c->scope, key, c->query->flags); | |
114 | if (!t) { | |
115 | r = dns_transaction_new(&t, c->scope, key, NULL, c->query->flags); | |
116 | if (r < 0) | |
117 | return r; | |
118 | } else if (set_contains(c->transactions, t)) | |
119 | return 0; | |
120 | } else { | |
121 | /* "Bypass" lookup with a query packet */ | |
122 | assert(bypass); | |
123 | ||
124 | r = dns_transaction_new(&t, c->scope, NULL, bypass, c->query->flags); | |
801ad6a6 LP |
125 | if (r < 0) |
126 | return r; | |
775ae354 | 127 | } |
801ad6a6 | 128 | |
35aa04e9 LP |
129 | r = set_ensure_allocated(&t->notify_query_candidates_done, NULL); |
130 | if (r < 0) | |
29bd6012 | 131 | return r; |
35aa04e9 | 132 | |
de7fef4b | 133 | r = set_ensure_put(&t->notify_query_candidates, NULL, c); |
801ad6a6 | 134 | if (r < 0) |
29bd6012 | 135 | return r; |
801ad6a6 | 136 | |
de7fef4b | 137 | r = set_ensure_put(&c->transactions, NULL, t); |
801ad6a6 | 138 | if (r < 0) { |
547973de | 139 | (void) set_remove(t->notify_query_candidates, c); |
29bd6012 | 140 | return r; |
801ad6a6 LP |
141 | } |
142 | ||
29bd6012 | 143 | TAKE_PTR(t); |
547973de | 144 | return 1; |
801ad6a6 LP |
145 | } |
146 | ||
147 | static int dns_query_candidate_go(DnsQueryCandidate *c) { | |
0e0fd08f | 148 | _cleanup_(dns_query_candidate_unrefp) DnsQueryCandidate *keep_c = NULL; |
801ad6a6 | 149 | DnsTransaction *t; |
801ad6a6 | 150 | int r; |
011696f7 | 151 | unsigned n = 0; |
801ad6a6 LP |
152 | |
153 | assert(c); | |
154 | ||
0e0fd08f ZJS |
155 | /* Let's keep a reference to the query while we're operating */ |
156 | keep_c = dns_query_candidate_ref(c); | |
4ea8b443 | 157 | |
801ad6a6 | 158 | /* Start the transactions that are not started yet */ |
90e74a66 | 159 | SET_FOREACH(t, c->transactions) { |
801ad6a6 LP |
160 | if (t->state != DNS_TRANSACTION_NULL) |
161 | continue; | |
162 | ||
163 | r = dns_transaction_go(t); | |
0e0fd08f | 164 | if (r < 0) |
801ad6a6 | 165 | return r; |
011696f7 LP |
166 | |
167 | n++; | |
801ad6a6 LP |
168 | } |
169 | ||
011696f7 | 170 | /* If there was nothing to start, then let's proceed immediately */ |
0e0fd08f | 171 | if (n == 0) |
011696f7 LP |
172 | dns_query_candidate_notify(c); |
173 | ||
801ad6a6 LP |
174 | return 0; |
175 | } | |
176 | ||
177 | static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { | |
178 | DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; | |
179 | DnsTransaction *t; | |
801ad6a6 LP |
180 | |
181 | assert(c); | |
182 | ||
183 | if (c->error_code != 0) | |
7cc6ed7b | 184 | return DNS_TRANSACTION_ERRNO; |
801ad6a6 | 185 | |
90e74a66 | 186 | SET_FOREACH(t, c->transactions) { |
801ad6a6 LP |
187 | |
188 | switch (t->state) { | |
189 | ||
5264131a LP |
190 | case DNS_TRANSACTION_NULL: |
191 | /* If there's a NULL transaction pending, then | |
192 | * this means not all transactions where | |
193 | * started yet, and we were called from within | |
194 | * the stackframe that is supposed to start | |
195 | * remaining transactions. In this case, | |
196 | * simply claim the candidate is pending. */ | |
197 | ||
801ad6a6 | 198 | case DNS_TRANSACTION_PENDING: |
547973de LP |
199 | case DNS_TRANSACTION_VALIDATING: |
200 | /* If there's one transaction currently in | |
201 | * VALIDATING state, then this means there's | |
202 | * also one in PENDING state, hence we can | |
203 | * return PENDING immediately. */ | |
204 | return DNS_TRANSACTION_PENDING; | |
801ad6a6 LP |
205 | |
206 | case DNS_TRANSACTION_SUCCESS: | |
207 | state = t->state; | |
208 | break; | |
209 | ||
210 | default: | |
211 | if (state != DNS_TRANSACTION_SUCCESS) | |
212 | state = t->state; | |
213 | ||
214 | break; | |
215 | } | |
216 | } | |
217 | ||
218 | return state; | |
219 | } | |
220 | ||
221 | static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) { | |
23b298bc | 222 | DnsQuestion *question; |
801ad6a6 LP |
223 | DnsResourceKey *key; |
224 | int n = 0, r; | |
225 | ||
226 | assert(c); | |
227 | ||
228 | dns_query_candidate_stop(c); | |
229 | ||
775ae354 LP |
230 | if (c->query->question_bypass) { |
231 | /* If this is a bypass query, then pass the original query packet along to the transaction */ | |
232 | ||
233 | assert(dns_question_size(c->query->question_bypass->question) == 1); | |
234 | ||
235 | if (!dns_scope_good_key(c->scope, c->query->question_bypass->question->keys[0])) | |
236 | return 0; | |
237 | ||
238 | r = dns_query_candidate_add_transaction(c, NULL, c->query->question_bypass); | |
239 | if (r < 0) | |
240 | goto fail; | |
241 | ||
242 | return 1; | |
243 | } | |
244 | ||
23b298bc LP |
245 | question = dns_query_question_for_protocol(c->query, c->scope->protocol); |
246 | ||
801ad6a6 | 247 | /* Create one transaction per question key */ |
23b298bc | 248 | DNS_QUESTION_FOREACH(key, question) { |
801ad6a6 | 249 | _cleanup_(dns_resource_key_unrefp) DnsResourceKey *new_key = NULL; |
011696f7 LP |
250 | DnsResourceKey *qkey; |
251 | ||
801ad6a6 LP |
252 | if (c->search_domain) { |
253 | r = dns_resource_key_new_append_suffix(&new_key, key, c->search_domain->name); | |
254 | if (r < 0) | |
255 | goto fail; | |
801ad6a6 | 256 | |
011696f7 LP |
257 | qkey = new_key; |
258 | } else | |
259 | qkey = key; | |
260 | ||
261 | if (!dns_scope_good_key(c->scope, qkey)) | |
262 | continue; | |
263 | ||
775ae354 | 264 | r = dns_query_candidate_add_transaction(c, qkey, NULL); |
801ad6a6 LP |
265 | if (r < 0) |
266 | goto fail; | |
267 | ||
268 | n++; | |
269 | } | |
270 | ||
271 | return n; | |
272 | ||
273 | fail: | |
274 | dns_query_candidate_stop(c); | |
275 | return r; | |
276 | } | |
277 | ||
547973de | 278 | void dns_query_candidate_notify(DnsQueryCandidate *c) { |
801ad6a6 LP |
279 | DnsTransactionState state; |
280 | int r; | |
281 | ||
282 | assert(c); | |
283 | ||
284 | state = dns_query_candidate_state(c); | |
285 | ||
547973de | 286 | if (DNS_TRANSACTION_IS_LIVE(state)) |
801ad6a6 LP |
287 | return; |
288 | ||
289 | if (state != DNS_TRANSACTION_SUCCESS && c->search_domain) { | |
290 | ||
291 | r = dns_query_candidate_next_search_domain(c); | |
292 | if (r < 0) | |
293 | goto fail; | |
294 | ||
295 | if (r > 0) { | |
296 | /* OK, there's another search domain to try, let's do so. */ | |
297 | ||
298 | r = dns_query_candidate_setup_transactions(c); | |
299 | if (r < 0) | |
300 | goto fail; | |
301 | ||
302 | if (r > 0) { | |
303 | /* New transactions where queued. Start them and wait */ | |
304 | ||
305 | r = dns_query_candidate_go(c); | |
306 | if (r < 0) | |
307 | goto fail; | |
308 | ||
309 | return; | |
310 | } | |
311 | } | |
312 | ||
313 | } | |
314 | ||
315 | dns_query_ready(c->query); | |
316 | return; | |
317 | ||
318 | fail: | |
fed66db0 | 319 | c->error_code = log_warning_errno(r, "Failed to follow search domains: %m"); |
801ad6a6 LP |
320 | dns_query_ready(c->query); |
321 | } | |
322 | ||
323 | static void dns_query_stop(DnsQuery *q) { | |
324 | DnsQueryCandidate *c; | |
325 | ||
326 | assert(q); | |
327 | ||
97935302 | 328 | q->timeout_event_source = sd_event_source_disable_unref(q->timeout_event_source); |
801ad6a6 LP |
329 | |
330 | LIST_FOREACH(candidates_by_query, c, q->candidates) | |
331 | dns_query_candidate_stop(c); | |
332 | } | |
333 | ||
0e0fd08f | 334 | static void dns_query_unref_candidates(DnsQuery *q) { |
7820b320 LP |
335 | assert(q); |
336 | ||
337 | while (q->candidates) | |
0e0fd08f | 338 | dns_query_candidate_unref(q->candidates); |
7820b320 LP |
339 | } |
340 | ||
341 | static void dns_query_reset_answer(DnsQuery *q) { | |
342 | assert(q); | |
343 | ||
344 | q->answer = dns_answer_unref(q->answer); | |
345 | q->answer_rcode = 0; | |
346 | q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; | |
7cc6ed7b | 347 | q->answer_errno = 0; |
6f055e43 | 348 | q->answer_query_flags = 0; |
7820b320 LP |
349 | q->answer_protocol = _DNS_PROTOCOL_INVALID; |
350 | q->answer_family = AF_UNSPEC; | |
351 | q->answer_search_domain = dns_search_domain_unref(q->answer_search_domain); | |
775ae354 | 352 | q->answer_full_packet = dns_packet_unref(q->answer_full_packet); |
7820b320 LP |
353 | } |
354 | ||
74b2466e | 355 | DnsQuery *dns_query_free(DnsQuery *q) { |
74b2466e LP |
356 | if (!q) |
357 | return NULL; | |
358 | ||
45ec7efb LP |
359 | while (q->auxiliary_queries) |
360 | dns_query_free(q->auxiliary_queries); | |
361 | ||
362 | if (q->auxiliary_for) { | |
363 | assert(q->auxiliary_for->n_auxiliary_queries > 0); | |
364 | q->auxiliary_for->n_auxiliary_queries--; | |
365 | LIST_REMOVE(auxiliary_queries, q->auxiliary_for->auxiliary_queries, q); | |
366 | } | |
367 | ||
0e0fd08f | 368 | dns_query_unref_candidates(q); |
322345fd | 369 | |
23b298bc LP |
370 | dns_question_unref(q->question_idna); |
371 | dns_question_unref(q->question_utf8); | |
775ae354 | 372 | dns_packet_unref(q->question_bypass); |
7820b320 LP |
373 | |
374 | dns_query_reset_answer(q); | |
322345fd | 375 | |
c9de4e0f | 376 | sd_bus_message_unref(q->bus_request); |
82bd6ddd | 377 | sd_bus_track_unref(q->bus_track); |
74b2466e | 378 | |
9581bb84 LP |
379 | if (q->varlink_request) { |
380 | varlink_set_userdata(q->varlink_request, NULL); | |
381 | varlink_unref(q->varlink_request); | |
382 | } | |
383 | ||
bde69bbd LP |
384 | if (q->request_packet) |
385 | hashmap_remove_value(q->stub_listener_extra ? | |
386 | q->stub_listener_extra->queries_by_packet : | |
387 | q->manager->stub_queries_by_packet, | |
388 | q->request_packet, | |
389 | q); | |
390 | ||
775ae354 LP |
391 | dns_packet_unref(q->request_packet); |
392 | dns_answer_unref(q->reply_answer); | |
393 | dns_answer_unref(q->reply_authoritative); | |
394 | dns_answer_unref(q->reply_additional); | |
b30bf55d | 395 | |
775ae354 | 396 | if (q->request_stream) { |
b30bf55d | 397 | /* Detach the stream from our query, in case something else keeps a reference to it. */ |
775ae354 LP |
398 | (void) set_remove(q->request_stream->queries, q); |
399 | q->request_stream = dns_stream_unref(q->request_stream); | |
b30bf55d LP |
400 | } |
401 | ||
23b298bc LP |
402 | free(q->request_address_string); |
403 | ||
39762fdf | 404 | if (q->manager) { |
74b2466e | 405 | LIST_REMOVE(queries, q->manager->dns_queries, q); |
39762fdf LP |
406 | q->manager->n_dns_queries--; |
407 | } | |
74b2466e | 408 | |
6b430fdb | 409 | return mfree(q); |
74b2466e LP |
410 | } |
411 | ||
23b298bc LP |
412 | int dns_query_new( |
413 | Manager *m, | |
414 | DnsQuery **ret, | |
415 | DnsQuestion *question_utf8, | |
416 | DnsQuestion *question_idna, | |
775ae354 | 417 | DnsPacket *question_bypass, |
17c8de63 LP |
418 | int ifindex, |
419 | uint64_t flags) { | |
23b298bc | 420 | |
74b2466e | 421 | _cleanup_(dns_query_freep) DnsQuery *q = NULL; |
775ae354 | 422 | char key_str[DNS_RESOURCE_KEY_STRING_MAX]; |
23b298bc | 423 | DnsResourceKey *key; |
faa133f3 | 424 | int r; |
74b2466e LP |
425 | |
426 | assert(m); | |
427 | ||
775ae354 LP |
428 | if (question_bypass) { |
429 | /* It's either a "bypass" query, or a regular one, but can't be both. */ | |
430 | if (question_utf8 || question_idna) | |
23b298bc LP |
431 | return -EINVAL; |
432 | ||
775ae354 LP |
433 | } else { |
434 | bool good = false; | |
23b298bc | 435 | |
1a71fe4e LP |
436 | /* This (primarily) checks two things: |
437 | * | |
438 | * 1. That the question is not empty | |
439 | * 2. That all RR keys in the question objects are for the same domain | |
440 | * | |
441 | * Or in other words, a single DnsQuery object may be used to look up A+AAAA combination for | |
442 | * the same domain name, or SRV+TXT (for DNS-SD services), but not for unrelated lookups. */ | |
443 | ||
775ae354 LP |
444 | if (dns_question_size(question_utf8) > 0) { |
445 | r = dns_question_is_valid_for_query(question_utf8); | |
23b298bc LP |
446 | if (r < 0) |
447 | return r; | |
448 | if (r == 0) | |
449 | return -EINVAL; | |
450 | ||
451 | good = true; | |
452 | } | |
23b298bc | 453 | |
775ae354 LP |
454 | /* If the IDNA and UTF8 questions are the same, merge their references */ |
455 | r = dns_question_is_equal(question_idna, question_utf8); | |
456 | if (r < 0) | |
457 | return r; | |
458 | if (r > 0) | |
459 | question_idna = question_utf8; | |
460 | else { | |
461 | if (dns_question_size(question_idna) > 0) { | |
462 | r = dns_question_is_valid_for_query(question_idna); | |
463 | if (r < 0) | |
464 | return r; | |
465 | if (r == 0) | |
466 | return -EINVAL; | |
467 | ||
468 | good = true; | |
469 | } | |
470 | } | |
471 | ||
472 | if (!good) /* don't allow empty queries */ | |
473 | return -EINVAL; | |
474 | } | |
74b2466e | 475 | |
39762fdf LP |
476 | if (m->n_dns_queries >= QUERIES_MAX) |
477 | return -EBUSY; | |
478 | ||
1ed31408 | 479 | q = new(DnsQuery, 1); |
74b2466e LP |
480 | if (!q) |
481 | return -ENOMEM; | |
482 | ||
1ed31408 LP |
483 | *q = (DnsQuery) { |
484 | .question_utf8 = dns_question_ref(question_utf8), | |
485 | .question_idna = dns_question_ref(question_idna), | |
775ae354 | 486 | .question_bypass = dns_packet_ref(question_bypass), |
1ed31408 LP |
487 | .ifindex = ifindex, |
488 | .flags = flags, | |
489 | .answer_dnssec_result = _DNSSEC_RESULT_INVALID, | |
490 | .answer_protocol = _DNS_PROTOCOL_INVALID, | |
491 | .answer_family = AF_UNSPEC, | |
492 | }; | |
322345fd | 493 | |
775ae354 LP |
494 | if (question_bypass) { |
495 | DNS_QUESTION_FOREACH(key, question_bypass->question) | |
496 | log_debug("Looking up bypass packet for %s.", | |
497 | dns_resource_key_to_string(key, key_str, sizeof key_str)); | |
498 | } else { | |
499 | /* First dump UTF8 question */ | |
500 | DNS_QUESTION_FOREACH(key, question_utf8) | |
501 | log_debug("Looking up RR for %s.", | |
502 | dns_resource_key_to_string(key, key_str, sizeof key_str)); | |
503 | ||
504 | /* And then dump the IDNA question, but only what hasn't been dumped already through the UTF8 question. */ | |
505 | DNS_QUESTION_FOREACH(key, question_idna) { | |
506 | r = dns_question_contains(question_utf8, key); | |
507 | if (r < 0) | |
508 | return r; | |
509 | if (r > 0) | |
510 | continue; | |
23b298bc | 511 | |
775ae354 LP |
512 | log_debug("Looking up IDNA RR for %s.", |
513 | dns_resource_key_to_string(key, key_str, sizeof key_str)); | |
514 | } | |
74b2466e LP |
515 | } |
516 | ||
517 | LIST_PREPEND(queries, m->dns_queries, q); | |
39762fdf | 518 | m->n_dns_queries++; |
74b2466e LP |
519 | q->manager = m; |
520 | ||
8ba9fd9c LP |
521 | if (ret) |
522 | *ret = q; | |
8ba9fd9c | 523 | |
775ae354 | 524 | TAKE_PTR(q); |
8ba9fd9c LP |
525 | return 0; |
526 | } | |
527 | ||
45ec7efb LP |
528 | int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for) { |
529 | assert(q); | |
530 | assert(auxiliary_for); | |
531 | ||
61233823 | 532 | /* Ensure that the query is not auxiliary yet, and |
45ec7efb LP |
533 | * nothing else is auxiliary to it either */ |
534 | assert(!q->auxiliary_for); | |
535 | assert(!q->auxiliary_queries); | |
536 | ||
537 | /* Ensure that the unit we shall be made auxiliary for isn't | |
538 | * auxiliary itself */ | |
539 | assert(!auxiliary_for->auxiliary_for); | |
540 | ||
541 | if (auxiliary_for->n_auxiliary_queries >= AUXILIARY_QUERIES_MAX) | |
542 | return -EAGAIN; | |
543 | ||
544 | LIST_PREPEND(auxiliary_queries, auxiliary_for->auxiliary_queries, q); | |
545 | q->auxiliary_for = auxiliary_for; | |
546 | ||
547 | auxiliary_for->n_auxiliary_queries++; | |
548 | return 0; | |
549 | } | |
550 | ||
65a01e82 | 551 | void dns_query_complete(DnsQuery *q, DnsTransactionState state) { |
8ba9fd9c | 552 | assert(q); |
547973de LP |
553 | assert(!DNS_TRANSACTION_IS_LIVE(state)); |
554 | assert(DNS_TRANSACTION_IS_LIVE(q->state)); | |
8ba9fd9c | 555 | |
65a01e82 LP |
556 | /* Note that this call might invalidate the query. Callers should hence not attempt to access the |
557 | * query or transaction after calling this function. */ | |
8ba9fd9c | 558 | |
8ba9fd9c LP |
559 | q->state = state; |
560 | ||
322345fd LP |
561 | dns_query_stop(q); |
562 | if (q->complete) | |
563 | q->complete(q); | |
8ba9fd9c LP |
564 | } |
565 | ||
566 | static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) { | |
567 | DnsQuery *q = userdata; | |
568 | ||
569 | assert(s); | |
570 | assert(q); | |
571 | ||
ec2c5e43 | 572 | dns_query_complete(q, DNS_TRANSACTION_TIMEOUT); |
8ba9fd9c LP |
573 | return 0; |
574 | } | |
575 | ||
801ad6a6 | 576 | static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) { |
0e0fd08f | 577 | _cleanup_(dns_query_candidate_unrefp) DnsQueryCandidate *c = NULL; |
faa133f3 LP |
578 | int r; |
579 | ||
580 | assert(q); | |
ec2c5e43 | 581 | assert(s); |
faa133f3 | 582 | |
801ad6a6 | 583 | r = dns_query_candidate_new(&c, q, s); |
faa133f3 LP |
584 | if (r < 0) |
585 | return r; | |
586 | ||
801ad6a6 | 587 | /* If this a single-label domain on DNS, we might append a suitable search domain first. */ |
3b5bd7d6 ZJS |
588 | if (!FLAGS_SET(q->flags, SD_RESOLVED_NO_SEARCH) && |
589 | dns_scope_name_wants_search_domain(s, dns_question_first_name(q->question_idna))) { | |
590 | /* OK, we want a search domain now. Let's find one for this scope */ | |
22f711bb | 591 | |
6da95857 | 592 | r = dns_query_candidate_next_search_domain(c); |
3b5bd7d6 | 593 | if (r < 0) |
7877e5ca | 594 | return r; |
faa133f3 LP |
595 | } |
596 | ||
801ad6a6 LP |
597 | r = dns_query_candidate_setup_transactions(c); |
598 | if (r < 0) | |
7877e5ca | 599 | return r; |
801ad6a6 | 600 | |
7877e5ca | 601 | TAKE_PTR(c); |
faa133f3 | 602 | return 0; |
faa133f3 LP |
603 | } |
604 | ||
78c6a153 | 605 | static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { |
2a1037af | 606 | _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; |
dd0bc0f1 | 607 | int r; |
2a1037af LP |
608 | |
609 | assert(q); | |
610 | assert(state); | |
611 | ||
dd0bc0f1 LP |
612 | /* Tries to synthesize localhost RR replies (and others) where appropriate. Note that this is done *after* the |
613 | * the normal lookup finished. The data from the network hence takes precedence over the data we | |
614 | * synthesize. (But note that many scopes refuse to resolve certain domain names) */ | |
2a1037af LP |
615 | |
616 | if (!IN_SET(*state, | |
3bbdc31d | 617 | DNS_TRANSACTION_RCODE_FAILURE, |
2a1037af LP |
618 | DNS_TRANSACTION_NO_SERVERS, |
619 | DNS_TRANSACTION_TIMEOUT, | |
edbcc1fd | 620 | DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, |
0791110f LP |
621 | DNS_TRANSACTION_NETWORK_DOWN, |
622 | DNS_TRANSACTION_NOT_FOUND)) | |
78c6a153 | 623 | return 0; |
2a1037af | 624 | |
775ae354 LP |
625 | if (FLAGS_SET(q->flags, SD_RESOLVED_NO_SYNTHESIZE)) |
626 | return 0; | |
627 | ||
839a4a20 LP |
628 | r = dns_synthesize_answer( |
629 | q->manager, | |
775ae354 | 630 | q->question_bypass ? q->question_bypass->question : q->question_utf8, |
839a4a20 | 631 | q->ifindex, |
dd0bc0f1 | 632 | &answer); |
acf06088 LP |
633 | if (r == -ENXIO) { |
634 | /* If we get ENXIO this tells us to generate NXDOMAIN unconditionally. */ | |
2a1037af | 635 | |
acf06088 LP |
636 | dns_query_reset_answer(q); |
637 | q->answer_rcode = DNS_RCODE_NXDOMAIN; | |
638 | q->answer_protocol = dns_synthesize_protocol(q->flags); | |
639 | q->answer_family = dns_synthesize_family(q->flags); | |
5c1790d1 | 640 | q->answer_query_flags = SD_RESOLVED_AUTHENTICATED|SD_RESOLVED_CONFIDENTIAL|SD_RESOLVED_SYNTHETIC; |
acf06088 LP |
641 | *state = DNS_TRANSACTION_RCODE_FAILURE; |
642 | ||
643 | return 0; | |
644 | } | |
839a4a20 LP |
645 | if (r <= 0) |
646 | return r; | |
2a1037af | 647 | |
839a4a20 | 648 | dns_query_reset_answer(q); |
2a1037af | 649 | |
1cc6c93a | 650 | q->answer = TAKE_PTR(answer); |
2a1037af | 651 | q->answer_rcode = DNS_RCODE_SUCCESS; |
dd0bc0f1 LP |
652 | q->answer_protocol = dns_synthesize_protocol(q->flags); |
653 | q->answer_family = dns_synthesize_family(q->flags); | |
5c1790d1 | 654 | q->answer_query_flags = SD_RESOLVED_AUTHENTICATED|SD_RESOLVED_CONFIDENTIAL|SD_RESOLVED_SYNTHETIC; |
2a1037af LP |
655 | |
656 | *state = DNS_TRANSACTION_SUCCESS; | |
78c6a153 LP |
657 | |
658 | return 1; | |
2a1037af LP |
659 | } |
660 | ||
dd0bc0f1 LP |
661 | static int dns_query_try_etc_hosts(DnsQuery *q) { |
662 | _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; | |
663 | int r; | |
664 | ||
665 | assert(q); | |
666 | ||
c805014a ZJS |
667 | /* Looks in /etc/hosts for matching entries. Note that this is done *before* the normal lookup is |
668 | * done. The data from /etc/hosts hence takes precedence over the network. */ | |
dd0bc0f1 | 669 | |
775ae354 LP |
670 | if (FLAGS_SET(q->flags, SD_RESOLVED_NO_SYNTHESIZE)) |
671 | return 0; | |
672 | ||
dd0bc0f1 LP |
673 | r = manager_etc_hosts_lookup( |
674 | q->manager, | |
775ae354 | 675 | q->question_bypass ? q->question_bypass->question : q->question_utf8, |
dd0bc0f1 LP |
676 | &answer); |
677 | if (r <= 0) | |
678 | return r; | |
679 | ||
680 | dns_query_reset_answer(q); | |
681 | ||
1cc6c93a | 682 | q->answer = TAKE_PTR(answer); |
dd0bc0f1 LP |
683 | q->answer_rcode = DNS_RCODE_SUCCESS; |
684 | q->answer_protocol = dns_synthesize_protocol(q->flags); | |
685 | q->answer_family = dns_synthesize_family(q->flags); | |
5c1790d1 | 686 | q->answer_query_flags = SD_RESOLVED_AUTHENTICATED|SD_RESOLVED_CONFIDENTIAL|SD_RESOLVED_SYNTHETIC; |
dd0bc0f1 LP |
687 | |
688 | return 1; | |
689 | } | |
690 | ||
322345fd | 691 | int dns_query_go(DnsQuery *q) { |
8ba9fd9c LP |
692 | DnsScopeMatch found = DNS_SCOPE_NO; |
693 | DnsScope *s, *first = NULL; | |
801ad6a6 | 694 | DnsQueryCandidate *c; |
8ba9fd9c LP |
695 | int r; |
696 | ||
697 | assert(q); | |
698 | ||
ec2c5e43 | 699 | if (q->state != DNS_TRANSACTION_NULL) |
8ba9fd9c LP |
700 | return 0; |
701 | ||
dd0bc0f1 LP |
702 | r = dns_query_try_etc_hosts(q); |
703 | if (r < 0) | |
704 | return r; | |
705 | if (r > 0) { | |
706 | dns_query_complete(q, DNS_TRANSACTION_SUCCESS); | |
707 | return 1; | |
708 | } | |
709 | ||
8ba9fd9c | 710 | LIST_FOREACH(scopes, s, q->manager->dns_scopes) { |
74b2466e | 711 | DnsScopeMatch match; |
23b298bc LP |
712 | const char *name; |
713 | ||
714 | name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol)); | |
715 | if (!name) | |
716 | continue; | |
74b2466e | 717 | |
51323288 | 718 | match = dns_scope_good_domain(s, q->ifindex, q->flags, name); |
a97a3b25 LP |
719 | if (match < 0) { |
720 | log_debug("Couldn't check if '%s' matches against scope, ignoring.", name); | |
74b2466e | 721 | continue; |
a97a3b25 | 722 | } |
74b2466e | 723 | |
a97a3b25 LP |
724 | if (match > found) { /* Does this match better? If so, remember how well it matched, and the first one |
725 | * that matches this well */ | |
726 | found = match; | |
74b2466e | 727 | first = s; |
74b2466e LP |
728 | } |
729 | } | |
730 | ||
2a1037af LP |
731 | if (found == DNS_SCOPE_NO) { |
732 | DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; | |
733 | ||
7cc6ed7b LP |
734 | r = dns_query_synthesize_reply(q, &state); |
735 | if (r < 0) | |
736 | return r; | |
737 | ||
d634711b LP |
738 | dns_query_complete(q, state); |
739 | return 1; | |
2a1037af | 740 | } |
74b2466e | 741 | |
801ad6a6 | 742 | r = dns_query_add_candidate(q, first); |
74b2466e | 743 | if (r < 0) |
ec2c5e43 | 744 | goto fail; |
74b2466e | 745 | |
74b2466e LP |
746 | LIST_FOREACH(scopes, s, first->scopes_next) { |
747 | DnsScopeMatch match; | |
23b298bc LP |
748 | const char *name; |
749 | ||
750 | name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol)); | |
751 | if (!name) | |
752 | continue; | |
74b2466e | 753 | |
51323288 | 754 | match = dns_scope_good_domain(s, q->ifindex, q->flags, name); |
a97a3b25 | 755 | if (match < 0) { |
4605de11 | 756 | log_debug("Couldn't check if '%s' matches against scope, ignoring.", name); |
a97a3b25 LP |
757 | continue; |
758 | } | |
74b2466e | 759 | |
a97a3b25 | 760 | if (match < found) |
74b2466e LP |
761 | continue; |
762 | ||
801ad6a6 | 763 | r = dns_query_add_candidate(q, s); |
74b2466e | 764 | if (r < 0) |
ec2c5e43 | 765 | goto fail; |
74b2466e LP |
766 | } |
767 | ||
ab88b6d0 | 768 | dns_query_reset_answer(q); |
74b2466e | 769 | |
39cf0351 | 770 | r = sd_event_add_time_relative( |
9a015429 LP |
771 | q->manager->event, |
772 | &q->timeout_event_source, | |
773 | clock_boottime_or_monotonic(), | |
39cf0351 | 774 | SD_RESOLVED_QUERY_TIMEOUT_USEC, |
4cbfd62b | 775 | 0, on_query_timeout, q); |
74b2466e LP |
776 | if (r < 0) |
777 | goto fail; | |
778 | ||
aa4a9deb LP |
779 | (void) sd_event_source_set_description(q->timeout_event_source, "query-timeout"); |
780 | ||
ec2c5e43 | 781 | q->state = DNS_TRANSACTION_PENDING; |
faa133f3 | 782 | q->block_ready++; |
74b2466e | 783 | |
801ad6a6 LP |
784 | /* Start the transactions */ |
785 | LIST_FOREACH(candidates_by_query, c, q->candidates) { | |
786 | r = dns_query_candidate_go(c); | |
787 | if (r < 0) { | |
788 | q->block_ready--; | |
ec2c5e43 | 789 | goto fail; |
801ad6a6 | 790 | } |
74b2466e LP |
791 | } |
792 | ||
faa133f3 LP |
793 | q->block_ready--; |
794 | dns_query_ready(q); | |
322345fd | 795 | |
8ba9fd9c | 796 | return 1; |
74b2466e LP |
797 | |
798 | fail: | |
8ba9fd9c | 799 | dns_query_stop(q); |
74b2466e LP |
800 | return r; |
801 | } | |
802 | ||
801ad6a6 | 803 | static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { |
ec2c5e43 | 804 | DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; |
43fc4baa | 805 | bool has_authenticated = false, has_non_authenticated = false, has_confidential = false, has_non_confidential = false; |
019036a4 | 806 | DnssecResult dnssec_result_authenticated = _DNSSEC_RESULT_INVALID, dnssec_result_non_authenticated = _DNSSEC_RESULT_INVALID; |
801ad6a6 | 807 | DnsTransaction *t; |
547973de | 808 | int r; |
74b2466e LP |
809 | |
810 | assert(q); | |
811 | ||
801ad6a6 | 812 | if (!c) { |
7cc6ed7b LP |
813 | r = dns_query_synthesize_reply(q, &state); |
814 | if (r < 0) | |
815 | goto fail; | |
816 | ||
801ad6a6 | 817 | dns_query_complete(q, state); |
74b2466e | 818 | return; |
801ad6a6 | 819 | } |
74b2466e | 820 | |
a7bf2ada LP |
821 | if (c->error_code != 0) { |
822 | /* If the candidate had an error condition of its own, start with that. */ | |
823 | state = DNS_TRANSACTION_ERRNO; | |
824 | q->answer = dns_answer_unref(q->answer); | |
825 | q->answer_rcode = 0; | |
826 | q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; | |
6f055e43 | 827 | q->answer_query_flags = 0; |
a7bf2ada | 828 | q->answer_errno = c->error_code; |
775ae354 | 829 | q->answer_full_packet = dns_packet_unref(q->answer_full_packet); |
a7bf2ada LP |
830 | } |
831 | ||
90e74a66 | 832 | SET_FOREACH(t, c->transactions) { |
74b2466e | 833 | |
801ad6a6 | 834 | switch (t->state) { |
934e9b10 | 835 | |
801ad6a6 | 836 | case DNS_TRANSACTION_SUCCESS: { |
775ae354 LP |
837 | /* We found a successful reply, merge it into the answer */ |
838 | ||
839 | if (state == DNS_TRANSACTION_SUCCESS) { | |
840 | r = dns_answer_extend(&q->answer, t->answer); | |
841 | if (r < 0) | |
842 | goto fail; | |
5c1790d1 LP |
843 | |
844 | q->answer_query_flags |= dns_transaction_source_to_query_flags(t->answer_source); | |
775ae354 LP |
845 | } else { |
846 | /* Override non-successful previous answers */ | |
847 | dns_answer_unref(q->answer); | |
848 | q->answer = dns_answer_ref(t->answer); | |
5c1790d1 LP |
849 | |
850 | q->answer_query_flags = dns_transaction_source_to_query_flags(t->answer_source); | |
775ae354 | 851 | } |
019036a4 | 852 | |
ae6a4bbf | 853 | q->answer_rcode = t->answer_rcode; |
7cc6ed7b | 854 | q->answer_errno = 0; |
801ad6a6 | 855 | |
775ae354 LP |
856 | dns_packet_unref(q->answer_full_packet); |
857 | q->answer_full_packet = dns_packet_ref(t->received); | |
858 | ||
6f055e43 | 859 | if (FLAGS_SET(t->answer_query_flags, SD_RESOLVED_AUTHENTICATED)) { |
931851e8 | 860 | has_authenticated = true; |
019036a4 LP |
861 | dnssec_result_authenticated = t->answer_dnssec_result; |
862 | } else { | |
931851e8 | 863 | has_non_authenticated = true; |
019036a4 LP |
864 | dnssec_result_non_authenticated = t->answer_dnssec_result; |
865 | } | |
931851e8 | 866 | |
43fc4baa LP |
867 | if (FLAGS_SET(t->answer_query_flags, SD_RESOLVED_CONFIDENTIAL)) |
868 | has_confidential = true; | |
869 | else | |
870 | has_non_confidential = true; | |
871 | ||
801ad6a6 LP |
872 | state = DNS_TRANSACTION_SUCCESS; |
873 | break; | |
874 | } | |
875 | ||
801ad6a6 | 876 | case DNS_TRANSACTION_NULL: |
547973de LP |
877 | case DNS_TRANSACTION_PENDING: |
878 | case DNS_TRANSACTION_VALIDATING: | |
801ad6a6 LP |
879 | case DNS_TRANSACTION_ABORTED: |
880 | /* Ignore transactions that didn't complete */ | |
881 | continue; | |
882 | ||
883 | default: | |
cbb1aabb | 884 | /* Any kind of failure? Store the data away, if there's nothing stored yet. */ |
019036a4 LP |
885 | if (state == DNS_TRANSACTION_SUCCESS) |
886 | continue; | |
934e9b10 | 887 | |
cbb1aabb | 888 | /* If there's already an authenticated negative reply stored, then prefer that over any unauthenticated one */ |
6f055e43 LP |
889 | if (FLAGS_SET(q->answer_query_flags, SD_RESOLVED_AUTHENTICATED) && |
890 | !FLAGS_SET(t->answer_query_flags, SD_RESOLVED_AUTHENTICATED)) | |
cbb1aabb LP |
891 | continue; |
892 | ||
775ae354 LP |
893 | dns_answer_unref(q->answer); |
894 | q->answer = dns_answer_ref(t->answer); | |
019036a4 LP |
895 | q->answer_rcode = t->answer_rcode; |
896 | q->answer_dnssec_result = t->answer_dnssec_result; | |
5c1790d1 | 897 | q->answer_query_flags = t->answer_query_flags | dns_transaction_source_to_query_flags(t->answer_source); |
7cc6ed7b | 898 | q->answer_errno = t->answer_errno; |
775ae354 LP |
899 | dns_packet_unref(q->answer_full_packet); |
900 | q->answer_full_packet = dns_packet_ref(t->received); | |
934e9b10 | 901 | |
019036a4 | 902 | state = t->state; |
801ad6a6 | 903 | break; |
74b2466e | 904 | } |
801ad6a6 | 905 | } |
74b2466e | 906 | |
019036a4 | 907 | if (state == DNS_TRANSACTION_SUCCESS) { |
6f055e43 | 908 | SET_FLAG(q->answer_query_flags, SD_RESOLVED_AUTHENTICATED, has_authenticated && !has_non_authenticated); |
43fc4baa | 909 | SET_FLAG(q->answer_query_flags, SD_RESOLVED_CONFIDENTIAL, has_confidential && !has_non_confidential); |
6f055e43 | 910 | q->answer_dnssec_result = FLAGS_SET(q->answer_query_flags, SD_RESOLVED_AUTHENTICATED) ? dnssec_result_authenticated : dnssec_result_non_authenticated; |
019036a4 LP |
911 | } |
912 | ||
801ad6a6 LP |
913 | q->answer_protocol = c->scope->protocol; |
914 | q->answer_family = c->scope->family; | |
934e9b10 | 915 | |
801ad6a6 LP |
916 | dns_search_domain_unref(q->answer_search_domain); |
917 | q->answer_search_domain = dns_search_domain_ref(c->search_domain); | |
7e8e0422 | 918 | |
7cc6ed7b LP |
919 | r = dns_query_synthesize_reply(q, &state); |
920 | if (r < 0) | |
921 | goto fail; | |
922 | ||
801ad6a6 | 923 | dns_query_complete(q, state); |
7cc6ed7b LP |
924 | return; |
925 | ||
926 | fail: | |
927 | q->answer_errno = -r; | |
928 | dns_query_complete(q, DNS_TRANSACTION_ERRNO); | |
801ad6a6 | 929 | } |
934e9b10 | 930 | |
801ad6a6 | 931 | void dns_query_ready(DnsQuery *q) { |
74b2466e | 932 | |
801ad6a6 LP |
933 | DnsQueryCandidate *bad = NULL, *c; |
934 | bool pending = false; | |
74b2466e | 935 | |
801ad6a6 | 936 | assert(q); |
547973de | 937 | assert(DNS_TRANSACTION_IS_LIVE(q->state)); |
e4501ed4 | 938 | |
801ad6a6 LP |
939 | /* Note that this call might invalidate the query. Callers |
940 | * should hence not attempt to access the query or transaction | |
941 | * after calling this function, unless the block_ready | |
942 | * counter was explicitly bumped before doing so. */ | |
943 | ||
944 | if (q->block_ready > 0) | |
945 | return; | |
946 | ||
947 | LIST_FOREACH(candidates_by_query, c, q->candidates) { | |
948 | DnsTransactionState state; | |
949 | ||
950 | state = dns_query_candidate_state(c); | |
951 | switch (state) { | |
952 | ||
953 | case DNS_TRANSACTION_SUCCESS: | |
547973de | 954 | /* One of the candidates is successful, |
801ad6a6 LP |
955 | * let's use it, and copy its data out */ |
956 | dns_query_accept(q, c); | |
e4501ed4 LP |
957 | return; |
958 | ||
801ad6a6 | 959 | case DNS_TRANSACTION_NULL: |
547973de LP |
960 | case DNS_TRANSACTION_PENDING: |
961 | case DNS_TRANSACTION_VALIDATING: | |
962 | /* One of the candidates is still going on, | |
963 | * let's maybe wait for it */ | |
801ad6a6 LP |
964 | pending = true; |
965 | break; | |
e4501ed4 | 966 | |
801ad6a6 LP |
967 | default: |
968 | /* Any kind of failure */ | |
969 | bad = c; | |
970 | break; | |
971 | } | |
faa133f3 | 972 | } |
74b2466e | 973 | |
801ad6a6 LP |
974 | if (pending) |
975 | return; | |
2a1037af | 976 | |
801ad6a6 | 977 | dns_query_accept(q, bad); |
74b2466e | 978 | } |
8ba9fd9c | 979 | |
45ec7efb | 980 | static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) { |
23b298bc LP |
981 | _cleanup_(dns_question_unrefp) DnsQuestion *nq_idna = NULL, *nq_utf8 = NULL; |
982 | int r, k; | |
8ba9fd9c LP |
983 | |
984 | assert(q); | |
985 | ||
313cefa1 | 986 | q->n_cname_redirects++; |
e0ae456a | 987 | if (q->n_cname_redirects > CNAME_REDIRECT_MAX) |
8ba9fd9c LP |
988 | return -ELOOP; |
989 | ||
23b298bc | 990 | r = dns_question_cname_redirect(q->question_idna, cname, &nq_idna); |
faa133f3 LP |
991 | if (r < 0) |
992 | return r; | |
4cba52cc | 993 | if (r > 0) |
8ec76e6a | 994 | log_debug("Following CNAME/DNAME %s → %s.", dns_question_first_name(q->question_idna), dns_question_first_name(nq_idna)); |
23b298bc LP |
995 | |
996 | k = dns_question_is_equal(q->question_idna, q->question_utf8); | |
997 | if (k < 0) | |
4cba52cc | 998 | return k; |
23b298bc LP |
999 | if (k > 0) { |
1000 | /* Same question? Shortcut new question generation */ | |
1001 | nq_utf8 = dns_question_ref(nq_idna); | |
1002 | k = r; | |
1003 | } else { | |
1004 | k = dns_question_cname_redirect(q->question_utf8, cname, &nq_utf8); | |
1005 | if (k < 0) | |
1006 | return k; | |
4cba52cc | 1007 | if (k > 0) |
8ec76e6a | 1008 | log_debug("Following UTF8 CNAME/DNAME %s → %s.", dns_question_first_name(q->question_utf8), dns_question_first_name(nq_utf8)); |
23b298bc | 1009 | } |
8ba9fd9c | 1010 | |
23b298bc LP |
1011 | if (r == 0 && k == 0) /* No actual cname happened? */ |
1012 | return -ELOOP; | |
1013 | ||
d46b79bb | 1014 | if (q->answer_protocol == DNS_PROTOCOL_DNS) |
8e5de09f LP |
1015 | /* Don't permit CNAME redirects from unicast DNS to LLMNR or MulticastDNS, so that global resources |
1016 | * cannot invade the local namespace. The opposite way we permit: local names may redirect to global | |
1017 | * ones. */ | |
8e5de09f | 1018 | q->flags &= ~(SD_RESOLVED_LLMNR|SD_RESOLVED_MDNS); /* mask away the local protocols */ |
8e5de09f LP |
1019 | |
1020 | /* Turn off searching for the new name */ | |
1021 | q->flags |= SD_RESOLVED_NO_SEARCH; | |
1022 | ||
23b298bc | 1023 | dns_question_unref(q->question_idna); |
1cc6c93a | 1024 | q->question_idna = TAKE_PTR(nq_idna); |
bc7669cf | 1025 | |
23b298bc | 1026 | dns_question_unref(q->question_utf8); |
1cc6c93a | 1027 | q->question_utf8 = TAKE_PTR(nq_utf8); |
8ba9fd9c | 1028 | |
0e0fd08f | 1029 | dns_query_unref_candidates(q); |
b1eea703 LP |
1030 | |
1031 | /* Note that we do *not* reset the answer here, because the answer we previously got might already | |
1032 | * include everything we need, let's check that first */ | |
322345fd | 1033 | |
8e5de09f | 1034 | q->state = DNS_TRANSACTION_NULL; |
59a89990 | 1035 | |
8ba9fd9c LP |
1036 | return 0; |
1037 | } | |
82bd6ddd | 1038 | |
1db8e6d1 | 1039 | int dns_query_process_cname_one(DnsQuery *q) { |
45ec7efb | 1040 | _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL; |
23b298bc | 1041 | DnsQuestion *question; |
45ec7efb | 1042 | DnsResourceRecord *rr; |
1a71fe4e LP |
1043 | bool full_match = true; |
1044 | DnsResourceKey *k; | |
45ec7efb LP |
1045 | int r; |
1046 | ||
1047 | assert(q); | |
1048 | ||
1db8e6d1 LP |
1049 | /* Processes a CNAME redirect if there's one. Returns one of three values: |
1050 | * | |
1051 | * CNAME_QUERY_MATCH → direct RR match, caller should just use the RRs in this answer (and not | |
1052 | * bother with any CNAME/DNAME stuff) | |
1053 | * | |
1054 | * CNAME_QUERY_NOMATCH → no match at all, neither direct nor CNAME/DNAME, caller might decide to | |
1055 | * restart query or take things as NODATA reply. | |
1056 | * | |
1057 | * CNAME_QUERY_CNAME → no direct RR match, but a CNAME/DNAME match that we now followed for one step. | |
1058 | * | |
1059 | * The function might also return a failure, in particular -ELOOP if we encountered too many | |
1060 | * CNAMEs/DNAMEs in a chain or if following CNAMEs/DNAMEs was turned off. | |
1061 | * | |
1062 | * Note that this function doesn't actually restart the query. The caller can decide to do that in | |
1063 | * case of CNAME_QUERY_CNAME, though. */ | |
1064 | ||
7588460a TG |
1065 | if (!IN_SET(q->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NULL)) |
1066 | return DNS_QUERY_NOMATCH; | |
45ec7efb | 1067 | |
23b298bc | 1068 | question = dns_query_question_for_protocol(q, q->answer_protocol); |
45ec7efb | 1069 | |
1a71fe4e LP |
1070 | /* Small reminder: our question will consist of one or more RR keys that match in name, but not in |
1071 | * record type. Specifically, when we do an address lookup the question will typically consist of one | |
1072 | * A and one AAAA key lookup for the same domain name. When we get a response from a server we need | |
1073 | * to check if the answer answers all our questions to use it. Note that a response of CNAME/DNAME | |
1074 | * can answer both an A and the AAAA question for us, but an A/AAAA response only the relevant | |
1075 | * type. | |
1076 | * | |
1077 | * Hence we first check of the answers we collected are sufficient to answer all our questions | |
1078 | * directly. If one question wasn't answered we go on, waiting for more replies. However, if there's | |
1079 | * a CNAME/DNAME response we use it, and redirect to it, regardless if it was a response to the A or | |
1080 | * the AAAA query.*/ | |
1081 | ||
1082 | DNS_QUESTION_FOREACH(k, question) { | |
1083 | bool match = false; | |
1084 | ||
1085 | DNS_ANSWER_FOREACH(rr, q->answer) { | |
1086 | r = dns_resource_key_match_rr(k, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); | |
1087 | if (r < 0) | |
1088 | return r; | |
1089 | if (r > 0) { | |
1090 | match = true; /* Yay, we found an RR that matches the key we are looking for */ | |
1091 | break; | |
1092 | } | |
1093 | } | |
1094 | ||
1095 | if (!match) { | |
1096 | /* Hmm. :-( there's no response for this key. This doesn't match. */ | |
1097 | full_match = false; | |
1098 | break; | |
1099 | } | |
1100 | } | |
45ec7efb | 1101 | |
1a71fe4e LP |
1102 | if (full_match) |
1103 | return DNS_QUERY_MATCH; /* The answer can answer our question in full, no need to follow CNAMEs/DNAMEs */ | |
1104 | ||
1105 | /* Let's see if there is a CNAME/DNAME to match. This case is simpler: we accept the CNAME/DNAME that | |
1106 | * matches any of our questions. */ | |
1107 | DNS_ANSWER_FOREACH(rr, q->answer) { | |
542e0c84 | 1108 | r = dns_question_matches_cname_or_dname(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); |
45ec7efb LP |
1109 | if (r < 0) |
1110 | return r; | |
1111 | if (r > 0 && !cname) | |
1112 | cname = dns_resource_record_ref(rr); | |
1113 | } | |
1114 | ||
1115 | if (!cname) | |
1a71fe4e | 1116 | return DNS_QUERY_NOMATCH; /* No match and no CNAME/DNAME to follow */ |
45ec7efb LP |
1117 | |
1118 | if (q->flags & SD_RESOLVED_NO_CNAME) | |
1119 | return -ELOOP; | |
1120 | ||
6f055e43 | 1121 | if (!FLAGS_SET(q->answer_query_flags, SD_RESOLVED_AUTHENTICATED)) |
28830a64 | 1122 | q->previous_redirect_unauthenticated = true; |
43fc4baa LP |
1123 | if (!FLAGS_SET(q->answer_query_flags, SD_RESOLVED_CONFIDENTIAL)) |
1124 | q->previous_redirect_non_confidential = true; | |
9ddf099f LP |
1125 | if (!FLAGS_SET(q->answer_query_flags, SD_RESOLVED_SYNTHETIC)) |
1126 | q->previous_redirect_non_synthetic = true; | |
28830a64 | 1127 | |
45ec7efb LP |
1128 | /* OK, let's actually follow the CNAME */ |
1129 | r = dns_query_cname_redirect(q, cname); | |
1130 | if (r < 0) | |
1131 | return r; | |
1132 | ||
1db8e6d1 LP |
1133 | return DNS_QUERY_CNAME; /* Tell caller that we did a single CNAME/DNAME redirection step */ |
1134 | } | |
45ec7efb | 1135 | |
1db8e6d1 LP |
1136 | int dns_query_process_cname_many(DnsQuery *q) { |
1137 | int r; | |
45ec7efb | 1138 | |
1db8e6d1 LP |
1139 | assert(q); |
1140 | ||
1141 | /* Follows CNAMEs through the current packet: as long as the current packet can fulfill our | |
1142 | * redirected CNAME queries we keep going, and restart the query once the current packet isn't good | |
1143 | * enough anymore. It's a wrapper around dns_query_process_cname_one() and returns the same values, | |
1144 | * but with extended semantics. Specifically: | |
1145 | * | |
1146 | * DNS_QUERY_MATCH → as above | |
1147 | * | |
1148 | * DNS_QUERY_CNAME → we ran into a CNAME/DNAME redirect that we could not answer from the current | |
1149 | * message, and thus restarted the query to resolve it. | |
1150 | * | |
1151 | * DNS_QUERY_NOMATCH → we reached the end of CNAME/DNAME chain, and there are no direct matches nor a | |
1152 | * CNAME/DNAME match. i.e. this is a NODATA case. | |
1153 | * | |
1154 | * Note that this function will restart the query for the caller if needed, and that's the case | |
1155 | * DNS_QUERY_CNAME is returned. | |
1156 | */ | |
1157 | ||
1158 | r = dns_query_process_cname_one(q); | |
1159 | if (r != DNS_QUERY_CNAME) | |
1160 | return r; /* The first redirect is special: if it doesn't answer the question that's no | |
1161 | * reason to restart the query, we just accept this as a NODATA answer. */ | |
1162 | ||
1163 | for (;;) { | |
1164 | r = dns_query_process_cname_one(q); | |
1165 | if (r < 0 || r == DNS_QUERY_MATCH) | |
1166 | return r; | |
1167 | if (r == DNS_QUERY_NOMATCH) { | |
1168 | /* OK, so we followed one or more CNAME/DNAME RR but the existing packet can't answer | |
1169 | * this. Let's restart the query hence, with the new question. Why the different | |
1170 | * handling than the first chain element? Because if the server answers a direct | |
1171 | * question with an empty answer then this is a NODATA response. But if it responds | |
1172 | * with a CNAME chain that ultimately is incomplete (i.e. a non-empty but truncated | |
1173 | * CNAME chain) then we better follow up ourselves and ask for the rest of the | |
1174 | * chain. This is particular relevant since our cache will store CNAME/DNAME | |
1175 | * redirects that we learnt about for lookups of certain DNS types, but later on we | |
1176 | * can reuse this data even for other DNS types, but in that case need to follow up | |
1177 | * with the final lookup of the chain ourselves with the RR type we ourselves are | |
1178 | * interested in. */ | |
1179 | r = dns_query_go(q); | |
1180 | if (r < 0) | |
1181 | return r; | |
1182 | ||
1183 | return DNS_QUERY_CNAME; | |
1184 | } | |
1185 | ||
1186 | /* So we found a CNAME that the existing packet already answers, again via a CNAME, let's | |
1187 | * continue going then. */ | |
1188 | assert(r == DNS_QUERY_CNAME); | |
1189 | } | |
45ec7efb LP |
1190 | } |
1191 | ||
23b298bc LP |
1192 | DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol) { |
1193 | assert(q); | |
1194 | ||
775ae354 LP |
1195 | if (q->question_bypass) |
1196 | return q->question_bypass->question; | |
1197 | ||
23b298bc LP |
1198 | switch (protocol) { |
1199 | ||
1200 | case DNS_PROTOCOL_DNS: | |
1201 | return q->question_idna; | |
1202 | ||
1203 | case DNS_PROTOCOL_MDNS: | |
1204 | case DNS_PROTOCOL_LLMNR: | |
1205 | return q->question_utf8; | |
1206 | ||
1207 | default: | |
1208 | return NULL; | |
1209 | } | |
1210 | } | |
1211 | ||
1212 | const char *dns_query_string(DnsQuery *q) { | |
1213 | const char *name; | |
1214 | int r; | |
1215 | ||
1216 | /* Returns a somewhat useful human-readable lookup key string for this query */ | |
1217 | ||
775ae354 LP |
1218 | if (q->question_bypass) |
1219 | return dns_question_first_name(q->question_bypass->question); | |
1220 | ||
23b298bc LP |
1221 | if (q->request_address_string) |
1222 | return q->request_address_string; | |
1223 | ||
1224 | if (q->request_address_valid) { | |
1225 | r = in_addr_to_string(q->request_family, &q->request_address, &q->request_address_string); | |
1226 | if (r >= 0) | |
1227 | return q->request_address_string; | |
1228 | } | |
1229 | ||
1230 | name = dns_question_first_name(q->question_utf8); | |
1231 | if (name) | |
1232 | return name; | |
1233 | ||
1234 | return dns_question_first_name(q->question_idna); | |
1235 | } | |
28830a64 LP |
1236 | |
1237 | bool dns_query_fully_authenticated(DnsQuery *q) { | |
1238 | assert(q); | |
1239 | ||
6f055e43 | 1240 | return FLAGS_SET(q->answer_query_flags, SD_RESOLVED_AUTHENTICATED) && !q->previous_redirect_unauthenticated; |
28830a64 | 1241 | } |
43fc4baa LP |
1242 | |
1243 | bool dns_query_fully_confidential(DnsQuery *q) { | |
1244 | assert(q); | |
1245 | ||
1246 | return FLAGS_SET(q->answer_query_flags, SD_RESOLVED_CONFIDENTIAL) && !q->previous_redirect_non_confidential; | |
1247 | } | |
4ad017cd | 1248 | |
9ddf099f | 1249 | bool dns_query_fully_authoritative(DnsQuery *q) { |
4ad017cd SB |
1250 | assert(q); |
1251 | ||
9ddf099f LP |
1252 | /* We are authoritative for everything synthetic (except if a previous CNAME/DNAME) wasn't |
1253 | * synthetic. (Note: SD_RESOLVED_SYNTHETIC is reset on each CNAME/DNAME, hence the explicit check for | |
1254 | * previous synthetic DNAME/CNAME redirections.)*/ | |
1255 | if ((q->answer_query_flags & SD_RESOLVED_SYNTHETIC) && !q->previous_redirect_non_synthetic) | |
1256 | return true; | |
1257 | ||
1258 | /* We are also authoritative for everything coming only from the trust anchor and the local | |
1259 | * zones. (Note: the SD_RESOLVED_FROM_xyz flags we merge on each redirect, hence no need to | |
1260 | * explicitly check previous redirects here.)*/ | |
1261 | return (q->answer_query_flags & SD_RESOLVED_FROM_MASK & ~(SD_RESOLVED_FROM_TRUST_ANCHOR | SD_RESOLVED_FROM_ZONE)) == 0; | |
4ad017cd | 1262 | } |