Handle NXDomain error when resolving IP address in Ruby DNS resolver

  Pi Ke        2017-07-16 01:39:23       4,551        0    

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.

DNS  NETWORK  RUBY ON RAILS  RUBY  NXDOMAIN 

       

  RELATED


  0 COMMENT


No comment for this article.



  RANDOM FUN

Learning Gin while drinking Gin