3 resolv.rb is a resolver library written in Ruby.
4 Since it is written in Ruby, it is thread-aware.
5 I.e. it can resolv many hostnames concurrently.
7 It is possible to lookup various resources of DNS using DNS module directly.
10 p Resolv.getaddress("www.ruby-lang.org")
11 p Resolv.getname("210.251.121.214")
13 Resolv::DNS.open {|dns|
14 p dns.getresources("www.ruby-lang.org", Resolv::DNS::Resource::IN::A).collect {|r| r.address}
15 p dns.getresources("ruby-lang.org", Resolv::DNS::Resource::IN::MX).collect {|r| [r.exchange.to_s, r.preference]}
21 --- Resolv.getaddress(name)
22 --- Resolv.getaddresses(name)
23 --- Resolv.each_address(name) {|address| ...}
24 They lookups IP addresses of ((|name|)) which represents a hostname
25 as a string by default resolver.
27 getaddress returns first entry of lookupped addresses.
28 getaddresses returns lookupped addresses as an array.
29 each_address iterates over lookupped addresses.
31 --- Resolv.getname(address)
32 --- Resolv.getnames(address)
33 --- Resolv.each_name(address) {|name| ...}
34 lookups hostnames of ((|address|)) which represents IP address as a string.
36 getname returns first entry of lookupped names.
37 getnames returns lookupped names as an array.
38 each_names iterates over lookupped names.
40 == Resolv::Hosts class
41 hostname resolver using /etc/hosts format.
44 --- Resolv::Hosts.new(hosts='/etc/hosts')
47 --- Resolv::Hosts#getaddress(name)
48 --- Resolv::Hosts#getaddresses(name)
49 --- Resolv::Hosts#each_address(name) {|address| ...}
50 address lookup methods.
52 --- Resolv::Hosts#getname(address)
53 --- Resolv::Hosts#getnames(address)
54 --- Resolv::Hosts#each_name(address) {|name| ...}
55 hostnames lookup methods.
61 --- Resolv::DNS.new(config_info=nil)
63 ((|config_info|)) should be nil, a string or a hash.
64 If nil is given, /etc/resolv.conf and platform specific information is used.
65 If a string is given, it should be a filename which format is same as /etc/resolv.conf.
66 If a hash is given, it may contains information for nameserver, search and ndots as follows.
68 Resolv::DNS.new({:nameserver=>["210.251.121.21"], :search=>["ruby-lang.org"], :ndots=>1})
70 --- Resolv::DNS.open(config_info=nil)
71 --- Resolv::DNS.open(config_info=nil) {|dns| ...}
76 --- Resolv::DNS#getaddress(name)
77 --- Resolv::DNS#getaddresses(name)
78 --- Resolv::DNS#each_address(name) {|address| ...}
79 address lookup methods.
81 ((|name|)) must be a instance of Resolv::DNS::Name or String. Lookupped
82 address is represented as an instance of Resolv::IPv4 or Resolv::IPv6.
84 --- Resolv::DNS#getname(address)
85 --- Resolv::DNS#getnames(address)
86 --- Resolv::DNS#each_name(address) {|name| ...}
87 hostnames lookup methods.
89 ((|address|)) must be a instance of Resolv::IPv4, Resolv::IPv6 or String.
90 Lookupped name is represented as an instance of Resolv::DNS::Name.
92 --- Resolv::DNS#getresource(name, typeclass)
93 --- Resolv::DNS#getresources(name, typeclass)
94 --- Resolv::DNS#each_resource(name, typeclass) {|resource| ...}
95 They lookup DNS resources of ((|name|)).
96 ((|name|)) must be a instance of Resolv::Name or String.
98 ((|typeclass|)) should be one of follows:
99 * Resolv::DNS::Resource::IN::ANY
100 * Resolv::DNS::Resource::IN::NS
101 * Resolv::DNS::Resource::IN::CNAME
102 * Resolv::DNS::Resource::IN::SOA
103 * Resolv::DNS::Resource::IN::HINFO
104 * Resolv::DNS::Resource::IN::MINFO
105 * Resolv::DNS::Resource::IN::MX
106 * Resolv::DNS::Resource::IN::TXT
107 * Resolv::DNS::Resource::IN::ANY
108 * Resolv::DNS::Resource::IN::A
109 * Resolv::DNS::Resource::IN::WKS
110 * Resolv::DNS::Resource::IN::PTR
111 * Resolv::DNS::Resource::IN::AAAA
113 Lookupped resource is represented as an instance of (a subclass of)
114 Resolv::DNS::Resource.
115 (Resolv::DNS::Resource::IN::A, etc.)
117 == Resolv::DNS::Resource::IN::NS class
119 == Resolv::DNS::Resource::IN::CNAME class
121 == Resolv::DNS::Resource::IN::SOA class
129 == Resolv::DNS::Resource::IN::HINFO class
132 == Resolv::DNS::Resource::IN::MINFO class
135 == Resolv::DNS::Resource::IN::MX class
138 == Resolv::DNS::Resource::IN::TXT class
140 == Resolv::DNS::Resource::IN::A class
142 == Resolv::DNS::Resource::IN::WKS class
146 == Resolv::DNS::Resource::IN::PTR class
148 == Resolv::DNS::Resource::IN::AAAA class
151 == Resolv::DNS::Name class
154 --- Resolv::DNS::Name.create(name)
157 --- Resolv::DNS::Name#to_s
159 == Resolv::DNS::Resource class
161 == Resolv::IPv4 class
163 --- Resolv::IPv4.create(address)
166 --- Resolv::IPv4#to_s
167 --- Resolv::IPv4#to_name
170 --- Resolv::IPv4::Regex
171 regular expression for IPv4 address.
173 == Resolv::IPv6 class
175 --- Resolv::IPv6.create(address)
178 --- Resolv::IPv6#to_s
179 --- Resolv::IPv6#to_name
182 --- Resolv::IPv6::Regex
183 regular expression for IPv6 address.
186 * NIS is not supported.
187 * /etc/nsswitch.conf is not supported.
188 * IPv6 is not supported.
198 def self.getaddress(name)
199 DefaultResolver.getaddress(name)
202 def self.getaddresses(name)
203 DefaultResolver.getaddresses(name)
206 def self.each_address(name, &block)
207 DefaultResolver.each_address(name, &block)
210 def self.getname(address)
211 DefaultResolver.getname(address)
214 def self.getnames(address)
215 DefaultResolver.getnames(address)
218 def self.each_name(address, &proc)
219 DefaultResolver.each_name(address, &proc)
222 def initialize(resolvers=[Hosts.new, DNS.new])
223 @resolvers = resolvers
227 each_address(name) {|address| return address}
228 raise ResolvError.new("no address for #{name}")
231 def getaddresses(name)
233 each_address(name) {|address| ret << address}
237 def each_address(name)
238 if AddressRegex =~ name
244 r.each_address(name) {|address|
253 each_name(address) {|name| return name}
254 raise ResolvError.new("no name for #{address}")
257 def getnames(address)
259 each_name(address) {|name| ret << name}
263 def each_name(address)
266 r.each_name(address) {|name|
274 class ResolvError < StandardError
277 class ResolvTimeout < TimeoutError
281 if /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
282 require 'win32/resolv'
283 DefaultFileName = Win32::Resolv.get_hosts_path
285 DefaultFileName = '/etc/hosts'
288 def initialize(filename = DefaultFileName)
302 addr, hostname, *aliases = line.split(/\s+/)
306 @addr2name[addr] = [] unless @addr2name.include? addr
307 @addr2name[addr] << hostname
308 @addr2name[addr] += aliases
309 @name2addr[hostname] = [] unless @name2addr.include? hostname
310 @name2addr[hostname] << addr
313 @name2addr[n] = [] unless @name2addr.include? n
314 @name2addr[n] << addr
318 @name2addr.each {|name, arr| arr.reverse!}
326 each_address(name) {|address| return address}
327 raise ResolvError.new("#{@filename} has no name: #{name}")
330 def getaddresses(name)
332 each_address(name) {|address| ret << address}
336 def each_address(name, &proc)
338 if @name2addr.include?(name)
339 @name2addr[name].each(&proc)
344 each_name(address) {|name| return name}
345 raise ResolvError.new("#{@filename} has no address: #{address}")
348 def getnames(address)
350 each_name(address) {|name| ret << name}
354 def each_name(address, &proc)
356 if @addr2name.include?(address)
357 @addr2name[address].each(&proc)
363 # STD0013 (RFC 1035, etc.)
364 # ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters
369 DNSThreadGroup = ThreadGroup.new
373 return dns unless block_given?
381 def initialize(config_info=nil)
383 @config = Config.new(config_info)
390 @config.lazy_initialize
392 if nameserver = @config.single?
393 @requester = Requester::ConnectedUDP.new(nameserver)
395 @requester = Requester::UnconnectedUDP.new
407 @requester.close if @requester
415 each_address(name) {|address| return address}
416 raise ResolvError.new("DNS result has no information for #{name}")
419 def getaddresses(name)
421 each_address(name) {|address| ret << address}
425 def each_address(name)
426 each_resource(name, Resource::IN::A) {|resource| yield resource.address}
430 each_name(address) {|name| return name}
431 raise ResolvError.new("DNS result has no information for #{address}")
434 def getnames(address)
436 each_name(address) {|name| ret << name}
440 def each_name(address)
445 ptr = IPv4.create(address).to_name
447 ptr = IPv6.create(address).to_name
449 raise ResolvError.new("cannot interpret as address: #{address}")
451 each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name}
454 def getresource(name, typeclass)
455 each_resource(name, typeclass) {|resource| return resource}
456 raise ResolvError.new("DNS result has no information for #{name}")
459 def getresources(name, typeclass)
461 each_resource(name, typeclass) {|resource| ret << resource}
465 def each_resource(name, typeclass, &proc)
470 @config.resolv(name) {|candidate, tout, nameserver|
473 msg.add_question(candidate, typeclass)
474 unless sender = senders[[candidate, nameserver]]
475 sender = senders[[candidate, nameserver]] =
476 @requester.sender(msg, candidate, q, nameserver)
479 reply = reply_name = nil
480 timeout(tout, ResolvTimeout) { reply, reply_name = q.pop }
483 extract_resources(reply, reply_name, typeclass, &proc)
486 raise Config::NXDomain.new(reply_name.to_s)
488 raise Config::OtherResolvError.new(reply_name.to_s)
496 def extract_resources(msg, name, typeclass)
497 if typeclass < Resource::ANY
498 n0 = Name.create(name)
499 msg.each_answer {|n, ttl, data|
500 yield data if n0 == n
504 n0 = Name.create(name)
505 msg.each_answer {|n, ttl, data|
517 msg.each_answer {|n, ttl, data|
533 thread, sock, @thread, @sock = @thread, @sock
547 @senders.delete_if {|k, s| s == arg }
549 @senders.delete_if {|k, s| s.queue == arg }
551 raise ArgumentError.new("neither Sender or Queue: #{arg}")
556 def initialize(msg, data, sock, queue)
565 @queue.push([msg, @data])
569 class UnconnectedUDP < Requester
572 @sock = UDPSocket.new
573 @sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD
576 @thread = Thread.new {
577 DNSThreadGroup.add Thread.current
579 reply, from = @sock.recvfrom(UDPSize)
581 Message.decode(reply)
583 STDERR.print("DNS message decoding error: #{reply.inspect}\n")
586 if s = @senders[[[from[3],from[1]],msg.id]]
589 #STDERR.print("non-handled DNS message: #{msg.inspect} from #{from.inspect}\n")
595 def sender(msg, data, queue, host, port=Port)
596 service = [host, port]
597 id = Thread.exclusive {
598 @id[service] = (@id[service] + 1) & 0xffff
601 request[0,2] = [id].pack('n')
602 return @senders[[service, id]] =
603 Sender.new(request, data, @sock, host, port, queue)
606 class Sender < Requester::Sender
607 def initialize(msg, data, sock, host, port, queue)
608 super(msg, data, sock, queue)
614 @sock.send(@msg, 0, @host, @port)
619 class ConnectedUDP < Requester
620 def initialize(host, port=Port)
624 @sock = UDPSocket.new(host.index(':') ? Socket::AF_INET6 : Socket::AF_INET)
625 @sock.connect(host, port)
626 @sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD
628 @thread = Thread.new {
629 DNSThreadGroup.add Thread.current
631 reply = @sock.recv(UDPSize)
633 Message.decode(reply)
635 STDERR.print("DNS message decoding error: #{reply.inspect}")
638 if s = @senders[msg.id]
641 #STDERR.print("non-handled DNS message: #{msg.inspect}")
647 def sender(msg, data, queue, host=@host, port=@port)
648 unless host == @host && port == @port
649 raise RequestError.new("host/port don't match: #{host}:#{port}")
651 id = Thread.exclusive { @id = (@id + 1) & 0xffff }
653 request[0,2] = [id].pack('n')
654 return @senders[id] = Sender.new(request, data, @sock, queue)
657 class Sender < Requester::Sender
664 class TCP < Requester
665 def initialize(host, port=Port)
669 @sock = TCPSocket.new
670 @sock.connect(host, port)
671 @sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD
674 @thread = Thread.new {
675 DNSThreadGroup.add Thread.current
677 len = @sock.read(2).unpack('n')
678 reply = @sock.read(len)
680 Message.decode(reply)
682 STDERR.print("DNS message decoding error: #{reply.inspect}")
685 if s = @senders[msg.id]
688 #STDERR.print("non-handled DNS message: #{msg.inspect}")
694 def sender(msg, data, queue, host=@host, port=@port)
695 unless host == @host && port == @port
696 raise RequestError.new("host/port don't match: #{host}:#{port}")
698 id = Thread.exclusive { @id = (@id + 1) & 0xffff }
700 request[0,2] = [request.length, id].pack('nn')
701 return @senders[id] = Sender.new(request, data, @sock, queue)
704 class Sender < Requester::Sender
712 class RequestError < StandardError
717 def initialize(config_info=nil)
719 @config_info = config_info
723 def Config.parse_resolv_conf(filename)
729 line.sub!(/[#;].*/, '')
730 keyword, *args = line.split(/\s+/)
747 when /\Andots:(\d+)\z/
754 return { :nameserver => nameserver, :search => search, :ndots => ndots }
757 def Config.default_config_hash(filename="/etc/resolv.conf")
758 if File.exist? filename
759 config_hash = Config.parse_resolv_conf(filename)
761 if /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
762 search, nameserver = Win32::Resolv.get_resolv_info
764 config_hash[:nameserver] = nameserver if nameserver
765 config_hash[:search] = [search].flatten if search
779 config_hash = Config.default_config_hash
781 config_hash = Config.parse_resolv_conf(@config_info)
783 config_hash = @config_info.dup
784 if String === config_hash[:nameserver]
785 config_hash[:nameserver] = [config_hash[:nameserver]]
787 if String === config_hash[:search]
788 config_hash[:search] = [config_hash[:search]]
791 raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}")
793 @nameserver = config_hash[:nameserver] if config_hash.include? :nameserver
794 @search = config_hash[:search] if config_hash.include? :search
795 @ndots = config_hash[:ndots] if config_hash.include? :ndots
797 @nameserver = ['0.0.0.0'] if @nameserver.empty?
799 @search = @search.map {|arg| Label.split(arg) }
801 hostname = Socket.gethostname
803 @search = [Label.split($')]
809 if !@nameserver.kind_of?(Array) ||
810 !@nameserver.all? {|ns| String === ns }
811 raise ArgumentError.new("invalid nameserver config: #{@nameserver.inspect}")
814 if !@search.kind_of?(Array) ||
815 !@search.all? {|ls| ls.all? {|l| Label::Str === l } }
816 raise ArgumentError.new("invalid search config: #{@search.inspect}")
819 if !@ndots.kind_of?(Integer)
820 raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}")
831 if @nameserver.length == 1
832 return @nameserver[0]
838 def generate_candidates(name)
840 name = Name.create(name)
844 if @ndots <= name.length - 1
845 candidates = [Name.new(name.to_a)]
849 candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)})
856 def generate_timeouts
857 ts = [InitialTimeout]
858 ts << ts[-1] * 2 / @nameserver.length
865 candidates = generate_candidates(name)
866 timeouts = generate_timeouts
868 candidates.each {|candidate|
870 timeouts.each {|tout|
871 @nameserver.each {|nameserver|
873 yield candidate, tout, nameserver
878 raise ResolvError.new("DNS resolv timeout: #{name}")
886 class NXDomain < ResolvError
889 class OtherResolvError < ResolvError
922 class DecodeError < StandardError
925 class EncodeError < StandardError
931 arg.scan(/[^\.]+/) {labels << Str.new($&)}
936 def initialize(string)
938 @downcase = string.downcase
940 attr_reader :string, :downcase
947 return "#<#{self.class} #{self.to_s}>"
951 return @downcase == other.downcase
959 return @downcase.hash
970 return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
972 raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
976 def initialize(labels, absolute=true)
982 "#<#{self.class}: #{self.to_s}#{@absolute ? '.' : ''}>"
990 return false unless Name === other
991 return @labels == other.to_a && @absolute == other.absolute?
995 # tests subdomain-of relation.
997 # domain = Resolv::DNS::Name.create("y.z")
998 # p Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true
999 # p Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true
1000 # p Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false
1001 # p Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false
1002 # p Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false
1003 # p Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false
1005 def subdomain_of?(other)
1006 raise ArgumentError, "not a domain name: #{other.inspect}" unless Name === other
1007 return false if @absolute != other.absolute?
1008 other_len = other.length
1009 return false if @labels.length <= other_len
1010 return @labels[-other_len, other_len] == other.to_a
1014 return @labels.hash ^ @absolute.hash
1022 return @labels.length
1029 # returns the domain name as a string.
1031 # The domain name doesn't have a trailing dot even if the name object is
1034 # p Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z"
1035 # p Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z"
1038 return @labels.join('.')
1045 def initialize(id = (@@identifier += 1) & 0xffff)
1051 @rd = 0 # recursion desired
1052 @ra = 0 # recursion available
1060 attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode
1061 attr_reader :question, :answer, :authority, :additional
1064 return @id == other.id &&
1066 @opcode == other.opcode &&
1071 @rcode == other.rcode &&
1072 @question == other.question &&
1073 @answer == other.answer &&
1074 @authority == other.authority &&
1075 @additional == other.additional
1078 def add_question(name, typeclass)
1079 @question << [Name.create(name), typeclass]
1083 @question.each {|name, typeclass|
1084 yield name, typeclass
1088 def add_answer(name, ttl, data)
1089 @answer << [Name.create(name), ttl, data]
1093 @answer.each {|name, ttl, data|
1094 yield name, ttl, data
1098 def add_authority(name, ttl, data)
1099 @authority << [Name.create(name), ttl, data]
1103 @authority.each {|name, ttl, data|
1104 yield name, ttl, data
1108 def add_additional(name, ttl, data)
1109 @additional << [Name.create(name), ttl, data]
1113 @additional.each {|name, ttl, data|
1114 yield name, ttl, data
1119 each_answer {|name, ttl, data| yield name, ttl, data}
1120 each_authority {|name, ttl, data| yield name, ttl, data}
1121 each_additional {|name, ttl, data| yield name, ttl, data}
1125 return MessageEncoder.new {|msg|
1126 msg.put_pack('nnnnnn',
1129 (@opcode & 15) << 11 |
1142 msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue)
1144 [@answer, @authority, @additional].each {|rr|
1148 msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl)
1149 msg.put_length16 {data.encode_rdata(msg)}
1155 class MessageEncoder
1170 def put_pack(template, *d)
1171 @data << d.pack(template)
1175 length_index = @data.length
1177 data_start = @data.length
1179 data_end = @data.length
1180 @data[length_index, 2] = [data_end - data_start].pack("n")
1184 self.put_pack("C", d.length)
1188 def put_string_list(ds)
1201 if idx = @names[domain]
1202 self.put_pack("n", 0xc000 | idx)
1205 @names[domain] = @data.length
1206 self.put_label(d[i])
1213 self.put_string(d.string)
1217 def Message.decode(m)
1219 MessageDecoder.new(m) {|msg|
1220 id, flag, qdcount, ancount, nscount, arcount =
1221 msg.get_unpack('nnnnnn')
1223 o.qr = (flag >> 15) & 1
1224 o.opcode = (flag >> 11) & 15
1225 o.aa = (flag >> 10) & 1
1226 o.tc = (flag >> 9) & 1
1227 o.rd = (flag >> 8) & 1
1228 o.ra = (flag >> 7) & 1
1231 name, typeclass = msg.get_question
1232 o.add_question(name, typeclass)
1235 name, ttl, data = msg.get_rr
1236 o.add_answer(name, ttl, data)
1239 name, ttl, data = msg.get_rr
1240 o.add_authority(name, ttl, data)
1243 name, ttl, data = msg.get_rr
1244 o.add_additional(name, ttl, data)
1250 class MessageDecoder
1251 def initialize(data)
1254 @limit = data.length
1259 len, = self.get_unpack('n')
1261 @limit = @index + len
1264 raise DecodeError.new("junk exists")
1265 elsif @limit < @index
1266 raise DecodeError.new("limit exceeded")
1272 def get_bytes(len = @limit - @index)
1273 d = @data[@index, len]
1278 def get_unpack(template)
1280 template.each_byte {|byte|
1289 raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'")
1292 raise DecodeError.new("limit exceeded") if @limit < @index + len
1293 arr = @data.unpack("@#{@index}#{template}")
1300 raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len
1301 d = @data[@index + 1, len]
1308 while @index < @limit
1309 strings << self.get_string
1315 return Name.new(self.get_labels)
1318 def get_labels(limit=nil)
1319 limit = @index if !limit || @index < limit
1327 idx = self.get_unpack('n')[0] & 0x3fff
1329 raise DecodeError.new("non-backward name pointer")
1333 d += self.get_labels(limit)
1344 return Label::Str.new(self.get_string)
1348 name = self.get_name
1349 type, klass = self.get_unpack("nn")
1350 return name, Resource.get_class(type, klass)
1354 name = self.get_name
1355 type, klass, ttl = self.get_unpack('nnN')
1356 typeclass = Resource.get_class(type, klass)
1357 return name, ttl, self.get_length16 {typeclass.decode_rdata(self)}
1363 def encode_rdata(msg)
1364 raise EncodeError.new("#{self.class} is query.")
1367 def self.decode_rdata(msg)
1368 raise DecodeError.new("#{self.class} is query.")
1372 class Resource < Query
1375 def encode_rdata(msg)
1376 raise NotImplementedError.new
1379 def self.decode_rdata(msg)
1380 raise NotImplementedError.new
1384 return self.class == other.class &&
1385 self.instance_variables == other.instance_variables &&
1386 self.instance_variables.collect {|name| self.instance_eval name} ==
1387 other.instance_variables.collect {|name| other.instance_eval name}
1391 return self == other
1396 self.instance_variables.each {|name|
1397 h ^= self.instance_eval("#{name}.hash")
1402 def self.get_class(type_value, class_value)
1403 return ClassHash[[type_value, class_value]] ||
1404 Generic.create(type_value, class_value)
1407 class Generic < Resource
1408 def initialize(data)
1413 def encode_rdata(msg)
1417 def self.decode_rdata(msg)
1418 return self.new(msg.get_bytes)
1421 def self.create(type_value, class_value)
1422 c = Class.new(Generic)
1423 c.const_set(:TypeValue, type_value)
1424 c.const_set(:ClassValue, class_value)
1425 Generic.const_set("Type#{type_value}_Class#{class_value}", c)
1426 ClassHash[[type_value, class_value]] = c
1431 class DomainName < Resource
1432 def initialize(name)
1437 def encode_rdata(msg)
1441 def self.decode_rdata(msg)
1442 return self.new(msg.get_name)
1446 # Standard (class generic) RRs
1449 class NS < DomainName
1453 class CNAME < DomainName
1457 class SOA < Resource
1460 def initialize(mname, rname, serial, refresh, retry_, expire, minimum)
1469 attr_reader :mname, :rname, :serial, :refresh, :retry, :expire, :minimum
1471 def encode_rdata(msg)
1472 msg.put_name(@mname)
1473 msg.put_name(@rname)
1474 msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum)
1477 def self.decode_rdata(msg)
1478 mname = msg.get_name
1479 rname = msg.get_name
1480 serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN')
1482 mname, rname, serial, refresh, retry_, expire, minimum)
1486 class PTR < DomainName
1490 class HINFO < Resource
1493 def initialize(cpu, os)
1497 attr_reader :cpu, :os
1499 def encode_rdata(msg)
1500 msg.put_string(@cpu)
1504 def self.decode_rdata(msg)
1505 cpu = msg.get_string
1507 return self.new(cpu, os)
1511 class MINFO < Resource
1514 def initialize(rmailbx, emailbx)
1518 attr_reader :rmailbx, :emailbx
1520 def encode_rdata(msg)
1521 msg.put_name(@rmailbx)
1522 msg.put_name(@emailbx)
1525 def self.decode_rdata(msg)
1526 rmailbx = msg.get_string
1527 emailbx = msg.get_string
1528 return self.new(rmailbx, emailbx)
1535 def initialize(preference, exchange)
1536 @preference = preference
1537 @exchange = exchange
1539 attr_reader :preference, :exchange
1541 def encode_rdata(msg)
1542 msg.put_pack('n', @preference)
1543 msg.put_name(@exchange)
1546 def self.decode_rdata(msg)
1547 preference, = msg.get_unpack('n')
1548 exchange = msg.get_name
1549 return self.new(preference, exchange)
1553 class TXT < Resource
1556 def initialize(first_string, *rest_strings)
1557 @strings = [first_string, *rest_strings]
1559 attr_reader :strings
1565 def encode_rdata(msg)
1566 msg.put_string_list(@strings)
1569 def self.decode_rdata(msg)
1570 strings = msg.get_string_list
1571 return self.new(*strings)
1579 ClassInsensitiveTypes = [
1580 NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, ANY
1583 # ARPA Internet specific RRs
1587 ClassInsensitiveTypes.each {|s|
1589 c.const_set(:TypeValue, s::TypeValue)
1590 c.const_set(:ClassValue, ClassValue)
1591 ClassHash[[s::TypeValue, ClassValue]] = c
1592 self.const_set(s.name.sub(/.*::/, ''), c)
1596 ClassHash[[TypeValue = 1, ClassValue = ClassValue]] = self
1598 def initialize(address)
1599 @address = IPv4.create(address)
1601 attr_reader :address
1603 def encode_rdata(msg)
1604 msg.put_bytes(@address.address)
1607 def self.decode_rdata(msg)
1608 return self.new(IPv4.new(msg.get_bytes(4)))
1612 class WKS < Resource
1613 ClassHash[[TypeValue = 11, ClassValue = ClassValue]] = self
1615 def initialize(address, protocol, bitmap)
1616 @address = IPv4.create(address)
1617 @protocol = protocol
1620 attr_reader :address, :protocol, :bitmap
1622 def encode_rdata(msg)
1623 msg.put_bytes(@address.address)
1624 msg.put_pack("n", @protocol)
1625 msg.put_bytes(@bitmap)
1628 def self.decode_rdata(msg)
1629 address = IPv4.new(msg.get_bytes(4))
1630 protocol, = msg.get_unpack("n")
1631 bitmap = msg.get_bytes
1632 return self.new(address, protocol, bitmap)
1636 class AAAA < Resource
1637 ClassHash[[TypeValue = 28, ClassValue = ClassValue]] = self
1639 def initialize(address)
1640 @address = IPv6.create(address)
1642 attr_reader :address
1644 def encode_rdata(msg)
1645 msg.put_bytes(@address.address)
1648 def self.decode_rdata(msg)
1649 return self.new(IPv6.new(msg.get_bytes(16)))
1653 # SRV resource record defined in RFC 2782
1655 # These records identify the hostname and port that a service is
1659 # _Service._Proto.Name TTL Class SRV Priority Weight Port Target
1661 # The fields specific to SRV are defined in RFC 2782 as meaning:
1662 # - +priority+ The priority of this target host. A client MUST attempt
1663 # to contact the target host with the lowest-numbered priority it can
1664 # reach; target hosts with the same priority SHOULD be tried in an
1665 # order defined by the weight field. The range is 0-65535. Note that
1666 # it is not widely implemented and should be set to zero.
1668 # - +weight+ A server selection mechanism. The weight field specifies
1669 # a relative weight for entries with the same priority. Larger weights
1670 # SHOULD be given a proportionately higher probability of being
1671 # selected. The range of this number is 0-65535. Domain administrators
1672 # SHOULD use Weight 0 when there isn't any server selection to do, to
1673 # make the RR easier to read for humans (less noisy). Note that it is
1674 # not widely implemented and should be set to zero.
1676 # - +port+ The port on this target host of this service. The range is 0-
1679 # - +target+ The domain name of the target host. A target of "." means
1680 # that the service is decidedly not available at this domain.
1681 class SRV < Resource
1682 ClassHash[[TypeValue = 33, ClassValue = ClassValue]] = self
1684 # Create a SRV resource record.
1685 def initialize(priority, weight, port, target)
1686 @priority = priority.to_int
1687 @weight = weight.to_int
1689 @target = Name.create(target)
1692 attr_reader :priority, :weight, :port, :target
1694 def encode_rdata(msg)
1695 msg.put_pack("n", @priority)
1696 msg.put_pack("n", @weight)
1697 msg.put_pack("n", @port)
1698 msg.put_name(@target)
1701 def self.decode_rdata(msg)
1702 priority, = msg.get_unpack("n")
1703 weight, = msg.get_unpack("n")
1704 port, = msg.get_unpack("n")
1705 target = msg.get_name
1706 return self.new(priority, weight, port, target)
1715 Regex = /\A(\d+)\.(\d+)\.(\d+)\.(\d+)\z/
1717 def self.create(arg)
1722 if (0..255) === (a = $1.to_i) &&
1723 (0..255) === (b = $2.to_i) &&
1724 (0..255) === (c = $3.to_i) &&
1725 (0..255) === (d = $4.to_i)
1726 return self.new([a, b, c, d].pack("CCCC"))
1728 raise ArgumentError.new("IPv4 address with invalid value: " + arg)
1731 raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}")
1735 def initialize(address)
1736 unless address.kind_of?(String) && address.length == 4
1737 raise ArgumentError.new('IPv4 address must be 4 bytes')
1741 attr_reader :address
1744 return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC"))
1748 return "#<#{self.class} #{self.to_s}>"
1752 return DNS::Name.create(
1753 '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse)
1757 return @address == other.address
1761 return self == other
1765 return @address.hash
1771 (?:[0-9A-Fa-f]{1,4}:){7}
1775 Regex_CompressedHex = /\A
1776 ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
1777 ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
1780 Regex_6Hex4Dec = /\A
1781 ((?:[0-9A-Fa-f]{1,4}:){6,6})
1782 (\d+)\.(\d+)\.(\d+)\.(\d+)
1785 Regex_CompressedHex4Dec = /\A
1786 ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
1787 ((?:[0-9A-Fa-f]{1,4}:)*)
1788 (\d+)\.(\d+)\.(\d+)\.(\d+)
1793 (?:#{Regex_CompressedHex}) |
1794 (?:#{Regex_6Hex4Dec}) |
1795 (?:#{Regex_CompressedHex4Dec})/x
1797 def self.create(arg)
1803 if Regex_8Hex =~ arg
1804 arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
1805 elsif Regex_CompressedHex =~ arg
1810 prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
1811 suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
1812 omitlen = 16 - a1.length - a2.length
1813 address << a1 << "\0" * omitlen << a2
1814 elsif Regex_6Hex4Dec =~ arg
1815 prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i
1816 if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
1817 prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
1818 address << [a, b, c, d].pack('CCCC')
1820 raise ArgumentError.new("not numeric IPv6 address: " + arg)
1822 elsif Regex_CompressedHex4Dec =~ arg
1823 prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i
1824 if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
1827 prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
1828 suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
1829 omitlen = 12 - a1.length - a2.length
1830 address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC')
1832 raise ArgumentError.new("not numeric IPv6 address: " + arg)
1835 raise ArgumentError.new("not numeric IPv6 address: " + arg)
1837 return IPv6.new(address)
1839 raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}")
1843 def initialize(address)
1844 unless address.kind_of?(String) && address.length == 16
1845 raise ArgumentError.new('IPv6 address must be 16 bytes')
1849 attr_reader :address
1852 address = sprintf("%X:%X:%X:%X:%X:%X:%X:%X", *@address.unpack("nnnnnnnn"))
1853 unless address.sub!(/(^|:)0(:0)+(:|$)/, '::')
1854 address.sub!(/(^|:)0(:|$)/, '::')
1860 return "#<#{self.class} #{self.to_s}>"
1864 # ip6.arpa should be searched too. [RFC3152]
1865 return DNS::Name.new(
1866 @address.unpack("H32")[0].split(//).reverse + ['ip6', 'int'])
1870 return @address == other.address
1874 return self == other
1878 return @address.hash
1882 DefaultResolver = self.new
1883 AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/