class Coolio::DNSResolver
A non-blocking DNS resolver. It provides interfaces for querying both /etc/hosts and nameserves listed in /etc/resolv.conf, or nameservers of your choosing.
Presently the client only supports UDP requests against your nameservers and cannot resolve anything with records larger than 512-bytes. Also, IPv6 is not presently supported.
DNSResolver objects are one-shot. Once they resolve a domain name they automatically detach themselves from the event loop and cannot be used again.
Constants
- DATAGRAM_SIZE
- DNS_PORT
- RETRIES
- TIMEOUT
Public Class Methods
so currently total is 12s before it will err due to timeouts if it errs due to inability to reach the DNS server [Errno::EHOSTUNREACH], same Query /etc/hosts (or the specified hostfile) for the given host
# File lib/cool.io/dns_resolver.rb, line 43 def self.hosts(host, hostfile = Resolv::Hosts::DefaultFileName) hosts = {} File.open(hostfile) do |f| f.each_line do |host_entry| entries = host_entry.gsub(/#.*$/, '').gsub(/\s+/, ' ').split(' ') addr = entries.shift entries.each { |e| hosts[e] ||= addr } end end hosts[host] end
Create a new Coolio::Watcher descended object to resolve the given hostname. If you so desire you can also specify a list of nameservers to query. By default the resolver will use nameservers listed in /etc/resolv.conf
# File lib/cool.io/dns_resolver.rb, line 60 def initialize(hostname, *nameservers) if nameservers.empty? nameservers = Resolv::DNS::Config.default_config_hash[:nameserver] raise RuntimeError, "no nameservers found" if nameservers.empty? # TODO just call resolve_failed, not raise [also handle Errno::ENOENT)] end @nameservers = nameservers @question = request_question hostname @socket = UDPSocket.new @timer = Timeout.new(self) super(@socket) end
Public Instance Methods
Attach the DNSResolver to the given event loop
# File lib/cool.io/dns_resolver.rb, line 76 def attach(evloop) send_request @timer.attach(evloop) super end
Detach the DNSResolver from the given event loop
# File lib/cool.io/dns_resolver.rb, line 83 def detach @timer.detach if @timer.attached? super end
Called when we receive a response indicating the name didn't resolve
# File lib/cool.io/dns_resolver.rb, line 93 def on_failure; end
Called when the name has successfully resolved to an address
# File lib/cool.io/dns_resolver.rb, line 89 def on_success(address); end
Called if we don't receive a response, defaults to calling #on_failure
# File lib/cool.io/dns_resolver.rb, line 97 def on_timeout on_failure end
Protected Instance Methods
Called by the subclass when the DNS response is available
# File lib/cool.io/dns_resolver.rb, line 116 def on_readable datagram = nil begin datagram = @socket.recvfrom_nonblock(DATAGRAM_SIZE).first rescue Errno::ECONNREFUSED end address = response_address datagram rescue nil address ? on_success(address) : on_failure detach end
# File lib/cool.io/dns_resolver.rb, line 143 def request_message # Standard query header message = [2, 1, 0].pack('nCC') # One entry qdcount = 1 # No answer, authority, or additional records ancount = nscount = arcount = 0 message << [qdcount, ancount, nscount, arcount].pack('nnnn') message << @question end
# File lib/cool.io/dns_resolver.rb, line 128 def request_question(hostname) raise ArgumentError, "hostname cannot be nil" if hostname.nil? # Query name message = hostname.split('.').map { |s| [s.size].pack('C') << s }.join + "\0" # Host address query qtype = 1 # Internet query qclass = 1 message << [qtype, qclass].pack('nn') end
# File lib/cool.io/dns_resolver.rb, line 157 def response_address(message) # Confirm the ID field id = message[0..1].unpack('n').first.to_i return unless id == 2 # Check the QR value and confirm this message is a response qr = message[2..2].unpack('B1').first.to_i return unless qr == 1 # Check the RCODE (lower nibble) and ensure there wasn't an error rcode = message[3..3].unpack('B8').first[4..7].to_i(2) return unless rcode == 0 # Extract the question and answer counts qdcount, _ancount = message[4..7].unpack('nn').map { |n| n.to_i } # We only asked one question return unless qdcount == 1 message.slice!(0, 12) # Make sure it's the same question return unless message[0..(@question.size-1)] == @question message.slice!(0, @question.size) # Extract the RDLENGTH while not message.empty? type = message[2..3].unpack('n').first.to_i rdlength = message[10..11].unpack('n').first.to_i rdata = message[12..(12 + rdlength - 1)] message.slice!(0, 12 + rdlength) # Only IPv4 supported next unless rdlength == 4 # If we got an Internet address back, return it return rdata.unpack('CCCC').join('.') if type == 1 end nil end
Send a request to the DNS server
# File lib/cool.io/dns_resolver.rb, line 106 def send_request nameserver = @nameservers.shift @nameservers << nameserver # rotate them begin @socket.send request_message, 0, @nameservers.first, DNS_PORT rescue Errno::EHOSTUNREACH # TODO figure out why it has to be wrapper here, when the other wrapper should be wrapping this one! end end