Project

General

Profile

Bug #16653

Weird behaviour of Resolv module

Added by evserykh (Evgeniy Serykh) 7 months ago. Updated 4 months ago.

Status:
Rejected
Priority:
Normal
Assignee:
-
Target version:
-
ruby -v:
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
[ruby-core:97258]

Description

I have to deal with DNS request. Here is some examples.

When I ask A records for example.com at some public DNS servers I get the results:
> Resolv::DNS.new(nameserver: ['8.8.8.8', '1.1.1.1']).getresources('example.com', Resolv::DNS::Resource::IN::A)
=> [#<Resolv::DNS::Resource::IN::A:0x00005631491d5918 @address=#<Resolv::IPv4 93.184.216.34>, @ttl=11607>]

When I ask A records at DNS servers responsible for given domain there is no answer:
> Resolv::DNS.new(nameserver: ['a.iana-servers.net', 'b.iana-servers.net']).getresources('example.com', Resolv::DNS::Resource::IN::A)
=> []

Updated by jeremyevans0 (Jeremy Evans) 7 months ago

Looks like the issue is using multiple domain names instead of IP addresses for the :nameserver option. Single domain name works, multiple IP addresses works, IP address and domain name works, multiple domain names does not work:

Resolv::DNS.new(nameserver: ['a.iana-servers.net', 'b.iana-servers.net']).getresources('example.com', Resolv::DNS::Resource::IN::A)
# => []

Resolv::DNS.new(nameserver: ['b.iana-servers.net']).getresources('example.com', Resolv::DNS::Resource::IN::A)
# => [#<Resolv::DNS::Resource::IN::A:0x0000177abba0b598 @address=#<Resolv::IPv4 93.184.216.34>, @ttl=86400>]

Resolv::DNS.new(nameserver: ['a.iana-servers.net']).getresources('example.com', Resolv::DNS::Resource::IN::A)
# => [#<Resolv::DNS::Resource::IN::A:0x0000177a82c89998 @address=#<Resolv::IPv4 93.184.216.34>, @ttl=86400>]

Resolv::DNS.new(nameserver: ['199.43.135.53', '199.43.133.53']).getresources('example.com', Resolv::DNS::Resource::IN::A)
# => [#<Resolv::DNS::Resource::IN::A:0x0000177a98047e58 @address=#<Resolv::IPv4 93.184.216.34>, @ttl=86400>]

Resolv::DNS.new(nameserver: ['a.iana-servers.net', '199.43.133.53']).getresources('example.com', Resolv::DNS::Resource::IN::A)
# => [#<Resolv::DNS::Resource::IN::A:0x0000177a8d8fe0c0 @address=#<Resolv::IPv4 93.184.216.34>, @ttl=86400>]

Resolv::DNS.new(nameserver: ['199.43.135.53', 'b.iana-servers.net']).getresources('example.com', Resolv::DNS::Resource::IN::A)
# => [#<Resolv::DNS::Resource::IN::A:0x0000177a1b8089a8 @address=#<Resolv::IPv4 93.184.216.34>, @ttl=86400>]

Updated by jeremyevans0 (Jeremy Evans) 4 months ago

  • Status changed from Open to Rejected

I've looked into this more. The underlying cause is that Resolv expects nameservers to be specified as IP addresses, not as domain names. When using multiple nameservers, Resolv tries to match the IP address of the nameserver responding to one of the nameservers given as input, and if you give a domain name as input, there will be no match.

It makes sense for Resolv to only handle IP addresses as nameservers, as if you are providing a domain name to use as a nameserver, where would you look up the IP address of that nameserver to know where to connect? You can't use the resolver you are setting up to resolve that. This is the same reason that no operating system allows you to use a domain name as a DNS server.

I was able to come up with a workaround that uses the system resolver:

diff --git a/lib/resolv.rb b/lib/resolv.rb
index e7b45e785a..d529550bdb 100644
--- a/lib/resolv.rb
+++ b/lib/resolv.rb
@@ -1027,12 +1027,33 @@ def lazy_initialize
             if config_hash.include? :nameserver_port
               @nameserver_port = config_hash[:nameserver_port].map {|ns, port| [ns, (port || Port)] }
             end
+
             @search = config_hash[:search] if config_hash.include? :search
             @ndots = config_hash[:ndots] if config_hash.include? :ndots

-            if @nameserver_port.empty?
+            case @nameserver_port.length
+            when 0
               @nameserver_port << ['0.0.0.0', Port]
+            when 1
+              # nothing
+            else
+              @nameserver_port = @nameserver_port.map do |host, port|
+                dns_name = true
+                case host
+                when /\A[0-9.]+\z/
+                  dns_name = false
+                when  /\A[a-fA-F0-9:]+\z/
+                  dns_name = host.index(':')
+                end
+
+                if dns_name
+                  host = Addrinfo.getaddrinfo(host, nil)[0]&.ip_address
+                end
+
+                [host, port]
+              end
             end
+
             if @search
               @search = @search.map {|arg| Label.split(arg) }
             else

However, I'm fairly sure this is a misuse of the resolv library, and therefore I'm going to close this. The fact that things appear to work with a single domain name is just an implementation detail, not by design (single nameserver uses Requester::ConnectedUDP instead of Requester::UnconnectedUDP).

Also available in: Atom PDF