1# Asynchronous DNS 2 3## Introduction 4 5Lws now features optional asynchronous, ie, nonblocking recursive DNS 6resolution done on the event loop, enable `-DLWS_WITH_SYS_ASYNC_DNS=1` 7at cmake to build it in. 8 9## Description 10 11The default libc name resolution is via libc `getaddrinfo()`, which is 12blocking, possibly for quite long periods (seconds). If you are 13taking care about latency, but want to create outgoing connections, 14you can't tolerate this exception from the rule that everything in 15lws is nonblocking. 16 17Lws' asynchronous DNS resolver creates a caching name resolver 18that directly queries the configured nameserver itself over UDP, 19from the event loop. 20 21It supports both ipv4 / A records and ipv6 / AAAA records (see later 22for a description about how). One server supported over UDP :53, 23and the nameserver is autodicovered on linux, windows, and freertos. 24 25Other features 26 27 - lws-style paranoid response parsing 28 - random unique tid generation to increase difficulty of poisoning 29 - it's really integrated with the lws event loop, it does not spawn 30 threads or use the libc resolver, and of course no blocking at all 31 - platform-specific server address capturing (from /etc/resolv.conf 32 on linux, windows apis on windows) 33 - LRU caching 34 - piggybacking (multiple requests before the first completes go on 35 a list on the first request, not spawn multiple requests) 36 - observes TTL in cache 37 - TTL and timeout use `lws_sul` timers on the event loop 38 - Uses CNAME resolution inside the same response if present, otherwise 39 recurses to resolve the CNAME (up to 3 deep) 40 - ipv6 pieces only built if cmake `LWS_IPV6` enabled 41 42## Api 43 44If enabled at cmake, the async DNS implementation is used automatically 45for lws client connections. It's also possible to call it directly, see 46the api-test-async-dns example for how. 47 48The Api follows that of `getaddrinfo()` but results are not created on 49the heap. Instead a single, const cached copy of the addrinfo struct 50chain is reference-counted, with `lws_async_dns_freeaddrinfo()` provided 51to deduct from the reference count. Cached items with a nonzero 52reference count can't be destroyed from the cache, so it's safe to keep 53a pointer to the results and iterate through them. 54 55## Dealing with IPv4 and IPv6 56 57DNS is a very old standard that has some quirks... one of them is that 58multiple queries are not supported in one packet, even though the protocol 59suggests it is. This creates problems on ipv6 enabled systems, where 60it may prefer to have AAAA results, but the server may only have A records. 61 62To square the circle, for ipv4 only systems (`LWS_IPV6=0`) the resolver 63requests only A records. For ipv6-capable systems, it always requests 64first A and then immediately afterwards AAAA records. 65 66To simplify the implementation, the tid b0 is used to differentiate 67between A (b0 = 0) and AAAA (b0 = 1) requests and responses using the 68same query body. 69 70The first response to come back is parsed, and a cache entry made... 71it leaves a note in the query about the address of the last `struct addrinfo` 72record. When the second response comes, a second allocation is made, 73but not added to the logical cache... instead it's chained on to the 74first cache entry and the `struct addrinfo` linked-list from the 75first cache entry is extended into the second one. At the time the 76second result arrives, the query is destroyed and the cached results 77provided on the result callback. 78 79## Recursion 80 81Where CNAMEs are returned, DNS servers may take two approaches... if the 82CNAME is also resolved by the same server and so it knows what it should 83resolve to, it may provide the CNAME resolution in the same response 84packet. 85 86In the case the CNAME is actually resolved by a different name server, 87the server with the CNAME does not have the information to hand to also 88resolve the CNAME in the same response. So it just leaves it for the 89client to sort out. 90 91The lws implementation can deal with both of these, first it "recurses" 92(it does not recurse on the process stack but uses its own manual stack) 93to look for results in the same packet that told it about the CNAME. If 94there are no results, it resets the query to look instead for the CNAME, 95and restarts it. It allows this to happen for 3 CNAME deep. 96 97At the end, either way, the cached result is set using the original 98query name and the results from the last CNAME in the chain. 99 100 101