In another post, we covered how to resolve SystemStackError when resolving IP address in Ruby. In this post, we would cover another common issue where a NXDomain error is returned when resolving an IP address. The NXDomain error means that the queried domain name does not exist in the DNS.
In Ruby, DNS resolver library will use /etc/resolv.conf by default get the name servers to resolve the domain name. There are multiple DNS name servers can be specified in /etc/resolv.conf with below format.
nameserver
nameserver
nameserver
...
By default, only the top MAXNS name servers(3 in Linux by default) will be used for resolving the IP address for performance consideration. How it works is that if the domain name cannot be found in the first name server, it will return a NXDomain error and skip the rest name servers in the list. However, if a timeout error occurs while resolving the domain name, the next name server will be tried. See below Resolv implementation.
def resolv(name) candidates = generate_candidates(name) timeouts = @timeouts || generate_timeouts begin candidates.each {|candidate| begin timeouts.each {|tout| @nameserver_port.each {|nameserver, port| begin yield candidate, tout, nameserver, port rescue ResolvTimeout end } } raise ResolvError.new("DNS resolv timeout: #{name}") rescue NXDomain end } rescue ResolvError end end
Since the rest of the name servers will be skipped if a NXDomain error is returned, it brings some inconvenience where one domain may be in the first name server while some in the second name server. This means some servers cannot be resolved even though they exist in some name servers. In many cases, one would desire that the next name server would be looked up if the domain name cannot be found in the previous name server instead of giving up directly.
To workaround this limitation, one could override the behavior of the default DNS resolver by creating a new one and replacing the default DNS resolver with the newly created one. One sample implementation would be something like.
def resolv(name) candidates = generate_candidates(name) timeouts = @timeouts || generate_timeouts begin candidates.each {|candidate| timeouts.each {|tout| @nameserver_port.each {|nameserver, port| begin yield candidate, tout, nameserver, port rescue ResolvTimeout rescue NXDomain next end } } raise ResolvError.new("DNS resolv timeout: #{name}") } rescue ResolvError end end
Be cautious that this would introduce some performance issue if lots of domain names are not in the first name server. In this case, one should tune the name server list so that most of the domain names to be searched are in the first name server.