[rubygems/rubygems] Use a constant empty tar header to avoid extra allocations
[ruby.git] / lib / resolv.rb
blobe36dbce2595d8269a7d8565686d809831067dd66
1 # frozen_string_literal: true
3 require 'socket'
4 require 'timeout'
5 require 'io/wait'
7 begin
8   require 'securerandom'
9 rescue LoadError
10 end
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.
16 # See also resolv-replace.rb to replace the libc resolver with Resolv.
18 # Resolv can look up various DNS resources using the DNS module directly.
20 # Examples:
22 #   p Resolv.getaddress "www.ruby-lang.org"
23 #   p Resolv.getname "210.251.121.214"
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
33 # == Bugs
35 # * NIS is not supported.
36 # * /etc/nsswitch.conf is not supported.
38 class Resolv
40   VERSION = "0.4.0"
42   ##
43   # Looks up the first IP address for +name+.
45   def self.getaddress(name)
46     DefaultResolver.getaddress(name)
47   end
49   ##
50   # Looks up all IP address for +name+.
52   def self.getaddresses(name)
53     DefaultResolver.getaddresses(name)
54   end
56   ##
57   # Iterates over all IP addresses for +name+.
59   def self.each_address(name, &block)
60     DefaultResolver.each_address(name, &block)
61   end
63   ##
64   # Looks up the hostname of +address+.
66   def self.getname(address)
67     DefaultResolver.getname(address)
68   end
70   ##
71   # Looks up all hostnames for +address+.
73   def self.getnames(address)
74     DefaultResolver.getnames(address)
75   end
77   ##
78   # Iterates over all hostnames for +address+.
80   def self.each_name(address, &proc)
81     DefaultResolver.each_name(address, &proc)
82   end
84   ##
85   # Creates a new Resolv using +resolvers+.
87   def initialize(resolvers=nil, use_ipv6: nil)
88     @resolvers = resolvers || [Hosts.new, DNS.new(DNS::Config.default_config_hash.merge(use_ipv6: use_ipv6))]
89   end
91   ##
92   # Looks up the first IP address for +name+.
94   def getaddress(name)
95     each_address(name) {|address| return address}
96     raise ResolvError.new("no address for #{name}")
97   end
99   ##
100   # Looks up all IP address for +name+.
102   def getaddresses(name)
103     ret = []
104     each_address(name) {|address| ret << address}
105     return ret
106   end
108   ##
109   # Iterates over all IP addresses for +name+.
111   def each_address(name)
112     if AddressRegex =~ name
113       yield name
114       return
115     end
116     yielded = false
117     @resolvers.each {|r|
118       r.each_address(name) {|address|
119         yield address.to_s
120         yielded = true
121       }
122       return if yielded
123     }
124   end
126   ##
127   # Looks up the hostname of +address+.
129   def getname(address)
130     each_name(address) {|name| return name}
131     raise ResolvError.new("no name for #{address}")
132   end
134   ##
135   # Looks up all hostnames for +address+.
137   def getnames(address)
138     ret = []
139     each_name(address) {|name| ret << name}
140     return ret
141   end
143   ##
144   # Iterates over all hostnames for +address+.
146   def each_name(address)
147     yielded = false
148     @resolvers.each {|r|
149       r.each_name(address) {|name|
150         yield name.to_s
151         yielded = true
152       }
153       return if yielded
154     }
155   end
157   ##
158   # Indicates a failure to resolve a name or address.
160   class ResolvError < StandardError; end
162   ##
163   # Indicates a timeout resolving a name or address.
165   class ResolvTimeout < Timeout::Error; end
167   ##
168   # Resolv::Hosts is a hostname resolver that uses the system hosts file.
170   class Hosts
171     if /mswin|mingw|cygwin/ =~ RUBY_PLATFORM and
172       begin
173         require 'win32/resolv'
174         DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL
175       rescue LoadError
176       end
177     end
178     DefaultFileName ||= '/etc/hosts'
180     ##
181     # Creates a new Resolv::Hosts, using +filename+ for its data source.
183     def initialize(filename = DefaultFileName)
184       @filename = filename
185       @mutex = Thread::Mutex.new
186       @initialized = nil
187     end
189     def lazy_initialize # :nodoc:
190       @mutex.synchronize {
191         unless @initialized
192           @name2addr = {}
193           @addr2name = {}
194           File.open(@filename, 'rb') {|f|
195             f.each {|line|
196               line.sub!(/#.*/, '')
197               addr, *hostnames = line.split(/\s+/)
198               next unless addr
199               (@addr2name[addr] ||= []).concat(hostnames)
200               hostnames.each {|hostname| (@name2addr[hostname] ||= []) << addr}
201             }
202           }
203           @name2addr.each {|name, arr| arr.reverse!}
204           @initialized = true
205         end
206       }
207       self
208     end
210     ##
211     # Gets the IP address of +name+ from the hosts file.
213     def getaddress(name)
214       each_address(name) {|address| return address}
215       raise ResolvError.new("#{@filename} has no name: #{name}")
216     end
218     ##
219     # Gets all IP addresses for +name+ from the hosts file.
221     def getaddresses(name)
222       ret = []
223       each_address(name) {|address| ret << address}
224       return ret
225     end
227     ##
228     # Iterates over all IP addresses for +name+ retrieved from the hosts file.
230     def each_address(name, &proc)
231       lazy_initialize
232       @name2addr[name]&.each(&proc)
233     end
235     ##
236     # Gets the hostname of +address+ from the hosts file.
238     def getname(address)
239       each_name(address) {|name| return name}
240       raise ResolvError.new("#{@filename} has no address: #{address}")
241     end
243     ##
244     # Gets all hostnames for +address+ from the hosts file.
246     def getnames(address)
247       ret = []
248       each_name(address) {|name| ret << name}
249       return ret
250     end
252     ##
253     # Iterates over all hostnames for +address+ retrieved from the hosts file.
255     def each_name(address, &proc)
256       lazy_initialize
257       @addr2name[address]&.each(&proc)
258     end
259   end
261   ##
262   # Resolv::DNS is a DNS stub resolver.
263   #
264   # Information taken from the following places:
265   #
266   # * STD0013
267   # * RFC 1035
268   # * ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters
269   # * etc.
271   class DNS
273     ##
274     # Default DNS Port
276     Port = 53
278     ##
279     # Default DNS UDP packet size
281     UDPSize = 512
283     ##
284     # Creates a new DNS resolver.  See Resolv::DNS.new for argument details.
285     #
286     # Yields the created DNS resolver to the block, if given, otherwise
287     # returns it.
289     def self.open(*args)
290       dns = new(*args)
291       return dns unless block_given?
292       begin
293         yield dns
294       ensure
295         dns.close
296       end
297     end
299     ##
300     # Creates a new DNS resolver.
301     #
302     # +config_info+ can be:
303     #
304     # nil:: Uses /etc/resolv.conf.
305     # String:: Path to a file using /etc/resolv.conf's format.
306     # Hash:: Must contain :nameserver, :search and :ndots keys.
307     # :nameserver_port can be used to specify port number of nameserver address.
308     # :raise_timeout_errors can be used to raise timeout errors
309     # as exceptions instead of treating the same as an NXDOMAIN response.
310     #
311     # The value of :nameserver should be an address string or
312     # an array of address strings.
313     # - :nameserver => '8.8.8.8'
314     # - :nameserver => ['8.8.8.8', '8.8.4.4']
315     #
316     # The value of :nameserver_port should be an array of
317     # pair of nameserver address and port number.
318     # - :nameserver_port => [['8.8.8.8', 53], ['8.8.4.4', 53]]
319     #
320     # Example:
321     #
322     #   Resolv::DNS.new(:nameserver => ['210.251.121.21'],
323     #                   :search => ['ruby-lang.org'],
324     #                   :ndots => 1)
326     def initialize(config_info=nil)
327       @mutex = Thread::Mutex.new
328       @config = Config.new(config_info)
329       @initialized = nil
330     end
332     # Sets the resolver timeouts.  This may be a single positive number
333     # or an array of positive numbers representing timeouts in seconds.
334     # If an array is specified, a DNS request will retry and wait for
335     # each successive interval in the array until a successful response
336     # is received.  Specifying +nil+ reverts to the default timeouts:
337     # [ 5, second = 5 * 2 / nameserver_count, 2 * second, 4 * second ]
338     #
339     # Example:
340     #
341     #   dns.timeouts = 3
342     #
343     def timeouts=(values)
344       @config.timeouts = values
345     end
347     def lazy_initialize # :nodoc:
348       @mutex.synchronize {
349         unless @initialized
350           @config.lazy_initialize
351           @initialized = true
352         end
353       }
354       self
355     end
357     ##
358     # Closes the DNS resolver.
360     def close
361       @mutex.synchronize {
362         if @initialized
363           @initialized = false
364         end
365       }
366     end
368     ##
369     # Gets the IP address of +name+ from the DNS resolver.
370     #
371     # +name+ can be a Resolv::DNS::Name or a String.  Retrieved address will
372     # be a Resolv::IPv4 or Resolv::IPv6
374     def getaddress(name)
375       each_address(name) {|address| return address}
376       raise ResolvError.new("DNS result has no information for #{name}")
377     end
379     ##
380     # Gets all IP addresses for +name+ from the DNS resolver.
381     #
382     # +name+ can be a Resolv::DNS::Name or a String.  Retrieved addresses will
383     # be a Resolv::IPv4 or Resolv::IPv6
385     def getaddresses(name)
386       ret = []
387       each_address(name) {|address| ret << address}
388       return ret
389     end
391     ##
392     # Iterates over all IP addresses for +name+ retrieved from the DNS
393     # resolver.
394     #
395     # +name+ can be a Resolv::DNS::Name or a String.  Retrieved addresses will
396     # be a Resolv::IPv4 or Resolv::IPv6
398     def each_address(name)
399       each_resource(name, Resource::IN::A) {|resource| yield resource.address}
400       if use_ipv6?
401         each_resource(name, Resource::IN::AAAA) {|resource| yield resource.address}
402       end
403     end
405     def use_ipv6? # :nodoc:
406       use_ipv6 = @config.use_ipv6?
407       unless use_ipv6.nil?
408         return use_ipv6
409       end
411       begin
412         list = Socket.ip_address_list
413       rescue NotImplementedError
414         return true
415       end
416       list.any? {|a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? }
417     end
418     private :use_ipv6?
420     ##
421     # Gets the hostname for +address+ from the DNS resolver.
422     #
423     # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String.  Retrieved
424     # name will be a Resolv::DNS::Name.
426     def getname(address)
427       each_name(address) {|name| return name}
428       raise ResolvError.new("DNS result has no information for #{address}")
429     end
431     ##
432     # Gets all hostnames for +address+ from the DNS resolver.
433     #
434     # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String.  Retrieved
435     # names will be Resolv::DNS::Name instances.
437     def getnames(address)
438       ret = []
439       each_name(address) {|name| ret << name}
440       return ret
441     end
443     ##
444     # Iterates over all hostnames for +address+ retrieved from the DNS
445     # resolver.
446     #
447     # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String.  Retrieved
448     # names will be Resolv::DNS::Name instances.
450     def each_name(address)
451       case address
452       when Name
453         ptr = address
454       when IPv4, IPv6
455         ptr = address.to_name
456       when IPv4::Regex
457         ptr = IPv4.create(address).to_name
458       when IPv6::Regex
459         ptr = IPv6.create(address).to_name
460       else
461         raise ResolvError.new("cannot interpret as address: #{address}")
462       end
463       each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name}
464     end
466     ##
467     # Look up the +typeclass+ DNS resource of +name+.
468     #
469     # +name+ must be a Resolv::DNS::Name or a String.
470     #
471     # +typeclass+ should be one of the following:
472     #
473     # * Resolv::DNS::Resource::IN::A
474     # * Resolv::DNS::Resource::IN::AAAA
475     # * Resolv::DNS::Resource::IN::ANY
476     # * Resolv::DNS::Resource::IN::CNAME
477     # * Resolv::DNS::Resource::IN::HINFO
478     # * Resolv::DNS::Resource::IN::MINFO
479     # * Resolv::DNS::Resource::IN::MX
480     # * Resolv::DNS::Resource::IN::NS
481     # * Resolv::DNS::Resource::IN::PTR
482     # * Resolv::DNS::Resource::IN::SOA
483     # * Resolv::DNS::Resource::IN::TXT
484     # * Resolv::DNS::Resource::IN::WKS
485     #
486     # Returned resource is represented as a Resolv::DNS::Resource instance,
487     # i.e. Resolv::DNS::Resource::IN::A.
489     def getresource(name, typeclass)
490       each_resource(name, typeclass) {|resource| return resource}
491       raise ResolvError.new("DNS result has no information for #{name}")
492     end
494     ##
495     # Looks up all +typeclass+ DNS resources for +name+.  See #getresource for
496     # argument details.
498     def getresources(name, typeclass)
499       ret = []
500       each_resource(name, typeclass) {|resource| ret << resource}
501       return ret
502     end
504     ##
505     # Iterates over all +typeclass+ DNS resources for +name+.  See
506     # #getresource for argument details.
508     def each_resource(name, typeclass, &proc)
509       fetch_resource(name, typeclass) {|reply, reply_name|
510         extract_resources(reply, reply_name, typeclass, &proc)
511       }
512     end
514     def fetch_resource(name, typeclass)
515       lazy_initialize
516       begin
517         requester = make_udp_requester
518       rescue Errno::EACCES
519         # fall back to TCP
520       end
521       senders = {}
522       begin
523         @config.resolv(name) {|candidate, tout, nameserver, port|
524           requester ||= make_tcp_requester(nameserver, port)
525           msg = Message.new
526           msg.rd = 1
527           msg.add_question(candidate, typeclass)
528           unless sender = senders[[candidate, nameserver, port]]
529             sender = requester.sender(msg, candidate, nameserver, port)
530             next if !sender
531             senders[[candidate, nameserver, port]] = sender
532           end
533           reply, reply_name = requester.request(sender, tout)
534           case reply.rcode
535           when RCode::NoError
536             if reply.tc == 1 and not Requester::TCP === requester
537               requester.close
538               # Retry via TCP:
539               requester = make_tcp_requester(nameserver, port)
540               senders = {}
541               # This will use TCP for all remaining candidates (assuming the
542               # current candidate does not already respond successfully via
543               # TCP).  This makes sense because we already know the full
544               # response will not fit in an untruncated UDP packet.
545               redo
546             else
547               yield(reply, reply_name)
548             end
549             return
550           when RCode::NXDomain
551             raise Config::NXDomain.new(reply_name.to_s)
552           else
553             raise Config::OtherResolvError.new(reply_name.to_s)
554           end
555         }
556       ensure
557         requester&.close
558       end
559     end
561     def make_udp_requester # :nodoc:
562       nameserver_port = @config.nameserver_port
563       if nameserver_port.length == 1
564         Requester::ConnectedUDP.new(*nameserver_port[0])
565       else
566         Requester::UnconnectedUDP.new(*nameserver_port)
567       end
568     end
570     def make_tcp_requester(host, port) # :nodoc:
571       return Requester::TCP.new(host, port)
572     end
574     def extract_resources(msg, name, typeclass) # :nodoc:
575       if typeclass < Resource::ANY
576         n0 = Name.create(name)
577         msg.each_resource {|n, ttl, data|
578           yield data if n0 == n
579         }
580       end
581       yielded = false
582       n0 = Name.create(name)
583       msg.each_resource {|n, ttl, data|
584         if n0 == n
585           case data
586           when typeclass
587             yield data
588             yielded = true
589           when Resource::CNAME
590             n0 = data.name
591           end
592         end
593       }
594       return if yielded
595       msg.each_resource {|n, ttl, data|
596         if n0 == n
597           case data
598           when typeclass
599             yield data
600           end
601         end
602       }
603     end
605     if defined? SecureRandom
606       def self.random(arg) # :nodoc:
607         begin
608           SecureRandom.random_number(arg)
609         rescue NotImplementedError
610           rand(arg)
611         end
612       end
613     else
614       def self.random(arg) # :nodoc:
615         rand(arg)
616       end
617     end
619     RequestID = {} # :nodoc:
620     RequestIDMutex = Thread::Mutex.new # :nodoc:
622     def self.allocate_request_id(host, port) # :nodoc:
623       id = nil
624       RequestIDMutex.synchronize {
625         h = (RequestID[[host, port]] ||= {})
626         begin
627           id = random(0x0000..0xffff)
628         end while h[id]
629         h[id] = true
630       }
631       id
632     end
634     def self.free_request_id(host, port, id) # :nodoc:
635       RequestIDMutex.synchronize {
636         key = [host, port]
637         if h = RequestID[key]
638           h.delete id
639           if h.empty?
640             RequestID.delete key
641           end
642         end
643       }
644     end
646     def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc:
647       begin
648         port = random(1024..65535)
649         udpsock.bind(bind_host, port)
650       rescue Errno::EADDRINUSE, # POSIX
651              Errno::EACCES, # SunOS: See PRIV_SYS_NFS in privileges(5)
652              Errno::EPERM # FreeBSD: security.mac.portacl.port_high is configurable.  See mac_portacl(4).
653         retry
654       end
655     end
657     class Requester # :nodoc:
658       def initialize
659         @senders = {}
660         @socks = nil
661       end
663       def request(sender, tout)
664         start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
665         timelimit = start + tout
666         begin
667           sender.send
668         rescue Errno::EHOSTUNREACH, # multi-homed IPv6 may generate this
669                Errno::ENETUNREACH
670           raise ResolvTimeout
671         end
672         while true
673           before_select = Process.clock_gettime(Process::CLOCK_MONOTONIC)
674           timeout = timelimit - before_select
675           if timeout <= 0
676             raise ResolvTimeout
677           end
678           if @socks.size == 1
679             select_result = @socks[0].wait_readable(timeout) ? [ @socks ] : nil
680           else
681             select_result = IO.select(@socks, nil, nil, timeout)
682           end
683           if !select_result
684             after_select = Process.clock_gettime(Process::CLOCK_MONOTONIC)
685             next if after_select < timelimit
686             raise ResolvTimeout
687           end
688           begin
689             reply, from = recv_reply(select_result[0])
690           rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD
691                  Errno::ECONNRESET # Windows
692             # No name server running on the server?
693             # Don't wait anymore.
694             raise ResolvTimeout
695           end
696           begin
697             msg = Message.decode(reply)
698           rescue DecodeError
699             next # broken DNS message ignored
700           end
701           if sender == sender_for(from, msg)
702             break
703           else
704             # unexpected DNS message ignored
705           end
706         end
707         return msg, sender.data
708       end
710       def sender_for(addr, msg)
711         @senders[[addr,msg.id]]
712       end
714       def close
715         socks = @socks
716         @socks = nil
717         socks&.each(&:close)
718       end
720       class Sender # :nodoc:
721         def initialize(msg, data, sock)
722           @msg = msg
723           @data = data
724           @sock = sock
725         end
726       end
728       class UnconnectedUDP < Requester # :nodoc:
729         def initialize(*nameserver_port)
730           super()
731           @nameserver_port = nameserver_port
732           @initialized = false
733           @mutex = Thread::Mutex.new
734         end
736         def lazy_initialize
737           @mutex.synchronize {
738             next if @initialized
739             @initialized = true
740             @socks_hash = {}
741             @socks = []
742             @nameserver_port.each {|host, port|
743               if host.index(':')
744                 bind_host = "::"
745                 af = Socket::AF_INET6
746               else
747                 bind_host = "0.0.0.0"
748                 af = Socket::AF_INET
749               end
750               next if @socks_hash[bind_host]
751               begin
752                 sock = UDPSocket.new(af)
753               rescue Errno::EAFNOSUPPORT, Errno::EPROTONOSUPPORT
754                 next # The kernel doesn't support the address family.
755               end
756               @socks << sock
757               @socks_hash[bind_host] = sock
758               sock.do_not_reverse_lookup = true
759               DNS.bind_random_port(sock, bind_host)
760             }
761           }
762           self
763         end
765         def recv_reply(readable_socks)
766           lazy_initialize
767           reply, from = readable_socks[0].recvfrom(UDPSize)
768           return reply, [from[3],from[1]]
769         end
771         def sender(msg, data, host, port=Port)
772           host = Addrinfo.ip(host).ip_address
773           lazy_initialize
774           sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"]
775           return nil if !sock
776           service = [host, port]
777           id = DNS.allocate_request_id(host, port)
778           request = msg.encode
779           request[0,2] = [id].pack('n')
780           return @senders[[service, id]] =
781             Sender.new(request, data, sock, host, port)
782         end
784         def close
785           @mutex.synchronize {
786             if @initialized
787               super
788               @senders.each_key {|service, id|
789                 DNS.free_request_id(service[0], service[1], id)
790               }
791               @initialized = false
792             end
793           }
794         end
796         class Sender < Requester::Sender # :nodoc:
797           def initialize(msg, data, sock, host, port)
798             super(msg, data, sock)
799             @host = host
800             @port = port
801           end
802           attr_reader :data
804           def send
805             raise "@sock is nil." if @sock.nil?
806             @sock.send(@msg, 0, @host, @port)
807           end
808         end
809       end
811       class ConnectedUDP < Requester # :nodoc:
812         def initialize(host, port=Port)
813           super()
814           @host = host
815           @port = port
816           @mutex = Thread::Mutex.new
817           @initialized = false
818         end
820         def lazy_initialize
821           @mutex.synchronize {
822             next if @initialized
823             @initialized = true
824             is_ipv6 = @host.index(':')
825             sock = UDPSocket.new(is_ipv6 ? Socket::AF_INET6 : Socket::AF_INET)
826             @socks = [sock]
827             sock.do_not_reverse_lookup = true
828             DNS.bind_random_port(sock, is_ipv6 ? "::" : "0.0.0.0")
829             sock.connect(@host, @port)
830           }
831           self
832         end
834         def recv_reply(readable_socks)
835           lazy_initialize
836           reply = readable_socks[0].recv(UDPSize)
837           return reply, nil
838         end
840         def sender(msg, data, host=@host, port=@port)
841           lazy_initialize
842           unless host == @host && port == @port
843             raise RequestError.new("host/port don't match: #{host}:#{port}")
844           end
845           id = DNS.allocate_request_id(@host, @port)
846           request = msg.encode
847           request[0,2] = [id].pack('n')
848           return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
849         end
851         def close
852           @mutex.synchronize do
853             if @initialized
854               super
855               @senders.each_key {|from, id|
856                 DNS.free_request_id(@host, @port, id)
857               }
858               @initialized = false
859             end
860           end
861         end
863         class Sender < Requester::Sender # :nodoc:
864           def send
865             raise "@sock is nil." if @sock.nil?
866             @sock.send(@msg, 0)
867           end
868           attr_reader :data
869         end
870       end
872       class MDNSOneShot < UnconnectedUDP # :nodoc:
873         def sender(msg, data, host, port=Port)
874           lazy_initialize
875           id = DNS.allocate_request_id(host, port)
876           request = msg.encode
877           request[0,2] = [id].pack('n')
878           sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"]
879           return @senders[id] =
880             UnconnectedUDP::Sender.new(request, data, sock, host, port)
881         end
883         def sender_for(addr, msg)
884           lazy_initialize
885           @senders[msg.id]
886         end
887       end
889       class TCP < Requester # :nodoc:
890         def initialize(host, port=Port)
891           super()
892           @host = host
893           @port = port
894           sock = TCPSocket.new(@host, @port)
895           @socks = [sock]
896           @senders = {}
897         end
899         def recv_reply(readable_socks)
900           len = readable_socks[0].read(2).unpack('n')[0]
901           reply = @socks[0].read(len)
902           return reply, nil
903         end
905         def sender(msg, data, host=@host, port=@port)
906           unless host == @host && port == @port
907             raise RequestError.new("host/port don't match: #{host}:#{port}")
908           end
909           id = DNS.allocate_request_id(@host, @port)
910           request = msg.encode
911           request[0,2] = [request.length, id].pack('nn')
912           return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
913         end
915         class Sender < Requester::Sender # :nodoc:
916           def send
917             @sock.print(@msg)
918             @sock.flush
919           end
920           attr_reader :data
921         end
923         def close
924           super
925           @senders.each_key {|from,id|
926             DNS.free_request_id(@host, @port, id)
927           }
928         end
929       end
931       ##
932       # Indicates a problem with the DNS request.
934       class RequestError < StandardError
935       end
936     end
938     class Config # :nodoc:
939       def initialize(config_info=nil)
940         @mutex = Thread::Mutex.new
941         @config_info = config_info
942         @initialized = nil
943         @timeouts = nil
944       end
946       def timeouts=(values)
947         if values
948           values = Array(values)
949           values.each do |t|
950             Numeric === t or raise ArgumentError, "#{t.inspect} is not numeric"
951             t > 0.0 or raise ArgumentError, "timeout=#{t} must be positive"
952           end
953           @timeouts = values
954         else
955           @timeouts = nil
956         end
957       end
959       def Config.parse_resolv_conf(filename)
960         nameserver = []
961         search = nil
962         ndots = 1
963         File.open(filename, 'rb') {|f|
964           f.each {|line|
965             line.sub!(/[#;].*/, '')
966             keyword, *args = line.split(/\s+/)
967             next unless keyword
968             case keyword
969             when 'nameserver'
970               nameserver.concat(args)
971             when 'domain'
972               next if args.empty?
973               search = [args[0]]
974             when 'search'
975               next if args.empty?
976               search = args
977             when 'options'
978               args.each {|arg|
979                 case arg
980                 when /\Andots:(\d+)\z/
981                   ndots = $1.to_i
982                 end
983               }
984             end
985           }
986         }
987         return { :nameserver => nameserver, :search => search, :ndots => ndots }
988       end
990       def Config.default_config_hash(filename="/etc/resolv.conf")
991         if File.exist? filename
992           config_hash = Config.parse_resolv_conf(filename)
993         else
994           if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
995             require 'win32/resolv'
996             search, nameserver = Win32::Resolv.get_resolv_info
997             config_hash = {}
998             config_hash[:nameserver] = nameserver if nameserver
999             config_hash[:search] = [search].flatten if search
1000           end
1001         end
1002         config_hash || {}
1003       end
1005       def lazy_initialize
1006         @mutex.synchronize {
1007           unless @initialized
1008             @nameserver_port = []
1009             @use_ipv6 = nil
1010             @search = nil
1011             @ndots = 1
1012             case @config_info
1013             when nil
1014               config_hash = Config.default_config_hash
1015             when String
1016               config_hash = Config.parse_resolv_conf(@config_info)
1017             when Hash
1018               config_hash = @config_info.dup
1019               if String === config_hash[:nameserver]
1020                 config_hash[:nameserver] = [config_hash[:nameserver]]
1021               end
1022               if String === config_hash[:search]
1023                 config_hash[:search] = [config_hash[:search]]
1024               end
1025             else
1026               raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}")
1027             end
1028             if config_hash.include? :nameserver
1029               @nameserver_port = config_hash[:nameserver].map {|ns| [ns, Port] }
1030             end
1031             if config_hash.include? :nameserver_port
1032               @nameserver_port = config_hash[:nameserver_port].map {|ns, port| [ns, (port || Port)] }
1033             end
1034             if config_hash.include? :use_ipv6
1035               @use_ipv6 = config_hash[:use_ipv6]
1036             end
1037             @search = config_hash[:search] if config_hash.include? :search
1038             @ndots = config_hash[:ndots] if config_hash.include? :ndots
1039             @raise_timeout_errors = config_hash[:raise_timeout_errors]
1041             if @nameserver_port.empty?
1042               @nameserver_port << ['0.0.0.0', Port]
1043             end
1044             if @search
1045               @search = @search.map {|arg| Label.split(arg) }
1046             else
1047               hostname = Socket.gethostname
1048               if /\./ =~ hostname
1049                 @search = [Label.split($')]
1050               else
1051                 @search = [[]]
1052               end
1053             end
1055             if !@nameserver_port.kind_of?(Array) ||
1056                @nameserver_port.any? {|ns_port|
1057                   !(Array === ns_port) ||
1058                   ns_port.length != 2
1059                   !(String === ns_port[0]) ||
1060                   !(Integer === ns_port[1])
1061                }
1062               raise ArgumentError.new("invalid nameserver config: #{@nameserver_port.inspect}")
1063             end
1065             if !@search.kind_of?(Array) ||
1066                !@search.all? {|ls| ls.all? {|l| Label::Str === l } }
1067               raise ArgumentError.new("invalid search config: #{@search.inspect}")
1068             end
1070             if !@ndots.kind_of?(Integer)
1071               raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}")
1072             end
1074             @initialized = true
1075           end
1076         }
1077         self
1078       end
1080       def single?
1081         lazy_initialize
1082         if @nameserver_port.length == 1
1083           return @nameserver_port[0]
1084         else
1085           return nil
1086         end
1087       end
1089       def nameserver_port
1090         @nameserver_port
1091       end
1093       def use_ipv6?
1094         @use_ipv6
1095       end
1097       def generate_candidates(name)
1098         candidates = nil
1099         name = Name.create(name)
1100         if name.absolute?
1101           candidates = [name]
1102         else
1103           if @ndots <= name.length - 1
1104             candidates = [Name.new(name.to_a)]
1105           else
1106             candidates = []
1107           end
1108           candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)})
1109           fname = Name.create("#{name}.")
1110           if !candidates.include?(fname)
1111             candidates << fname
1112           end
1113         end
1114         return candidates
1115       end
1117       InitialTimeout = 5
1119       def generate_timeouts
1120         ts = [InitialTimeout]
1121         ts << ts[-1] * 2 / @nameserver_port.length
1122         ts << ts[-1] * 2
1123         ts << ts[-1] * 2
1124         return ts
1125       end
1127       def resolv(name)
1128         candidates = generate_candidates(name)
1129         timeouts = @timeouts || generate_timeouts
1130         timeout_error = false
1131         begin
1132           candidates.each {|candidate|
1133             begin
1134               timeouts.each {|tout|
1135                 @nameserver_port.each {|nameserver, port|
1136                   begin
1137                     yield candidate, tout, nameserver, port
1138                   rescue ResolvTimeout
1139                   end
1140                 }
1141               }
1142               timeout_error = true
1143               raise ResolvError.new("DNS resolv timeout: #{name}")
1144             rescue NXDomain
1145             end
1146           }
1147         rescue ResolvError
1148           raise if @raise_timeout_errors && timeout_error
1149         end
1150       end
1152       ##
1153       # Indicates no such domain was found.
1155       class NXDomain < ResolvError
1156       end
1158       ##
1159       # Indicates some other unhandled resolver error was encountered.
1161       class OtherResolvError < ResolvError
1162       end
1163     end
1165     module OpCode # :nodoc:
1166       Query = 0
1167       IQuery = 1
1168       Status = 2
1169       Notify = 4
1170       Update = 5
1171     end
1173     module RCode # :nodoc:
1174       NoError = 0
1175       FormErr = 1
1176       ServFail = 2
1177       NXDomain = 3
1178       NotImp = 4
1179       Refused = 5
1180       YXDomain = 6
1181       YXRRSet = 7
1182       NXRRSet = 8
1183       NotAuth = 9
1184       NotZone = 10
1185       BADVERS = 16
1186       BADSIG = 16
1187       BADKEY = 17
1188       BADTIME = 18
1189       BADMODE = 19
1190       BADNAME = 20
1191       BADALG = 21
1192     end
1194     ##
1195     # Indicates that the DNS response was unable to be decoded.
1197     class DecodeError < StandardError
1198     end
1200     ##
1201     # Indicates that the DNS request was unable to be encoded.
1203     class EncodeError < StandardError
1204     end
1206     module Label # :nodoc:
1207       def self.split(arg)
1208         labels = []
1209         arg.scan(/[^\.]+/) {labels << Str.new($&)}
1210         return labels
1211       end
1213       class Str # :nodoc:
1214         def initialize(string)
1215           @string = string
1216           # case insensivity of DNS labels doesn't apply non-ASCII characters. [RFC 4343]
1217           # This assumes @string is given in ASCII compatible encoding.
1218           @downcase = string.b.downcase
1219         end
1220         attr_reader :string, :downcase
1222         def to_s
1223           return @string
1224         end
1226         def inspect
1227           return "#<#{self.class} #{self}>"
1228         end
1230         def ==(other)
1231           return self.class == other.class && @downcase == other.downcase
1232         end
1234         def eql?(other)
1235           return self == other
1236         end
1238         def hash
1239           return @downcase.hash
1240         end
1241       end
1242     end
1244     ##
1245     # A representation of a DNS name.
1247     class Name
1249       ##
1250       # Creates a new DNS name from +arg+.  +arg+ can be:
1251       #
1252       # Name:: returns +arg+.
1253       # String:: Creates a new Name.
1255       def self.create(arg)
1256         case arg
1257         when Name
1258           return arg
1259         when String
1260           return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
1261         else
1262           raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
1263         end
1264       end
1266       def initialize(labels, absolute=true) # :nodoc:
1267         labels = labels.map {|label|
1268           case label
1269           when String then Label::Str.new(label)
1270           when Label::Str then label
1271           else
1272             raise ArgumentError, "unexpected label: #{label.inspect}"
1273           end
1274         }
1275         @labels = labels
1276         @absolute = absolute
1277       end
1279       def inspect # :nodoc:
1280         "#<#{self.class}: #{self}#{@absolute ? '.' : ''}>"
1281       end
1283       ##
1284       # True if this name is absolute.
1286       def absolute?
1287         return @absolute
1288       end
1290       def ==(other) # :nodoc:
1291         return false unless Name === other
1292         return false unless @absolute == other.absolute?
1293         return @labels == other.to_a
1294       end
1296       alias eql? == # :nodoc:
1298       ##
1299       # Returns true if +other+ is a subdomain.
1300       #
1301       # Example:
1302       #
1303       #   domain = Resolv::DNS::Name.create("y.z")
1304       #   p Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true
1305       #   p Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true
1306       #   p Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false
1307       #   p Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false
1308       #   p Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false
1309       #   p Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false
1310       #
1312       def subdomain_of?(other)
1313         raise ArgumentError, "not a domain name: #{other.inspect}" unless Name === other
1314         return false if @absolute != other.absolute?
1315         other_len = other.length
1316         return false if @labels.length <= other_len
1317         return @labels[-other_len, other_len] == other.to_a
1318       end
1320       def hash # :nodoc:
1321         return @labels.hash ^ @absolute.hash
1322       end
1324       def to_a # :nodoc:
1325         return @labels
1326       end
1328       def length # :nodoc:
1329         return @labels.length
1330       end
1332       def [](i) # :nodoc:
1333         return @labels[i]
1334       end
1336       ##
1337       # returns the domain name as a string.
1338       #
1339       # The domain name doesn't have a trailing dot even if the name object is
1340       # absolute.
1341       #
1342       # Example:
1343       #
1344       #   p Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z"
1345       #   p Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z"
1347       def to_s
1348         return @labels.join('.')
1349       end
1350     end
1352     class Message # :nodoc:
1353       @@identifier = -1
1355       def initialize(id = (@@identifier += 1) & 0xffff)
1356         @id = id
1357         @qr = 0
1358         @opcode = 0
1359         @aa = 0
1360         @tc = 0
1361         @rd = 0 # recursion desired
1362         @ra = 0 # recursion available
1363         @rcode = 0
1364         @question = []
1365         @answer = []
1366         @authority = []
1367         @additional = []
1368       end
1370       attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode
1371       attr_reader :question, :answer, :authority, :additional
1373       def ==(other)
1374         return @id == other.id &&
1375                @qr == other.qr &&
1376                @opcode == other.opcode &&
1377                @aa == other.aa &&
1378                @tc == other.tc &&
1379                @rd == other.rd &&
1380                @ra == other.ra &&
1381                @rcode == other.rcode &&
1382                @question == other.question &&
1383                @answer == other.answer &&
1384                @authority == other.authority &&
1385                @additional == other.additional
1386       end
1388       def add_question(name, typeclass)
1389         @question << [Name.create(name), typeclass]
1390       end
1392       def each_question
1393         @question.each {|name, typeclass|
1394           yield name, typeclass
1395         }
1396       end
1398       def add_answer(name, ttl, data)
1399         @answer << [Name.create(name), ttl, data]
1400       end
1402       def each_answer
1403         @answer.each {|name, ttl, data|
1404           yield name, ttl, data
1405         }
1406       end
1408       def add_authority(name, ttl, data)
1409         @authority << [Name.create(name), ttl, data]
1410       end
1412       def each_authority
1413         @authority.each {|name, ttl, data|
1414           yield name, ttl, data
1415         }
1416       end
1418       def add_additional(name, ttl, data)
1419         @additional << [Name.create(name), ttl, data]
1420       end
1422       def each_additional
1423         @additional.each {|name, ttl, data|
1424           yield name, ttl, data
1425         }
1426       end
1428       def each_resource
1429         each_answer {|name, ttl, data| yield name, ttl, data}
1430         each_authority {|name, ttl, data| yield name, ttl, data}
1431         each_additional {|name, ttl, data| yield name, ttl, data}
1432       end
1434       def encode
1435         return MessageEncoder.new {|msg|
1436           msg.put_pack('nnnnnn',
1437             @id,
1438             (@qr & 1) << 15 |
1439             (@opcode & 15) << 11 |
1440             (@aa & 1) << 10 |
1441             (@tc & 1) << 9 |
1442             (@rd & 1) << 8 |
1443             (@ra & 1) << 7 |
1444             (@rcode & 15),
1445             @question.length,
1446             @answer.length,
1447             @authority.length,
1448             @additional.length)
1449           @question.each {|q|
1450             name, typeclass = q
1451             msg.put_name(name)
1452             msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue)
1453           }
1454           [@answer, @authority, @additional].each {|rr|
1455             rr.each {|r|
1456               name, ttl, data = r
1457               msg.put_name(name)
1458               msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl)
1459               msg.put_length16 {data.encode_rdata(msg)}
1460             }
1461           }
1462         }.to_s
1463       end
1465       class MessageEncoder # :nodoc:
1466         def initialize
1467           @data = ''.dup
1468           @names = {}
1469           yield self
1470         end
1472         def to_s
1473           return @data
1474         end
1476         def put_bytes(d)
1477           @data << d
1478         end
1480         def put_pack(template, *d)
1481           @data << d.pack(template)
1482         end
1484         def put_length16
1485           length_index = @data.length
1486           @data << "\0\0"
1487           data_start = @data.length
1488           yield
1489           data_end = @data.length
1490           @data[length_index, 2] = [data_end - data_start].pack("n")
1491         end
1493         def put_string(d)
1494           self.put_pack("C", d.length)
1495           @data << d
1496         end
1498         def put_string_list(ds)
1499           ds.each {|d|
1500             self.put_string(d)
1501           }
1502         end
1504         def put_name(d, compress: true)
1505           put_labels(d.to_a, compress: compress)
1506         end
1508         def put_labels(d, compress: true)
1509           d.each_index {|i|
1510             domain = d[i..-1]
1511             if compress && idx = @names[domain]
1512               self.put_pack("n", 0xc000 | idx)
1513               return
1514             else
1515               if @data.length < 0x4000
1516                 @names[domain] = @data.length
1517               end
1518               self.put_label(d[i])
1519             end
1520           }
1521           @data << "\0"
1522         end
1524         def put_label(d)
1525           self.put_string(d.to_s)
1526         end
1527       end
1529       def Message.decode(m)
1530         o = Message.new(0)
1531         MessageDecoder.new(m) {|msg|
1532           id, flag, qdcount, ancount, nscount, arcount =
1533             msg.get_unpack('nnnnnn')
1534           o.id = id
1535           o.tc = (flag >> 9) & 1
1536           o.rcode = flag & 15
1537           return o unless o.tc.zero?
1539           o.qr = (flag >> 15) & 1
1540           o.opcode = (flag >> 11) & 15
1541           o.aa = (flag >> 10) & 1
1542           o.rd = (flag >> 8) & 1
1543           o.ra = (flag >> 7) & 1
1544           (1..qdcount).each {
1545             name, typeclass = msg.get_question
1546             o.add_question(name, typeclass)
1547           }
1548           (1..ancount).each {
1549             name, ttl, data = msg.get_rr
1550             o.add_answer(name, ttl, data)
1551           }
1552           (1..nscount).each {
1553             name, ttl, data = msg.get_rr
1554             o.add_authority(name, ttl, data)
1555           }
1556           (1..arcount).each {
1557             name, ttl, data = msg.get_rr
1558             o.add_additional(name, ttl, data)
1559           }
1560         }
1561         return o
1562       end
1564       class MessageDecoder # :nodoc:
1565         def initialize(data)
1566           @data = data
1567           @index = 0
1568           @limit = data.bytesize
1569           yield self
1570         end
1572         def inspect
1573           "\#<#{self.class}: #{@data.byteslice(0, @index).inspect} #{@data.byteslice(@index..-1).inspect}>"
1574         end
1576         def get_length16
1577           len, = self.get_unpack('n')
1578           save_limit = @limit
1579           @limit = @index + len
1580           d = yield(len)
1581           if @index < @limit
1582             raise DecodeError.new("junk exists")
1583           elsif @limit < @index
1584             raise DecodeError.new("limit exceeded")
1585           end
1586           @limit = save_limit
1587           return d
1588         end
1590         def get_bytes(len = @limit - @index)
1591           raise DecodeError.new("limit exceeded") if @limit < @index + len
1592           d = @data.byteslice(@index, len)
1593           @index += len
1594           return d
1595         end
1597         def get_unpack(template)
1598           len = 0
1599           template.each_byte {|byte|
1600             byte = "%c" % byte
1601             case byte
1602             when ?c, ?C
1603               len += 1
1604             when ?n
1605               len += 2
1606             when ?N
1607               len += 4
1608             else
1609               raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'")
1610             end
1611           }
1612           raise DecodeError.new("limit exceeded") if @limit < @index + len
1613           arr = @data.unpack("@#{@index}#{template}")
1614           @index += len
1615           return arr
1616         end
1618         def get_string
1619           raise DecodeError.new("limit exceeded") if @limit <= @index
1620           len = @data.getbyte(@index)
1621           raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len
1622           d = @data.byteslice(@index + 1, len)
1623           @index += 1 + len
1624           return d
1625         end
1627         def get_string_list
1628           strings = []
1629           while @index < @limit
1630             strings << self.get_string
1631           end
1632           strings
1633         end
1635         def get_list
1636           [].tap do |values|
1637             while @index < @limit
1638               values << yield
1639             end
1640           end
1641         end
1643         def get_name
1644           return Name.new(self.get_labels)
1645         end
1647         def get_labels
1648           prev_index = @index
1649           save_index = nil
1650           d = []
1651           while true
1652             raise DecodeError.new("limit exceeded") if @limit <= @index
1653             case @data.getbyte(@index)
1654             when 0
1655               @index += 1
1656               if save_index
1657                 @index = save_index
1658               end
1659               return d
1660             when 192..255
1661               idx = self.get_unpack('n')[0] & 0x3fff
1662               if prev_index <= idx
1663                 raise DecodeError.new("non-backward name pointer")
1664               end
1665               prev_index = idx
1666               if !save_index
1667                 save_index = @index
1668               end
1669               @index = idx
1670             else
1671               d << self.get_label
1672             end
1673           end
1674         end
1676         def get_label
1677           return Label::Str.new(self.get_string)
1678         end
1680         def get_question
1681           name = self.get_name
1682           type, klass = self.get_unpack("nn")
1683           return name, Resource.get_class(type, klass)
1684         end
1686         def get_rr
1687           name = self.get_name
1688           type, klass, ttl = self.get_unpack('nnN')
1689           typeclass = Resource.get_class(type, klass)
1690           res = self.get_length16 do
1691             begin
1692               typeclass.decode_rdata self
1693             rescue => e
1694               raise DecodeError, e.message, e.backtrace
1695             end
1696           end
1697           res.instance_variable_set :@ttl, ttl
1698           return name, ttl, res
1699         end
1700       end
1701     end
1703     ##
1704     # SvcParams for service binding RRs. [RFC9460]
1706     class SvcParams
1707       include Enumerable
1709       ##
1710       # Create a list of SvcParams with the given initial content.
1711       #
1712       # +params+ has to be an enumerable of +SvcParam+s.
1713       # If its content has +SvcParam+s with the duplicate key,
1714       # the one appears last takes precedence.
1716       def initialize(params = [])
1717         @params = {}
1719         params.each do |param|
1720           add param
1721         end
1722       end
1724       ##
1725       # Get SvcParam for the given +key+ in this list.
1727       def [](key)
1728         @params[canonical_key(key)]
1729       end
1731       ##
1732       # Get the number of SvcParams in this list.
1734       def count
1735         @params.count
1736       end
1738       ##
1739       # Get whether this list is empty.
1741       def empty?
1742         @params.empty?
1743       end
1745       ##
1746       # Add the SvcParam +param+ to this list, overwriting the existing one with the same key.
1748       def add(param)
1749         @params[param.class.key_number] = param
1750       end
1752       ##
1753       # Remove the +SvcParam+ with the given +key+ and return it.
1755       def delete(key)
1756         @params.delete(canonical_key(key))
1757       end
1759       ##
1760       # Enumerate the +SvcParam+s in this list.
1762       def each(&block)
1763         return enum_for(:each) unless block
1764         @params.each_value(&block)
1765       end
1767       def encode(msg) # :nodoc:
1768         @params.keys.sort.each do |key|
1769           msg.put_pack('n', key)
1770           msg.put_length16 do
1771             @params.fetch(key).encode(msg)
1772           end
1773         end
1774       end
1776       def self.decode(msg) # :nodoc:
1777         params = msg.get_list do
1778           key, = msg.get_unpack('n')
1779           msg.get_length16 do
1780             SvcParam::ClassHash[key].decode(msg)
1781           end
1782         end
1784         return self.new(params)
1785       end
1787       private
1789       def canonical_key(key) # :nodoc:
1790         case key
1791         when Integer
1792           key
1793         when /\Akey(\d+)\z/
1794           Integer($1)
1795         when Symbol
1796           SvcParam::ClassHash[key].key_number
1797         else
1798           raise TypeError, 'key must be either String or Symbol'
1799         end
1800       end
1801     end
1804     ##
1805     # Base class for SvcParam. [RFC9460]
1807     class SvcParam
1809       ##
1810       # Get the presentation name of the SvcParamKey.
1812       def self.key_name
1813         const_get(:KeyName)
1814       end
1816       ##
1817       # Get the registered number of the SvcParamKey.
1819       def self.key_number
1820         const_get(:KeyNumber)
1821       end
1823       ClassHash = Hash.new do |h, key| # :nodoc:
1824         case key
1825         when Integer
1826           Generic.create(key)
1827         when /\Akey(?<key>\d+)\z/
1828           Generic.create(key.to_int)
1829         when Symbol
1830           raise KeyError, "unknown key #{key}"
1831         else
1832           raise TypeError, 'key must be either String or Symbol'
1833         end
1834       end
1836       ##
1837       # Generic SvcParam abstract class.
1839       class Generic < SvcParam
1841         ##
1842         # SvcParamValue in wire-format byte string.
1844         attr_reader :value
1846         ##
1847         # Create generic SvcParam
1849         def initialize(value)
1850           @value = value
1851         end
1853         def encode(msg) # :nodoc:
1854           msg.put_bytes(@value)
1855         end
1857         def self.decode(msg) # :nodoc:
1858           return self.new(msg.get_bytes)
1859         end
1861         def self.create(key_number)
1862           c = Class.new(Generic)
1863           key_name = :"key#{key_number}"
1864           c.const_set(:KeyName, key_name)
1865           c.const_set(:KeyNumber, key_number)
1866           self.const_set(:"Key#{key_number}", c)
1867           ClassHash[key_name] = ClassHash[key_number] = c
1868           return c
1869         end
1870       end
1872       ##
1873       # "mandatory" SvcParam -- Mandatory keys in service binding RR
1875       class Mandatory < SvcParam
1876         KeyName = :mandatory
1877         KeyNumber = 0
1878         ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
1880         ##
1881         # Mandatory keys.
1883         attr_reader :keys
1885         ##
1886         # Initialize "mandatory" ScvParam.
1888         def initialize(keys)
1889           @keys = keys.map(&:to_int)
1890         end
1892         def encode(msg) # :nodoc:
1893           @keys.sort.each do |key|
1894             msg.put_pack('n', key)
1895           end
1896         end
1898         def self.decode(msg) # :nodoc:
1899           keys = msg.get_list { msg.get_unpack('n')[0] }
1900           return self.new(keys)
1901         end
1902       end
1904       ##
1905       # "alpn" SvcParam -- Additional supported protocols
1907       class ALPN < SvcParam
1908         KeyName = :alpn
1909         KeyNumber = 1
1910         ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
1912         ##
1913         # Supported protocol IDs.
1915         attr_reader :protocol_ids
1917         ##
1918         # Initialize "alpn" ScvParam.
1920         def initialize(protocol_ids)
1921           @protocol_ids = protocol_ids.map(&:to_str)
1922         end
1924         def encode(msg) # :nodoc:
1925           msg.put_string_list(@protocol_ids)
1926         end
1928         def self.decode(msg) # :nodoc:
1929           return self.new(msg.get_string_list)
1930         end
1931       end
1933       ##
1934       # "no-default-alpn" SvcParam -- No support for default protocol
1936       class NoDefaultALPN < SvcParam
1937         KeyName = :'no-default-alpn'
1938         KeyNumber = 2
1939         ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
1941         def encode(msg) # :nodoc:
1942           # no payload
1943         end
1945         def self.decode(msg) # :nodoc:
1946           return self.new
1947         end
1948       end
1950       ##
1951       # "port" SvcParam -- Port for alternative endpoint
1953       class Port < SvcParam
1954         KeyName = :port
1955         KeyNumber = 3
1956         ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
1958         ##
1959         # Port number.
1961         attr_reader :port
1963         ##
1964         # Initialize "port" ScvParam.
1966         def initialize(port)
1967           @port = port.to_int
1968         end
1970         def encode(msg) # :nodoc:
1971           msg.put_pack('n', @port)
1972         end
1974         def self.decode(msg) # :nodoc:
1975           port, = msg.get_unpack('n')
1976           return self.new(port)
1977         end
1978       end
1980       ##
1981       # "ipv4hint" SvcParam -- IPv4 address hints
1983       class IPv4Hint < SvcParam
1984         KeyName = :ipv4hint
1985         KeyNumber = 4
1986         ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
1988         ##
1989         # Set of IPv4 addresses.
1991         attr_reader :addresses
1993         ##
1994         # Initialize "ipv4hint" ScvParam.
1996         def initialize(addresses)
1997           @addresses = addresses.map {|address| IPv4.create(address) }
1998         end
2000         def encode(msg) # :nodoc:
2001           @addresses.each do |address|
2002             msg.put_bytes(address.address)
2003           end
2004         end
2006         def self.decode(msg) # :nodoc:
2007           addresses = msg.get_list { IPv4.new(msg.get_bytes(4)) }
2008           return self.new(addresses)
2009         end
2010       end
2012       ##
2013       # "ipv6hint" SvcParam -- IPv6 address hints
2015       class IPv6Hint < SvcParam
2016         KeyName = :ipv6hint
2017         KeyNumber = 6
2018         ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
2020         ##
2021         # Set of IPv6 addresses.
2023         attr_reader :addresses
2025         ##
2026         # Initialize "ipv6hint" ScvParam.
2028         def initialize(addresses)
2029           @addresses = addresses.map {|address| IPv6.create(address) }
2030         end
2032         def encode(msg) # :nodoc:
2033           @addresses.each do |address|
2034             msg.put_bytes(address.address)
2035           end
2036         end
2038         def self.decode(msg) # :nodoc:
2039           addresses = msg.get_list { IPv6.new(msg.get_bytes(16)) }
2040           return self.new(addresses)
2041         end
2042       end
2044       ##
2045       # "dohpath" SvcParam -- DNS over HTTPS path template [RFC9461]
2047       class DoHPath < SvcParam
2048         KeyName = :dohpath
2049         KeyNumber = 7
2050         ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
2052         ##
2053         # URI template for DoH queries.
2055         attr_reader :template
2057         ##
2058         # Initialize "dohpath" ScvParam.
2060         def initialize(template)
2061           @template = template.encode('utf-8')
2062         end
2064         def encode(msg) # :nodoc:
2065           msg.put_bytes(@template)
2066         end
2068         def self.decode(msg) # :nodoc:
2069           template = msg.get_bytes.force_encoding('utf-8')
2070           return self.new(template)
2071         end
2072       end
2073     end
2075     ##
2076     # A DNS query abstract class.
2078     class Query
2079       def encode_rdata(msg) # :nodoc:
2080         raise EncodeError.new("#{self.class} is query.")
2081       end
2083       def self.decode_rdata(msg) # :nodoc:
2084         raise DecodeError.new("#{self.class} is query.")
2085       end
2086     end
2088     ##
2089     # A DNS resource abstract class.
2091     class Resource < Query
2093       ##
2094       # Remaining Time To Live for this Resource.
2096       attr_reader :ttl
2098       ClassHash = {} # :nodoc:
2100       def encode_rdata(msg) # :nodoc:
2101         raise NotImplementedError.new
2102       end
2104       def self.decode_rdata(msg) # :nodoc:
2105         raise NotImplementedError.new
2106       end
2108       def ==(other) # :nodoc:
2109         return false unless self.class == other.class
2110         s_ivars = self.instance_variables
2111         s_ivars.sort!
2112         s_ivars.delete :@ttl
2113         o_ivars = other.instance_variables
2114         o_ivars.sort!
2115         o_ivars.delete :@ttl
2116         return s_ivars == o_ivars &&
2117           s_ivars.collect {|name| self.instance_variable_get name} ==
2118             o_ivars.collect {|name| other.instance_variable_get name}
2119       end
2121       def eql?(other) # :nodoc:
2122         return self == other
2123       end
2125       def hash # :nodoc:
2126         h = 0
2127         vars = self.instance_variables
2128         vars.delete :@ttl
2129         vars.each {|name|
2130           h ^= self.instance_variable_get(name).hash
2131         }
2132         return h
2133       end
2135       def self.get_class(type_value, class_value) # :nodoc:
2136         return ClassHash[[type_value, class_value]] ||
2137                Generic.create(type_value, class_value)
2138       end
2140       ##
2141       # A generic resource abstract class.
2143       class Generic < Resource
2145         ##
2146         # Creates a new generic resource.
2148         def initialize(data)
2149           @data = data
2150         end
2152         ##
2153         # Data for this generic resource.
2155         attr_reader :data
2157         def encode_rdata(msg) # :nodoc:
2158           msg.put_bytes(data)
2159         end
2161         def self.decode_rdata(msg) # :nodoc:
2162           return self.new(msg.get_bytes)
2163         end
2165         def self.create(type_value, class_value) # :nodoc:
2166           c = Class.new(Generic)
2167           c.const_set(:TypeValue, type_value)
2168           c.const_set(:ClassValue, class_value)
2169           Generic.const_set("Type#{type_value}_Class#{class_value}", c)
2170           ClassHash[[type_value, class_value]] = c
2171           return c
2172         end
2173       end
2175       ##
2176       # Domain Name resource abstract class.
2178       class DomainName < Resource
2180         ##
2181         # Creates a new DomainName from +name+.
2183         def initialize(name)
2184           @name = name
2185         end
2187         ##
2188         # The name of this DomainName.
2190         attr_reader :name
2192         def encode_rdata(msg) # :nodoc:
2193           msg.put_name(@name)
2194         end
2196         def self.decode_rdata(msg) # :nodoc:
2197           return self.new(msg.get_name)
2198         end
2199       end
2201       # Standard (class generic) RRs
2203       ClassValue = nil # :nodoc:
2205       ##
2206       # An authoritative name server.
2208       class NS < DomainName
2209         TypeValue = 2 # :nodoc:
2210       end
2212       ##
2213       # The canonical name for an alias.
2215       class CNAME < DomainName
2216         TypeValue = 5 # :nodoc:
2217       end
2219       ##
2220       # Start Of Authority resource.
2222       class SOA < Resource
2224         TypeValue = 6 # :nodoc:
2226         ##
2227         # Creates a new SOA record.  See the attr documentation for the
2228         # details of each argument.
2230         def initialize(mname, rname, serial, refresh, retry_, expire, minimum)
2231           @mname = mname
2232           @rname = rname
2233           @serial = serial
2234           @refresh = refresh
2235           @retry = retry_
2236           @expire = expire
2237           @minimum = minimum
2238         end
2240         ##
2241         # Name of the host where the master zone file for this zone resides.
2243         attr_reader :mname
2245         ##
2246         # The person responsible for this domain name.
2248         attr_reader :rname
2250         ##
2251         # The version number of the zone file.
2253         attr_reader :serial
2255         ##
2256         # How often, in seconds, a secondary name server is to check for
2257         # updates from the primary name server.
2259         attr_reader :refresh
2261         ##
2262         # How often, in seconds, a secondary name server is to retry after a
2263         # failure to check for a refresh.
2265         attr_reader :retry
2267         ##
2268         # Time in seconds that a secondary name server is to use the data
2269         # before refreshing from the primary name server.
2271         attr_reader :expire
2273         ##
2274         # The minimum number of seconds to be used for TTL values in RRs.
2276         attr_reader :minimum
2278         def encode_rdata(msg) # :nodoc:
2279           msg.put_name(@mname)
2280           msg.put_name(@rname)
2281           msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum)
2282         end
2284         def self.decode_rdata(msg) # :nodoc:
2285           mname = msg.get_name
2286           rname = msg.get_name
2287           serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN')
2288           return self.new(
2289             mname, rname, serial, refresh, retry_, expire, minimum)
2290         end
2291       end
2293       ##
2294       # A Pointer to another DNS name.
2296       class PTR < DomainName
2297         TypeValue = 12 # :nodoc:
2298       end
2300       ##
2301       # Host Information resource.
2303       class HINFO < Resource
2305         TypeValue = 13 # :nodoc:
2307         ##
2308         # Creates a new HINFO running +os+ on +cpu+.
2310         def initialize(cpu, os)
2311           @cpu = cpu
2312           @os = os
2313         end
2315         ##
2316         # CPU architecture for this resource.
2318         attr_reader :cpu
2320         ##
2321         # Operating system for this resource.
2323         attr_reader :os
2325         def encode_rdata(msg) # :nodoc:
2326           msg.put_string(@cpu)
2327           msg.put_string(@os)
2328         end
2330         def self.decode_rdata(msg) # :nodoc:
2331           cpu = msg.get_string
2332           os = msg.get_string
2333           return self.new(cpu, os)
2334         end
2335       end
2337       ##
2338       # Mailing list or mailbox information.
2340       class MINFO < Resource
2342         TypeValue = 14 # :nodoc:
2344         def initialize(rmailbx, emailbx)
2345           @rmailbx = rmailbx
2346           @emailbx = emailbx
2347         end
2349         ##
2350         # Domain name responsible for this mail list or mailbox.
2352         attr_reader :rmailbx
2354         ##
2355         # Mailbox to use for error messages related to the mail list or mailbox.
2357         attr_reader :emailbx
2359         def encode_rdata(msg) # :nodoc:
2360           msg.put_name(@rmailbx)
2361           msg.put_name(@emailbx)
2362         end
2364         def self.decode_rdata(msg) # :nodoc:
2365           rmailbx = msg.get_string
2366           emailbx = msg.get_string
2367           return self.new(rmailbx, emailbx)
2368         end
2369       end
2371       ##
2372       # Mail Exchanger resource.
2374       class MX < Resource
2376         TypeValue= 15 # :nodoc:
2378         ##
2379         # Creates a new MX record with +preference+, accepting mail at
2380         # +exchange+.
2382         def initialize(preference, exchange)
2383           @preference = preference
2384           @exchange = exchange
2385         end
2387         ##
2388         # The preference for this MX.
2390         attr_reader :preference
2392         ##
2393         # The host of this MX.
2395         attr_reader :exchange
2397         def encode_rdata(msg) # :nodoc:
2398           msg.put_pack('n', @preference)
2399           msg.put_name(@exchange)
2400         end
2402         def self.decode_rdata(msg) # :nodoc:
2403           preference, = msg.get_unpack('n')
2404           exchange = msg.get_name
2405           return self.new(preference, exchange)
2406         end
2407       end
2409       ##
2410       # Unstructured text resource.
2412       class TXT < Resource
2414         TypeValue = 16 # :nodoc:
2416         def initialize(first_string, *rest_strings)
2417           @strings = [first_string, *rest_strings]
2418         end
2420         ##
2421         # Returns an Array of Strings for this TXT record.
2423         attr_reader :strings
2425         ##
2426         # Returns the concatenated string from +strings+.
2428         def data
2429           @strings.join("")
2430         end
2432         def encode_rdata(msg) # :nodoc:
2433           msg.put_string_list(@strings)
2434         end
2436         def self.decode_rdata(msg) # :nodoc:
2437           strings = msg.get_string_list
2438           return self.new(*strings)
2439         end
2440       end
2442       ##
2443       # Location resource
2445       class LOC < Resource
2447         TypeValue = 29 # :nodoc:
2449         def initialize(version, ssize, hprecision, vprecision, latitude, longitude, altitude)
2450           @version    = version
2451           @ssize      = Resolv::LOC::Size.create(ssize)
2452           @hprecision = Resolv::LOC::Size.create(hprecision)
2453           @vprecision = Resolv::LOC::Size.create(vprecision)
2454           @latitude   = Resolv::LOC::Coord.create(latitude)
2455           @longitude  = Resolv::LOC::Coord.create(longitude)
2456           @altitude   = Resolv::LOC::Alt.create(altitude)
2457         end
2459         ##
2460         # Returns the version value for this LOC record which should always be 00
2462         attr_reader :version
2464         ##
2465         # The spherical size of this LOC
2466         # in meters using scientific notation as 2 integers of XeY
2468         attr_reader :ssize
2470         ##
2471         # The horizontal precision using ssize type values
2472         # in meters using scientific notation as 2 integers of XeY
2473         # for precision use value/2 e.g. 2m = +/-1m
2475         attr_reader :hprecision
2477         ##
2478         # The vertical precision using ssize type values
2479         # in meters using scientific notation as 2 integers of XeY
2480         # for precision use value/2 e.g. 2m = +/-1m
2482         attr_reader :vprecision
2484         ##
2485         # The latitude for this LOC where 2**31 is the equator
2486         # in thousandths of an arc second as an unsigned 32bit integer
2488         attr_reader :latitude
2490         ##
2491         # The longitude for this LOC where 2**31 is the prime meridian
2492         # in thousandths of an arc second as an unsigned 32bit integer
2494         attr_reader :longitude
2496         ##
2497         # The altitude of the LOC above a reference sphere whose surface sits 100km below the WGS84 spheroid
2498         # in centimeters as an unsigned 32bit integer
2500         attr_reader :altitude
2503         def encode_rdata(msg) # :nodoc:
2504           msg.put_bytes(@version)
2505           msg.put_bytes(@ssize.scalar)
2506           msg.put_bytes(@hprecision.scalar)
2507           msg.put_bytes(@vprecision.scalar)
2508           msg.put_bytes(@latitude.coordinates)
2509           msg.put_bytes(@longitude.coordinates)
2510           msg.put_bytes(@altitude.altitude)
2511         end
2513         def self.decode_rdata(msg) # :nodoc:
2514           version    = msg.get_bytes(1)
2515           ssize      = msg.get_bytes(1)
2516           hprecision = msg.get_bytes(1)
2517           vprecision = msg.get_bytes(1)
2518           latitude   = msg.get_bytes(4)
2519           longitude  = msg.get_bytes(4)
2520           altitude   = msg.get_bytes(4)
2521           return self.new(
2522             version,
2523             Resolv::LOC::Size.new(ssize),
2524             Resolv::LOC::Size.new(hprecision),
2525             Resolv::LOC::Size.new(vprecision),
2526             Resolv::LOC::Coord.new(latitude,"lat"),
2527             Resolv::LOC::Coord.new(longitude,"lon"),
2528             Resolv::LOC::Alt.new(altitude)
2529           )
2530         end
2531       end
2533       ##
2534       # A Query type requesting any RR.
2536       class ANY < Query
2537         TypeValue = 255 # :nodoc:
2538       end
2540       ##
2541       # CAA resource record defined in RFC 8659
2542       #
2543       # These records identify certificate authority allowed to issue
2544       # certificates for the given domain.
2546       class CAA < Resource
2547         TypeValue = 257
2549         ##
2550         # Creates a new CAA for +flags+, +tag+ and +value+.
2552         def initialize(flags, tag, value)
2553           unless (0..255) === flags
2554             raise ArgumentError.new('flags must be an Integer between 0 and 255')
2555           end
2556           unless (1..15) === tag.bytesize
2557             raise ArgumentError.new('length of tag must be between 1 and 15')
2558           end
2560           @flags = flags
2561           @tag = tag
2562           @value = value
2563         end
2565         ##
2566         # Flags for this proprty:
2567         # - Bit 0 : 0 = not critical, 1 = critical
2569         attr_reader :flags
2571         ##
2572         # Property tag ("issue", "issuewild", "iodef"...).
2574         attr_reader :tag
2576         ##
2577         # Property value.
2579         attr_reader :value
2581         ##
2582         # Whether the critical flag is set on this property.
2584         def critical?
2585           flags & 0x80 != 0
2586         end
2588         def encode_rdata(msg) # :nodoc:
2589           msg.put_pack('C', @flags)
2590           msg.put_string(@tag)
2591           msg.put_bytes(@value)
2592         end
2594         def self.decode_rdata(msg) # :nodoc:
2595           flags, = msg.get_unpack('C')
2596           tag = msg.get_string
2597           value = msg.get_bytes
2598           self.new flags, tag, value
2599         end
2600       end
2602       ClassInsensitiveTypes = [ # :nodoc:
2603         NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, LOC, ANY, CAA
2604       ]
2606       ##
2607       # module IN contains ARPA Internet specific RRs.
2609       module IN
2611         ClassValue = 1 # :nodoc:
2613         ClassInsensitiveTypes.each {|s|
2614           c = Class.new(s)
2615           c.const_set(:TypeValue, s::TypeValue)
2616           c.const_set(:ClassValue, ClassValue)
2617           ClassHash[[s::TypeValue, ClassValue]] = c
2618           self.const_set(s.name.sub(/.*::/, ''), c)
2619         }
2621         ##
2622         # IPv4 Address resource
2624         class A < Resource
2625           TypeValue = 1
2626           ClassValue = IN::ClassValue
2627           ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
2629           ##
2630           # Creates a new A for +address+.
2632           def initialize(address)
2633             @address = IPv4.create(address)
2634           end
2636           ##
2637           # The Resolv::IPv4 address for this A.
2639           attr_reader :address
2641           def encode_rdata(msg) # :nodoc:
2642             msg.put_bytes(@address.address)
2643           end
2645           def self.decode_rdata(msg) # :nodoc:
2646             return self.new(IPv4.new(msg.get_bytes(4)))
2647           end
2648         end
2650         ##
2651         # Well Known Service resource.
2653         class WKS < Resource
2654           TypeValue = 11
2655           ClassValue = IN::ClassValue
2656           ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
2658           def initialize(address, protocol, bitmap)
2659             @address = IPv4.create(address)
2660             @protocol = protocol
2661             @bitmap = bitmap
2662           end
2664           ##
2665           # The host these services run on.
2667           attr_reader :address
2669           ##
2670           # IP protocol number for these services.
2672           attr_reader :protocol
2674           ##
2675           # A bit map of enabled services on this host.
2676           #
2677           # If protocol is 6 (TCP) then the 26th bit corresponds to the SMTP
2678           # service (port 25).  If this bit is set, then an SMTP server should
2679           # be listening on TCP port 25; if zero, SMTP service is not
2680           # supported.
2682           attr_reader :bitmap
2684           def encode_rdata(msg) # :nodoc:
2685             msg.put_bytes(@address.address)
2686             msg.put_pack("n", @protocol)
2687             msg.put_bytes(@bitmap)
2688           end
2690           def self.decode_rdata(msg) # :nodoc:
2691             address = IPv4.new(msg.get_bytes(4))
2692             protocol, = msg.get_unpack("n")
2693             bitmap = msg.get_bytes
2694             return self.new(address, protocol, bitmap)
2695           end
2696         end
2698         ##
2699         # An IPv6 address record.
2701         class AAAA < Resource
2702           TypeValue = 28
2703           ClassValue = IN::ClassValue
2704           ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
2706           ##
2707           # Creates a new AAAA for +address+.
2709           def initialize(address)
2710             @address = IPv6.create(address)
2711           end
2713           ##
2714           # The Resolv::IPv6 address for this AAAA.
2716           attr_reader :address
2718           def encode_rdata(msg) # :nodoc:
2719             msg.put_bytes(@address.address)
2720           end
2722           def self.decode_rdata(msg) # :nodoc:
2723             return self.new(IPv6.new(msg.get_bytes(16)))
2724           end
2725         end
2727         ##
2728         # SRV resource record defined in RFC 2782
2729         #
2730         # These records identify the hostname and port that a service is
2731         # available at.
2733         class SRV < Resource
2734           TypeValue = 33
2735           ClassValue = IN::ClassValue
2736           ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
2738           # Create a SRV resource record.
2739           #
2740           # See the documentation for #priority, #weight, #port and #target
2741           # for +priority+, +weight+, +port and +target+ respectively.
2743           def initialize(priority, weight, port, target)
2744             @priority = priority.to_int
2745             @weight = weight.to_int
2746             @port = port.to_int
2747             @target = Name.create(target)
2748           end
2750           # The priority of this target host.
2751           #
2752           # A client MUST attempt to contact the target host with the
2753           # lowest-numbered priority it can reach; target hosts with the same
2754           # priority SHOULD be tried in an order defined by the weight field.
2755           # The range is 0-65535.  Note that it is not widely implemented and
2756           # should be set to zero.
2758           attr_reader :priority
2760           # A server selection mechanism.
2761           #
2762           # The weight field specifies a relative weight for entries with the
2763           # same priority. Larger weights SHOULD be given a proportionately
2764           # higher probability of being selected. The range of this number is
2765           # 0-65535.  Domain administrators SHOULD use Weight 0 when there
2766           # isn't any server selection to do, to make the RR easier to read
2767           # for humans (less noisy). Note that it is not widely implemented
2768           # and should be set to zero.
2770           attr_reader :weight
2772           # The port on this target host of this service.
2773           #
2774           # The range is 0-65535.
2776           attr_reader :port
2778           # The domain name of the target host.
2779           #
2780           # A target of "." means that the service is decidedly not available
2781           # at this domain.
2783           attr_reader :target
2785           def encode_rdata(msg) # :nodoc:
2786             msg.put_pack("n", @priority)
2787             msg.put_pack("n", @weight)
2788             msg.put_pack("n", @port)
2789             msg.put_name(@target, compress: false)
2790           end
2792           def self.decode_rdata(msg) # :nodoc:
2793             priority, = msg.get_unpack("n")
2794             weight,   = msg.get_unpack("n")
2795             port,     = msg.get_unpack("n")
2796             target    = msg.get_name
2797             return self.new(priority, weight, port, target)
2798           end
2799         end
2801         ##
2802         # Common implementation for SVCB-compatible resource records.
2804         class ServiceBinding
2806           ##
2807           # Create a service binding resource record.
2809           def initialize(priority, target, params = [])
2810             @priority = priority.to_int
2811             @target = Name.create(target)
2812             @params = SvcParams.new(params)
2813           end
2815           ##
2816           # The priority of this target host.
2817           #
2818           # The range is 0-65535.
2819           # If set to 0, this RR is in AliasMode. Otherwise, it is in ServiceMode.
2821           attr_reader :priority
2823           ##
2824           # The domain name of the target host.
2826           attr_reader :target
2828           ##
2829           # The service parameters for the target host.
2831           attr_reader :params
2833           ##
2834           # Whether this RR is in AliasMode.
2836           def alias_mode?
2837             self.priority == 0
2838           end
2840           ##
2841           # Whether this RR is in ServiceMode.
2843           def service_mode?
2844             !alias_mode?
2845           end
2847           def encode_rdata(msg) # :nodoc:
2848             msg.put_pack("n", @priority)
2849             msg.put_name(@target, compress: false)
2850             @params.encode(msg)
2851           end
2853           def self.decode_rdata(msg) # :nodoc:
2854             priority, = msg.get_unpack("n")
2855             target    = msg.get_name
2856             params    = SvcParams.decode(msg)
2857             return self.new(priority, target, params)
2858           end
2859         end
2861         ##
2862         # SVCB resource record [RFC9460]
2864         class SVCB < ServiceBinding
2865           TypeValue = 64
2866           ClassValue = IN::ClassValue
2867           ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
2868         end
2870         ##
2871         # HTTPS resource record [RFC9460]
2873         class HTTPS < ServiceBinding
2874           TypeValue = 65
2875           ClassValue = IN::ClassValue
2876           ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
2877         end
2878       end
2879     end
2880   end
2882   ##
2883   # A Resolv::DNS IPv4 address.
2885   class IPv4
2887     ##
2888     # Regular expression IPv4 addresses must match.
2890     Regex256 = /0
2891                |1(?:[0-9][0-9]?)?
2892                |2(?:[0-4][0-9]?|5[0-5]?|[6-9])?
2893                |[3-9][0-9]?/x
2894     Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/
2896     def self.create(arg)
2897       case arg
2898       when IPv4
2899         return arg
2900       when Regex
2901         if (0..255) === (a = $1.to_i) &&
2902            (0..255) === (b = $2.to_i) &&
2903            (0..255) === (c = $3.to_i) &&
2904            (0..255) === (d = $4.to_i)
2905           return self.new([a, b, c, d].pack("CCCC"))
2906         else
2907           raise ArgumentError.new("IPv4 address with invalid value: " + arg)
2908         end
2909       else
2910         raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}")
2911       end
2912     end
2914     def initialize(address) # :nodoc:
2915       unless address.kind_of?(String)
2916         raise ArgumentError, 'IPv4 address must be a string'
2917       end
2918       unless address.length == 4
2919         raise ArgumentError, "IPv4 address expects 4 bytes but #{address.length} bytes"
2920       end
2921       @address = address
2922     end
2924     ##
2925     # A String representation of this IPv4 address.
2927     ##
2928     # The raw IPv4 address as a String.
2930     attr_reader :address
2932     def to_s # :nodoc:
2933       return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC"))
2934     end
2936     def inspect # :nodoc:
2937       return "#<#{self.class} #{self}>"
2938     end
2940     ##
2941     # Turns this IPv4 address into a Resolv::DNS::Name.
2943     def to_name
2944       return DNS::Name.create(
2945         '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse)
2946     end
2948     def ==(other) # :nodoc:
2949       return @address == other.address
2950     end
2952     def eql?(other) # :nodoc:
2953       return self == other
2954     end
2956     def hash # :nodoc:
2957       return @address.hash
2958     end
2959   end
2961   ##
2962   # A Resolv::DNS IPv6 address.
2964   class IPv6
2966     ##
2967     # IPv6 address format a:b:c:d:e:f:g:h
2968     Regex_8Hex = /\A
2969       (?:[0-9A-Fa-f]{1,4}:){7}
2970          [0-9A-Fa-f]{1,4}
2971       \z/x
2973     ##
2974     # Compressed IPv6 address format a::b
2976     Regex_CompressedHex = /\A
2977       ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
2978       ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
2979       \z/x
2981     ##
2982     # IPv4 mapped IPv6 address format a:b:c:d:e:f:w.x.y.z
2984     Regex_6Hex4Dec = /\A
2985       ((?:[0-9A-Fa-f]{1,4}:){6,6})
2986       (\d+)\.(\d+)\.(\d+)\.(\d+)
2987       \z/x
2989     ##
2990     # Compressed IPv4 mapped IPv6 address format a::b:w.x.y.z
2992     Regex_CompressedHex4Dec = /\A
2993       ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
2994       ((?:[0-9A-Fa-f]{1,4}:)*)
2995       (\d+)\.(\d+)\.(\d+)\.(\d+)
2996       \z/x
2998     ##
2999     # IPv6 link local address format fe80:b:c:d:e:f:g:h%em1
3000     Regex_8HexLinkLocal = /\A
3001       [Ff][Ee]80
3002       (?::[0-9A-Fa-f]{1,4}){7}
3003       %[-0-9A-Za-z._~]+
3004       \z/x
3006     ##
3007     # Compressed IPv6 link local address format fe80::b%em1
3009     Regex_CompressedHexLinkLocal = /\A
3010       [Ff][Ee]80:
3011       (?:
3012         ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
3013         ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
3014         |
3015         :((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
3016       )?
3017       :[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+
3018       \z/x
3020     ##
3021     # A composite IPv6 address Regexp.
3023     Regex = /
3024       (?:#{Regex_8Hex}) |
3025       (?:#{Regex_CompressedHex}) |
3026       (?:#{Regex_6Hex4Dec}) |
3027       (?:#{Regex_CompressedHex4Dec}) |
3028       (?:#{Regex_8HexLinkLocal}) |
3029       (?:#{Regex_CompressedHexLinkLocal})
3030       /x
3032     ##
3033     # Creates a new IPv6 address from +arg+ which may be:
3034     #
3035     # IPv6:: returns +arg+.
3036     # String:: +arg+ must match one of the IPv6::Regex* constants
3038     def self.create(arg)
3039       case arg
3040       when IPv6
3041         return arg
3042       when String
3043         address = ''.b
3044         if Regex_8Hex =~ arg
3045           arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
3046         elsif Regex_CompressedHex =~ arg
3047           prefix = $1
3048           suffix = $2
3049           a1 = ''.b
3050           a2 = ''.b
3051           prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
3052           suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
3053           omitlen = 16 - a1.length - a2.length
3054           address << a1 << "\0" * omitlen << a2
3055         elsif Regex_6Hex4Dec =~ arg
3056           prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i
3057           if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
3058             prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
3059             address << [a, b, c, d].pack('CCCC')
3060           else
3061             raise ArgumentError.new("not numeric IPv6 address: " + arg)
3062           end
3063         elsif Regex_CompressedHex4Dec =~ arg
3064           prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i
3065           if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
3066             a1 = ''.b
3067             a2 = ''.b
3068             prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
3069             suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
3070             omitlen = 12 - a1.length - a2.length
3071             address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC')
3072           else
3073             raise ArgumentError.new("not numeric IPv6 address: " + arg)
3074           end
3075         else
3076           raise ArgumentError.new("not numeric IPv6 address: " + arg)
3077         end
3078         return IPv6.new(address)
3079       else
3080         raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}")
3081       end
3082     end
3084     def initialize(address) # :nodoc:
3085       unless address.kind_of?(String) && address.length == 16
3086         raise ArgumentError.new('IPv6 address must be 16 bytes')
3087       end
3088       @address = address
3089     end
3091     ##
3092     # The raw IPv6 address as a String.
3094     attr_reader :address
3096     def to_s # :nodoc:
3097       sprintf("%x:%x:%x:%x:%x:%x:%x:%x", *@address.unpack("nnnnnnnn")).sub(/(^|:)0(:0)+(:|$)/, '::')
3098     end
3100     def inspect # :nodoc:
3101       return "#<#{self.class} #{self}>"
3102     end
3104     ##
3105     # Turns this IPv6 address into a Resolv::DNS::Name.
3106     #--
3107     # ip6.arpa should be searched too. [RFC3152]
3109     def to_name
3110       return DNS::Name.new(
3111         @address.unpack("H32")[0].split(//).reverse + ['ip6', 'arpa'])
3112     end
3114     def ==(other) # :nodoc:
3115       return @address == other.address
3116     end
3118     def eql?(other) # :nodoc:
3119       return self == other
3120     end
3122     def hash # :nodoc:
3123       return @address.hash
3124     end
3125   end
3127   ##
3128   # Resolv::MDNS is a one-shot Multicast DNS (mDNS) resolver.  It blindly
3129   # makes queries to the mDNS addresses without understanding anything about
3130   # multicast ports.
3131   #
3132   # Information taken form the following places:
3133   #
3134   # * RFC 6762
3136   class MDNS < DNS
3138     ##
3139     # Default mDNS Port
3141     Port = 5353
3143     ##
3144     # Default IPv4 mDNS address
3146     AddressV4 = '224.0.0.251'
3148     ##
3149     # Default IPv6 mDNS address
3151     AddressV6 = 'ff02::fb'
3153     ##
3154     # Default mDNS addresses
3156     Addresses = [
3157       [AddressV4, Port],
3158       [AddressV6, Port],
3159     ]
3161     ##
3162     # Creates a new one-shot Multicast DNS (mDNS) resolver.
3163     #
3164     # +config_info+ can be:
3165     #
3166     # nil::
3167     #   Uses the default mDNS addresses
3168     #
3169     # Hash::
3170     #   Must contain :nameserver or :nameserver_port like
3171     #   Resolv::DNS#initialize.
3173     def initialize(config_info=nil)
3174       if config_info then
3175         super({ nameserver_port: Addresses }.merge(config_info))
3176       else
3177         super(nameserver_port: Addresses)
3178       end
3179     end
3181     ##
3182     # Iterates over all IP addresses for +name+ retrieved from the mDNS
3183     # resolver, provided name ends with "local".  If the name does not end in
3184     # "local" no records will be returned.
3185     #
3186     # +name+ can be a Resolv::DNS::Name or a String.  Retrieved addresses will
3187     # be a Resolv::IPv4 or Resolv::IPv6
3189     def each_address(name)
3190       name = Resolv::DNS::Name.create(name)
3192       return unless name[-1].to_s == 'local'
3194       super(name)
3195     end
3197     def make_udp_requester # :nodoc:
3198       nameserver_port = @config.nameserver_port
3199       Requester::MDNSOneShot.new(*nameserver_port)
3200     end
3202   end
3204   module LOC
3206     ##
3207     # A Resolv::LOC::Size
3209     class Size
3211       Regex = /^(\d+\.*\d*)[m]$/
3213       ##
3214       # Creates a new LOC::Size from +arg+ which may be:
3215       #
3216       # LOC::Size:: returns +arg+.
3217       # String:: +arg+ must match the LOC::Size::Regex constant
3219       def self.create(arg)
3220         case arg
3221         when Size
3222           return arg
3223         when String
3224           scalar = ''
3225           if Regex =~ arg
3226             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")
3227           else
3228             raise ArgumentError.new("not a properly formed Size string: " + arg)
3229           end
3230           return Size.new(scalar)
3231         else
3232           raise ArgumentError.new("cannot interpret as Size: #{arg.inspect}")
3233         end
3234       end
3236       def initialize(scalar)
3237         @scalar = scalar
3238       end
3240       ##
3241       # The raw size
3243       attr_reader :scalar
3245       def to_s # :nodoc:
3246         s = @scalar.unpack("H2").join.to_s
3247         return ((s[0].to_i)*(10**(s[1].to_i-2))).to_s << "m"
3248       end
3250       def inspect # :nodoc:
3251         return "#<#{self.class} #{self}>"
3252       end
3254       def ==(other) # :nodoc:
3255         return @scalar == other.scalar
3256       end
3258       def eql?(other) # :nodoc:
3259         return self == other
3260       end
3262       def hash # :nodoc:
3263         return @scalar.hash
3264       end
3266     end
3268     ##
3269     # A Resolv::LOC::Coord
3271     class Coord
3273       Regex = /^(\d+)\s(\d+)\s(\d+\.\d+)\s([NESW])$/
3275       ##
3276       # Creates a new LOC::Coord from +arg+ which may be:
3277       #
3278       # LOC::Coord:: returns +arg+.
3279       # String:: +arg+ must match the LOC::Coord::Regex constant
3281       def self.create(arg)
3282         case arg
3283         when Coord
3284           return arg
3285         when String
3286           coordinates = ''
3287           if Regex =~ arg && $1.to_f < 180
3288             m = $~
3289             hemi = (m[4][/[NE]/]) || (m[4][/[SW]/]) ? 1 : -1
3290             coordinates = [ ((m[1].to_i*(36e5)) + (m[2].to_i*(6e4)) +
3291                              (m[3].to_f*(1e3))) * hemi+(2**31) ].pack("N")
3292             orientation = m[4][/[NS]/] ? 'lat' : 'lon'
3293           else
3294             raise ArgumentError.new("not a properly formed Coord string: " + arg)
3295           end
3296           return Coord.new(coordinates,orientation)
3297         else
3298           raise ArgumentError.new("cannot interpret as Coord: #{arg.inspect}")
3299         end
3300       end
3302       def initialize(coordinates,orientation)
3303         unless coordinates.kind_of?(String)
3304           raise ArgumentError.new("Coord must be a 32bit unsigned integer in hex format: #{coordinates.inspect}")
3305         end
3306         unless orientation.kind_of?(String) && orientation[/^lon$|^lat$/]
3307           raise ArgumentError.new('Coord expects orientation to be a String argument of "lat" or "lon"')
3308         end
3309         @coordinates = coordinates
3310         @orientation = orientation
3311       end
3313       ##
3314       # The raw coordinates
3316       attr_reader :coordinates
3318       ## The orientation of the hemisphere as 'lat' or 'lon'
3320       attr_reader :orientation
3322       def to_s # :nodoc:
3323           c = @coordinates.unpack("N").join.to_i
3324           val      = (c - (2**31)).abs
3325           fracsecs = (val % 1e3).to_i.to_s
3326           val      = val / 1e3
3327           secs     = (val % 60).to_i.to_s
3328           val      = val / 60
3329           mins     = (val % 60).to_i.to_s
3330           degs     = (val / 60).to_i.to_s
3331           posi = (c >= 2**31)
3332           case posi
3333           when true
3334             hemi = @orientation[/^lat$/] ? "N" : "E"
3335           else
3336             hemi = @orientation[/^lon$/] ? "W" : "S"
3337           end
3338           return degs << " " << mins << " " << secs << "." << fracsecs << " " << hemi
3339       end
3341       def inspect # :nodoc:
3342         return "#<#{self.class} #{self}>"
3343       end
3345       def ==(other) # :nodoc:
3346         return @coordinates == other.coordinates
3347       end
3349       def eql?(other) # :nodoc:
3350         return self == other
3351       end
3353       def hash # :nodoc:
3354         return @coordinates.hash
3355       end
3357     end
3359     ##
3360     # A Resolv::LOC::Alt
3362     class Alt
3364       Regex = /^([+-]*\d+\.*\d*)[m]$/
3366       ##
3367       # Creates a new LOC::Alt from +arg+ which may be:
3368       #
3369       # LOC::Alt:: returns +arg+.
3370       # String:: +arg+ must match the LOC::Alt::Regex constant
3372       def self.create(arg)
3373         case arg
3374         when Alt
3375           return arg
3376         when String
3377           altitude = ''
3378           if Regex =~ arg
3379             altitude = [($1.to_f*(1e2))+(1e7)].pack("N")
3380           else
3381             raise ArgumentError.new("not a properly formed Alt string: " + arg)
3382           end
3383           return Alt.new(altitude)
3384         else
3385           raise ArgumentError.new("cannot interpret as Alt: #{arg.inspect}")
3386         end
3387       end
3389       def initialize(altitude)
3390         @altitude = altitude
3391       end
3393       ##
3394       # The raw altitude
3396       attr_reader :altitude
3398       def to_s # :nodoc:
3399         a = @altitude.unpack("N").join.to_i
3400         return ((a.to_f/1e2)-1e5).to_s + "m"
3401       end
3403       def inspect # :nodoc:
3404         return "#<#{self.class} #{self}>"
3405       end
3407       def ==(other) # :nodoc:
3408         return @altitude == other.altitude
3409       end
3411       def eql?(other) # :nodoc:
3412         return self == other
3413       end
3415       def hash # :nodoc:
3416         return @altitude.hash
3417       end
3419     end
3421   end
3423   ##
3424   # Default resolver to use for Resolv class methods.
3426   DefaultResolver = self.new
3428   ##
3429   # Replaces the resolvers in the default resolver with +new_resolvers+.  This
3430   # allows resolvers to be changed for resolv-replace.
3432   def DefaultResolver.replace_resolvers new_resolvers
3433     @resolvers = new_resolvers
3434   end
3436   ##
3437   # Address Regexp to use for matching IP addresses.
3439   AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/