apple / swift-async-dns-resolver

A Swift library for asynchronous DNS requests, wrapping c-ares with Swift-friendly APIs and data structures.
Apache License 2.0
82 stars 12 forks source link

Queries for non-existent record type take long time to eventually time out #37

Open gmilos opened 1 month ago

gmilos commented 1 month ago

On macOS, an attempt to resolve name for which the particular record type doesn't exist, results in long (~30s) delay.

For example:

AsyncDNSResolver().queryA(name: "some.domain.without.A.record")

An investigation showed that adding the additional kDNSServiceFlagsReturnIntermediates, to the DNSServiceQueryRecord invocation fixes the delay problem. However, now the code has to handle the possibility that CNAME gets returned instead of the requested record type, thus requiring it to inspect the rrclass and rrtype returned to the innermost handler here. If CNAME is returned, the desired query should be repeated on that CNAME, otherwise an error should be generated.

PRs welcome if someone beats me to the implementation.

Tested with https://github.com/apple/swift-async-dns-resolver/releases/tag/0.4.0

StuartCheshire commented 1 month ago

The kDNSServiceFlagsReturnIntermediates flag doesn’t return a CNAME instead of the requested record type, it returns a CNAME as well as the requested record type. These intermediate answers are just progress reports of what is happening.

Similarly, a “No Such Record” result is not the final answer. It means “No Such Record right now, but if you wait a bit longer, there might be one later.” On a device like an iPhone this change in results can happen because the user turns off airplane mode, or switches to a different Wi-Fi network, or brings up VPN, or other similar changes. It can also happen if an administrator updates the authoritative DNS and new answers become available.

You can see this using a command like this on a Mac: dns-sd -intermediates -Gv4v6 www.apple.com

Run the command with no network connection, and you’ll see “No Such Record”, meaning No Such Record right now. Leave the command running and connect to a Wi-Fi network, and you will see the set of available answers change.

The results generated using kDNSServiceFlagsReturnIntermediates should not change the fundamental operation of your code. The intention is that they can be useful hints to update some kind of progress UI, but they don’t change how your program works.

gmilos commented 1 month ago

@StuartCheshire thanks for the additional context, that's v. useful. In particular thanks for (a) clarifying that CNAME-s are delivered in addition to the requested record type, (b) highlighting how the different network conditions affect the DNSSD operations. I don't think this is handled in the swift wrapper in any thought out way.

However, what I was pointing at is that kDNSServiceFlagsReturnIntermediates affects what callbacks the handler gets. Assuming still that we're querying a non-existent A record, we see the following sequences:

  1. DNSServiceQueryRecord, configuring the query & the callback
  2. DNSServiceProcessResult
  3. ~30s delay
  4. callback with errorCode = -65568, i.e. kDNSServiceErr_Timeout

The kDNSServiceFlagsReturnIntermediates flag is therefore significant beyond returning CNAMEs. It also delivers kDNSServiceErr_NoSuchRecord proactively. This is a useful signal for the callback to consider. @StuartCheshire is there a more canonical way to get that, other than as a side-effect of kDNSServiceFlagsReturnIntermediates?

Let me also mention slightly related fact that today's implementation is blocking (particularly bad for supposedly async library). This makes it impossible to cancel DNSServiceProcessResult. Because of the 30s, uncancellable timeout, it makes the library unusable for querying non-existent records. Cancelability is already being discussed here: https://github.com/apple/swift-async-dns-resolver/issues/32 and here: https://github.com/apple/swift-async-dns-resolver/pull/34. It needs to be fixed in addition to deciding how to handle queries for non-existent records.