Class: Celluloid::IO::DNSResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/celluloid/io/dns_resolver.rb

Overview

Asynchronous DNS resolver using Celluloid::IO::UDPSocket

Constant Summary collapse

RESOLV_CONF =
'/etc/resolv.conf'
HOSTS =
'/etc/hosts'
DNS_PORT =
53

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeDNSResolver

Returns a new instance of DNSResolver.



35
36
37
38
39
40
41
42
43
44
# File 'lib/celluloid/io/dns_resolver.rb', line 35

def initialize
  @nameservers, @hosts = self.class.nameservers, self.class.hosts
  
  # TODO: fall back on other nameservers if the first one is unavailable
  @server = @nameservers.first

  # The non-blocking secret sauce is here, as this is actually a
  # Celluloid::IO::UDPSocket
  @socket = UDPSocket.new
end

Class Method Details

.generate_idObject



14
15
16
# File 'lib/celluloid/io/dns_resolver.rb', line 14

def self.generate_id
  @mutex.synchronize { @identifier = (@identifier + 1) & 0xFFFF }
end

.hosts(hostfile = HOSTS) ⇒ Object

FIXME: Y U NO Resolv::Hosts?



23
24
25
26
27
28
29
30
31
32
33
# File 'lib/celluloid/io/dns_resolver.rb', line 23

def self.hosts(hostfile = HOSTS)
  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
end

.nameservers(config = RESOLV_CONF) ⇒ Object



18
19
20
# File 'lib/celluloid/io/dns_resolver.rb', line 18

def self.nameservers(config = RESOLV_CONF)
  File.read(config).scan(/^\s*nameserver\s+([0-9.:]+)/).flatten
end

Instance Method Details

#resolve(hostname) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/celluloid/io/dns_resolver.rb', line 46

def resolve(hostname)
  host = @hosts[hostname]
  if host
    begin
      return Resolv::IPv4.create(host)
    rescue ArgumentError
    end

    begin
      return Resolv::IPv6.create(host)
    rescue ArgumentError
    end
    
    raise Resolv::ResolvError, "invalid entry in hosts file: #{host}"
  end
  
  query = Resolv::DNS::Message.new
  query.id = self.class.generate_id
  query.rd = 1
  query.add_question hostname, Resolv::DNS::Resource::IN::A
  
  @socket.send query.encode, 0, @server, DNS_PORT
  data, _ = @socket.recvfrom(512)
  response = Resolv::DNS::Message.decode(data)
  
  addrs = []
  # The answer might include IN::CNAME entries so filters them out
  # to include IN::A & IN::AAAA entries only.
  response.each_answer { |name, ttl, value| addrs << (value.respond_to?(:address) ? value.address : nil) }
  addrs.compact!
  
  return if addrs.empty?
  return addrs.first if addrs.size == 1
  addrs
end