1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
//! Resolver Architecture Documentation //! //! <i>This module does not contain any code. It is intended for more //! detailed documentation of the resolver. Currently, that is limited to //! a short introduction to its architecture.</i> //! //! Let’s start with a bunch of terms: the *resolver* is the collection of //! all things necessary for answer DNS *queries*. It relies on a number //! of *services* that represent a single upstream DNS server. Services //! answer *requests*, that is, they take a DNS request message and try //! transforming it into a DNS response message or a failure. //! //! A query is processed by sending requests to various services according //! to the resolver configuration until one request succeeds or there is a //! fatal error (currently, that only happens when the question is broken) //! or we run out of services to ask. Thus, a query issues a sequence of //! requests to a number of services. //! //! Services are futures spawned into a tokio reactor core. For accepting //! requests, they have a tokio channel receiver and run until that receiver //! is disconnected. For responding to requests they are oneshots. Once a //! service has determined the outcome for a request, it simply completes //! that oneshot with the result, waking up the query and allowing it to //! move on. //! //! In order to do their work, queries need the sending end of the services’ //! channels as well as the resolver configuration. This is bundled into an //! internal type named `Core`. Since `Sender`s are not `Sync`, types that //! contain them do not fulfill the requirements for implementing //! `Future::boxed()`. This is the reason for the somewhat awkward //! `ResolverTask`. It keeps its own copy of the core as a `TaskRc<Core>` //! but it can only create that once there is a task. //! //! This also means that each resolver future has its own clone of the core //! which seems a bit unnecessary. The other option would be to keep the //! core in an `Arc<Mutex<Core>>` which may have negative consequences in //! high load situations. Perhaps there is an even better way, //! either by changing the way queries communicate with services or through //! something clever? Generally, though, the cloning shouldn’t be too bad. //! Typical resolver configurations amount to either two or four services //! (one or two servers with UDP and TCP each), plus the configuration //! which is stored behind an Arc. But at least the `TaskRc<_>` means that //! all queries within the same future share one core (typically, address //! lookups run `A` and `AAAA` queries in parallel). //! //! There’s two types of services: datagram services and stream services. //! Currently, each of these has one concrete implementation: UDP service //! and TCP service respectively. Encrypted variants will be added later. //! //! Both types of services are lazy. They will only open an actual socket //! when the first request arrives. This is because in a typical //! configuration, the second server and all the streaming variants are //! likely to be unused. //! //! Since UDP sockets are somewhat cheap, the datagram service keeps its //! socket once opened, so it is basically just a future that first waits //! for a request on its receiver and, once one arrives, transforms into //! the actual service. //! //! For stream services, things are a little more difficult. After //! connecting, they keep their socket open for some time expecting further //! requests (there is also an option to reopen the connection for each //! request but that has not yet been implemented). So the future has to //! shuttle back and forth between an idle state that waits for a request //! and an active state doing actual work and eventually timing out, //! returning to idle state. The implementation is somewhat complicated //! by the fact that we need to move the receiving end of the channel between //! these states, so simple approaches like `Future::select()` don’t //! suffice. //! //! Both types of services are capable of multiplexing requests. DNS //! messages contain an ID chosen by the client and repeated in the server’s //! response for that purpose. Before sending a request, the service picks //! a random ID (which means that resends will have a new ID), keeps sent //! requests in a map, and matches incoming responses against that map. //! The map also serves to time out requests if they linger for too long.