How to enable DNS over TCP for resolving names on linux
How do I configure linux in general to allow dns over tcp?
We discovered today that several different linux servers we use are not able to resolve DNS names with many ip addresses in the response. These response packets would be over 512 bytes, so the server sends back a UDP packet with the truncated bit set. The various processes on the server (in this case, Java, and the nslookup command) simply stop resolving the name at this point. They don't follow up by making the same request over TCP, is I hear they should.
This part is specific to alpinelinux: On alpine linux, I can enable dns lookups over TCP by installing the bind-utils package. that makes other programs on the system start properly handling truncated responses by switching to TCP. I don't know what that particular package is doing to configure this, and I so i'm left with this quesiton:
Bonus question, can this be enabled for just java without changing anything else?
We are seeing this on more than just alpine, though we stumbled upon a package that has this effect on alpine.
Solution 1:
As stated in https://www.rfc-editor.org/rfc/rfc5966:
In the absence of EDNS0 (Extension Mechanisms for DNS 0) (see below), the standard
behaviour of any DNS server needing to send a UDP response that would exceed the
512-byte limit is for the server to truncate the answer so that it fits within that
limit and then set the TC flag in the response header. When the client receives such
a response, it takes the TC flag as an indication that it should retry over TCP
instead.
This other RFC https://www.ietf.org/rfc/rfc2181.txt also has some clarifications which include the use of the TC (truncated) header bit:
Where TC is set, the partial RRSet that would not completely fit may be left in the
response. When a DNS client receives a reply with TC set, it should ignore that
response, and query again, using a mechanism, such as a TCP connection, that will
permit larger replies.
The client does not know in advance that the response will be too large, so it will query the server via UDP, except for zone transfer queries which always use TCP. The server will respond via UDP and will include as much as possible and set the truncated header bit. The client must then resend the request via TCP and get the full response. So, you don't have to configure anything to force clients to always use TCP to send DNS request because they just do that on receiving an answer with the TC bit set.
You just have to make sure there is not any firewall which is blocking TCP traffic on port 53. You may also check that you have not been bitten by some bug.
Solution 2:
You might have solved this already by now, but I've been running into the same situation recently with Alpine Linux and a range of Ruby applications.
The issue here is that musl libc doesn't implement DNS over TCP (forgive me the layman terms here). By installing packages like bind-tools you'll get binaries that are shipping their own/a different DNS resolver, so those are able to talk DNS over TCP.
We've solved this by using a custom resolver that supports TCP within our Ruby applications (something that's encouraged by Rich Felker, the maintainer of musl libc). In fact, it ships with Ruby's standard library, so it's not really a big change.
Most environments have their own solver implemented, so you should be able to find one for whatever language/environment you're using.
Here are some resources:
- https://wiki.musl-libc.org/functional-differences-from-glibc.html#Name-Resolver/DNS
- https://www.linkedin.com/pulse/musl-libc-alpines-greatest-weakness-rogan-lynch/
- https://twitter.com/richfelker/status/994629795551031296?lang=en
- https://www.openwall.com/lists/musl/2020/04/21/8