"Fossies" - the Fresh Open Source Software Archive

Member "ruby-2.7.4/lib/resolv.rb" (7 Jul 2021, 75280 Bytes) of package /linux/misc/ruby-2.7.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Ruby source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 # frozen_string_literal: true
    2 
    3 require 'socket'
    4 require 'timeout'
    5 require 'io/wait'
    6 
    7 begin
    8   require 'securerandom'
    9 rescue LoadError
   10 end
   11 
   12 # Resolv is a thread-aware DNS resolver library written in Ruby.  Resolv can
   13 # handle multiple DNS requests concurrently without blocking the entire Ruby
   14 # interpreter.
   15 #
   16 # See also resolv-replace.rb to replace the libc resolver with Resolv.
   17 #
   18 # Resolv can look up various DNS resources using the DNS module directly.
   19 #
   20 # Examples:
   21 #
   22 #   p Resolv.getaddress "www.ruby-lang.org"
   23 #   p Resolv.getname "210.251.121.214"
   24 #
   25 #   Resolv::DNS.open do |dns|
   26 #     ress = dns.getresources "www.ruby-lang.org", Resolv::DNS::Resource::IN::A
   27 #     p ress.map(&:address)
   28 #     ress = dns.getresources "ruby-lang.org", Resolv::DNS::Resource::IN::MX
   29 #     p ress.map { |r| [r.exchange.to_s, r.preference] }
   30 #   end
   31 #
   32 #
   33 # == Bugs
   34 #
   35 # * NIS is not supported.
   36 # * /etc/nsswitch.conf is not supported.
   37 
   38 class Resolv
   39 
   40   ##
   41   # Looks up the first IP address for +name+.
   42 
   43   def self.getaddress(name)
   44     DefaultResolver.getaddress(name)
   45   end
   46 
   47   ##
   48   # Looks up all IP address for +name+.
   49 
   50   def self.getaddresses(name)
   51     DefaultResolver.getaddresses(name)
   52   end
   53 
   54   ##
   55   # Iterates over all IP addresses for +name+.
   56 
   57   def self.each_address(name, &block)
   58     DefaultResolver.each_address(name, &block)
   59   end
   60 
   61   ##
   62   # Looks up the hostname of +address+.
   63 
   64   def self.getname(address)
   65     DefaultResolver.getname(address)
   66   end
   67 
   68   ##
   69   # Looks up all hostnames for +address+.
   70 
   71   def self.getnames(address)
   72     DefaultResolver.getnames(address)
   73   end
   74 
   75   ##
   76   # Iterates over all hostnames for +address+.
   77 
   78   def self.each_name(address, &proc)
   79     DefaultResolver.each_name(address, &proc)
   80   end
   81 
   82   ##
   83   # Creates a new Resolv using +resolvers+.
   84 
   85   def initialize(resolvers=[Hosts.new, DNS.new])
   86     @resolvers = resolvers
   87   end
   88 
   89   ##
   90   # Looks up the first IP address for +name+.
   91 
   92   def getaddress(name)
   93     each_address(name) {|address| return address}
   94     raise ResolvError.new("no address for #{name}")
   95   end
   96 
   97   ##
   98   # Looks up all IP address for +name+.
   99 
  100   def getaddresses(name)
  101     ret = []
  102     each_address(name) {|address| ret << address}
  103     return ret
  104   end
  105 
  106   ##
  107   # Iterates over all IP addresses for +name+.
  108 
  109   def each_address(name)
  110     if AddressRegex =~ name
  111       yield name
  112       return
  113     end
  114     yielded = false
  115     @resolvers.each {|r|
  116       r.each_address(name) {|address|
  117         yield address.to_s
  118         yielded = true
  119       }
  120       return if yielded
  121     }
  122   end
  123 
  124   ##
  125   # Looks up the hostname of +address+.
  126 
  127   def getname(address)
  128     each_name(address) {|name| return name}
  129     raise ResolvError.new("no name for #{address}")
  130   end
  131 
  132   ##
  133   # Looks up all hostnames for +address+.
  134 
  135   def getnames(address)
  136     ret = []
  137     each_name(address) {|name| ret << name}
  138     return ret
  139   end
  140 
  141   ##
  142   # Iterates over all hostnames for +address+.
  143 
  144   def each_name(address)
  145     yielded = false
  146     @resolvers.each {|r|
  147       r.each_name(address) {|name|
  148         yield name.to_s
  149         yielded = true
  150       }
  151       return if yielded
  152     }
  153   end
  154 
  155   ##
  156   # Indicates a failure to resolve a name or address.
  157 
  158   class ResolvError < StandardError; end
  159 
  160   ##
  161   # Indicates a timeout resolving a name or address.
  162 
  163   class ResolvTimeout < Timeout::Error; end
  164 
  165   ##
  166   # Resolv::Hosts is a hostname resolver that uses the system hosts file.
  167 
  168   class Hosts
  169     if /mswin|mingw|cygwin/ =~ RUBY_PLATFORM and
  170       begin
  171         require 'win32/resolv'
  172         DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL
  173       rescue LoadError
  174       end
  175     end
  176     DefaultFileName ||= '/etc/hosts'
  177 
  178     ##
  179     # Creates a new Resolv::Hosts, using +filename+ for its data source.
  180 
  181     def initialize(filename = DefaultFileName)
  182       @filename = filename
  183       @mutex = Thread::Mutex.new
  184       @initialized = nil
  185     end
  186 
  187     def lazy_initialize # :nodoc:
  188       @mutex.synchronize {
  189         unless @initialized
  190           @name2addr = {}
  191           @addr2name = {}
  192           File.open(@filename, 'rb') {|f|
  193             f.each {|line|
  194               line.sub!(/#.*/, '')
  195               addr, hostname, *aliases = line.split(/\s+/)
  196               next unless addr
  197               @addr2name[addr] = [] unless @addr2name.include? addr
  198               @addr2name[addr] << hostname
  199               @addr2name[addr] += aliases
  200               @name2addr[hostname] = [] unless @name2addr.include? hostname
  201               @name2addr[hostname] << addr
  202               aliases.each {|n|
  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
  214 
  215     ##
  216     # Gets the IP address of +name+ from the hosts file.
  217 
  218     def getaddress(name)
  219       each_address(name) {|address| return address}
  220       raise ResolvError.new("#{@filename} has no name: #{name}")
  221     end
  222 
  223     ##
  224     # Gets all IP addresses for +name+ from the hosts file.
  225 
  226     def getaddresses(name)
  227       ret = []
  228       each_address(name) {|address| ret << address}
  229       return ret
  230     end
  231 
  232     ##
  233     # Iterates over all IP addresses for +name+ retrieved from the hosts file.
  234 
  235     def each_address(name, &proc)
  236       lazy_initialize
  237       @name2addr[name]&.each(&proc)
  238     end
  239 
  240     ##
  241     # Gets the hostname of +address+ from the hosts file.
  242 
  243     def getname(address)
  244       each_name(address) {|name| return name}
  245       raise ResolvError.new("#{@filename} has no address: #{address}")
  246     end
  247 
  248     ##
  249     # Gets all hostnames for +address+ from the hosts file.
  250 
  251     def getnames(address)
  252       ret = []
  253       each_name(address) {|name| ret << name}
  254       return ret
  255     end
  256 
  257     ##
  258     # Iterates over all hostnames for +address+ retrieved from the hosts file.
  259 
  260     def each_name(address, &proc)
  261       lazy_initialize
  262       @addr2name[address]&.each(&proc)
  263     end
  264   end
  265 
  266   ##
  267   # Resolv::DNS is a DNS stub resolver.
  268   #
  269   # Information taken from the following places:
  270   #
  271   # * STD0013
  272   # * RFC 1035
  273   # * ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters
  274   # * etc.
  275 
  276   class DNS
  277 
  278     ##
  279     # Default DNS Port
  280 
  281     Port = 53
  282 
  283     ##
  284     # Default DNS UDP packet size
  285 
  286     UDPSize = 512
  287 
  288     ##
  289     # Creates a new DNS resolver.  See Resolv::DNS.new for argument details.
  290     #
  291     # Yields the created DNS resolver to the block, if given, otherwise
  292     # returns it.
  293 
  294     def self.open(*args)
  295       dns = new(*args)
  296       return dns unless block_given?
  297       begin
  298         yield dns
  299       ensure
  300         dns.close
  301       end
  302     end
  303 
  304     ##
  305     # Creates a new DNS resolver.
  306     #
  307     # +config_info+ can be:
  308     #
  309     # nil:: Uses /etc/resolv.conf.
  310     # String:: Path to a file using /etc/resolv.conf's format.
  311     # Hash:: Must contain :nameserver, :search and :ndots keys.
  312     # :nameserver_port can be used to specify port number of nameserver address.
  313     #
  314     # The value of :nameserver should be an address string or
  315     # an array of address strings.
  316     # - :nameserver => '8.8.8.8'
  317     # - :nameserver => ['8.8.8.8', '8.8.4.4']
  318     #
  319     # The value of :nameserver_port should be an array of
  320     # pair of nameserver address and port number.
  321     # - :nameserver_port => [['8.8.8.8', 53], ['8.8.4.4', 53]]
  322     #
  323     # Example:
  324     #
  325     #   Resolv::DNS.new(:nameserver => ['210.251.121.21'],
  326     #                   :search => ['ruby-lang.org'],
  327     #                   :ndots => 1)
  328 
  329     def initialize(config_info=nil)
  330       @mutex = Thread::Mutex.new
  331       @config = Config.new(config_info)
  332       @initialized = nil
  333     end
  334 
  335     # Sets the resolver timeouts.  This may be a single positive number
  336     # or an array of positive numbers representing timeouts in seconds.
  337     # If an array is specified, a DNS request will retry and wait for
  338     # each successive interval in the array until a successful response
  339     # is received.  Specifying +nil+ reverts to the default timeouts:
  340     # [ 5, second = 5 * 2 / nameserver_count, 2 * second, 4 * second ]
  341     #
  342     # Example:
  343     #
  344     #   dns.timeouts = 3
  345     #
  346     def timeouts=(values)
  347       @config.timeouts = values
  348     end
  349 
  350     def lazy_initialize # :nodoc:
  351       @mutex.synchronize {
  352         unless @initialized
  353           @config.lazy_initialize
  354           @initialized = true
  355         end
  356       }
  357       self
  358     end
  359 
  360     ##
  361     # Closes the DNS resolver.
  362 
  363     def close
  364       @mutex.synchronize {
  365         if @initialized
  366           @initialized = false
  367         end
  368       }
  369     end
  370 
  371     ##
  372     # Gets the IP address of +name+ from the DNS resolver.
  373     #
  374     # +name+ can be a Resolv::DNS::Name or a String.  Retrieved address will
  375     # be a Resolv::IPv4 or Resolv::IPv6
  376 
  377     def getaddress(name)
  378       each_address(name) {|address| return address}
  379       raise ResolvError.new("DNS result has no information for #{name}")
  380     end
  381 
  382     ##
  383     # Gets all IP addresses for +name+ from the DNS resolver.
  384     #
  385     # +name+ can be a Resolv::DNS::Name or a String.  Retrieved addresses will
  386     # be a Resolv::IPv4 or Resolv::IPv6
  387 
  388     def getaddresses(name)
  389       ret = []
  390       each_address(name) {|address| ret << address}
  391       return ret
  392     end
  393 
  394     ##
  395     # Iterates over all IP addresses for +name+ retrieved from the DNS
  396     # resolver.
  397     #
  398     # +name+ can be a Resolv::DNS::Name or a String.  Retrieved addresses will
  399     # be a Resolv::IPv4 or Resolv::IPv6
  400 
  401     def each_address(name)
  402       each_resource(name, Resource::IN::A) {|resource| yield resource.address}
  403       if use_ipv6?
  404         each_resource(name, Resource::IN::AAAA) {|resource| yield resource.address}
  405       end
  406     end
  407 
  408     def use_ipv6? # :nodoc:
  409       begin
  410         list = Socket.ip_address_list
  411       rescue NotImplementedError
  412         return true
  413       end
  414       list.any? {|a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? }
  415     end
  416     private :use_ipv6?
  417 
  418     ##
  419     # Gets the hostname for +address+ from the DNS resolver.
  420     #
  421     # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String.  Retrieved
  422     # name will be a Resolv::DNS::Name.
  423 
  424     def getname(address)
  425       each_name(address) {|name| return name}
  426       raise ResolvError.new("DNS result has no information for #{address}")
  427     end
  428 
  429     ##
  430     # Gets all hostnames for +address+ from the DNS resolver.
  431     #
  432     # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String.  Retrieved
  433     # names will be Resolv::DNS::Name instances.
  434 
  435     def getnames(address)
  436       ret = []
  437       each_name(address) {|name| ret << name}
  438       return ret
  439     end
  440 
  441     ##
  442     # Iterates over all hostnames for +address+ retrieved from the DNS
  443     # resolver.
  444     #
  445     # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String.  Retrieved
  446     # names will be Resolv::DNS::Name instances.
  447 
  448     def each_name(address)
  449       case address
  450       when Name
  451         ptr = address
  452       when IPv4, IPv6
  453         ptr = address.to_name
  454       when IPv4::Regex
  455         ptr = IPv4.create(address).to_name
  456       when IPv6::Regex
  457         ptr = IPv6.create(address).to_name
  458       else
  459         raise ResolvError.new("cannot interpret as address: #{address}")
  460       end
  461       each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name}
  462     end
  463 
  464     ##
  465     # Look up the +typeclass+ DNS resource of +name+.
  466     #
  467     # +name+ must be a Resolv::DNS::Name or a String.
  468     #
  469     # +typeclass+ should be one of the following:
  470     #
  471     # * Resolv::DNS::Resource::IN::A
  472     # * Resolv::DNS::Resource::IN::AAAA
  473     # * Resolv::DNS::Resource::IN::ANY
  474     # * Resolv::DNS::Resource::IN::CNAME
  475     # * Resolv::DNS::Resource::IN::HINFO
  476     # * Resolv::DNS::Resource::IN::MINFO
  477     # * Resolv::DNS::Resource::IN::MX
  478     # * Resolv::DNS::Resource::IN::NS
  479     # * Resolv::DNS::Resource::IN::PTR
  480     # * Resolv::DNS::Resource::IN::SOA
  481     # * Resolv::DNS::Resource::IN::TXT
  482     # * Resolv::DNS::Resource::IN::WKS
  483     #
  484     # Returned resource is represented as a Resolv::DNS::Resource instance,
  485     # i.e. Resolv::DNS::Resource::IN::A.
  486 
  487     def getresource(name, typeclass)
  488       each_resource(name, typeclass) {|resource| return resource}
  489       raise ResolvError.new("DNS result has no information for #{name}")
  490     end
  491 
  492     ##
  493     # Looks up all +typeclass+ DNS resources for +name+.  See #getresource for
  494     # argument details.
  495 
  496     def getresources(name, typeclass)
  497       ret = []
  498       each_resource(name, typeclass) {|resource| ret << resource}
  499       return ret
  500     end
  501 
  502     ##
  503     # Iterates over all +typeclass+ DNS resources for +name+.  See
  504     # #getresource for argument details.
  505 
  506     def each_resource(name, typeclass, &proc)
  507       fetch_resource(name, typeclass) {|reply, reply_name|
  508         extract_resources(reply, reply_name, typeclass, &proc)
  509       }
  510     end
  511 
  512     def fetch_resource(name, typeclass)
  513       lazy_initialize
  514       begin
  515         requester = make_udp_requester
  516       rescue Errno::EACCES
  517         # fall back to TCP
  518       end
  519       senders = {}
  520       begin
  521         @config.resolv(name) {|candidate, tout, nameserver, port|
  522           requester ||= make_tcp_requester(nameserver, port)
  523           msg = Message.new
  524           msg.rd = 1
  525           msg.add_question(candidate, typeclass)
  526           unless sender = senders[[candidate, nameserver, port]]
  527             sender = requester.sender(msg, candidate, nameserver, port)
  528             next if !sender
  529             senders[[candidate, nameserver, port]] = sender
  530           end
  531           reply, reply_name = requester.request(sender, tout)
  532           case reply.rcode
  533           when RCode::NoError
  534             if reply.tc == 1 and not Requester::TCP === requester
  535               requester.close
  536               # Retry via TCP:
  537               requester = make_tcp_requester(nameserver, port)
  538               senders = {}
  539               # This will use TCP for all remaining candidates (assuming the
  540               # current candidate does not already respond successfully via
  541               # TCP).  This makes sense because we already know the full
  542               # response will not fit in an untruncated UDP packet.
  543               redo
  544             else
  545               yield(reply, reply_name)
  546             end
  547             return
  548           when RCode::NXDomain
  549             raise Config::NXDomain.new(reply_name.to_s)
  550           else
  551             raise Config::OtherResolvError.new(reply_name.to_s)
  552           end
  553         }
  554       ensure
  555         requester&.close
  556       end
  557     end
  558 
  559     def make_udp_requester # :nodoc:
  560       nameserver_port = @config.nameserver_port
  561       if nameserver_port.length == 1
  562         Requester::ConnectedUDP.new(*nameserver_port[0])
  563       else
  564         Requester::UnconnectedUDP.new(*nameserver_port)
  565       end
  566     end
  567 
  568     def make_tcp_requester(host, port) # :nodoc:
  569       return Requester::TCP.new(host, port)
  570     end
  571 
  572     def extract_resources(msg, name, typeclass) # :nodoc:
  573       if typeclass < Resource::ANY
  574         n0 = Name.create(name)
  575         msg.each_resource {|n, ttl, data|
  576           yield data if n0 == n
  577         }
  578       end
  579       yielded = false
  580       n0 = Name.create(name)
  581       msg.each_resource {|n, ttl, data|
  582         if n0 == n
  583           case data
  584           when typeclass
  585             yield data
  586             yielded = true
  587           when Resource::CNAME
  588             n0 = data.name
  589           end
  590         end
  591       }
  592       return if yielded
  593       msg.each_resource {|n, ttl, data|
  594         if n0 == n
  595           case data
  596           when typeclass
  597             yield data
  598           end
  599         end
  600       }
  601     end
  602 
  603     if defined? SecureRandom
  604       def self.random(arg) # :nodoc:
  605         begin
  606           SecureRandom.random_number(arg)
  607         rescue NotImplementedError
  608           rand(arg)
  609         end
  610       end
  611     else
  612       def self.random(arg) # :nodoc:
  613         rand(arg)
  614       end
  615     end
  616 
  617     RequestID = {} # :nodoc:
  618     RequestIDMutex = Thread::Mutex.new # :nodoc:
  619 
  620     def self.allocate_request_id(host, port) # :nodoc:
  621       id = nil
  622       RequestIDMutex.synchronize {
  623         h = (RequestID[[host, port]] ||= {})
  624         begin
  625           id = random(0x0000..0xffff)
  626         end while h[id]
  627         h[id] = true
  628       }
  629       id
  630     end
  631 
  632     def self.free_request_id(host, port, id) # :nodoc:
  633       RequestIDMutex.synchronize {
  634         key = [host, port]
  635         if h = RequestID[key]
  636           h.delete id
  637           if h.empty?
  638             RequestID.delete key
  639           end
  640         end
  641       }
  642     end
  643 
  644     def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc:
  645       begin
  646         port = random(1024..65535)
  647         udpsock.bind(bind_host, port)
  648       rescue Errno::EADDRINUSE, # POSIX
  649              Errno::EACCES, # SunOS: See PRIV_SYS_NFS in privileges(5)
  650              Errno::EPERM # FreeBSD: security.mac.portacl.port_high is configurable.  See mac_portacl(4).
  651         retry
  652       end
  653     end
  654 
  655     class Requester # :nodoc:
  656       def initialize
  657         @senders = {}
  658         @socks = nil
  659       end
  660 
  661       def request(sender, tout)
  662         start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  663         timelimit = start + tout
  664         begin
  665           sender.send
  666         rescue Errno::EHOSTUNREACH, # multi-homed IPv6 may generate this
  667                Errno::ENETUNREACH
  668           raise ResolvTimeout
  669         end
  670         while true
  671           before_select = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  672           timeout = timelimit - before_select
  673           if timeout <= 0
  674             raise ResolvTimeout
  675           end
  676           if @socks.size == 1
  677             select_result = @socks[0].wait_readable(timeout) ? [ @socks ] : nil
  678           else
  679             select_result = IO.select(@socks, nil, nil, timeout)
  680           end
  681           if !select_result
  682             after_select = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  683             next if after_select < timelimit
  684             raise ResolvTimeout
  685           end
  686           begin
  687             reply, from = recv_reply(select_result[0])
  688           rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD
  689                  Errno::ECONNRESET # Windows
  690             # No name server running on the server?
  691             # Don't wait anymore.
  692             raise ResolvTimeout
  693           end
  694           begin
  695             msg = Message.decode(reply)
  696           rescue DecodeError
  697             next # broken DNS message ignored
  698           end
  699           if sender == sender_for(from, msg)
  700             break
  701           else
  702             # unexpected DNS message ignored
  703           end
  704         end
  705         return msg, sender.data
  706       end
  707 
  708       def sender_for(addr, msg)
  709         @senders[[addr,msg.id]]
  710       end
  711 
  712       def close
  713         socks = @socks
  714         @socks = nil
  715         socks&.each(&:close)
  716       end
  717 
  718       class Sender # :nodoc:
  719         def initialize(msg, data, sock)
  720           @msg = msg
  721           @data = data
  722           @sock = sock
  723         end
  724       end
  725 
  726       class UnconnectedUDP < Requester # :nodoc:
  727         def initialize(*nameserver_port)
  728           super()
  729           @nameserver_port = nameserver_port
  730           @initialized = false
  731           @mutex = Thread::Mutex.new
  732         end
  733 
  734         def lazy_initialize
  735           @mutex.synchronize {
  736             next if @initialized
  737             @initialized = true
  738             @socks_hash = {}
  739             @socks = []
  740             @nameserver_port.each {|host, port|
  741               if host.index(':')
  742                 bind_host = "::"
  743                 af = Socket::AF_INET6
  744               else
  745                 bind_host = "0.0.0.0"
  746                 af = Socket::AF_INET
  747               end
  748               next if @socks_hash[bind_host]
  749               begin
  750                 sock = UDPSocket.new(af)
  751               rescue Errno::EAFNOSUPPORT
  752                 next # The kernel doesn't support the address family.
  753               end
  754               @socks << sock
  755               @socks_hash[bind_host] = sock
  756               sock.do_not_reverse_lookup = true
  757               DNS.bind_random_port(sock, bind_host)
  758             }
  759           }
  760           self
  761         end
  762 
  763         def recv_reply(readable_socks)
  764           lazy_initialize
  765           reply, from = readable_socks[0].recvfrom(UDPSize)
  766           return reply, [from[3],from[1]]
  767         end
  768 
  769         def sender(msg, data, host, port=Port)
  770           lazy_initialize
  771           sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"]
  772           return nil if !sock
  773           service = [host, port]
  774           id = DNS.allocate_request_id(host, port)
  775           request = msg.encode
  776           request[0,2] = [id].pack('n')
  777           return @senders[[service, id]] =
  778             Sender.new(request, data, sock, host, port)
  779         end
  780 
  781         def close
  782           @mutex.synchronize {
  783             if @initialized
  784               super
  785               @senders.each_key {|service, id|
  786                 DNS.free_request_id(service[0], service[1], id)
  787               }
  788               @initialized = false
  789             end
  790           }
  791         end
  792 
  793         class Sender < Requester::Sender # :nodoc:
  794           def initialize(msg, data, sock, host, port)
  795             super(msg, data, sock)
  796             @host = host
  797             @port = port
  798           end
  799           attr_reader :data
  800 
  801           def send
  802             raise "@sock is nil." if @sock.nil?
  803             @sock.send(@msg, 0, @host, @port)
  804           end
  805         end
  806       end
  807 
  808       class ConnectedUDP < Requester # :nodoc:
  809         def initialize(host, port=Port)
  810           super()
  811           @host = host
  812           @port = port
  813           @mutex = Thread::Mutex.new
  814           @initialized = false
  815         end
  816 
  817         def lazy_initialize
  818           @mutex.synchronize {
  819             next if @initialized
  820             @initialized = true
  821             is_ipv6 = @host.index(':')
  822             sock = UDPSocket.new(is_ipv6 ? Socket::AF_INET6 : Socket::AF_INET)
  823             @socks = [sock]
  824             sock.do_not_reverse_lookup = true
  825             DNS.bind_random_port(sock, is_ipv6 ? "::" : "0.0.0.0")
  826             sock.connect(@host, @port)
  827           }
  828           self
  829         end
  830 
  831         def recv_reply(readable_socks)
  832           lazy_initialize
  833           reply = readable_socks[0].recv(UDPSize)
  834           return reply, nil
  835         end
  836 
  837         def sender(msg, data, host=@host, port=@port)
  838           lazy_initialize
  839           unless host == @host && port == @port
  840             raise RequestError.new("host/port don't match: #{host}:#{port}")
  841           end
  842           id = DNS.allocate_request_id(@host, @port)
  843           request = msg.encode
  844           request[0,2] = [id].pack('n')
  845           return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
  846         end
  847 
  848         def close
  849           @mutex.synchronize do
  850             if @initialized
  851               super
  852               @senders.each_key {|from, id|
  853                 DNS.free_request_id(@host, @port, id)
  854               }
  855               @initialized = false
  856             end
  857           end
  858         end
  859 
  860         class Sender < Requester::Sender # :nodoc:
  861           def send
  862             raise "@sock is nil." if @sock.nil?
  863             @sock.send(@msg, 0)
  864           end
  865           attr_reader :data
  866         end
  867       end
  868 
  869       class MDNSOneShot < UnconnectedUDP # :nodoc:
  870         def sender(msg, data, host, port=Port)
  871           lazy_initialize
  872           id = DNS.allocate_request_id(host, port)
  873           request = msg.encode
  874           request[0,2] = [id].pack('n')
  875           sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"]
  876           return @senders[id] =
  877             UnconnectedUDP::Sender.new(request, data, sock, host, port)
  878         end
  879 
  880         def sender_for(addr, msg)
  881           lazy_initialize
  882           @senders[msg.id]
  883         end
  884       end
  885 
  886       class TCP < Requester # :nodoc:
  887         def initialize(host, port=Port)
  888           super()
  889           @host = host
  890           @port = port
  891           sock = TCPSocket.new(@host, @port)
  892           @socks = [sock]
  893           @senders = {}
  894         end
  895 
  896         def recv_reply(readable_socks)
  897           len = readable_socks[0].read(2).unpack('n')[0]
  898           reply = @socks[0].read(len)
  899           return reply, nil
  900         end
  901 
  902         def sender(msg, data, host=@host, port=@port)
  903           unless host == @host && port == @port
  904             raise RequestError.new("host/port don't match: #{host}:#{port}")
  905           end
  906           id = DNS.allocate_request_id(@host, @port)
  907           request = msg.encode
  908           request[0,2] = [request.length, id].pack('nn')
  909           return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
  910         end
  911 
  912         class Sender < Requester::Sender # :nodoc:
  913           def send
  914             @sock.print(@msg)
  915             @sock.flush
  916           end
  917           attr_reader :data
  918         end
  919 
  920         def close
  921           super
  922           @senders.each_key {|from,id|
  923             DNS.free_request_id(@host, @port, id)
  924           }
  925         end
  926       end
  927 
  928       ##
  929       # Indicates a problem with the DNS request.
  930 
  931       class RequestError < StandardError
  932       end
  933     end
  934 
  935     class Config # :nodoc:
  936       def initialize(config_info=nil)
  937         @mutex = Thread::Mutex.new
  938         @config_info = config_info
  939         @initialized = nil
  940         @timeouts = nil
  941       end
  942 
  943       def timeouts=(values)
  944         if values
  945           values = Array(values)
  946           values.each do |t|
  947             Numeric === t or raise ArgumentError, "#{t.inspect} is not numeric"
  948             t > 0.0 or raise ArgumentError, "timeout=#{t} must be positive"
  949           end
  950           @timeouts = values
  951         else
  952           @timeouts = nil
  953         end
  954       end
  955 
  956       def Config.parse_resolv_conf(filename)
  957         nameserver = []
  958         search = nil
  959         ndots = 1
  960         File.open(filename, 'rb') {|f|
  961           f.each {|line|
  962             line.sub!(/[#;].*/, '')
  963             keyword, *args = line.split(/\s+/)
  964             next unless keyword
  965             case keyword
  966             when 'nameserver'
  967               nameserver += args
  968             when 'domain'
  969               next if args.empty?
  970               search = [args[0]]
  971             when 'search'
  972               next if args.empty?
  973               search = args
  974             when 'options'
  975               args.each {|arg|
  976                 case arg
  977                 when /\Andots:(\d+)\z/
  978                   ndots = $1.to_i
  979                 end
  980               }
  981             end
  982           }
  983         }
  984         return { :nameserver => nameserver, :search => search, :ndots => ndots }
  985       end
  986 
  987       def Config.default_config_hash(filename="/etc/resolv.conf")
  988         if File.exist? filename
  989           config_hash = Config.parse_resolv_conf(filename)
  990         else
  991           if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
  992             require 'win32/resolv'
  993             search, nameserver = Win32::Resolv.get_resolv_info
  994             config_hash = {}
  995             config_hash[:nameserver] = nameserver if nameserver
  996             config_hash[:search] = [search].flatten if search
  997           end
  998         end
  999         config_hash || {}
 1000       end
 1001 
 1002       def lazy_initialize
 1003         @mutex.synchronize {
 1004           unless @initialized
 1005             @nameserver_port = []
 1006             @search = nil
 1007             @ndots = 1
 1008             case @config_info
 1009             when nil
 1010               config_hash = Config.default_config_hash
 1011             when String
 1012               config_hash = Config.parse_resolv_conf(@config_info)
 1013             when Hash
 1014               config_hash = @config_info.dup
 1015               if String === config_hash[:nameserver]
 1016                 config_hash[:nameserver] = [config_hash[:nameserver]]
 1017               end
 1018               if String === config_hash[:search]
 1019                 config_hash[:search] = [config_hash[:search]]
 1020               end
 1021             else
 1022               raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}")
 1023             end
 1024             if config_hash.include? :nameserver
 1025               @nameserver_port = config_hash[:nameserver].map {|ns| [ns, Port] }
 1026             end
 1027             if config_hash.include? :nameserver_port
 1028               @nameserver_port = config_hash[:nameserver_port].map {|ns, port| [ns, (port || Port)] }
 1029             end
 1030             @search = config_hash[:search] if config_hash.include? :search
 1031             @ndots = config_hash[:ndots] if config_hash.include? :ndots
 1032 
 1033             if @nameserver_port.empty?
 1034               @nameserver_port << ['0.0.0.0', Port]
 1035             end
 1036             if @search
 1037               @search = @search.map {|arg| Label.split(arg) }
 1038             else
 1039               hostname = Socket.gethostname
 1040               if /\./ =~ hostname
 1041                 @search = [Label.split($')]
 1042               else
 1043                 @search = [[]]
 1044               end
 1045             end
 1046 
 1047             if !@nameserver_port.kind_of?(Array) ||
 1048                @nameserver_port.any? {|ns_port|
 1049                   !(Array === ns_port) ||
 1050                   ns_port.length != 2
 1051                   !(String === ns_port[0]) ||
 1052                   !(Integer === ns_port[1])
 1053                }
 1054               raise ArgumentError.new("invalid nameserver config: #{@nameserver_port.inspect}")
 1055             end
 1056 
 1057             if !@search.kind_of?(Array) ||
 1058                !@search.all? {|ls| ls.all? {|l| Label::Str === l } }
 1059               raise ArgumentError.new("invalid search config: #{@search.inspect}")
 1060             end
 1061 
 1062             if !@ndots.kind_of?(Integer)
 1063               raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}")
 1064             end
 1065 
 1066             @initialized = true
 1067           end
 1068         }
 1069         self
 1070       end
 1071 
 1072       def single?
 1073         lazy_initialize
 1074         if @nameserver_port.length == 1
 1075           return @nameserver_port[0]
 1076         else
 1077           return nil
 1078         end
 1079       end
 1080 
 1081       def nameserver_port
 1082         @nameserver_port
 1083       end
 1084 
 1085       def generate_candidates(name)
 1086         candidates = nil
 1087         name = Name.create(name)
 1088         if name.absolute?
 1089           candidates = [name]
 1090         else
 1091           if @ndots <= name.length - 1
 1092             candidates = [Name.new(name.to_a)]
 1093           else
 1094             candidates = []
 1095           end
 1096           candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)})
 1097           fname = Name.create("#{name}.")
 1098           if !candidates.include?(fname)
 1099             candidates << fname
 1100           end
 1101         end
 1102         return candidates
 1103       end
 1104 
 1105       InitialTimeout = 5
 1106 
 1107       def generate_timeouts
 1108         ts = [InitialTimeout]
 1109         ts << ts[-1] * 2 / @nameserver_port.length
 1110         ts << ts[-1] * 2
 1111         ts << ts[-1] * 2
 1112         return ts
 1113       end
 1114 
 1115       def resolv(name)
 1116         candidates = generate_candidates(name)
 1117         timeouts = @timeouts || generate_timeouts
 1118         begin
 1119           candidates.each {|candidate|
 1120             begin
 1121               timeouts.each {|tout|
 1122                 @nameserver_port.each {|nameserver, port|
 1123                   begin
 1124                     yield candidate, tout, nameserver, port
 1125                   rescue ResolvTimeout
 1126                   end
 1127                 }
 1128               }
 1129               raise ResolvError.new("DNS resolv timeout: #{name}")
 1130             rescue NXDomain
 1131             end
 1132           }
 1133         rescue ResolvError
 1134         end
 1135       end
 1136 
 1137       ##
 1138       # Indicates no such domain was found.
 1139 
 1140       class NXDomain < ResolvError
 1141       end
 1142 
 1143       ##
 1144       # Indicates some other unhandled resolver error was encountered.
 1145 
 1146       class OtherResolvError < ResolvError
 1147       end
 1148     end
 1149 
 1150     module OpCode # :nodoc:
 1151       Query = 0
 1152       IQuery = 1
 1153       Status = 2
 1154       Notify = 4
 1155       Update = 5
 1156     end
 1157 
 1158     module RCode # :nodoc:
 1159       NoError = 0
 1160       FormErr = 1
 1161       ServFail = 2
 1162       NXDomain = 3
 1163       NotImp = 4
 1164       Refused = 5
 1165       YXDomain = 6
 1166       YXRRSet = 7
 1167       NXRRSet = 8
 1168       NotAuth = 9
 1169       NotZone = 10
 1170       BADVERS = 16
 1171       BADSIG = 16
 1172       BADKEY = 17
 1173       BADTIME = 18
 1174       BADMODE = 19
 1175       BADNAME = 20
 1176       BADALG = 21
 1177     end
 1178 
 1179     ##
 1180     # Indicates that the DNS response was unable to be decoded.
 1181 
 1182     class DecodeError < StandardError
 1183     end
 1184 
 1185     ##
 1186     # Indicates that the DNS request was unable to be encoded.
 1187 
 1188     class EncodeError < StandardError
 1189     end
 1190 
 1191     module Label # :nodoc:
 1192       def self.split(arg)
 1193         labels = []
 1194         arg.scan(/[^\.]+/) {labels << Str.new($&)}
 1195         return labels
 1196       end
 1197 
 1198       class Str # :nodoc:
 1199         def initialize(string)
 1200           @string = string
 1201           # case insensivity of DNS labels doesn't apply non-ASCII characters. [RFC 4343]
 1202           # This assumes @string is given in ASCII compatible encoding.
 1203           @downcase = string.b.downcase
 1204         end
 1205         attr_reader :string, :downcase
 1206 
 1207         def to_s
 1208           return @string
 1209         end
 1210 
 1211         def inspect
 1212           return "#<#{self.class} #{self}>"
 1213         end
 1214 
 1215         def ==(other)
 1216           return self.class == other.class && @downcase == other.downcase
 1217         end
 1218 
 1219         def eql?(other)
 1220           return self == other
 1221         end
 1222 
 1223         def hash
 1224           return @downcase.hash
 1225         end
 1226       end
 1227     end
 1228 
 1229     ##
 1230     # A representation of a DNS name.
 1231 
 1232     class Name
 1233 
 1234       ##
 1235       # Creates a new DNS name from +arg+.  +arg+ can be:
 1236       #
 1237       # Name:: returns +arg+.
 1238       # String:: Creates a new Name.
 1239 
 1240       def self.create(arg)
 1241         case arg
 1242         when Name
 1243           return arg
 1244         when String
 1245           return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
 1246         else
 1247           raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
 1248         end
 1249       end
 1250 
 1251       def initialize(labels, absolute=true) # :nodoc:
 1252         labels = labels.map {|label|
 1253           case label
 1254           when String then Label::Str.new(label)
 1255           when Label::Str then label
 1256           else
 1257             raise ArgumentError, "unexpected label: #{label.inspect}"
 1258           end
 1259         }
 1260         @labels = labels
 1261         @absolute = absolute
 1262       end
 1263 
 1264       def inspect # :nodoc:
 1265         "#<#{self.class}: #{self}#{@absolute ? '.' : ''}>"
 1266       end
 1267 
 1268       ##
 1269       # True if this name is absolute.
 1270 
 1271       def absolute?
 1272         return @absolute
 1273       end
 1274 
 1275       def ==(other) # :nodoc:
 1276         return false unless Name === other
 1277         return false unless @absolute == other.absolute?
 1278         return @labels == other.to_a
 1279       end
 1280 
 1281       alias eql? == # :nodoc:
 1282 
 1283       ##
 1284       # Returns true if +other+ is a subdomain.
 1285       #
 1286       # Example:
 1287       #
 1288       #   domain = Resolv::DNS::Name.create("y.z")
 1289       #   p Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true
 1290       #   p Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true
 1291       #   p Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false
 1292       #   p Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false
 1293       #   p Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false
 1294       #   p Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false
 1295       #
 1296 
 1297       def subdomain_of?(other)
 1298         raise ArgumentError, "not a domain name: #{other.inspect}" unless Name === other
 1299         return false if @absolute != other.absolute?
 1300         other_len = other.length
 1301         return false if @labels.length <= other_len
 1302         return @labels[-other_len, other_len] == other.to_a
 1303       end
 1304 
 1305       def hash # :nodoc:
 1306         return @labels.hash ^ @absolute.hash
 1307       end
 1308 
 1309       def to_a # :nodoc:
 1310         return @labels
 1311       end
 1312 
 1313       def length # :nodoc:
 1314         return @labels.length
 1315       end
 1316 
 1317       def [](i) # :nodoc:
 1318         return @labels[i]
 1319       end
 1320 
 1321       ##
 1322       # returns the domain name as a string.
 1323       #
 1324       # The domain name doesn't have a trailing dot even if the name object is
 1325       # absolute.
 1326       #
 1327       # Example:
 1328       #
 1329       #   p Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z"
 1330       #   p Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z"
 1331 
 1332       def to_s
 1333         return @labels.join('.')
 1334       end
 1335     end
 1336 
 1337     class Message # :nodoc:
 1338       @@identifier = -1
 1339 
 1340       def initialize(id = (@@identifier += 1) & 0xffff)
 1341         @id = id
 1342         @qr = 0
 1343         @opcode = 0
 1344         @aa = 0
 1345         @tc = 0
 1346         @rd = 0 # recursion desired
 1347         @ra = 0 # recursion available
 1348         @rcode = 0
 1349         @question = []
 1350         @answer = []
 1351         @authority = []
 1352         @additional = []
 1353       end
 1354 
 1355       attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode
 1356       attr_reader :question, :answer, :authority, :additional
 1357 
 1358       def ==(other)
 1359         return @id == other.id &&
 1360                @qr == other.qr &&
 1361                @opcode == other.opcode &&
 1362                @aa == other.aa &&
 1363                @tc == other.tc &&
 1364                @rd == other.rd &&
 1365                @ra == other.ra &&
 1366                @rcode == other.rcode &&
 1367                @question == other.question &&
 1368                @answer == other.answer &&
 1369                @authority == other.authority &&
 1370                @additional == other.additional
 1371       end
 1372 
 1373       def add_question(name, typeclass)
 1374         @question << [Name.create(name), typeclass]
 1375       end
 1376 
 1377       def each_question
 1378         @question.each {|name, typeclass|
 1379           yield name, typeclass
 1380         }
 1381       end
 1382 
 1383       def add_answer(name, ttl, data)
 1384         @answer << [Name.create(name), ttl, data]
 1385       end
 1386 
 1387       def each_answer
 1388         @answer.each {|name, ttl, data|
 1389           yield name, ttl, data
 1390         }
 1391       end
 1392 
 1393       def add_authority(name, ttl, data)
 1394         @authority << [Name.create(name), ttl, data]
 1395       end
 1396 
 1397       def each_authority
 1398         @authority.each {|name, ttl, data|
 1399           yield name, ttl, data
 1400         }
 1401       end
 1402 
 1403       def add_additional(name, ttl, data)
 1404         @additional << [Name.create(name), ttl, data]
 1405       end
 1406 
 1407       def each_additional
 1408         @additional.each {|name, ttl, data|
 1409           yield name, ttl, data
 1410         }
 1411       end
 1412 
 1413       def each_resource
 1414         each_answer {|name, ttl, data| yield name, ttl, data}
 1415         each_authority {|name, ttl, data| yield name, ttl, data}
 1416         each_additional {|name, ttl, data| yield name, ttl, data}
 1417       end
 1418 
 1419       def encode
 1420         return MessageEncoder.new {|msg|
 1421           msg.put_pack('nnnnnn',
 1422             @id,
 1423             (@qr & 1) << 15 |
 1424             (@opcode & 15) << 11 |
 1425             (@aa & 1) << 10 |
 1426             (@tc & 1) << 9 |
 1427             (@rd & 1) << 8 |
 1428             (@ra & 1) << 7 |
 1429             (@rcode & 15),
 1430             @question.length,
 1431             @answer.length,
 1432             @authority.length,
 1433             @additional.length)
 1434           @question.each {|q|
 1435             name, typeclass = q
 1436             msg.put_name(name)
 1437             msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue)
 1438           }
 1439           [@answer, @authority, @additional].each {|rr|
 1440             rr.each {|r|
 1441               name, ttl, data = r
 1442               msg.put_name(name)
 1443               msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl)
 1444               msg.put_length16 {data.encode_rdata(msg)}
 1445             }
 1446           }
 1447         }.to_s
 1448       end
 1449 
 1450       class MessageEncoder # :nodoc:
 1451         def initialize
 1452           @data = ''.dup
 1453           @names = {}
 1454           yield self
 1455         end
 1456 
 1457         def to_s
 1458           return @data
 1459         end
 1460 
 1461         def put_bytes(d)
 1462           @data << d
 1463         end
 1464 
 1465         def put_pack(template, *d)
 1466           @data << d.pack(template)
 1467         end
 1468 
 1469         def put_length16
 1470           length_index = @data.length
 1471           @data << "\0\0"
 1472           data_start = @data.length
 1473           yield
 1474           data_end = @data.length
 1475           @data[length_index, 2] = [data_end - data_start].pack("n")
 1476         end
 1477 
 1478         def put_string(d)
 1479           self.put_pack("C", d.length)
 1480           @data << d
 1481         end
 1482 
 1483         def put_string_list(ds)
 1484           ds.each {|d|
 1485             self.put_string(d)
 1486           }
 1487         end
 1488 
 1489         def put_name(d)
 1490           put_labels(d.to_a)
 1491         end
 1492 
 1493         def put_labels(d)
 1494           d.each_index {|i|
 1495             domain = d[i..-1]
 1496             if idx = @names[domain]
 1497               self.put_pack("n", 0xc000 | idx)
 1498               return
 1499             else
 1500               if @data.length < 0x4000
 1501                 @names[domain] = @data.length
 1502               end
 1503               self.put_label(d[i])
 1504             end
 1505           }
 1506           @data << "\0"
 1507         end
 1508 
 1509         def put_label(d)
 1510           self.put_string(d.to_s)
 1511         end
 1512       end
 1513 
 1514       def Message.decode(m)
 1515         o = Message.new(0)
 1516         MessageDecoder.new(m) {|msg|
 1517           id, flag, qdcount, ancount, nscount, arcount =
 1518             msg.get_unpack('nnnnnn')
 1519           o.id = id
 1520           o.qr = (flag >> 15) & 1
 1521           o.opcode = (flag >> 11) & 15
 1522           o.aa = (flag >> 10) & 1
 1523           o.tc = (flag >> 9) & 1
 1524           o.rd = (flag >> 8) & 1
 1525           o.ra = (flag >> 7) & 1
 1526           o.rcode = flag & 15
 1527           (1..qdcount).each {
 1528             name, typeclass = msg.get_question
 1529             o.add_question(name, typeclass)
 1530           }
 1531           (1..ancount).each {
 1532             name, ttl, data = msg.get_rr
 1533             o.add_answer(name, ttl, data)
 1534           }
 1535           (1..nscount).each {
 1536             name, ttl, data = msg.get_rr
 1537             o.add_authority(name, ttl, data)
 1538           }
 1539           (1..arcount).each {
 1540             name, ttl, data = msg.get_rr
 1541             o.add_additional(name, ttl, data)
 1542           }
 1543         }
 1544         return o
 1545       end
 1546 
 1547       class MessageDecoder # :nodoc:
 1548         def initialize(data)
 1549           @data = data
 1550           @index = 0
 1551           @limit = data.bytesize
 1552           yield self
 1553         end
 1554 
 1555         def inspect
 1556           "\#<#{self.class}: #{@data.byteslice(0, @index).inspect} #{@data.byteslice(@index..-1).inspect}>"
 1557         end
 1558 
 1559         def get_length16
 1560           len, = self.get_unpack('n')
 1561           save_limit = @limit
 1562           @limit = @index + len
 1563           d = yield(len)
 1564           if @index < @limit
 1565             raise DecodeError.new("junk exists")
 1566           elsif @limit < @index
 1567             raise DecodeError.new("limit exceeded")
 1568           end
 1569           @limit = save_limit
 1570           return d
 1571         end
 1572 
 1573         def get_bytes(len = @limit - @index)
 1574           raise DecodeError.new("limit exceeded") if @limit < @index + len
 1575           d = @data.byteslice(@index, len)
 1576           @index += len
 1577           return d
 1578         end
 1579 
 1580         def get_unpack(template)
 1581           len = 0
 1582           template.each_byte {|byte|
 1583             byte = "%c" % byte
 1584             case byte
 1585             when ?c, ?C
 1586               len += 1
 1587             when ?n
 1588               len += 2
 1589             when ?N
 1590               len += 4
 1591             else
 1592               raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'")
 1593             end
 1594           }
 1595           raise DecodeError.new("limit exceeded") if @limit < @index + len
 1596           arr = @data.unpack("@#{@index}#{template}")
 1597           @index += len
 1598           return arr
 1599         end
 1600 
 1601         def get_string
 1602           raise DecodeError.new("limit exceeded") if @limit <= @index
 1603           len = @data.getbyte(@index)
 1604           raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len
 1605           d = @data.byteslice(@index + 1, len)
 1606           @index += 1 + len
 1607           return d
 1608         end
 1609 
 1610         def get_string_list
 1611           strings = []
 1612           while @index < @limit
 1613             strings << self.get_string
 1614           end
 1615           strings
 1616         end
 1617 
 1618         def get_name
 1619           return Name.new(self.get_labels)
 1620         end
 1621 
 1622         def get_labels
 1623           prev_index = @index
 1624           save_index = nil
 1625           d = []
 1626           while true
 1627             raise DecodeError.new("limit exceeded") if @limit <= @index
 1628             case @data.getbyte(@index)
 1629             when 0
 1630               @index += 1
 1631               if save_index
 1632                 @index = save_index
 1633               end
 1634               return d
 1635             when 192..255
 1636               idx = self.get_unpack('n')[0] & 0x3fff
 1637               if prev_index <= idx
 1638                 raise DecodeError.new("non-backward name pointer")
 1639               end
 1640               prev_index = idx
 1641               if !save_index
 1642                 save_index = @index
 1643               end
 1644               @index = idx
 1645             else
 1646               d << self.get_label
 1647             end
 1648           end
 1649         end
 1650 
 1651         def get_label
 1652           return Label::Str.new(self.get_string)
 1653         end
 1654 
 1655         def get_question
 1656           name = self.get_name
 1657           type, klass = self.get_unpack("nn")
 1658           return name, Resource.get_class(type, klass)
 1659         end
 1660 
 1661         def get_rr
 1662           name = self.get_name
 1663           type, klass, ttl = self.get_unpack('nnN')
 1664           typeclass = Resource.get_class(type, klass)
 1665           res = self.get_length16 do
 1666             begin
 1667               typeclass.decode_rdata self
 1668             rescue => e
 1669               raise DecodeError, e.message, e.backtrace
 1670             end
 1671           end
 1672           res.instance_variable_set :@ttl, ttl
 1673           return name, ttl, res
 1674         end
 1675       end
 1676     end
 1677 
 1678     ##
 1679     # A DNS query abstract class.
 1680 
 1681     class Query
 1682       def encode_rdata(msg) # :nodoc:
 1683         raise EncodeError.new("#{self.class} is query.")
 1684       end
 1685 
 1686       def self.decode_rdata(msg) # :nodoc:
 1687         raise DecodeError.new("#{self.class} is query.")
 1688       end
 1689     end
 1690 
 1691     ##
 1692     # A DNS resource abstract class.
 1693 
 1694     class Resource < Query
 1695 
 1696       ##
 1697       # Remaining Time To Live for this Resource.
 1698 
 1699       attr_reader :ttl
 1700 
 1701       ClassHash = {} # :nodoc:
 1702 
 1703       def encode_rdata(msg) # :nodoc:
 1704         raise NotImplementedError.new
 1705       end
 1706 
 1707       def self.decode_rdata(msg) # :nodoc:
 1708         raise NotImplementedError.new
 1709       end
 1710 
 1711       def ==(other) # :nodoc:
 1712         return false unless self.class == other.class
 1713         s_ivars = self.instance_variables
 1714         s_ivars.sort!
 1715         s_ivars.delete :@ttl
 1716         o_ivars = other.instance_variables
 1717         o_ivars.sort!
 1718         o_ivars.delete :@ttl
 1719         return s_ivars == o_ivars &&
 1720           s_ivars.collect {|name| self.instance_variable_get name} ==
 1721             o_ivars.collect {|name| other.instance_variable_get name}
 1722       end
 1723 
 1724       def eql?(other) # :nodoc:
 1725         return self == other
 1726       end
 1727 
 1728       def hash # :nodoc:
 1729         h = 0
 1730         vars = self.instance_variables
 1731         vars.delete :@ttl
 1732         vars.each {|name|
 1733           h ^= self.instance_variable_get(name).hash
 1734         }
 1735         return h
 1736       end
 1737 
 1738       def self.get_class(type_value, class_value) # :nodoc:
 1739         return ClassHash[[type_value, class_value]] ||
 1740                Generic.create(type_value, class_value)
 1741       end
 1742 
 1743       ##
 1744       # A generic resource abstract class.
 1745 
 1746       class Generic < Resource
 1747 
 1748         ##
 1749         # Creates a new generic resource.
 1750 
 1751         def initialize(data)
 1752           @data = data
 1753         end
 1754 
 1755         ##
 1756         # Data for this generic resource.
 1757 
 1758         attr_reader :data
 1759 
 1760         def encode_rdata(msg) # :nodoc:
 1761           msg.put_bytes(data)
 1762         end
 1763 
 1764         def self.decode_rdata(msg) # :nodoc:
 1765           return self.new(msg.get_bytes)
 1766         end
 1767 
 1768         def self.create(type_value, class_value) # :nodoc:
 1769           c = Class.new(Generic)
 1770           c.const_set(:TypeValue, type_value)
 1771           c.const_set(:ClassValue, class_value)
 1772           Generic.const_set("Type#{type_value}_Class#{class_value}", c)
 1773           ClassHash[[type_value, class_value]] = c
 1774           return c
 1775         end
 1776       end
 1777 
 1778       ##
 1779       # Domain Name resource abstract class.
 1780 
 1781       class DomainName < Resource
 1782 
 1783         ##
 1784         # Creates a new DomainName from +name+.
 1785 
 1786         def initialize(name)
 1787           @name = name
 1788         end
 1789 
 1790         ##
 1791         # The name of this DomainName.
 1792 
 1793         attr_reader :name
 1794 
 1795         def encode_rdata(msg) # :nodoc:
 1796           msg.put_name(@name)
 1797         end
 1798 
 1799         def self.decode_rdata(msg) # :nodoc:
 1800           return self.new(msg.get_name)
 1801         end
 1802       end
 1803 
 1804       # Standard (class generic) RRs
 1805 
 1806       ClassValue = nil # :nodoc:
 1807 
 1808       ##
 1809       # An authoritative name server.
 1810 
 1811       class NS < DomainName
 1812         TypeValue = 2 # :nodoc:
 1813       end
 1814 
 1815       ##
 1816       # The canonical name for an alias.
 1817 
 1818       class CNAME < DomainName
 1819         TypeValue = 5 # :nodoc:
 1820       end
 1821 
 1822       ##
 1823       # Start Of Authority resource.
 1824 
 1825       class SOA < Resource
 1826 
 1827         TypeValue = 6 # :nodoc:
 1828 
 1829         ##
 1830         # Creates a new SOA record.  See the attr documentation for the
 1831         # details of each argument.
 1832 
 1833         def initialize(mname, rname, serial, refresh, retry_, expire, minimum)
 1834           @mname = mname
 1835           @rname = rname
 1836           @serial = serial
 1837           @refresh = refresh
 1838           @retry = retry_
 1839           @expire = expire
 1840           @minimum = minimum
 1841         end
 1842 
 1843         ##
 1844         # Name of the host where the master zone file for this zone resides.
 1845 
 1846         attr_reader :mname
 1847 
 1848         ##
 1849         # The person responsible for this domain name.
 1850 
 1851         attr_reader :rname
 1852 
 1853         ##
 1854         # The version number of the zone file.
 1855 
 1856         attr_reader :serial
 1857 
 1858         ##
 1859         # How often, in seconds, a secondary name server is to check for
 1860         # updates from the primary name server.
 1861 
 1862         attr_reader :refresh
 1863 
 1864         ##
 1865         # How often, in seconds, a secondary name server is to retry after a
 1866         # failure to check for a refresh.
 1867 
 1868         attr_reader :retry
 1869 
 1870         ##
 1871         # Time in seconds that a secondary name server is to use the data
 1872         # before refreshing from the primary name server.
 1873 
 1874         attr_reader :expire
 1875 
 1876         ##
 1877         # The minimum number of seconds to be used for TTL values in RRs.
 1878 
 1879         attr_reader :minimum
 1880 
 1881         def encode_rdata(msg) # :nodoc:
 1882           msg.put_name(@mname)
 1883           msg.put_name(@rname)
 1884           msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum)
 1885         end
 1886 
 1887         def self.decode_rdata(msg) # :nodoc:
 1888           mname = msg.get_name
 1889           rname = msg.get_name
 1890           serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN')
 1891           return self.new(
 1892             mname, rname, serial, refresh, retry_, expire, minimum)
 1893         end
 1894       end
 1895 
 1896       ##
 1897       # A Pointer to another DNS name.
 1898 
 1899       class PTR < DomainName
 1900         TypeValue = 12 # :nodoc:
 1901       end
 1902 
 1903       ##
 1904       # Host Information resource.
 1905 
 1906       class HINFO < Resource
 1907 
 1908         TypeValue = 13 # :nodoc:
 1909 
 1910         ##
 1911         # Creates a new HINFO running +os+ on +cpu+.
 1912 
 1913         def initialize(cpu, os)
 1914           @cpu = cpu
 1915           @os = os
 1916         end
 1917 
 1918         ##
 1919         # CPU architecture for this resource.
 1920 
 1921         attr_reader :cpu
 1922 
 1923         ##
 1924         # Operating system for this resource.
 1925 
 1926         attr_reader :os
 1927 
 1928         def encode_rdata(msg) # :nodoc:
 1929           msg.put_string(@cpu)
 1930           msg.put_string(@os)
 1931         end
 1932 
 1933         def self.decode_rdata(msg) # :nodoc:
 1934           cpu = msg.get_string
 1935           os = msg.get_string
 1936           return self.new(cpu, os)
 1937         end
 1938       end
 1939 
 1940       ##
 1941       # Mailing list or mailbox information.
 1942 
 1943       class MINFO < Resource
 1944 
 1945         TypeValue = 14 # :nodoc:
 1946 
 1947         def initialize(rmailbx, emailbx)
 1948           @rmailbx = rmailbx
 1949           @emailbx = emailbx
 1950         end
 1951 
 1952         ##
 1953         # Domain name responsible for this mail list or mailbox.
 1954 
 1955         attr_reader :rmailbx
 1956 
 1957         ##
 1958         # Mailbox to use for error messages related to the mail list or mailbox.
 1959 
 1960         attr_reader :emailbx
 1961 
 1962         def encode_rdata(msg) # :nodoc:
 1963           msg.put_name(@rmailbx)
 1964           msg.put_name(@emailbx)
 1965         end
 1966 
 1967         def self.decode_rdata(msg) # :nodoc:
 1968           rmailbx = msg.get_string
 1969           emailbx = msg.get_string
 1970           return self.new(rmailbx, emailbx)
 1971         end
 1972       end
 1973 
 1974       ##
 1975       # Mail Exchanger resource.
 1976 
 1977       class MX < Resource
 1978 
 1979         TypeValue= 15 # :nodoc:
 1980 
 1981         ##
 1982         # Creates a new MX record with +preference+, accepting mail at
 1983         # +exchange+.
 1984 
 1985         def initialize(preference, exchange)
 1986           @preference = preference
 1987           @exchange = exchange
 1988         end
 1989 
 1990         ##
 1991         # The preference for this MX.
 1992 
 1993         attr_reader :preference
 1994 
 1995         ##
 1996         # The host of this MX.
 1997 
 1998         attr_reader :exchange
 1999 
 2000         def encode_rdata(msg) # :nodoc:
 2001           msg.put_pack('n', @preference)
 2002           msg.put_name(@exchange)
 2003         end
 2004 
 2005         def self.decode_rdata(msg) # :nodoc:
 2006           preference, = msg.get_unpack('n')
 2007           exchange = msg.get_name
 2008           return self.new(preference, exchange)
 2009         end
 2010       end
 2011 
 2012       ##
 2013       # Unstructured text resource.
 2014 
 2015       class TXT < Resource
 2016 
 2017         TypeValue = 16 # :nodoc:
 2018 
 2019         def initialize(first_string, *rest_strings)
 2020           @strings = [first_string, *rest_strings]
 2021         end
 2022 
 2023         ##
 2024         # Returns an Array of Strings for this TXT record.
 2025 
 2026         attr_reader :strings
 2027 
 2028         ##
 2029         # Returns the concatenated string from +strings+.
 2030 
 2031         def data
 2032           @strings.join("")
 2033         end
 2034 
 2035         def encode_rdata(msg) # :nodoc:
 2036           msg.put_string_list(@strings)
 2037         end
 2038 
 2039         def self.decode_rdata(msg) # :nodoc:
 2040           strings = msg.get_string_list
 2041           return self.new(*strings)
 2042         end
 2043       end
 2044 
 2045       ##
 2046       # Location resource
 2047 
 2048       class LOC < Resource
 2049 
 2050         TypeValue = 29 # :nodoc:
 2051 
 2052         def initialize(version, ssize, hprecision, vprecision, latitude, longitude, altitude)
 2053           @version    = version
 2054           @ssize      = Resolv::LOC::Size.create(ssize)
 2055           @hprecision = Resolv::LOC::Size.create(hprecision)
 2056           @vprecision = Resolv::LOC::Size.create(vprecision)
 2057           @latitude   = Resolv::LOC::Coord.create(latitude)
 2058           @longitude  = Resolv::LOC::Coord.create(longitude)
 2059           @altitude   = Resolv::LOC::Alt.create(altitude)
 2060         end
 2061 
 2062         ##
 2063         # Returns the version value for this LOC record which should always be 00
 2064 
 2065         attr_reader :version
 2066 
 2067         ##
 2068         # The spherical size of this LOC
 2069         # in meters using scientific notation as 2 integers of XeY
 2070 
 2071         attr_reader :ssize
 2072 
 2073         ##
 2074         # The horizontal precision using ssize type values
 2075         # in meters using scientific notation as 2 integers of XeY
 2076         # for precision use value/2 e.g. 2m = +/-1m
 2077 
 2078         attr_reader :hprecision
 2079 
 2080         ##
 2081         # The vertical precision using ssize type values
 2082         # in meters using scientific notation as 2 integers of XeY
 2083         # for precision use value/2 e.g. 2m = +/-1m
 2084 
 2085         attr_reader :vprecision
 2086 
 2087         ##
 2088         # The latitude for this LOC where 2**31 is the equator
 2089         # in thousandths of an arc second as an unsigned 32bit integer
 2090 
 2091         attr_reader :latitude
 2092 
 2093         ##
 2094         # The longitude for this LOC where 2**31 is the prime meridian
 2095         # in thousandths of an arc second as an unsigned 32bit integer
 2096 
 2097         attr_reader :longitude
 2098 
 2099         ##
 2100         # The altitude of the LOC above a reference sphere whose surface sits 100km below the WGS84 spheroid
 2101         # in centimeters as an unsigned 32bit integer
 2102 
 2103         attr_reader :altitude
 2104 
 2105 
 2106         def encode_rdata(msg) # :nodoc:
 2107           msg.put_bytes(@version)
 2108           msg.put_bytes(@ssize.scalar)
 2109           msg.put_bytes(@hprecision.scalar)
 2110           msg.put_bytes(@vprecision.scalar)
 2111           msg.put_bytes(@latitude.coordinates)
 2112           msg.put_bytes(@longitude.coordinates)
 2113           msg.put_bytes(@altitude.altitude)
 2114         end
 2115 
 2116         def self.decode_rdata(msg) # :nodoc:
 2117           version    = msg.get_bytes(1)
 2118           ssize      = msg.get_bytes(1)
 2119           hprecision = msg.get_bytes(1)
 2120           vprecision = msg.get_bytes(1)
 2121           latitude   = msg.get_bytes(4)
 2122           longitude  = msg.get_bytes(4)
 2123           altitude   = msg.get_bytes(4)
 2124           return self.new(
 2125             version,
 2126             Resolv::LOC::Size.new(ssize),
 2127             Resolv::LOC::Size.new(hprecision),
 2128             Resolv::LOC::Size.new(vprecision),
 2129             Resolv::LOC::Coord.new(latitude,"lat"),
 2130             Resolv::LOC::Coord.new(longitude,"lon"),
 2131             Resolv::LOC::Alt.new(altitude)
 2132           )
 2133         end
 2134       end
 2135 
 2136       ##
 2137       # A Query type requesting any RR.
 2138 
 2139       class ANY < Query
 2140         TypeValue = 255 # :nodoc:
 2141       end
 2142 
 2143       ClassInsensitiveTypes = [ # :nodoc:
 2144         NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, LOC, ANY
 2145       ]
 2146 
 2147       ##
 2148       # module IN contains ARPA Internet specific RRs.
 2149 
 2150       module IN
 2151 
 2152         ClassValue = 1 # :nodoc:
 2153 
 2154         ClassInsensitiveTypes.each {|s|
 2155           c = Class.new(s)
 2156           c.const_set(:TypeValue, s::TypeValue)
 2157           c.const_set(:ClassValue, ClassValue)
 2158           ClassHash[[s::TypeValue, ClassValue]] = c
 2159           self.const_set(s.name.sub(/.*::/, ''), c)
 2160         }
 2161 
 2162         ##
 2163         # IPv4 Address resource
 2164 
 2165         class A < Resource
 2166           TypeValue = 1
 2167           ClassValue = IN::ClassValue
 2168           ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
 2169 
 2170           ##
 2171           # Creates a new A for +address+.
 2172 
 2173           def initialize(address)
 2174             @address = IPv4.create(address)
 2175           end
 2176 
 2177           ##
 2178           # The Resolv::IPv4 address for this A.
 2179 
 2180           attr_reader :address
 2181 
 2182           def encode_rdata(msg) # :nodoc:
 2183             msg.put_bytes(@address.address)
 2184           end
 2185 
 2186           def self.decode_rdata(msg) # :nodoc:
 2187             return self.new(IPv4.new(msg.get_bytes(4)))
 2188           end
 2189         end
 2190 
 2191         ##
 2192         # Well Known Service resource.
 2193 
 2194         class WKS < Resource
 2195           TypeValue = 11
 2196           ClassValue = IN::ClassValue
 2197           ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
 2198 
 2199           def initialize(address, protocol, bitmap)
 2200             @address = IPv4.create(address)
 2201             @protocol = protocol
 2202             @bitmap = bitmap
 2203           end
 2204 
 2205           ##
 2206           # The host these services run on.
 2207 
 2208           attr_reader :address
 2209 
 2210           ##
 2211           # IP protocol number for these services.
 2212 
 2213           attr_reader :protocol
 2214 
 2215           ##
 2216           # A bit map of enabled services on this host.
 2217           #
 2218           # If protocol is 6 (TCP) then the 26th bit corresponds to the SMTP
 2219           # service (port 25).  If this bit is set, then an SMTP server should
 2220           # be listening on TCP port 25; if zero, SMTP service is not
 2221           # supported.
 2222 
 2223           attr_reader :bitmap
 2224 
 2225           def encode_rdata(msg) # :nodoc:
 2226             msg.put_bytes(@address.address)
 2227             msg.put_pack("n", @protocol)
 2228             msg.put_bytes(@bitmap)
 2229           end
 2230 
 2231           def self.decode_rdata(msg) # :nodoc:
 2232             address = IPv4.new(msg.get_bytes(4))
 2233             protocol, = msg.get_unpack("n")
 2234             bitmap = msg.get_bytes
 2235             return self.new(address, protocol, bitmap)
 2236           end
 2237         end
 2238 
 2239         ##
 2240         # An IPv6 address record.
 2241 
 2242         class AAAA < Resource
 2243           TypeValue = 28
 2244           ClassValue = IN::ClassValue
 2245           ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
 2246 
 2247           ##
 2248           # Creates a new AAAA for +address+.
 2249 
 2250           def initialize(address)
 2251             @address = IPv6.create(address)
 2252           end
 2253 
 2254           ##
 2255           # The Resolv::IPv6 address for this AAAA.
 2256 
 2257           attr_reader :address
 2258 
 2259           def encode_rdata(msg) # :nodoc:
 2260             msg.put_bytes(@address.address)
 2261           end
 2262 
 2263           def self.decode_rdata(msg) # :nodoc:
 2264             return self.new(IPv6.new(msg.get_bytes(16)))
 2265           end
 2266         end
 2267 
 2268         ##
 2269         # SRV resource record defined in RFC 2782
 2270         #
 2271         # These records identify the hostname and port that a service is
 2272         # available at.
 2273 
 2274         class SRV < Resource
 2275           TypeValue = 33
 2276           ClassValue = IN::ClassValue
 2277           ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
 2278 
 2279           # Create a SRV resource record.
 2280           #
 2281           # See the documentation for #priority, #weight, #port and #target
 2282           # for +priority+, +weight+, +port and +target+ respectively.
 2283 
 2284           def initialize(priority, weight, port, target)
 2285             @priority = priority.to_int
 2286             @weight = weight.to_int
 2287             @port = port.to_int
 2288             @target = Name.create(target)
 2289           end
 2290 
 2291           # The priority of this target host.
 2292           #
 2293           # A client MUST attempt to contact the target host with the
 2294           # lowest-numbered priority it can reach; target hosts with the same
 2295           # priority SHOULD be tried in an order defined by the weight field.
 2296           # The range is 0-65535.  Note that it is not widely implemented and
 2297           # should be set to zero.
 2298 
 2299           attr_reader :priority
 2300 
 2301           # A server selection mechanism.
 2302           #
 2303           # The weight field specifies a relative weight for entries with the
 2304           # same priority. Larger weights SHOULD be given a proportionately
 2305           # higher probability of being selected. The range of this number is
 2306           # 0-65535.  Domain administrators SHOULD use Weight 0 when there
 2307           # isn't any server selection to do, to make the RR easier to read
 2308           # for humans (less noisy). Note that it is not widely implemented
 2309           # and should be set to zero.
 2310 
 2311           attr_reader :weight
 2312 
 2313           # The port on this target host of this service.
 2314           #
 2315           # The range is 0-65535.
 2316 
 2317           attr_reader :port
 2318 
 2319           # The domain name of the target host.
 2320           #
 2321           # A target of "." means that the service is decidedly not available
 2322           # at this domain.
 2323 
 2324           attr_reader :target
 2325 
 2326           def encode_rdata(msg) # :nodoc:
 2327             msg.put_pack("n", @priority)
 2328             msg.put_pack("n", @weight)
 2329             msg.put_pack("n", @port)
 2330             msg.put_name(@target)
 2331           end
 2332 
 2333           def self.decode_rdata(msg) # :nodoc:
 2334             priority, = msg.get_unpack("n")
 2335             weight,   = msg.get_unpack("n")
 2336             port,     = msg.get_unpack("n")
 2337             target    = msg.get_name
 2338             return self.new(priority, weight, port, target)
 2339           end
 2340         end
 2341       end
 2342     end
 2343   end
 2344 
 2345   ##
 2346   # A Resolv::DNS IPv4 address.
 2347 
 2348   class IPv4
 2349 
 2350     ##
 2351     # Regular expression IPv4 addresses must match.
 2352 
 2353     Regex256 = /0
 2354                |1(?:[0-9][0-9]?)?
 2355                |2(?:[0-4][0-9]?|5[0-5]?|[6-9])?
 2356                |[3-9][0-9]?/x
 2357     Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/
 2358 
 2359     def self.create(arg)
 2360       case arg
 2361       when IPv4
 2362         return arg
 2363       when Regex
 2364         if (0..255) === (a = $1.to_i) &&
 2365            (0..255) === (b = $2.to_i) &&
 2366            (0..255) === (c = $3.to_i) &&
 2367            (0..255) === (d = $4.to_i)
 2368           return self.new([a, b, c, d].pack("CCCC"))
 2369         else
 2370           raise ArgumentError.new("IPv4 address with invalid value: " + arg)
 2371         end
 2372       else
 2373         raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}")
 2374       end
 2375     end
 2376 
 2377     def initialize(address) # :nodoc:
 2378       unless address.kind_of?(String)
 2379         raise ArgumentError, 'IPv4 address must be a string'
 2380       end
 2381       unless address.length == 4
 2382         raise ArgumentError, "IPv4 address expects 4 bytes but #{address.length} bytes"
 2383       end
 2384       @address = address
 2385     end
 2386 
 2387     ##
 2388     # A String representation of this IPv4 address.
 2389 
 2390     ##
 2391     # The raw IPv4 address as a String.
 2392 
 2393     attr_reader :address
 2394 
 2395     def to_s # :nodoc:
 2396       return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC"))
 2397     end
 2398 
 2399     def inspect # :nodoc:
 2400       return "#<#{self.class} #{self}>"
 2401     end
 2402 
 2403     ##
 2404     # Turns this IPv4 address into a Resolv::DNS::Name.
 2405 
 2406     def to_name
 2407       return DNS::Name.create(
 2408         '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse)
 2409     end
 2410 
 2411     def ==(other) # :nodoc:
 2412       return @address == other.address
 2413     end
 2414 
 2415     def eql?(other) # :nodoc:
 2416       return self == other
 2417     end
 2418 
 2419     def hash # :nodoc:
 2420       return @address.hash
 2421     end
 2422   end
 2423 
 2424   ##
 2425   # A Resolv::DNS IPv6 address.
 2426 
 2427   class IPv6
 2428 
 2429     ##
 2430     # IPv6 address format a:b:c:d:e:f:g:h
 2431     Regex_8Hex = /\A
 2432       (?:[0-9A-Fa-f]{1,4}:){7}
 2433          [0-9A-Fa-f]{1,4}
 2434       \z/x
 2435 
 2436     ##
 2437     # Compressed IPv6 address format a::b
 2438 
 2439     Regex_CompressedHex = /\A
 2440       ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
 2441       ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
 2442       \z/x
 2443 
 2444     ##
 2445     # IPv4 mapped IPv6 address format a:b:c:d:e:f:w.x.y.z
 2446 
 2447     Regex_6Hex4Dec = /\A
 2448       ((?:[0-9A-Fa-f]{1,4}:){6,6})
 2449       (\d+)\.(\d+)\.(\d+)\.(\d+)
 2450       \z/x
 2451 
 2452     ##
 2453     # Compressed IPv4 mapped IPv6 address format a::b:w.x.y.z
 2454 
 2455     Regex_CompressedHex4Dec = /\A
 2456       ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
 2457       ((?:[0-9A-Fa-f]{1,4}:)*)
 2458       (\d+)\.(\d+)\.(\d+)\.(\d+)
 2459       \z/x
 2460 
 2461     ##
 2462     # A composite IPv6 address Regexp.
 2463 
 2464     Regex = /
 2465       (?:#{Regex_8Hex}) |
 2466       (?:#{Regex_CompressedHex}) |
 2467       (?:#{Regex_6Hex4Dec}) |
 2468       (?:#{Regex_CompressedHex4Dec})/x
 2469 
 2470     ##
 2471     # Creates a new IPv6 address from +arg+ which may be:
 2472     #
 2473     # IPv6:: returns +arg+.
 2474     # String:: +arg+ must match one of the IPv6::Regex* constants
 2475 
 2476     def self.create(arg)
 2477       case arg
 2478       when IPv6
 2479         return arg
 2480       when String
 2481         address = ''.b
 2482         if Regex_8Hex =~ arg
 2483           arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
 2484         elsif Regex_CompressedHex =~ arg
 2485           prefix = $1
 2486           suffix = $2
 2487           a1 = ''.b
 2488           a2 = ''.b
 2489           prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
 2490           suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
 2491           omitlen = 16 - a1.length - a2.length
 2492           address << a1 << "\0" * omitlen << a2
 2493         elsif Regex_6Hex4Dec =~ arg
 2494           prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i
 2495           if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
 2496             prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
 2497             address << [a, b, c, d].pack('CCCC')
 2498           else
 2499             raise ArgumentError.new("not numeric IPv6 address: " + arg)
 2500           end
 2501         elsif Regex_CompressedHex4Dec =~ arg
 2502           prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i
 2503           if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
 2504             a1 = ''.b
 2505             a2 = ''.b
 2506             prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
 2507             suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
 2508             omitlen = 12 - a1.length - a2.length
 2509             address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC')
 2510           else
 2511             raise ArgumentError.new("not numeric IPv6 address: " + arg)
 2512           end
 2513         else
 2514           raise ArgumentError.new("not numeric IPv6 address: " + arg)
 2515         end
 2516         return IPv6.new(address)
 2517       else
 2518         raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}")
 2519       end
 2520     end
 2521 
 2522     def initialize(address) # :nodoc:
 2523       unless address.kind_of?(String) && address.length == 16
 2524         raise ArgumentError.new('IPv6 address must be 16 bytes')
 2525       end
 2526       @address = address
 2527     end
 2528 
 2529     ##
 2530     # The raw IPv6 address as a String.
 2531 
 2532     attr_reader :address
 2533 
 2534     def to_s # :nodoc:
 2535       address = sprintf("%x:%x:%x:%x:%x:%x:%x:%x", *@address.unpack("nnnnnnnn"))
 2536       unless address.sub!(/(^|:)0(:0)+(:|$)/, '::')
 2537         address.sub!(/(^|:)0(:|$)/, '::')
 2538       end
 2539       return address
 2540     end
 2541 
 2542     def inspect # :nodoc:
 2543       return "#<#{self.class} #{self}>"
 2544     end
 2545 
 2546     ##
 2547     # Turns this IPv6 address into a Resolv::DNS::Name.
 2548     #--
 2549     # ip6.arpa should be searched too. [RFC3152]
 2550 
 2551     def to_name
 2552       return DNS::Name.new(
 2553         @address.unpack("H32")[0].split(//).reverse + ['ip6', 'arpa'])
 2554     end
 2555 
 2556     def ==(other) # :nodoc:
 2557       return @address == other.address
 2558     end
 2559 
 2560     def eql?(other) # :nodoc:
 2561       return self == other
 2562     end
 2563 
 2564     def hash # :nodoc:
 2565       return @address.hash
 2566     end
 2567   end
 2568 
 2569   ##
 2570   # Resolv::MDNS is a one-shot Multicast DNS (mDNS) resolver.  It blindly
 2571   # makes queries to the mDNS addresses without understanding anything about
 2572   # multicast ports.
 2573   #
 2574   # Information taken form the following places:
 2575   #
 2576   # * RFC 6762
 2577 
 2578   class MDNS < DNS
 2579 
 2580     ##
 2581     # Default mDNS Port
 2582 
 2583     Port = 5353
 2584 
 2585     ##
 2586     # Default IPv4 mDNS address
 2587 
 2588     AddressV4 = '224.0.0.251'
 2589 
 2590     ##
 2591     # Default IPv6 mDNS address
 2592 
 2593     AddressV6 = 'ff02::fb'
 2594 
 2595     ##
 2596     # Default mDNS addresses
 2597 
 2598     Addresses = [
 2599       [AddressV4, Port],
 2600       [AddressV6, Port],
 2601     ]
 2602 
 2603     ##
 2604     # Creates a new one-shot Multicast DNS (mDNS) resolver.
 2605     #
 2606     # +config_info+ can be:
 2607     #
 2608     # nil::
 2609     #   Uses the default mDNS addresses
 2610     #
 2611     # Hash::
 2612     #   Must contain :nameserver or :nameserver_port like
 2613     #   Resolv::DNS#initialize.
 2614 
 2615     def initialize(config_info=nil)
 2616       if config_info then
 2617         super({ nameserver_port: Addresses }.merge(config_info))
 2618       else
 2619         super(nameserver_port: Addresses)
 2620       end
 2621     end
 2622 
 2623     ##
 2624     # Iterates over all IP addresses for +name+ retrieved from the mDNS
 2625     # resolver, provided name ends with "local".  If the name does not end in
 2626     # "local" no records will be returned.
 2627     #
 2628     # +name+ can be a Resolv::DNS::Name or a String.  Retrieved addresses will
 2629     # be a Resolv::IPv4 or Resolv::IPv6
 2630 
 2631     def each_address(name)
 2632       name = Resolv::DNS::Name.create(name)
 2633 
 2634       return unless name[-1].to_s == 'local'
 2635 
 2636       super(name)
 2637     end
 2638 
 2639     def make_udp_requester # :nodoc:
 2640       nameserver_port = @config.nameserver_port
 2641       Requester::MDNSOneShot.new(*nameserver_port)
 2642     end
 2643 
 2644   end
 2645 
 2646   module LOC
 2647 
 2648     ##
 2649     # A Resolv::LOC::Size
 2650 
 2651     class Size
 2652 
 2653       Regex = /^(\d+\.*\d*)[m]$/
 2654 
 2655       ##
 2656       # Creates a new LOC::Size from +arg+ which may be:
 2657       #
 2658       # LOC::Size:: returns +arg+.
 2659       # String:: +arg+ must match the LOC::Size::Regex constant
 2660 
 2661       def self.create(arg)
 2662         case arg
 2663         when Size
 2664           return arg
 2665         when String
 2666           scalar = ''
 2667           if Regex =~ arg
 2668             scalar = [(($1.to_f*(1e2)).to_i.to_s[0].to_i*(2**4)+(($1.to_f*(1e2)).to_i.to_s.length-1))].pack("C")
 2669           else
 2670             raise ArgumentError.new("not a properly formed Size string: " + arg)
 2671           end
 2672           return Size.new(scalar)
 2673         else
 2674           raise ArgumentError.new("cannot interpret as Size: #{arg.inspect}")
 2675         end
 2676       end
 2677 
 2678       def initialize(scalar)
 2679         @scalar = scalar
 2680       end
 2681 
 2682       ##
 2683       # The raw size
 2684 
 2685       attr_reader :scalar
 2686 
 2687       def to_s # :nodoc:
 2688         s = @scalar.unpack("H2").join.to_s
 2689         return ((s[0].to_i)*(10**(s[1].to_i-2))).to_s << "m"
 2690       end
 2691 
 2692       def inspect # :nodoc:
 2693         return "#<#{self.class} #{self}>"
 2694       end
 2695 
 2696       def ==(other) # :nodoc:
 2697         return @scalar == other.scalar
 2698       end
 2699 
 2700       def eql?(other) # :nodoc:
 2701         return self == other
 2702       end
 2703 
 2704       def hash # :nodoc:
 2705         return @scalar.hash
 2706       end
 2707 
 2708     end
 2709 
 2710     ##
 2711     # A Resolv::LOC::Coord
 2712 
 2713     class Coord
 2714 
 2715       Regex = /^(\d+)\s(\d+)\s(\d+\.\d+)\s([NESW])$/
 2716 
 2717       ##
 2718       # Creates a new LOC::Coord from +arg+ which may be:
 2719       #
 2720       # LOC::Coord:: returns +arg+.
 2721       # String:: +arg+ must match the LOC::Coord::Regex constant
 2722 
 2723       def self.create(arg)
 2724         case arg
 2725         when Coord
 2726           return arg
 2727         when String
 2728           coordinates = ''
 2729           if Regex =~ arg && $1.to_f < 180
 2730             m = $~
 2731             hemi = (m[4][/[NE]/]) || (m[4][/[SW]/]) ? 1 : -1
 2732             coordinates = [ ((m[1].to_i*(36e5)) + (m[2].to_i*(6e4)) +
 2733                              (m[3].to_f*(1e3))) * hemi+(2**31) ].pack("N")
 2734             orientation = m[4][/[NS]/] ? 'lat' : 'lon'
 2735           else
 2736             raise ArgumentError.new("not a properly formed Coord string: " + arg)
 2737           end
 2738           return Coord.new(coordinates,orientation)
 2739         else
 2740           raise ArgumentError.new("cannot interpret as Coord: #{arg.inspect}")
 2741         end
 2742       end
 2743 
 2744       def initialize(coordinates,orientation)
 2745         unless coordinates.kind_of?(String)
 2746           raise ArgumentError.new("Coord must be a 32bit unsigned integer in hex format: #{coordinates.inspect}")
 2747         end
 2748         unless orientation.kind_of?(String) && orientation[/^lon$|^lat$/]
 2749           raise ArgumentError.new('Coord expects orientation to be a String argument of "lat" or "lon"')
 2750         end
 2751         @coordinates = coordinates
 2752         @orientation = orientation
 2753       end
 2754 
 2755       ##
 2756       # The raw coordinates
 2757 
 2758       attr_reader :coordinates
 2759 
 2760       ## The orientation of the hemisphere as 'lat' or 'lon'
 2761 
 2762       attr_reader :orientation
 2763 
 2764       def to_s # :nodoc:
 2765           c = @coordinates.unpack("N").join.to_i
 2766           val      = (c - (2**31)).abs
 2767           fracsecs = (val % 1e3).to_i.to_s
 2768           val      = val / 1e3
 2769           secs     = (val % 60).to_i.to_s
 2770           val      = val / 60
 2771           mins     = (val % 60).to_i.to_s
 2772           degs     = (val / 60).to_i.to_s
 2773           posi = (c >= 2**31)
 2774           case posi
 2775           when true
 2776             hemi = @orientation[/^lat$/] ? "N" : "E"
 2777           else
 2778             hemi = @orientation[/^lon$/] ? "W" : "S"
 2779           end
 2780           return degs << " " << mins << " " << secs << "." << fracsecs << " " << hemi
 2781       end
 2782 
 2783       def inspect # :nodoc:
 2784         return "#<#{self.class} #{self}>"
 2785       end
 2786 
 2787       def ==(other) # :nodoc:
 2788         return @coordinates == other.coordinates
 2789       end
 2790 
 2791       def eql?(other) # :nodoc:
 2792         return self == other
 2793       end
 2794 
 2795       def hash # :nodoc:
 2796         return @coordinates.hash
 2797       end
 2798 
 2799     end
 2800 
 2801     ##
 2802     # A Resolv::LOC::Alt
 2803 
 2804     class Alt
 2805 
 2806       Regex = /^([+-]*\d+\.*\d*)[m]$/
 2807 
 2808       ##
 2809       # Creates a new LOC::Alt from +arg+ which may be:
 2810       #
 2811       # LOC::Alt:: returns +arg+.
 2812       # String:: +arg+ must match the LOC::Alt::Regex constant
 2813 
 2814       def self.create(arg)
 2815         case arg
 2816         when Alt
 2817           return arg
 2818         when String
 2819           altitude = ''
 2820           if Regex =~ arg
 2821             altitude = [($1.to_f*(1e2))+(1e7)].pack("N")
 2822           else
 2823             raise ArgumentError.new("not a properly formed Alt string: " + arg)
 2824           end
 2825           return Alt.new(altitude)
 2826         else
 2827           raise ArgumentError.new("cannot interpret as Alt: #{arg.inspect}")
 2828         end
 2829       end
 2830 
 2831       def initialize(altitude)
 2832         @altitude = altitude
 2833       end
 2834 
 2835       ##
 2836       # The raw altitude
 2837 
 2838       attr_reader :altitude
 2839 
 2840       def to_s # :nodoc:
 2841         a = @altitude.unpack("N").join.to_i
 2842         return ((a.to_f/1e2)-1e5).to_s + "m"
 2843       end
 2844 
 2845       def inspect # :nodoc:
 2846         return "#<#{self.class} #{self}>"
 2847       end
 2848 
 2849       def ==(other) # :nodoc:
 2850         return @altitude == other.altitude
 2851       end
 2852 
 2853       def eql?(other) # :nodoc:
 2854         return self == other
 2855       end
 2856 
 2857       def hash # :nodoc:
 2858         return @altitude.hash
 2859       end
 2860 
 2861     end
 2862 
 2863   end
 2864 
 2865   ##
 2866   # Default resolver to use for Resolv class methods.
 2867 
 2868   DefaultResolver = self.new
 2869 
 2870   ##
 2871   # Replaces the resolvers in the default resolver with +new_resolvers+.  This
 2872   # allows resolvers to be changed for resolv-replace.
 2873 
 2874   def DefaultResolver.replace_resolvers new_resolvers
 2875     @resolvers = new_resolvers
 2876   end
 2877 
 2878   ##
 2879   # Address Regexp to use for matching IP addresses.
 2880 
 2881   AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/
 2882 
 2883 end
 2884