* io.c (rb_open_file): encoding in mode string was ignored if perm is
[ruby-svn.git] / lib / resolv.rb
blobd1494b46c93ef0e83663b7c4f39093d482892681
1 require 'socket'
2 require 'fcntl'
3 require 'timeout'
4 require 'thread'
6 begin
7   require 'securerandom'
8 rescue LoadError
9 end
11 # Resolv is a thread-aware DNS resolver library written in Ruby.  Resolv can
12 # handle multiple DNS requests concurrently without blocking.  The ruby
13 # interpreter.
15 # See also resolv-replace.rb to replace the libc resolver with # Resolv.
16
17 # Resolv can look up various DNS resources using the DNS module directly.
18
19 # Examples:
20
21 #   p Resolv.getaddress "www.ruby-lang.org"
22 #   p Resolv.getname "210.251.121.214"
23
24 #   Resolv::DNS.open do |dns|
25 #     ress = dns.getresources "www.ruby-lang.org", Resolv::DNS::Resource::IN::A
26 #     p ress.map { |r| r.address }
27 #     ress = dns.getresources "ruby-lang.org", Resolv::DNS::Resource::IN::MX
28 #     p ress.map { |r| [r.exchange.to_s, r.preference] }
29 #   end
30
31
32 # == Bugs
33
34 # * NIS is not supported.
35 # * /etc/nsswitch.conf is not supported.
37 class Resolv
39   ##
40   # Looks up the first IP address for +name+.
41   
42   def self.getaddress(name)
43     DefaultResolver.getaddress(name)
44   end
46   ##
47   # Looks up all IP address for +name+.
48   
49   def self.getaddresses(name)
50     DefaultResolver.getaddresses(name)
51   end
53   ##
54   # Iterates over all IP addresses for +name+.
56   def self.each_address(name, &block)
57     DefaultResolver.each_address(name, &block)
58   end
60   ##
61   # Looks up the hostname of +address+.
63   def self.getname(address)
64     DefaultResolver.getname(address)
65   end
67   ##
68   # Looks up all hostnames for +address+.
70   def self.getnames(address)
71     DefaultResolver.getnames(address)
72   end
74   ##
75   # Iterates over all hostnames for +address+.
77   def self.each_name(address, &proc)
78     DefaultResolver.each_name(address, &proc)
79   end
81   ##
82   # Creates a new Resolv using +resolvers+.
84   def initialize(resolvers=[Hosts.new, DNS.new])
85     @resolvers = resolvers
86   end
88   ##
89   # Looks up the first IP address for +name+.
90   
91   def getaddress(name)
92     each_address(name) {|address| return address}
93     raise ResolvError.new("no address for #{name}")
94   end
96   ##
97   # Looks up all IP address for +name+.
98   
99   def getaddresses(name)
100     ret = []
101     each_address(name) {|address| ret << address}
102     return ret
103   end
105   ##
106   # Iterates over all IP addresses for +name+.
108   def each_address(name)
109     if AddressRegex =~ name
110       yield name
111       return
112     end
113     yielded = false
114     @resolvers.each {|r|
115       r.each_address(name) {|address|
116         yield address.to_s
117         yielded = true
118       }
119       return if yielded
120     }
121   end
123   ##
124   # Looks up the hostname of +address+.
126   def getname(address)
127     each_name(address) {|name| return name}
128     raise ResolvError.new("no name for #{address}")
129   end
131   ##
132   # Looks up all hostnames for +address+.
134   def getnames(address)
135     ret = []
136     each_name(address) {|name| ret << name}
137     return ret
138   end
140   ##
141   # Iterates over all hostnames for +address+.
143   def each_name(address)
144     yielded = false
145     @resolvers.each {|r|
146       r.each_name(address) {|name|
147         yield name.to_s
148         yielded = true
149       }
150       return if yielded
151     }
152   end
154   ##
155   # Indicates a failure to resolve a name or address.
157   class ResolvError < StandardError; end
159   ##
160   # Indicates a timeout resolving a name or address.
162   class ResolvTimeout < TimeoutError; end
164   ##
165   # DNS::Hosts is a hostname resolver that uses the system hosts file.
167   class Hosts
168     if /mswin32|mingw|bccwin/ =~ RUBY_PLATFORM
169       require 'win32/resolv'
170       DefaultFileName = Win32::Resolv.get_hosts_path
171     else
172       DefaultFileName = '/etc/hosts'
173     end
175     ##
176     # Creates a new DNS::Hosts, using +filename+ for its data source.
178     def initialize(filename = DefaultFileName)
179       @filename = filename
180       @mutex = Mutex.new
181       @initialized = nil
182     end
184     def lazy_initialize # :nodoc:
185       @mutex.synchronize {
186         unless @initialized
187           @name2addr = {}
188           @addr2name = {}
189           open(@filename) {|f|
190             f.each {|line|
191               line.sub!(/#.*/, '')
192               addr, hostname, *aliases = line.split(/\s+/)
193               next unless addr
194               addr.untaint
195               hostname.untaint
196               @addr2name[addr] = [] unless @addr2name.include? addr
197               @addr2name[addr] << hostname
198               @addr2name[addr] += aliases
199               @name2addr[hostname] = [] unless @name2addr.include? hostname
200               @name2addr[hostname] << addr
201               aliases.each {|n|
202                 n.untaint
203                 @name2addr[n] = [] unless @name2addr.include? n
204                 @name2addr[n] << addr
205               }
206             }
207           }
208           @name2addr.each {|name, arr| arr.reverse!}
209           @initialized = true
210         end
211       }
212       self
213     end
215     ##
216     # Gets the IP address of +name+ from the hosts file.
218     def getaddress(name)
219       each_address(name) {|address| return address}
220       raise ResolvError.new("#{@filename} has no name: #{name}")
221     end
223     ##
224     # Gets all IP addresses for +name+ from the hosts file.
226     def getaddresses(name)
227       ret = []
228       each_address(name) {|address| ret << address}
229       return ret
230     end
232     ##
233     # Iterates over all IP addresses for +name+ retrieved from the hosts file.
235     def each_address(name, &proc)
236       lazy_initialize
237       if @name2addr.include?(name)
238         @name2addr[name].each(&proc)
239       end
240     end
242     ##
243     # Gets the hostname of +address+ from the hosts file.
245     def getname(address)
246       each_name(address) {|name| return name}
247       raise ResolvError.new("#{@filename} has no address: #{address}")
248     end
250     ##
251     # Gets all hostnames for +address+ from the hosts file.
253     def getnames(address)
254       ret = []
255       each_name(address) {|name| ret << name}
256       return ret
257     end
259     ##
260     # Iterates over all hostnames for +address+ retrieved from the hosts file.
262     def each_name(address, &proc)
263       lazy_initialize
264       if @addr2name.include?(address)
265         @addr2name[address].each(&proc)
266       end
267     end
268   end
270   ##
271   # Resolv::DNS is a DNS stub resolver.
272   #
273   # Information taken from the following places:
274   #
275   # * STD0013
276   # * RFC 1035
277   # * ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters
278   # * etc.
280   class DNS
282     ##
283     # Default DNS Port
285     Port = 53
287     ##
288     # Default DNS UDP packet size
290     UDPSize = 512
292     ##
293     # Creates a new DNS resolver.  See Resolv::DNS.new for argument details.
294     #
295     # Yields the created DNS resolver to the block, if given, otherwise
296     # returns it.
298     def self.open(*args)
299       dns = new(*args)
300       return dns unless block_given?
301       begin
302         yield dns
303       ensure
304         dns.close
305       end
306     end
308     ##
309     # Creates a new DNS resolver.
310     #
311     # +config_info+ can be:
312     # 
313     # nil:: Uses /etc/resolv.conf.
314     # String:: Path to a file using /etc/resolv.conf's format.
315     # Hash:: Must contain :nameserver, :search and :ndots keys.
316     #
317     # Example:
318     #
319     #   Resolv::DNS.new(:nameserver => ['210.251.121.21'],
320     #                   :search => ['ruby-lang.org'],
321     #                   :ndots => 1)
323     def initialize(config_info=nil)
324       @mutex = Mutex.new
325       @config = Config.new(config_info)
326       @initialized = nil
327     end
329     def lazy_initialize # :nodoc:
330       @mutex.synchronize {
331         unless @initialized
332           @config.lazy_initialize
333           @initialized = true
334         end
335       }
336       self
337     end
339     ##
340     # Closes the DNS resolver.
342     def close
343       @mutex.synchronize {
344         if @initialized
345           @initialized = false
346         end
347       }
348     end
350     ##
351     # Gets the IP address of +name+ from the DNS resolver.
352     #
353     # +name+ can be a Resolv::DNS::Name or a String.  Retrieved address will
354     # be a Resolv::IPv4 or Resolv::IPv6
356     def getaddress(name)
357       each_address(name) {|address| return address}
358       raise ResolvError.new("DNS result has no information for #{name}")
359     end
361     ##
362     # Gets all IP addresses for +name+ from the DNS resolver.
363     #
364     # +name+ can be a Resolv::DNS::Name or a String.  Retrieved addresses will
365     # be a Resolv::IPv4 or Resolv::IPv6
367     def getaddresses(name)
368       ret = []
369       each_address(name) {|address| ret << address}
370       return ret
371     end
373     ##
374     # Iterates over all IP addresses for +name+ retrieved from the DNS
375     # resolver.
376     #
377     # +name+ can be a Resolv::DNS::Name or a String.  Retrieved addresses will
378     # be a Resolv::IPv4 or Resolv::IPv6
380     def each_address(name)
381       each_resource(name, Resource::IN::A) {|resource| yield resource.address}
382       each_resource(name, Resource::IN::AAAA) {|resource| yield resource.address}
383     end
385     ##
386     # Gets the hostname for +address+ from the DNS resolver.
387     #
388     # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String.  Retrieved
389     # name will be a Resolv::DNS::Name.
391     def getname(address)
392       each_name(address) {|name| return name}
393       raise ResolvError.new("DNS result has no information for #{address}")
394     end
396     ##
397     # Gets all hostnames for +address+ from the DNS resolver.
398     #
399     # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String.  Retrieved
400     # names will be Resolv::DNS::Name instances.
402     def getnames(address)
403       ret = []
404       each_name(address) {|name| ret << name}
405       return ret
406     end
408     ##
409     # Iterates over all hostnames for +address+ retrieved from the DNS
410     # resolver.
411     #
412     # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String.  Retrieved
413     # names will be Resolv::DNS::Name instances.
415     def each_name(address)
416       case address
417       when Name
418         ptr = address
419       when IPv4::Regex
420         ptr = IPv4.create(address).to_name
421       when IPv6::Regex
422         ptr = IPv6.create(address).to_name
423       else
424         raise ResolvError.new("cannot interpret as address: #{address}")
425       end
426       each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name}
427     end
429     ##
430     # Look up the +typeclass+ DNS resource of +name+.
431     #
432     # +name+ must be a Resolv::DNS::Name or a String.
433     #
434     # +typeclass+ should be one of the following:
435     #
436     # * Resolv::DNS::Resource::IN::A
437     # * Resolv::DNS::Resource::IN::AAAA
438     # * Resolv::DNS::Resource::IN::ANY
439     # * Resolv::DNS::Resource::IN::CNAME
440     # * Resolv::DNS::Resource::IN::HINFO
441     # * Resolv::DNS::Resource::IN::MINFO
442     # * Resolv::DNS::Resource::IN::MX
443     # * Resolv::DNS::Resource::IN::NS
444     # * Resolv::DNS::Resource::IN::PTR
445     # * Resolv::DNS::Resource::IN::SOA
446     # * Resolv::DNS::Resource::IN::TXT
447     # * Resolv::DNS::Resource::IN::WKS
448     #
449     # Returned resource is represented as a Resolv::DNS::Resource instance,
450     # i.e. Resolv::DNS::Resource::IN::A.
452     def getresource(name, typeclass)
453       each_resource(name, typeclass) {|resource| return resource}
454       raise ResolvError.new("DNS result has no information for #{name}")
455     end
457     ##
458     # Looks up all +typeclass+ DNS resources for +name+.  See #getresource for
459     # argument details.
460   
461     def getresources(name, typeclass)
462       ret = []
463       each_resource(name, typeclass) {|resource| ret << resource}
464       return ret
465     end
467     ##
468     # Iterates over all +typeclass+ DNS resources for +name+.  See
469     # #getresource for argument details.
470   
471     def each_resource(name, typeclass, &proc)
472       lazy_initialize
473       requester = make_requester
474       senders = {}
475       begin
476         @config.resolv(name) {|candidate, tout, nameserver|
477           msg = Message.new
478           msg.rd = 1
479           msg.add_question(candidate, typeclass)
480           unless sender = senders[[candidate, nameserver]]
481             sender = senders[[candidate, nameserver]] =
482               requester.sender(msg, candidate, nameserver)
483           end
484           reply, reply_name = requester.request(sender, tout)
485           case reply.rcode
486           when RCode::NoError
487             extract_resources(reply, reply_name, typeclass, &proc)
488             return
489           when RCode::NXDomain
490             raise Config::NXDomain.new(reply_name.to_s)
491           else
492             raise Config::OtherResolvError.new(reply_name.to_s)
493           end
494         }
495       ensure
496         requester.close
497       end
498     end
500     def make_requester # :nodoc:
501       if nameserver = @config.single?
502         Requester::ConnectedUDP.new(nameserver)
503       else
504         Requester::UnconnectedUDP.new
505       end
506     end
508     def extract_resources(msg, name, typeclass) # :nodoc:
509       if typeclass < Resource::ANY
510         n0 = Name.create(name)
511         msg.each_answer {|n, ttl, data|
512           yield data if n0 == n
513         }
514       end
515       yielded = false
516       n0 = Name.create(name)
517       msg.each_answer {|n, ttl, data|
518         if n0 == n
519           case data
520           when typeclass
521             yield data
522             yielded = true
523           when Resource::CNAME
524             n0 = data.name
525           end
526         end
527       }
528       return if yielded
529       msg.each_answer {|n, ttl, data|
530         if n0 == n
531           case data
532           when typeclass
533             yield data
534           end
535         end
536       }
537     end
539     if defined? SecureRandom
540       def self.random(arg) # :nodoc:
541         begin
542           SecureRandom.random_number(arg)
543         rescue NotImplementedError
544           rand(arg)
545         end
546       end
547     else
548       def self.random(arg) # :nodoc:
549         rand(arg)
550       end
551     end
554     def self.rangerand(range) # :nodoc:
555       base = range.begin
556       len = range.end - range.begin
557       if !range.exclude_end?
558         len += 1
559       end
560       base + random(len)
561     end
563     RequestID = {}
564     RequestIDMutex = Mutex.new
566     def self.allocate_request_id(host, port) # :nodoc:
567       id = nil
568       RequestIDMutex.synchronize {
569         h = (RequestID[[host, port]] ||= {})
570         begin
571           id = rangerand(0x0000..0xffff)
572         end while h[id] 
573         h[id] = true
574       }
575       id
576     end
578     def self.free_request_id(host, port, id) # :nodoc:
579       RequestIDMutex.synchronize {
580         key = [host, port]
581         if h = RequestID[key]
582           h.delete id
583           if h.empty?
584             RequestID.delete key
585           end
586         end
587       }
588     end
590     def self.bind_random_port(udpsock) # :nodoc:
591       begin
592         port = rangerand(1024..65535)
593         udpsock.bind("", port)
594       rescue Errno::EADDRINUSE
595         retry
596       end
597     end
599     class Requester # :nodoc:
600       def initialize
601         @senders = {}
602         @sock = nil
603       end
605       def request(sender, tout)
606         timelimit = Time.now + tout
607         sender.send
608         while (now = Time.now) < timelimit
609           timeout = timelimit - now
610           if !IO.select([@sock], nil, nil, timeout)
611             raise ResolvTimeout
612           end
613           reply, from = recv_reply
614           begin
615             msg = Message.decode(reply)
616           rescue DecodeError
617             next # broken DNS message ignored
618           end
619           if s = @senders[[from,msg.id]]
620             break
621           else
622             # unexpected DNS message ignored
623           end
624         end
625         return msg, s.data
626       end
628       def close
629         sock = @sock
630         @sock = nil
631         sock.close if sock
632       end
634       class Sender # :nodoc:
635         def initialize(msg, data, sock)
636           @msg = msg
637           @data = data
638           @sock = sock
639         end
640       end
642       class UnconnectedUDP < Requester # :nodoc:
643         def initialize
644           super()
645           @sock = UDPSocket.new
646           @sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined? Fcntl::F_SETFD
647           DNS.bind_random_port(@sock)
648         end
650         def recv_reply
651           reply, from = @sock.recvfrom(UDPSize)
652           return reply, [from[3],from[1]]
653         end
655         def sender(msg, data, host, port=Port)
656           service = [host, port]
657           id = DNS.allocate_request_id(host, port)
658           request = msg.encode
659           request[0,2] = [id].pack('n')
660           return @senders[[service, id]] =
661             Sender.new(request, data, @sock, host, port)
662         end
664         def close
665           super
666           @senders.each_key {|service, id|
667             DNS.free_request_id(service[0], service[1], id)
668           }
669         end
671         class Sender < Requester::Sender # :nodoc:
672           def initialize(msg, data, sock, host, port)
673             super(msg, data, sock)
674             @host = host
675             @port = port
676           end
677           attr_reader :data
679           def send
680             @sock.send(@msg, 0, @host, @port)
681           end
682         end
683       end
685       class ConnectedUDP < Requester # :nodoc:
686         def initialize(host, port=Port)
687           super()
688           @host = host
689           @port = port
690           @sock = UDPSocket.new(host.index(':') ? Socket::AF_INET6 : Socket::AF_INET)
691           DNS.bind_random_port(@sock)
692           @sock.connect(host, port)
693           @sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined? Fcntl::F_SETFD
694         end
696         def recv_reply
697           reply = @sock.recv(UDPSize)
698           return reply, nil
699         end
701         def sender(msg, data, host=@host, port=@port)
702           unless host == @host && port == @port
703             raise RequestError.new("host/port don't match: #{host}:#{port}")
704           end
705           id = DNS.allocate_request_id(@host, @port)
706           request = msg.encode
707           request[0,2] = [id].pack('n')
708           return @senders[[nil,id]] = Sender.new(request, data, @sock)
709         end
711         def close
712           super
713           @senders.each_key {|from, id|
714             DNS.free_request_id(@host, @port, id)
715           }
716         end
718         class Sender < Requester::Sender # :nodoc:
719           def send
720             @sock.send(@msg, 0)
721           end
722           attr_reader :data
723         end
724       end
726       class TCP < Requester # :nodoc:
727         def initialize(host, port=Port)
728           super()
729           @host = host
730           @port = port
731           @sock = TCPSocket.new(@host, @port)
732           @sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined? Fcntl::F_SETFD
733           @senders = {}
734         end
736         def recv_reply
737           len = @sock.read(2).unpack('n')[0]
738           reply = @sock.read(len)
739           return reply, nil
740         end
742         def sender(msg, data, host=@host, port=@port)
743           unless host == @host && port == @port
744             raise RequestError.new("host/port don't match: #{host}:#{port}")
745           end
746           id = DNS.allocate_request_id(@host, @port)
747           request = msg.encode
748           request[0,2] = [request.length, id].pack('nn')
749           return @senders[[nil,id]] = Sender.new(request, data, @sock)
750         end
752         class Sender < Requester::Sender # :nodoc:
753           def send
754             @sock.print(@msg)
755             @sock.flush
756           end
757           attr_reader :data
758         end
760         def close
761           super
762           @senders.each_key {|from,id|
763             DNS.free_request_id(@host, @port, id)
764           }
765         end
766       end
768       ##
769       # Indicates a problem with the DNS request.
771       class RequestError < StandardError
772       end
773     end
775     class Config # :nodoc:
776       def initialize(config_info=nil)
777         @mutex = Mutex.new
778         @config_info = config_info
779         @initialized = nil
780       end
782       def Config.parse_resolv_conf(filename)
783         nameserver = []
784         search = nil
785         ndots = 1
786         open(filename) {|f|
787           f.each {|line|
788             line.sub!(/[#;].*/, '')
789             keyword, *args = line.split(/\s+/)
790             args.each { |arg|
791               arg.untaint
792             }
793             next unless keyword
794             case keyword
795             when 'nameserver'
796               nameserver += args
797             when 'domain'
798               next if args.empty?
799               search = [args[0]]
800             when 'search'
801               next if args.empty?
802               search = args
803             when 'options'
804               args.each {|arg|
805                 case arg
806                 when /\Andots:(\d+)\z/
807                   ndots = $1.to_i
808                 end
809               }
810             end
811           }
812         }
813         return { :nameserver => nameserver, :search => search, :ndots => ndots }
814       end
816       def Config.default_config_hash(filename="/etc/resolv.conf")
817         if File.exist? filename
818           config_hash = Config.parse_resolv_conf(filename)
819         else
820           if /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
821             require 'win32/resolv'
822             search, nameserver = Win32::Resolv.get_resolv_info
823             config_hash = {}
824             config_hash[:nameserver] = nameserver if nameserver
825             config_hash[:search] = [search].flatten if search
826           end
827         end
828         config_hash
829       end
831       def lazy_initialize
832         @mutex.synchronize {
833           unless @initialized
834             @nameserver = []
835             @search = nil
836             @ndots = 1
837             case @config_info
838             when nil
839               config_hash = Config.default_config_hash
840             when String
841               config_hash = Config.parse_resolv_conf(@config_info)
842             when Hash
843               config_hash = @config_info.dup
844               if String === config_hash[:nameserver]
845                 config_hash[:nameserver] = [config_hash[:nameserver]]
846               end
847               if String === config_hash[:search]
848                 config_hash[:search] = [config_hash[:search]]
849               end
850             else
851               raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}")
852             end
853             @nameserver = config_hash[:nameserver] if config_hash.include? :nameserver
854             @search = config_hash[:search] if config_hash.include? :search
855             @ndots = config_hash[:ndots] if config_hash.include? :ndots
857             @nameserver = ['0.0.0.0'] if @nameserver.empty?
858             if @search
859               @search = @search.map {|arg| Label.split(arg) }
860             else
861               hostname = Socket.gethostname
862               if /\./ =~ hostname
863                 @search = [Label.split($')]
864               else
865                 @search = [[]]
866               end
867             end
869             if !@nameserver.kind_of?(Array) ||
870                !@nameserver.all? {|ns| String === ns }
871               raise ArgumentError.new("invalid nameserver config: #{@nameserver.inspect}")
872             end
874             if !@search.kind_of?(Array) ||
875                !@search.all? {|ls| ls.all? {|l| Label::Str === l } }
876               raise ArgumentError.new("invalid search config: #{@search.inspect}")
877             end
879             if !@ndots.kind_of?(Integer)
880               raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}")
881             end
883             @initialized = true
884           end
885         }
886         self
887       end
889       def single?
890         lazy_initialize
891         if @nameserver.length == 1
892           return @nameserver[0]
893         else
894           return nil
895         end
896       end
898       def generate_candidates(name)
899         candidates = nil
900         name = Name.create(name)
901         if name.absolute?
902           candidates = [name]
903         else
904           if @ndots <= name.length - 1
905             candidates = [Name.new(name.to_a)]
906           else
907             candidates = []
908           end
909           candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)})
910         end
911         return candidates
912       end
914       InitialTimeout = 5
916       def generate_timeouts
917         ts = [InitialTimeout]
918         ts << ts[-1] * 2 / @nameserver.length
919         ts << ts[-1] * 2
920         ts << ts[-1] * 2
921         return ts
922       end
924       def resolv(name)
925         candidates = generate_candidates(name)
926         timeouts = generate_timeouts
927         begin
928           candidates.each {|candidate|
929             begin
930               timeouts.each {|tout|
931                 @nameserver.each {|nameserver|
932                   begin
933                     yield candidate, tout, nameserver
934                   rescue ResolvTimeout
935                   end
936                 }
937               }
938               raise ResolvError.new("DNS resolv timeout: #{name}")
939             rescue NXDomain
940             end
941           }
942         rescue ResolvError
943         end
944       end
946       ##
947       # Indicates no such domain was found.
949       class NXDomain < ResolvError
950       end
952       ##
953       # Indicates some other unhandled resolver error was encountered.
955       class OtherResolvError < ResolvError
956       end
957     end
959     module OpCode # :nodoc:
960       Query = 0
961       IQuery = 1
962       Status = 2
963       Notify = 4
964       Update = 5
965     end
967     module RCode # :nodoc:
968       NoError = 0
969       FormErr = 1
970       ServFail = 2
971       NXDomain = 3
972       NotImp = 4
973       Refused = 5
974       YXDomain = 6
975       YXRRSet = 7
976       NXRRSet = 8
977       NotAuth = 9
978       NotZone = 10
979       BADVERS = 16
980       BADSIG = 16
981       BADKEY = 17
982       BADTIME = 18
983       BADMODE = 19
984       BADNAME = 20
985       BADALG = 21
986     end
988     ##
989     # Indicates that the DNS response was unable to be decoded.
991     class DecodeError < StandardError
992     end
994     ##
995     # Indicates that the DNS request was unable to be encoded.
997     class EncodeError < StandardError
998     end
1000     module Label # :nodoc:
1001       def self.split(arg)
1002         labels = []
1003         arg.scan(/[^\.]+/) {labels << Str.new($&)}
1004         return labels
1005       end
1007       class Str # :nodoc:
1008         def initialize(string)
1009           @string = string
1010           @downcase = string.downcase
1011         end
1012         attr_reader :string, :downcase
1014         def to_s
1015           return @string
1016         end
1018         def inspect
1019           return "#<#{self.class} #{self.to_s}>"
1020         end
1022         def ==(other)
1023           return @downcase == other.downcase
1024         end
1026         def eql?(other)
1027           return self == other
1028         end
1030         def hash
1031           return @downcase.hash
1032         end
1033       end
1034     end
1036     ##
1037     # A representation of a DNS name.
1039     class Name
1040       
1041       ##
1042       # Creates a new DNS name from +arg+.  +arg+ can be:
1043       #
1044       # Name:: returns +arg+.
1045       # String:: Creates a new Name.
1047       def self.create(arg)
1048         case arg
1049         when Name
1050           return arg
1051         when String
1052           return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
1053         else
1054           raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
1055         end
1056       end
1058       def initialize(labels, absolute=true) # :nodoc:
1059         @labels = labels
1060         @absolute = absolute
1061       end
1063       def inspect # :nodoc:
1064         "#<#{self.class}: #{self.to_s}#{@absolute ? '.' : ''}>"
1065       end
1067       ##
1068       # True if this name is absolute.
1070       def absolute?
1071         return @absolute
1072       end
1074       def ==(other) # :nodoc:
1075         return false unless Name === other
1076         return @labels.join == other.to_a.join && @absolute == other.absolute?
1077       end
1079       alias eql? == # :nodoc:
1081       ##
1082       # Returns true if +other+ is a subdomain.
1083       #
1084       # Example:
1085       #
1086       #   domain = Resolv::DNS::Name.create("y.z")
1087       #   p Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true
1088       #   p Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true
1089       #   p Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false
1090       #   p Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false
1091       #   p Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false
1092       #   p Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false
1093       #
1095       def subdomain_of?(other)
1096         raise ArgumentError, "not a domain name: #{other.inspect}" unless Name === other
1097         return false if @absolute != other.absolute?
1098         other_len = other.length
1099         return false if @labels.length <= other_len
1100         return @labels[-other_len, other_len] == other.to_a
1101       end
1103       def hash # :nodoc:
1104         return @labels.hash ^ @absolute.hash
1105       end
1107       def to_a # :nodoc:
1108         return @labels
1109       end
1111       def length # :nodoc:
1112         return @labels.length
1113       end
1115       def [](i) # :nodoc:
1116         return @labels[i]
1117       end
1119       ##
1120       # returns the domain name as a string.
1121       #
1122       # The domain name doesn't have a trailing dot even if the name object is
1123       # absolute.
1124       #
1125       # Example:
1126       #
1127       #   p Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z"
1128       #   p Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z"
1130       def to_s
1131         return @labels.join('.')
1132       end
1133     end
1135     class Message # :nodoc:
1136       @@identifier = -1
1138       def initialize(id = (@@identifier += 1) & 0xffff)
1139         @id = id
1140         @qr = 0
1141         @opcode = 0
1142         @aa = 0
1143         @tc = 0
1144         @rd = 0 # recursion desired
1145         @ra = 0 # recursion available
1146         @rcode = 0
1147         @question = []
1148         @answer = []
1149         @authority = []
1150         @additional = []
1151       end
1153       attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode
1154       attr_reader :question, :answer, :authority, :additional
1156       def ==(other)
1157         return @id == other.id &&
1158                @qr == other.qr &&
1159                @opcode == other.opcode &&
1160                @aa == other.aa &&
1161                @tc == other.tc &&
1162                @rd == other.rd &&
1163                @ra == other.ra &&
1164                @rcode == other.rcode &&
1165                @question == other.question &&
1166                @answer == other.answer &&
1167                @authority == other.authority &&
1168                @additional == other.additional
1169       end
1171       def add_question(name, typeclass)
1172         @question << [Name.create(name), typeclass]
1173       end
1175       def each_question
1176         @question.each {|name, typeclass|
1177           yield name, typeclass
1178         }
1179       end
1181       def add_answer(name, ttl, data)
1182         @answer << [Name.create(name), ttl, data]
1183       end
1185       def each_answer
1186         @answer.each {|name, ttl, data|
1187           yield name, ttl, data
1188         }
1189       end
1191       def add_authority(name, ttl, data)
1192         @authority << [Name.create(name), ttl, data]
1193       end
1195       def each_authority
1196         @authority.each {|name, ttl, data|
1197           yield name, ttl, data
1198         }
1199       end
1201       def add_additional(name, ttl, data)
1202         @additional << [Name.create(name), ttl, data]
1203       end
1205       def each_additional
1206         @additional.each {|name, ttl, data|
1207           yield name, ttl, data
1208         }
1209       end
1211       def each_resource
1212         each_answer {|name, ttl, data| yield name, ttl, data}
1213         each_authority {|name, ttl, data| yield name, ttl, data}
1214         each_additional {|name, ttl, data| yield name, ttl, data}
1215       end
1217       def encode
1218         return MessageEncoder.new {|msg|
1219           msg.put_pack('nnnnnn',
1220             @id,
1221             (@qr & 1) << 15 |
1222             (@opcode & 15) << 11 |
1223             (@aa & 1) << 10 |
1224             (@tc & 1) << 9 |
1225             (@rd & 1) << 8 |
1226             (@ra & 1) << 7 |
1227             (@rcode & 15),
1228             @question.length,
1229             @answer.length,
1230             @authority.length,
1231             @additional.length)
1232           @question.each {|q|
1233             name, typeclass = q
1234             msg.put_name(name)
1235             msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue)
1236           }
1237           [@answer, @authority, @additional].each {|rr|
1238             rr.each {|r|
1239               name, ttl, data = r
1240               msg.put_name(name)
1241               msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl)
1242               msg.put_length16 {data.encode_rdata(msg)}
1243             }
1244           }
1245         }.to_s
1246       end
1248       class MessageEncoder # :nodoc:
1249         def initialize
1250           @data = ''
1251           @names = {}
1252           yield self
1253         end
1255         def to_s
1256           return @data
1257         end
1259         def put_bytes(d)
1260           @data << d
1261         end
1263         def put_pack(template, *d)
1264           @data << d.pack(template)
1265         end
1267         def put_length16
1268           length_index = @data.length
1269           @data << "\0\0"
1270           data_start = @data.length
1271           yield
1272           data_end = @data.length
1273           @data[length_index, 2] = [data_end - data_start].pack("n")
1274         end
1276         def put_string(d)
1277           self.put_pack("C", d.length)
1278           @data << d
1279         end
1281         def put_string_list(ds)
1282           ds.each {|d|
1283             self.put_string(d)
1284           }
1285         end
1287         def put_name(d)
1288           put_labels(d.to_a)
1289         end
1291         def put_labels(d)
1292           d.each_index {|i|
1293             domain = d[i..-1]
1294             if idx = @names[domain]
1295               self.put_pack("n", 0xc000 | idx)
1296               return
1297             else
1298               @names[domain] = @data.length
1299               self.put_label(d[i])
1300             end
1301           }
1302           @data << "\0"
1303         end
1305         def put_label(d)
1306           self.put_string(d.to_s)
1307         end
1308       end
1310       def Message.decode(m)
1311         o = Message.new(0)
1312         MessageDecoder.new(m) {|msg|
1313           id, flag, qdcount, ancount, nscount, arcount =
1314             msg.get_unpack('nnnnnn')
1315           o.id = id
1316           o.qr = (flag >> 15) & 1
1317           o.opcode = (flag >> 11) & 15
1318           o.aa = (flag >> 10) & 1
1319           o.tc = (flag >> 9) & 1
1320           o.rd = (flag >> 8) & 1
1321           o.ra = (flag >> 7) & 1
1322           o.rcode = flag & 15
1323           (1..qdcount).each {
1324             name, typeclass = msg.get_question
1325             o.add_question(name, typeclass)
1326           }
1327           (1..ancount).each {
1328             name, ttl, data = msg.get_rr
1329             o.add_answer(name, ttl, data)
1330           }
1331           (1..nscount).each {
1332             name, ttl, data = msg.get_rr
1333             o.add_authority(name, ttl, data)
1334           }
1335           (1..arcount).each {
1336             name, ttl, data = msg.get_rr
1337             o.add_additional(name, ttl, data)
1338           }
1339         }
1340         return o
1341       end
1343       class MessageDecoder # :nodoc:
1344         def initialize(data)
1345           @data = data
1346           @index = 0
1347           @limit = data.length
1348           yield self
1349         end
1351         def get_length16
1352           len, = self.get_unpack('n')
1353           save_limit = @limit
1354           @limit = @index + len
1355           d = yield(len)
1356           if @index < @limit
1357             raise DecodeError.new("junk exists")
1358           elsif @limit < @index
1359             raise DecodeError.new("limit exceeded")
1360           end
1361           @limit = save_limit
1362           return d
1363         end
1365         def get_bytes(len = @limit - @index)
1366           d = @data[@index, len]
1367           @index += len
1368           return d
1369         end
1371         def get_unpack(template)
1372           len = 0
1373           template.each_byte {|byte|
1374             byte = "%c" % byte
1375             case byte
1376             when ?c, ?C
1377               len += 1
1378             when ?n
1379               len += 2
1380             when ?N
1381               len += 4
1382             else
1383               raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'")
1384             end
1385           }
1386           raise DecodeError.new("limit exceeded") if @limit < @index + len
1387           arr = @data.unpack("@#{@index}#{template}")
1388           @index += len
1389           return arr
1390         end
1392         def get_string
1393           len = @data[@index].ord
1394           raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len
1395           d = @data[@index + 1, len]
1396           @index += 1 + len
1397           return d
1398         end
1400         def get_string_list
1401           strings = []
1402           while @index < @limit
1403             strings << self.get_string
1404           end
1405           strings
1406         end
1408         def get_name
1409           return Name.new(self.get_labels)
1410         end
1412         def get_labels(limit=nil)
1413           limit = @index if !limit || @index < limit
1414           d = []
1415           while true
1416             case @data[@index].ord
1417             when 0
1418               @index += 1
1419               return d
1420             when 192..255
1421               idx = self.get_unpack('n')[0] & 0x3fff
1422               if limit <= idx
1423                 raise DecodeError.new("non-backward name pointer")
1424               end
1425               save_index = @index
1426               @index = idx
1427               d += self.get_labels(limit)
1428               @index = save_index
1429               return d
1430             else
1431               d << self.get_label
1432             end
1433           end
1434           return d
1435         end
1437         def get_label
1438           return Label::Str.new(self.get_string)
1439         end
1441         def get_question
1442           name = self.get_name
1443           type, klass = self.get_unpack("nn")
1444           return name, Resource.get_class(type, klass)
1445         end
1447         def get_rr
1448           name = self.get_name
1449           type, klass, ttl = self.get_unpack('nnN')
1450           typeclass = Resource.get_class(type, klass)
1451           res = self.get_length16 { typeclass.decode_rdata self }
1452           res.instance_variable_set :@ttl, ttl
1453           return name, ttl, res
1454         end
1455       end
1456     end
1458     ##
1459     # A DNS query abstract class.
1461     class Query
1462       def encode_rdata(msg) # :nodoc:
1463         raise EncodeError.new("#{self.class} is query.") 
1464       end
1466       def self.decode_rdata(msg) # :nodoc:
1467         raise DecodeError.new("#{self.class} is query.") 
1468       end
1469     end
1471     ##
1472     # A DNS resource abstract class.
1474     class Resource < Query
1476       ##
1477       # Remaining Time To Live for this Resource.
1479       attr_reader :ttl
1481       ClassHash = {} # :nodoc:
1483       def encode_rdata(msg) # :nodoc:
1484         raise NotImplementedError.new
1485       end
1487       def self.decode_rdata(msg) # :nodoc:
1488         raise NotImplementedError.new
1489       end
1491       def ==(other) # :nodoc:
1492         return false unless self.class == other.class
1493         s_ivars = self.instance_variables
1494         s_ivars.sort!
1495         s_ivars.delete "@ttl"
1496         o_ivars = other.instance_variables
1497         o_ivars.sort!
1498         o_ivars.delete "@ttl"
1499         return s_ivars == o_ivars &&
1500           s_ivars.collect {|name| self.instance_variable_get name} ==
1501             o_ivars.collect {|name| other.instance_variable_get name}
1502       end
1504       def eql?(other) # :nodoc:
1505         return self == other
1506       end
1508       def hash # :nodoc:
1509         h = 0
1510         vars = self.instance_variables
1511         vars.delete "@ttl"
1512         vars.each {|name|
1513           h ^= self.instance_variable_get(name).hash
1514         }
1515         return h
1516       end
1518       def self.get_class(type_value, class_value) # :nodoc:
1519         return ClassHash[[type_value, class_value]] ||
1520                Generic.create(type_value, class_value)
1521       end
1523       ##
1524       # A generic resource abstract class.
1526       class Generic < Resource
1528         ##
1529         # Creates a new generic resource.
1531         def initialize(data)
1532           @data = data
1533         end
1535         ##
1536         # Data for this generic resource.
1538         attr_reader :data
1540         def encode_rdata(msg) # :nodoc:
1541           msg.put_bytes(data)
1542         end
1544         def self.decode_rdata(msg) # :nodoc:
1545           return self.new(msg.get_bytes)
1546         end
1548         def self.create(type_value, class_value) # :nodoc:
1549           c = Class.new(Generic)
1550           c.const_set(:TypeValue, type_value)
1551           c.const_set(:ClassValue, class_value)
1552           Generic.const_set("Type#{type_value}_Class#{class_value}", c)
1553           ClassHash[[type_value, class_value]] = c
1554           return c
1555         end
1556       end
1558       ##
1559       # Domain Name resource abstract class.
1561       class DomainName < Resource
1563         ##
1564         # Creates a new DomainName from +name+.
1566         def initialize(name)
1567           @name = name
1568         end
1570         ##
1571         # The name of this DomainName.
1573         attr_reader :name
1575         def encode_rdata(msg) # :nodoc:
1576           msg.put_name(@name)
1577         end
1579         def self.decode_rdata(msg) # :nodoc:
1580           return self.new(msg.get_name)
1581         end
1582       end
1584       # Standard (class generic) RRs
1586       ClassValue = nil # :nodoc:
1588       ##
1589       # An authoritative name server.
1591       class NS < DomainName
1592         TypeValue = 2 # :nodoc:
1593       end
1595       ##
1596       # The canonical name for an alias.
1598       class CNAME < DomainName
1599         TypeValue = 5 # :nodoc:
1600       end
1602       ##
1603       # Start Of Authority resource.
1605       class SOA < Resource
1607         TypeValue = 6 # :nodoc:
1609         ##
1610         # Creates a new SOA record.  See the attr documentation for the
1611         # details of each argument.
1613         def initialize(mname, rname, serial, refresh, retry_, expire, minimum)
1614           @mname = mname
1615           @rname = rname
1616           @serial = serial
1617           @refresh = refresh
1618           @retry = retry_
1619           @expire = expire
1620           @minimum = minimum
1621         end
1623         ##
1624         # Name of the host where the master zone file for this zone resides.
1626         attr_reader :mname
1628         ##
1629         # The person responsible for this domain name.
1631         attr_reader :rname
1633         ##
1634         # The version number of the zone file.
1636         attr_reader :serial
1638         ##
1639         # How often, in seconds, a secondary name server is to check for
1640         # updates from the primary name server.
1642         attr_reader :refresh
1644         ##
1645         # How often, in seconds, a secondary name server is to retry after a
1646         # failure to check for a refresh.
1648         attr_reader :retry
1650         ##
1651         # Time in seconds that a secondary name server is to use the data
1652         # before refreshing from the primary name server.
1654         attr_reader :expire
1656         ##
1657         # The minimum number of seconds to be used for TTL values in RRs.
1659         attr_reader :minimum
1661         def encode_rdata(msg) # :nodoc:
1662           msg.put_name(@mname)
1663           msg.put_name(@rname)
1664           msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum)
1665         end
1667         def self.decode_rdata(msg) # :nodoc:
1668           mname = msg.get_name
1669           rname = msg.get_name
1670           serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN')
1671           return self.new(
1672             mname, rname, serial, refresh, retry_, expire, minimum)
1673         end
1674       end
1676       ##
1677       # A Pointer to another DNS name.
1679       class PTR < DomainName
1680         TypeValue = 12 # :nodoc:
1681       end
1683       ##
1684       # Host Information resource.
1686       class HINFO < Resource
1688         TypeValue = 13 # :nodoc:
1690         ##
1691         # Creates a new HINFO running +os+ on +cpu+.
1693         def initialize(cpu, os)
1694           @cpu = cpu
1695           @os = os
1696         end
1698         ##
1699         # CPU architecture for this resource.
1701         attr_reader :cpu
1703         ##
1704         # Operating system for this resource.
1706         attr_reader :os
1708         def encode_rdata(msg) # :nodoc:
1709           msg.put_string(@cpu)
1710           msg.put_string(@os)
1711         end
1713         def self.decode_rdata(msg) # :nodoc:
1714           cpu = msg.get_string
1715           os = msg.get_string
1716           return self.new(cpu, os)
1717         end
1718       end
1720       ##
1721       # Mailing list or mailbox information.
1723       class MINFO < Resource
1725         TypeValue = 14 # :nodoc:
1727         def initialize(rmailbx, emailbx)
1728           @rmailbx = rmailbx
1729           @emailbx = emailbx
1730         end
1732         ##
1733         # Domain name responsible for this mail list or mailbox.
1735         attr_reader :rmailbx
1737         ##
1738         # Mailbox to use for error messages related to the mail list or mailbox.
1740         attr_reader :emailbx
1742         def encode_rdata(msg) # :nodoc:
1743           msg.put_name(@rmailbx)
1744           msg.put_name(@emailbx)
1745         end
1747         def self.decode_rdata(msg) # :nodoc:
1748           rmailbx = msg.get_string
1749           emailbx = msg.get_string
1750           return self.new(rmailbx, emailbx)
1751         end
1752       end
1754       ##
1755       # Mail Exchanger resource.
1757       class MX < Resource
1759         TypeValue= 15 # :nodoc:
1761         ##
1762         # Creates a new MX record with +preference+, accepting mail at
1763         # +exchange+.
1765         def initialize(preference, exchange)
1766           @preference = preference
1767           @exchange = exchange
1768         end
1770         ##
1771         # The preference for this MX.
1773         attr_reader :preference
1775         ##
1776         # The host of this MX.
1778         attr_reader :exchange
1780         def encode_rdata(msg) # :nodoc:
1781           msg.put_pack('n', @preference)
1782           msg.put_name(@exchange)
1783         end
1785         def self.decode_rdata(msg) # :nodoc:
1786           preference, = msg.get_unpack('n')
1787           exchange = msg.get_name
1788           return self.new(preference, exchange)
1789         end
1790       end
1792       ##
1793       # Unstructured text resource.
1795       class TXT < Resource
1797         TypeValue = 16 # :nodoc:
1799         def initialize(first_string, *rest_strings)
1800           @strings = [first_string, *rest_strings]
1801         end
1803         ##
1804         # Returns an Array of Strings for this TXT record.
1806         attr_reader :strings
1808         ##
1809         # Returns the first string from +strings+.
1811         def data
1812           @strings[0]
1813         end
1815         def encode_rdata(msg) # :nodoc:
1816           msg.put_string_list(@strings)
1817         end
1819         def self.decode_rdata(msg) # :nodoc:
1820           strings = msg.get_string_list
1821           return self.new(*strings)
1822         end
1823       end
1825       ##
1826       # A Query type requesting any RR.
1828       class ANY < Query
1829         TypeValue = 255 # :nodoc:
1830       end
1832       ClassInsensitiveTypes = [ # :nodoc:
1833         NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, ANY
1834       ]
1836       ##
1837       # module IN contains ARPA Internet specific RRs.
1839       module IN
1841         ClassValue = 1 # :nodoc:
1843         ClassInsensitiveTypes.each {|s|
1844           c = Class.new(s)
1845           c.const_set(:TypeValue, s::TypeValue)
1846           c.const_set(:ClassValue, ClassValue)
1847           ClassHash[[s::TypeValue, ClassValue]] = c
1848           self.const_set(s.name.sub(/.*::/, ''), c)
1849         }
1851         ##
1852         # IPv4 Address resource
1854         class A < Resource
1855           TypeValue = 1
1856           ClassValue = IN::ClassValue
1857           ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
1859           ##
1860           # Creates a new A for +address+.
1862           def initialize(address)
1863             @address = IPv4.create(address)
1864           end
1866           ##
1867           # The Resolv::IPv4 address for this A.
1869           attr_reader :address
1871           def encode_rdata(msg) # :nodoc:
1872             msg.put_bytes(@address.address)
1873           end
1875           def self.decode_rdata(msg) # :nodoc:
1876             return self.new(IPv4.new(msg.get_bytes(4)))
1877           end
1878         end
1880         ##
1881         # Well Known Service resource.
1883         class WKS < Resource
1884           TypeValue = 11
1885           ClassValue = IN::ClassValue
1886           ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
1888           def initialize(address, protocol, bitmap)
1889             @address = IPv4.create(address)
1890             @protocol = protocol
1891             @bitmap = bitmap
1892           end
1894           ##
1895           # The host these services run on.
1897           attr_reader :address
1899           ##
1900           # IP protocol number for these services.
1902           attr_reader :protocol
1904           ##
1905           # A bit map of enabled services on this host.
1906           #
1907           # If protocol is 6 (TCP) then the 26th bit corresponds to the SMTP
1908           # service (port 25).  If this bit is set, then an SMTP server should
1909           # be listening on TCP port 25; if zero, SMTP service is not
1910           # supported.
1912           attr_reader :bitmap
1914           def encode_rdata(msg) # :nodoc:
1915             msg.put_bytes(@address.address)
1916             msg.put_pack("n", @protocol)
1917             msg.put_bytes(@bitmap)
1918           end
1920           def self.decode_rdata(msg) # :nodoc:
1921             address = IPv4.new(msg.get_bytes(4))
1922             protocol, = msg.get_unpack("n")
1923             bitmap = msg.get_bytes
1924             return self.new(address, protocol, bitmap)
1925           end
1926         end
1928         ##
1929         # An IPv6 address record.
1931         class AAAA < Resource
1932           TypeValue = 28
1933           ClassValue = IN::ClassValue
1934           ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
1936           ##
1937           # Creates a new AAAA for +address+.
1939           def initialize(address)
1940             @address = IPv6.create(address)
1941           end
1942           
1943           ##
1944           # The Resolv::IPv6 address for this AAAA.
1946           attr_reader :address
1948           def encode_rdata(msg) # :nodoc:
1949             msg.put_bytes(@address.address)
1950           end
1952           def self.decode_rdata(msg) # :nodoc:
1953             return self.new(IPv6.new(msg.get_bytes(16)))
1954           end
1955         end
1957         ##
1958         # SRV resource record defined in RFC 2782
1959         # 
1960         # These records identify the hostname and port that a service is
1961         # available at.
1963         class SRV < Resource
1964           TypeValue = 33
1965           ClassValue = IN::ClassValue
1966           ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
1968           # Create a SRV resource record.
1969           #
1970           # See the documentation for #priority, #weight, #port and #target
1971           # for +priority+, +weight+, +port and +target+ respectively.
1973           def initialize(priority, weight, port, target)
1974             @priority = priority.to_int
1975             @weight = weight.to_int
1976             @port = port.to_int
1977             @target = Name.create(target)
1978           end
1980           # The priority of this target host.
1981           #
1982           # A client MUST attempt to contact the target host with the
1983           # lowest-numbered priority it can reach; target hosts with the same
1984           # priority SHOULD be tried in an order defined by the weight field.
1985           # The range is 0-65535.  Note that it is not widely implemented and
1986           # should be set to zero.
1988           attr_reader :priority
1990           # A server selection mechanism.
1991           #
1992           # The weight field specifies a relative weight for entries with the
1993           # same priority. Larger weights SHOULD be given a proportionately
1994           # higher probability of being selected. The range of this number is
1995           # 0-65535.  Domain administrators SHOULD use Weight 0 when there
1996           # isn't any server selection to do, to make the RR easier to read
1997           # for humans (less noisy). Note that it is not widely implemented
1998           # and should be set to zero.
2000           attr_reader :weight
2002           # The port on this target host of this service.
2003           #
2004           # The range is 0-65535.
2006           attr_reader :port
2008           # The domain name of the target host.
2009           #
2010           # A target of "." means that the service is decidedly not available
2011           # at this domain.
2013           attr_reader :target
2015           def encode_rdata(msg) # :nodoc:
2016             msg.put_pack("n", @priority)
2017             msg.put_pack("n", @weight)
2018             msg.put_pack("n", @port)
2019             msg.put_name(@target)
2020           end
2022           def self.decode_rdata(msg) # :nodoc:
2023             priority, = msg.get_unpack("n")
2024             weight,   = msg.get_unpack("n")
2025             port,     = msg.get_unpack("n")
2026             target    = msg.get_name
2027             return self.new(priority, weight, port, target)
2028           end
2029         end
2030       end
2031     end
2032   end
2034   ##
2035   # A Resolv::DNS IPv4 address.
2037   class IPv4
2039     ##
2040     # Regular expression IPv4 addresses must match.
2042     Regex = /\A(\d+)\.(\d+)\.(\d+)\.(\d+)\z/
2044     def self.create(arg)
2045       case arg
2046       when IPv4
2047         return arg
2048       when Regex
2049         if (0..255) === (a = $1.to_i) &&
2050            (0..255) === (b = $2.to_i) &&
2051            (0..255) === (c = $3.to_i) &&
2052            (0..255) === (d = $4.to_i)
2053           return self.new([a, b, c, d].pack("CCCC"))
2054         else
2055           raise ArgumentError.new("IPv4 address with invalid value: " + arg)
2056         end
2057       else
2058         raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}")
2059       end
2060     end
2062     def initialize(address) # :nodoc:
2063       unless address.kind_of?(String) && address.length == 4
2064         raise ArgumentError.new('IPv4 address must be 4 bytes')
2065       end
2066       @address = address
2067     end
2069     ##
2070     # A String representation of this IPv4 address.
2072     ##
2073     # The raw IPv4 address as a String.
2075     attr_reader :address
2077     def to_s # :nodoc:
2078       return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC"))
2079     end
2081     def inspect # :nodoc:
2082       return "#<#{self.class} #{self.to_s}>"
2083     end
2085     ##
2086     # Turns this IPv4 address into a Resolv::DNS::Name.
2088     def to_name
2089       return DNS::Name.create(
2090         '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse)
2091     end
2093     def ==(other) # :nodoc:
2094       return @address == other.address
2095     end
2097     def eql?(other) # :nodoc:
2098       return self == other
2099     end
2101     def hash # :nodoc:
2102       return @address.hash
2103     end
2104   end
2106   ##
2107   # A Resolv::DNS IPv6 address.
2109   class IPv6
2111     ##
2112     # IPv6 address format a:b:c:d:e:f:g:h
2113     Regex_8Hex = /\A
2114       (?:[0-9A-Fa-f]{1,4}:){7}
2115          [0-9A-Fa-f]{1,4}
2116       \z/x
2118     ##
2119     # Compressed IPv6 address format a::b
2121     Regex_CompressedHex = /\A
2122       ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
2123       ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
2124       \z/x
2126     ##
2127     # IPv4 mapped IPv6 address format a:b:c:d:e:f:w.x.y.z
2129     Regex_6Hex4Dec = /\A
2130       ((?:[0-9A-Fa-f]{1,4}:){6,6})
2131       (\d+)\.(\d+)\.(\d+)\.(\d+)
2132       \z/x
2134     ##
2135     # Compressed IPv4 mapped IPv6 address format a::b:w.x.y.z
2137     Regex_CompressedHex4Dec = /\A
2138       ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
2139       ((?:[0-9A-Fa-f]{1,4}:)*)
2140       (\d+)\.(\d+)\.(\d+)\.(\d+)
2141       \z/x
2143     ##
2144     # A composite IPv6 address Regexp.
2146     Regex = /
2147       (?:#{Regex_8Hex}) |
2148       (?:#{Regex_CompressedHex}) |
2149       (?:#{Regex_6Hex4Dec}) |
2150       (?:#{Regex_CompressedHex4Dec})/x
2152     ##
2153     # Creates a new IPv6 address from +arg+ which may be:
2154     #
2155     # IPv6:: returns +arg+.
2156     # String:: +arg+ must match one of the IPv6::Regex* constants
2158     def self.create(arg)
2159       case arg
2160       when IPv6
2161         return arg
2162       when String
2163         address = ''
2164         if Regex_8Hex =~ arg
2165           arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
2166         elsif Regex_CompressedHex =~ arg
2167           prefix = $1
2168           suffix = $2
2169           a1 = ''
2170           a2 = ''
2171           prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
2172           suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
2173           omitlen = 16 - a1.length - a2.length
2174           address << a1 << "\0" * omitlen << a2
2175         elsif Regex_6Hex4Dec =~ arg
2176           prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i
2177           if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
2178             prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
2179             address << [a, b, c, d].pack('CCCC')
2180           else
2181             raise ArgumentError.new("not numeric IPv6 address: " + arg)
2182           end
2183         elsif Regex_CompressedHex4Dec =~ arg
2184           prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i
2185           if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
2186             a1 = ''
2187             a2 = ''
2188             prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
2189             suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
2190             omitlen = 12 - a1.length - a2.length
2191             address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC')
2192           else
2193             raise ArgumentError.new("not numeric IPv6 address: " + arg)
2194           end
2195         else
2196           raise ArgumentError.new("not numeric IPv6 address: " + arg)
2197         end
2198         return IPv6.new(address)
2199       else
2200         raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}")
2201       end
2202     end
2204     def initialize(address) # :nodoc:
2205       unless address.kind_of?(String) && address.length == 16
2206         raise ArgumentError.new('IPv6 address must be 16 bytes')
2207       end
2208       @address = address
2209     end
2211     ##
2212     # The raw IPv6 address as a String.
2214     attr_reader :address
2216     def to_s # :nodoc:
2217       address = sprintf("%X:%X:%X:%X:%X:%X:%X:%X", *@address.unpack("nnnnnnnn"))
2218       unless address.sub!(/(^|:)0(:0)+(:|$)/, '::')
2219         address.sub!(/(^|:)0(:|$)/, '::')
2220       end
2221       return address
2222     end
2224     def inspect # :nodoc:
2225       return "#<#{self.class} #{self.to_s}>"
2226     end
2228     ##
2229     # Turns this IPv6 address into a Resolv::DNS::Name.
2230     #--
2231     # ip6.arpa should be searched too. [RFC3152]
2233     def to_name
2234       return DNS::Name.new(
2235         @address.unpack("H32")[0].split(//).reverse + ['ip6', 'arpa'])
2236     end
2238     def ==(other) # :nodoc:
2239       return @address == other.address
2240     end
2242     def eql?(other) # :nodoc:
2243       return self == other
2244     end
2246     def hash # :nodoc:
2247       return @address.hash
2248     end
2249   end
2251   ##
2252   # Default resolver to use for Resolv class methods.
2254   DefaultResolver = self.new
2256   ##
2257   # Address Regexp to use for matching IP addresses.
2259   AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/