]>
Commit | Line | Data |
---|---|---|
5eef597e MP |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2014 Lennart Poettering | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU Lesser General Public License as published by | |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | Lesser General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU Lesser General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
22 | #include "af-list.h" | |
23 | ||
7035cd9e | 24 | #include "resolved-llmnr.h" |
5eef597e | 25 | #include "resolved-dns-transaction.h" |
e3bff60a | 26 | #include "random-util.h" |
13d276d0 | 27 | #include "dns-domain.h" |
5eef597e MP |
28 | |
29 | DnsTransaction* dns_transaction_free(DnsTransaction *t) { | |
30 | DnsQuery *q; | |
31 | DnsZoneItem *i; | |
32 | ||
33 | if (!t) | |
34 | return NULL; | |
35 | ||
36 | sd_event_source_unref(t->timeout_event_source); | |
37 | ||
5eef597e MP |
38 | dns_packet_unref(t->sent); |
39 | dns_packet_unref(t->received); | |
40 | dns_answer_unref(t->cached); | |
41 | ||
13d276d0 MP |
42 | sd_event_source_unref(t->dns_udp_event_source); |
43 | safe_close(t->dns_udp_fd); | |
7035cd9e MP |
44 | |
45 | dns_server_unref(t->server); | |
5eef597e MP |
46 | dns_stream_free(t->stream); |
47 | ||
48 | if (t->scope) { | |
13d276d0 | 49 | hashmap_remove(t->scope->transactions, t->key); |
5eef597e MP |
50 | |
51 | if (t->id != 0) | |
52 | hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id)); | |
53 | } | |
54 | ||
13d276d0 MP |
55 | dns_resource_key_unref(t->key); |
56 | ||
5eef597e MP |
57 | while ((q = set_steal_first(t->queries))) |
58 | set_remove(q->transactions, t); | |
59 | set_free(t->queries); | |
60 | ||
61 | while ((i = set_steal_first(t->zone_items))) | |
62 | i->probe_transaction = NULL; | |
63 | set_free(t->zone_items); | |
64 | ||
65 | free(t); | |
66 | return NULL; | |
67 | } | |
68 | ||
69 | DEFINE_TRIVIAL_CLEANUP_FUNC(DnsTransaction*, dns_transaction_free); | |
70 | ||
71 | void dns_transaction_gc(DnsTransaction *t) { | |
72 | assert(t); | |
73 | ||
74 | if (t->block_gc > 0) | |
75 | return; | |
76 | ||
77 | if (set_isempty(t->queries) && set_isempty(t->zone_items)) | |
78 | dns_transaction_free(t); | |
79 | } | |
80 | ||
13d276d0 | 81 | int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) { |
5eef597e MP |
82 | _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL; |
83 | int r; | |
84 | ||
85 | assert(ret); | |
86 | assert(s); | |
13d276d0 | 87 | assert(key); |
5eef597e MP |
88 | |
89 | r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL); | |
90 | if (r < 0) | |
91 | return r; | |
92 | ||
13d276d0 MP |
93 | r = hashmap_ensure_allocated(&s->transactions, &dns_resource_key_hash_ops); |
94 | if (r < 0) | |
95 | return r; | |
96 | ||
5eef597e MP |
97 | t = new0(DnsTransaction, 1); |
98 | if (!t) | |
99 | return -ENOMEM; | |
100 | ||
13d276d0 MP |
101 | t->dns_udp_fd = -1; |
102 | t->key = dns_resource_key_ref(key); | |
5eef597e | 103 | |
13d276d0 | 104 | /* Find a fresh, unused transaction id */ |
5eef597e MP |
105 | do |
106 | random_bytes(&t->id, sizeof(t->id)); | |
107 | while (t->id == 0 || | |
108 | hashmap_get(s->manager->dns_transactions, UINT_TO_PTR(t->id))); | |
109 | ||
110 | r = hashmap_put(s->manager->dns_transactions, UINT_TO_PTR(t->id), t); | |
111 | if (r < 0) { | |
112 | t->id = 0; | |
113 | return r; | |
114 | } | |
115 | ||
13d276d0 MP |
116 | r = hashmap_put(s->transactions, t->key, t); |
117 | if (r < 0) { | |
118 | hashmap_remove(s->manager->dns_transactions, UINT_TO_PTR(t->id)); | |
119 | return r; | |
120 | } | |
121 | ||
5eef597e MP |
122 | t->scope = s; |
123 | ||
124 | if (ret) | |
125 | *ret = t; | |
126 | ||
127 | t = NULL; | |
128 | ||
129 | return 0; | |
130 | } | |
131 | ||
132 | static void dns_transaction_stop(DnsTransaction *t) { | |
133 | assert(t); | |
134 | ||
135 | t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); | |
136 | t->stream = dns_stream_free(t->stream); | |
137 | } | |
138 | ||
139 | static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { | |
140 | _cleanup_free_ char *pretty = NULL; | |
141 | DnsZoneItem *z; | |
142 | ||
143 | assert(t); | |
144 | assert(p); | |
145 | ||
146 | if (manager_our_packet(t->scope->manager, p) != 0) | |
147 | return; | |
148 | ||
149 | in_addr_to_string(p->family, &p->sender, &pretty); | |
150 | ||
151 | log_debug("Transaction on scope %s on %s/%s got tentative packet from %s", | |
152 | dns_protocol_to_string(t->scope->protocol), | |
153 | t->scope->link ? t->scope->link->name : "*", | |
154 | t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family), | |
155 | pretty); | |
156 | ||
157 | /* RFC 4795, Section 4.1 says that the peer with the | |
158 | * lexicographically smaller IP address loses */ | |
159 | if (memcmp(&p->sender, &p->destination, FAMILY_ADDRESS_SIZE(p->family)) >= 0) { | |
160 | log_debug("Peer has lexicographically larger IP address and thus lost in the conflict."); | |
161 | return; | |
162 | } | |
163 | ||
164 | log_debug("We have the lexicographically larger IP address and thus lost in the conflict."); | |
165 | ||
166 | t->block_gc++; | |
167 | while ((z = set_first(t->zone_items))) { | |
168 | /* First, make sure the zone item drops the reference | |
169 | * to us */ | |
170 | dns_zone_item_probe_stop(z); | |
171 | ||
172 | /* Secondly, report this as conflict, so that we might | |
173 | * look for a different hostname */ | |
174 | dns_zone_item_conflict(z); | |
175 | } | |
176 | t->block_gc--; | |
177 | ||
178 | dns_transaction_gc(t); | |
179 | } | |
180 | ||
181 | void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { | |
182 | DnsQuery *q; | |
183 | DnsZoneItem *z; | |
184 | Iterator i; | |
185 | ||
186 | assert(t); | |
187 | assert(!IN_SET(state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)); | |
188 | ||
5eef597e MP |
189 | /* Note that this call might invalidate the query. Callers |
190 | * should hence not attempt to access the query or transaction | |
191 | * after calling this function. */ | |
192 | ||
193 | log_debug("Transaction on scope %s on %s/%s now complete with <%s>", | |
194 | dns_protocol_to_string(t->scope->protocol), | |
195 | t->scope->link ? t->scope->link->name : "*", | |
196 | t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family), | |
197 | dns_transaction_state_to_string(state)); | |
198 | ||
199 | t->state = state; | |
200 | ||
201 | dns_transaction_stop(t); | |
202 | ||
203 | /* Notify all queries that are interested, but make sure the | |
204 | * transaction isn't freed while we are still looking at it */ | |
205 | t->block_gc++; | |
206 | SET_FOREACH(q, t->queries, i) | |
207 | dns_query_ready(q); | |
208 | SET_FOREACH(z, t->zone_items, i) | |
209 | dns_zone_item_ready(z); | |
210 | t->block_gc--; | |
211 | ||
212 | dns_transaction_gc(t); | |
213 | } | |
214 | ||
215 | static int on_stream_complete(DnsStream *s, int error) { | |
216 | _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; | |
217 | DnsTransaction *t; | |
218 | ||
219 | assert(s); | |
220 | assert(s->transaction); | |
221 | ||
222 | /* Copy the data we care about out of the stream before we | |
223 | * destroy it. */ | |
224 | t = s->transaction; | |
225 | p = dns_packet_ref(s->read_packet); | |
226 | ||
227 | t->stream = dns_stream_free(t->stream); | |
228 | ||
229 | if (error != 0) { | |
230 | dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); | |
231 | return 0; | |
232 | } | |
233 | ||
234 | if (dns_packet_validate_reply(p) <= 0) { | |
235 | log_debug("Invalid LLMNR TCP packet."); | |
236 | dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); | |
237 | return 0; | |
238 | } | |
239 | ||
240 | dns_scope_check_conflicts(t->scope, p); | |
241 | ||
242 | t->block_gc++; | |
243 | dns_transaction_process_reply(t, p); | |
244 | t->block_gc--; | |
245 | ||
246 | /* If the response wasn't useful, then complete the transition now */ | |
247 | if (t->state == DNS_TRANSACTION_PENDING) | |
248 | dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); | |
249 | ||
250 | return 0; | |
251 | } | |
252 | ||
253 | static int dns_transaction_open_tcp(DnsTransaction *t) { | |
7035cd9e | 254 | DnsServer *server = NULL; |
5eef597e MP |
255 | _cleanup_close_ int fd = -1; |
256 | int r; | |
257 | ||
258 | assert(t); | |
259 | ||
260 | if (t->stream) | |
261 | return 0; | |
262 | ||
13d276d0 MP |
263 | switch (t->scope->protocol) { |
264 | case DNS_PROTOCOL_DNS: | |
7035cd9e | 265 | fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53, &server); |
13d276d0 | 266 | break; |
5eef597e | 267 | |
13d276d0 MP |
268 | case DNS_PROTOCOL_LLMNR: |
269 | /* When we already received a reply to this (but it was truncated), send to its sender address */ | |
5eef597e | 270 | if (t->received) |
7035cd9e | 271 | fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port, NULL); |
5eef597e MP |
272 | else { |
273 | union in_addr_union address; | |
e3bff60a | 274 | int family = AF_UNSPEC; |
5eef597e MP |
275 | |
276 | /* Otherwise, try to talk to the owner of a | |
277 | * the IP address, in case this is a reverse | |
278 | * PTR lookup */ | |
13d276d0 MP |
279 | |
280 | r = dns_name_address(DNS_RESOURCE_KEY_NAME(t->key), &family, &address); | |
5eef597e MP |
281 | if (r < 0) |
282 | return r; | |
283 | if (r == 0) | |
284 | return -EINVAL; | |
13d276d0 MP |
285 | if (family != t->scope->family) |
286 | return -ESRCH; | |
5eef597e | 287 | |
7035cd9e | 288 | fd = dns_scope_tcp_socket(t->scope, family, &address, LLMNR_PORT, NULL); |
5eef597e | 289 | } |
13d276d0 MP |
290 | |
291 | break; | |
292 | ||
293 | default: | |
5eef597e | 294 | return -EAFNOSUPPORT; |
13d276d0 | 295 | } |
5eef597e MP |
296 | |
297 | if (fd < 0) | |
298 | return fd; | |
299 | ||
300 | r = dns_stream_new(t->scope->manager, &t->stream, t->scope->protocol, fd); | |
301 | if (r < 0) | |
302 | return r; | |
303 | ||
304 | fd = -1; | |
305 | ||
306 | r = dns_stream_write_packet(t->stream, t->sent); | |
307 | if (r < 0) { | |
308 | t->stream = dns_stream_free(t->stream); | |
309 | return r; | |
310 | } | |
311 | ||
7035cd9e MP |
312 | dns_server_unref(t->server); |
313 | t->server = dns_server_ref(server); | |
5eef597e MP |
314 | t->received = dns_packet_unref(t->received); |
315 | t->stream->complete = on_stream_complete; | |
316 | t->stream->transaction = t; | |
317 | ||
318 | /* The interface index is difficult to determine if we are | |
319 | * connecting to the local host, hence fill this in right away | |
320 | * instead of determining it from the socket */ | |
321 | if (t->scope->link) | |
322 | t->stream->ifindex = t->scope->link->ifindex; | |
323 | ||
324 | return 0; | |
325 | } | |
326 | ||
7035cd9e MP |
327 | static void dns_transaction_next_dns_server(DnsTransaction *t) { |
328 | assert(t); | |
329 | ||
330 | t->server = dns_server_unref(t->server); | |
13d276d0 MP |
331 | t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source); |
332 | t->dns_udp_fd = safe_close(t->dns_udp_fd); | |
7035cd9e MP |
333 | |
334 | dns_scope_next_dns_server(t->scope); | |
335 | } | |
336 | ||
5eef597e | 337 | void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { |
13d276d0 | 338 | usec_t ts; |
5eef597e MP |
339 | int r; |
340 | ||
341 | assert(t); | |
342 | assert(p); | |
343 | assert(t->state == DNS_TRANSACTION_PENDING); | |
13d276d0 MP |
344 | assert(t->scope); |
345 | assert(t->scope->manager); | |
5eef597e MP |
346 | |
347 | /* Note that this call might invalidate the query. Callers | |
348 | * should hence not attempt to access the query or transaction | |
349 | * after calling this function. */ | |
350 | ||
13d276d0 MP |
351 | switch (t->scope->protocol) { |
352 | case DNS_PROTOCOL_LLMNR: | |
5eef597e MP |
353 | assert(t->scope->link); |
354 | ||
355 | /* For LLMNR we will not accept any packets from other | |
356 | * interfaces */ | |
357 | ||
358 | if (p->ifindex != t->scope->link->ifindex) | |
359 | return; | |
360 | ||
361 | if (p->family != t->scope->family) | |
362 | return; | |
363 | ||
364 | /* Tentative packets are not full responses but still | |
365 | * useful for identifying uniqueness conflicts during | |
366 | * probing. */ | |
7035cd9e | 367 | if (DNS_PACKET_LLMNR_T(p)) { |
5eef597e MP |
368 | dns_transaction_tentative(t, p); |
369 | return; | |
370 | } | |
13d276d0 MP |
371 | |
372 | break; | |
373 | ||
374 | case DNS_PROTOCOL_DNS: | |
375 | break; | |
376 | ||
377 | default: | |
378 | assert_not_reached("Invalid DNS protocol."); | |
5eef597e MP |
379 | } |
380 | ||
5eef597e MP |
381 | if (t->received != p) { |
382 | dns_packet_unref(t->received); | |
383 | t->received = dns_packet_ref(p); | |
384 | } | |
385 | ||
386 | if (p->ipproto == IPPROTO_TCP) { | |
387 | if (DNS_PACKET_TC(p)) { | |
388 | /* Truncated via TCP? Somebody must be fucking with us */ | |
389 | dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); | |
390 | return; | |
391 | } | |
392 | ||
393 | if (DNS_PACKET_ID(p) != t->id) { | |
394 | /* Not the reply to our query? Somebody must be fucking with us */ | |
395 | dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); | |
396 | return; | |
397 | } | |
398 | } | |
399 | ||
13d276d0 MP |
400 | assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); |
401 | ||
402 | switch (t->scope->protocol) { | |
403 | case DNS_PROTOCOL_DNS: | |
404 | assert(t->server); | |
405 | ||
406 | dns_server_packet_received(t->server, ts - t->start_usec); | |
407 | ||
408 | break; | |
409 | case DNS_PROTOCOL_LLMNR: | |
410 | case DNS_PROTOCOL_MDNS: | |
411 | dns_scope_packet_received(t->scope, ts - t->start_usec); | |
412 | ||
413 | break; | |
414 | default: | |
415 | break; | |
416 | } | |
417 | ||
5eef597e MP |
418 | if (DNS_PACKET_TC(p)) { |
419 | /* Response was truncated, let's try again with good old TCP */ | |
420 | r = dns_transaction_open_tcp(t); | |
421 | if (r == -ESRCH) { | |
422 | /* No servers found? Damn! */ | |
423 | dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); | |
424 | return; | |
425 | } | |
426 | if (r < 0) { | |
427 | /* On LLMNR, if we cannot connect to the host, | |
428 | * we immediately give up */ | |
429 | if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { | |
430 | dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); | |
431 | return; | |
432 | } | |
433 | ||
434 | /* On DNS, couldn't send? Try immediately again, with a new server */ | |
7035cd9e | 435 | dns_transaction_next_dns_server(t); |
5eef597e MP |
436 | |
437 | r = dns_transaction_go(t); | |
438 | if (r < 0) { | |
439 | dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); | |
440 | return; | |
441 | } | |
442 | ||
443 | return; | |
444 | } | |
445 | } | |
446 | ||
447 | /* Parse and update the cache */ | |
448 | r = dns_packet_extract(p); | |
449 | if (r < 0) { | |
450 | dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); | |
451 | return; | |
452 | } | |
453 | ||
7035cd9e | 454 | /* Only consider responses with equivalent query section to the request */ |
13d276d0 | 455 | if (p->question->n_keys != 1 || dns_resource_key_equal(p->question->keys[0], t->key) <= 0) { |
7035cd9e MP |
456 | dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); |
457 | return; | |
458 | } | |
459 | ||
5eef597e | 460 | /* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */ |
6300502b | 461 | dns_cache_put(&t->scope->cache, t->key, DNS_PACKET_RCODE(p), p->answer, DNS_PACKET_ANCOUNT(p), 0, p->family, &p->sender); |
5eef597e MP |
462 | |
463 | if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS) | |
464 | dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); | |
465 | else | |
466 | dns_transaction_complete(t, DNS_TRANSACTION_FAILURE); | |
467 | } | |
468 | ||
7035cd9e MP |
469 | static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { |
470 | _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; | |
471 | DnsTransaction *t = userdata; | |
472 | int r; | |
473 | ||
474 | assert(t); | |
475 | assert(t->scope); | |
476 | ||
477 | r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p); | |
478 | if (r <= 0) | |
479 | return r; | |
480 | ||
481 | if (dns_packet_validate_reply(p) > 0 && | |
13d276d0 | 482 | DNS_PACKET_ID(p) == t->id) |
7035cd9e | 483 | dns_transaction_process_reply(t, p); |
13d276d0 | 484 | else |
7035cd9e MP |
485 | log_debug("Invalid DNS packet."); |
486 | ||
487 | return 0; | |
488 | } | |
489 | ||
490 | static int dns_transaction_emit(DnsTransaction *t) { | |
491 | int r; | |
492 | ||
493 | assert(t); | |
494 | ||
495 | if (t->scope->protocol == DNS_PROTOCOL_DNS && !t->server) { | |
496 | DnsServer *server = NULL; | |
497 | _cleanup_close_ int fd = -1; | |
498 | ||
499 | fd = dns_scope_udp_dns_socket(t->scope, &server); | |
500 | if (fd < 0) | |
501 | return fd; | |
502 | ||
13d276d0 | 503 | r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t); |
7035cd9e MP |
504 | if (r < 0) |
505 | return r; | |
506 | ||
13d276d0 | 507 | t->dns_udp_fd = fd; |
7035cd9e MP |
508 | fd = -1; |
509 | t->server = dns_server_ref(server); | |
510 | } | |
511 | ||
13d276d0 | 512 | r = dns_scope_emit(t->scope, t->dns_udp_fd, t->sent); |
7035cd9e MP |
513 | if (r < 0) |
514 | return r; | |
515 | ||
516 | return 0; | |
517 | } | |
518 | ||
5eef597e MP |
519 | static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { |
520 | DnsTransaction *t = userdata; | |
521 | int r; | |
522 | ||
523 | assert(s); | |
524 | assert(t); | |
525 | ||
526 | /* Timeout reached? Try again, with a new server */ | |
7035cd9e | 527 | dns_transaction_next_dns_server(t); |
5eef597e | 528 | |
13d276d0 MP |
529 | /* ... and possibly increased timeout */ |
530 | if (t->server) | |
531 | dns_server_packet_lost(t->server, usec - t->start_usec); | |
532 | else | |
533 | dns_scope_packet_lost(t->scope, usec - t->start_usec); | |
534 | ||
5eef597e MP |
535 | r = dns_transaction_go(t); |
536 | if (r < 0) | |
537 | dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); | |
538 | ||
539 | return 0; | |
540 | } | |
541 | ||
542 | static int dns_transaction_make_packet(DnsTransaction *t) { | |
543 | _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; | |
5eef597e MP |
544 | int r; |
545 | ||
546 | assert(t); | |
547 | ||
548 | if (t->sent) | |
549 | return 0; | |
550 | ||
551 | r = dns_packet_new_query(&p, t->scope->protocol, 0); | |
552 | if (r < 0) | |
553 | return r; | |
554 | ||
13d276d0 MP |
555 | r = dns_scope_good_key(t->scope, t->key); |
556 | if (r < 0) | |
557 | return r; | |
558 | if (r == 0) | |
5eef597e MP |
559 | return -EDOM; |
560 | ||
13d276d0 MP |
561 | r = dns_packet_append_key(p, t->key, NULL); |
562 | if (r < 0) | |
563 | return r; | |
564 | ||
565 | DNS_PACKET_HEADER(p)->qdcount = htobe16(1); | |
5eef597e MP |
566 | DNS_PACKET_HEADER(p)->id = t->id; |
567 | ||
568 | t->sent = p; | |
569 | p = NULL; | |
570 | ||
571 | return 0; | |
572 | } | |
573 | ||
13d276d0 MP |
574 | static usec_t transaction_get_resend_timeout(DnsTransaction *t) { |
575 | assert(t); | |
576 | assert(t->scope); | |
577 | ||
578 | switch (t->scope->protocol) { | |
579 | case DNS_PROTOCOL_DNS: | |
580 | assert(t->server); | |
581 | ||
582 | return t->server->resend_timeout; | |
583 | case DNS_PROTOCOL_LLMNR: | |
584 | case DNS_PROTOCOL_MDNS: | |
585 | return t->scope->resend_timeout; | |
586 | default: | |
587 | assert_not_reached("Invalid DNS protocol."); | |
588 | } | |
589 | } | |
590 | ||
5eef597e MP |
591 | int dns_transaction_go(DnsTransaction *t) { |
592 | bool had_stream; | |
13d276d0 | 593 | usec_t ts; |
5eef597e MP |
594 | int r; |
595 | ||
596 | assert(t); | |
597 | ||
598 | had_stream = !!t->stream; | |
599 | ||
600 | dns_transaction_stop(t); | |
601 | ||
602 | log_debug("Excercising transaction on scope %s on %s/%s", | |
603 | dns_protocol_to_string(t->scope->protocol), | |
604 | t->scope->link ? t->scope->link->name : "*", | |
605 | t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family)); | |
606 | ||
607 | if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) { | |
608 | dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); | |
609 | return 0; | |
610 | } | |
611 | ||
612 | if (t->scope->protocol == DNS_PROTOCOL_LLMNR && had_stream) { | |
613 | /* If we already tried via a stream, then we don't | |
614 | * retry on LLMNR. See RFC 4795, Section 2.7. */ | |
615 | dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); | |
616 | return 0; | |
617 | } | |
618 | ||
13d276d0 MP |
619 | assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); |
620 | ||
5eef597e | 621 | t->n_attempts++; |
13d276d0 | 622 | t->start_usec = ts; |
5eef597e MP |
623 | t->received = dns_packet_unref(t->received); |
624 | t->cached = dns_answer_unref(t->cached); | |
625 | t->cached_rcode = 0; | |
626 | ||
627 | /* Check the cache, but only if this transaction is not used | |
628 | * for probing or verifying a zone item. */ | |
629 | if (set_isempty(t->zone_items)) { | |
630 | ||
631 | /* Before trying the cache, let's make sure we figured out a | |
632 | * server to use. Should this cause a change of server this | |
633 | * might flush the cache. */ | |
634 | dns_scope_get_dns_server(t->scope); | |
635 | ||
636 | /* Let's then prune all outdated entries */ | |
637 | dns_cache_prune(&t->scope->cache); | |
638 | ||
13d276d0 | 639 | r = dns_cache_lookup(&t->scope->cache, t->key, &t->cached_rcode, &t->cached); |
5eef597e MP |
640 | if (r < 0) |
641 | return r; | |
642 | if (r > 0) { | |
5eef597e MP |
643 | if (t->cached_rcode == DNS_RCODE_SUCCESS) |
644 | dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); | |
645 | else | |
646 | dns_transaction_complete(t, DNS_TRANSACTION_FAILURE); | |
647 | return 0; | |
648 | } | |
649 | } | |
650 | ||
651 | if (t->scope->protocol == DNS_PROTOCOL_LLMNR && !t->initial_jitter) { | |
652 | usec_t jitter; | |
653 | ||
654 | /* RFC 4795 Section 2.7 suggests all queries should be | |
655 | * delayed by a random time from 0 to JITTER_INTERVAL. */ | |
656 | ||
657 | t->initial_jitter = true; | |
658 | ||
659 | random_bytes(&jitter, sizeof(jitter)); | |
660 | jitter %= LLMNR_JITTER_INTERVAL_USEC; | |
661 | ||
662 | r = sd_event_add_time( | |
663 | t->scope->manager->event, | |
664 | &t->timeout_event_source, | |
665 | clock_boottime_or_monotonic(), | |
13d276d0 | 666 | ts + jitter, |
5eef597e MP |
667 | LLMNR_JITTER_INTERVAL_USEC, |
668 | on_transaction_timeout, t); | |
669 | if (r < 0) | |
670 | return r; | |
671 | ||
672 | t->n_attempts = 0; | |
673 | t->state = DNS_TRANSACTION_PENDING; | |
674 | ||
675 | log_debug("Delaying LLMNR transaction for " USEC_FMT "us.", jitter); | |
676 | return 0; | |
677 | } | |
678 | ||
5eef597e MP |
679 | /* Otherwise, we need to ask the network */ |
680 | r = dns_transaction_make_packet(t); | |
681 | if (r == -EDOM) { | |
682 | /* Not the right request to make on this network? | |
683 | * (i.e. an A request made on IPv6 or an AAAA request | |
684 | * made on IPv4, on LLMNR or mDNS.) */ | |
685 | dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); | |
686 | return 0; | |
687 | } | |
688 | if (r < 0) | |
689 | return r; | |
690 | ||
691 | if (t->scope->protocol == DNS_PROTOCOL_LLMNR && | |
13d276d0 MP |
692 | (dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "in-addr.arpa") > 0 || |
693 | dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "ip6.arpa") > 0)) { | |
5eef597e MP |
694 | |
695 | /* RFC 4795, Section 2.4. says reverse lookups shall | |
696 | * always be made via TCP on LLMNR */ | |
697 | r = dns_transaction_open_tcp(t); | |
698 | } else { | |
699 | /* Try via UDP, and if that fails due to large size try via TCP */ | |
7035cd9e | 700 | r = dns_transaction_emit(t); |
5eef597e MP |
701 | if (r == -EMSGSIZE) |
702 | r = dns_transaction_open_tcp(t); | |
703 | } | |
704 | if (r == -ESRCH) { | |
705 | /* No servers to send this to? */ | |
706 | dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); | |
707 | return 0; | |
7035cd9e | 708 | } else if (r < 0) { |
5eef597e MP |
709 | if (t->scope->protocol != DNS_PROTOCOL_DNS) { |
710 | dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); | |
711 | return 0; | |
712 | } | |
713 | ||
714 | /* Couldn't send? Try immediately again, with a new server */ | |
7035cd9e | 715 | dns_transaction_next_dns_server(t); |
5eef597e MP |
716 | |
717 | return dns_transaction_go(t); | |
718 | } | |
719 | ||
720 | r = sd_event_add_time( | |
721 | t->scope->manager->event, | |
722 | &t->timeout_event_source, | |
723 | clock_boottime_or_monotonic(), | |
13d276d0 | 724 | ts + transaction_get_resend_timeout(t), 0, |
5eef597e MP |
725 | on_transaction_timeout, t); |
726 | if (r < 0) | |
727 | return r; | |
728 | ||
729 | t->state = DNS_TRANSACTION_PENDING; | |
730 | return 1; | |
731 | } | |
732 | ||
733 | static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = { | |
734 | [DNS_TRANSACTION_NULL] = "null", | |
735 | [DNS_TRANSACTION_PENDING] = "pending", | |
736 | [DNS_TRANSACTION_FAILURE] = "failure", | |
737 | [DNS_TRANSACTION_SUCCESS] = "success", | |
738 | [DNS_TRANSACTION_NO_SERVERS] = "no-servers", | |
739 | [DNS_TRANSACTION_TIMEOUT] = "timeout", | |
740 | [DNS_TRANSACTION_ATTEMPTS_MAX_REACHED] = "attempts-max-reached", | |
741 | [DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply", | |
742 | [DNS_TRANSACTION_RESOURCES] = "resources", | |
743 | [DNS_TRANSACTION_ABORTED] = "aborted", | |
744 | }; | |
745 | DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState); |