Fix up Rubinius specific library specs.
[rbx.git] / lib / resolv.rb
blob49e40bdf073fc24799d42cd74d1047e4348540f5
1 =begin
2 = resolv library
3 resolv.rb is a resolver library written in Ruby.
4 Since it is written in Ruby, it is thread-aware.
5 I.e. it can resolv many hostnames concurrently.
7 It is possible to lookup various resources of DNS using DNS module directly.
9 == example
10   p Resolv.getaddress("www.ruby-lang.org")
11   p Resolv.getname("210.251.121.214")
13   Resolv::DNS.open {|dns|
14     p dns.getresources("www.ruby-lang.org", Resolv::DNS::Resource::IN::A).collect {|r| r.address}
15     p dns.getresources("ruby-lang.org", Resolv::DNS::Resource::IN::MX).collect {|r| [r.exchange.to_s, r.preference]}
16   }
18 == Resolv class
20 === class methods
21 --- Resolv.getaddress(name)
22 --- Resolv.getaddresses(name)
23 --- Resolv.each_address(name) {|address| ...}
24     They lookups IP addresses of ((|name|)) which represents a hostname
25     as a string by default resolver.
27     getaddress returns first entry of lookupped addresses.
28     getaddresses returns lookupped addresses as an array.
29     each_address iterates over lookupped addresses.
31 --- Resolv.getname(address)
32 --- Resolv.getnames(address)
33 --- Resolv.each_name(address) {|name| ...}
34     lookups hostnames of ((|address|)) which represents IP address as a string.
36     getname returns first entry of lookupped names.
37     getnames returns lookupped names as an array.
38     each_names iterates over lookupped names.
40 == Resolv::Hosts class
41 hostname resolver using /etc/hosts format.
43 === class methods
44 --- Resolv::Hosts.new(hosts='/etc/hosts')
46 === methods
47 --- Resolv::Hosts#getaddress(name)
48 --- Resolv::Hosts#getaddresses(name)
49 --- Resolv::Hosts#each_address(name) {|address| ...}
50     address lookup methods.
52 --- Resolv::Hosts#getname(address)
53 --- Resolv::Hosts#getnames(address)
54 --- Resolv::Hosts#each_name(address) {|name| ...}
55     hostnames lookup methods.
57 == Resolv::DNS class
58 DNS stub resolver.
60 === class methods
61 --- Resolv::DNS.new(config_info=nil)
63     ((|config_info|)) should be nil, a string or a hash.
64     If nil is given, /etc/resolv.conf and platform specific information is used.
65     If a string is given, it should be a filename which format is same as /etc/resolv.conf.
66     If a hash is given, it may contains information for nameserver, search and ndots as follows.
68       Resolv::DNS.new({:nameserver=>["210.251.121.21"], :search=>["ruby-lang.org"], :ndots=>1})
70 --- Resolv::DNS.open(config_info=nil)
71 --- Resolv::DNS.open(config_info=nil) {|dns| ...}
73 === methods
74 --- Resolv::DNS#close
76 --- Resolv::DNS#getaddress(name)
77 --- Resolv::DNS#getaddresses(name)
78 --- Resolv::DNS#each_address(name) {|address| ...}
79     address lookup methods.
81     ((|name|)) must be a instance of Resolv::DNS::Name or String.  Lookupped
82     address is represented as an instance of Resolv::IPv4 or Resolv::IPv6.
84 --- Resolv::DNS#getname(address)
85 --- Resolv::DNS#getnames(address)
86 --- Resolv::DNS#each_name(address) {|name| ...}
87     hostnames lookup methods.
89     ((|address|)) must be a instance of Resolv::IPv4, Resolv::IPv6 or String.
90     Lookupped name is represented as an instance of Resolv::DNS::Name.
92 --- Resolv::DNS#getresource(name, typeclass)
93 --- Resolv::DNS#getresources(name, typeclass)
94 --- Resolv::DNS#each_resource(name, typeclass) {|resource| ...}
95     They lookup DNS resources of ((|name|)).
96     ((|name|)) must be a instance of Resolv::Name or String.
98     ((|typeclass|)) should be one of follows:
99     * Resolv::DNS::Resource::IN::ANY
100     * Resolv::DNS::Resource::IN::NS
101     * Resolv::DNS::Resource::IN::CNAME
102     * Resolv::DNS::Resource::IN::SOA
103     * Resolv::DNS::Resource::IN::HINFO
104     * Resolv::DNS::Resource::IN::MINFO
105     * Resolv::DNS::Resource::IN::MX
106     * Resolv::DNS::Resource::IN::TXT
107     * Resolv::DNS::Resource::IN::ANY
108     * Resolv::DNS::Resource::IN::A
109     * Resolv::DNS::Resource::IN::WKS
110     * Resolv::DNS::Resource::IN::PTR
111     * Resolv::DNS::Resource::IN::AAAA
113     Lookupped resource is represented as an instance of (a subclass of)
114     Resolv::DNS::Resource. 
115     (Resolv::DNS::Resource::IN::A, etc.)
117 == Resolv::DNS::Resource::IN::NS class
118 --- name
119 == Resolv::DNS::Resource::IN::CNAME class
120 --- name
121 == Resolv::DNS::Resource::IN::SOA class
122 --- mname
123 --- rname
124 --- serial
125 --- refresh
126 --- retry
127 --- expire
128 --- minimum
129 == Resolv::DNS::Resource::IN::HINFO class
130 --- cpu
131 --- os
132 == Resolv::DNS::Resource::IN::MINFO class
133 --- rmailbx
134 --- emailbx
135 == Resolv::DNS::Resource::IN::MX class
136 --- preference
137 --- exchange
138 == Resolv::DNS::Resource::IN::TXT class
139 --- data
140 == Resolv::DNS::Resource::IN::A class
141 --- address
142 == Resolv::DNS::Resource::IN::WKS class
143 --- address
144 --- protocol
145 --- bitmap
146 == Resolv::DNS::Resource::IN::PTR class
147 --- name
148 == Resolv::DNS::Resource::IN::AAAA class
149 --- address
151 == Resolv::DNS::Name class
153 === class methods
154 --- Resolv::DNS::Name.create(name)
156 === methods
157 --- Resolv::DNS::Name#to_s
159 == Resolv::DNS::Resource class
161 == Resolv::IPv4 class
162 === class methods
163 --- Resolv::IPv4.create(address)
165 === methods
166 --- Resolv::IPv4#to_s
167 --- Resolv::IPv4#to_name
169 === constants
170 --- Resolv::IPv4::Regex
171     regular expression for IPv4 address.
173 == Resolv::IPv6 class
174 === class methods
175 --- Resolv::IPv6.create(address)
177 === methods
178 --- Resolv::IPv6#to_s
179 --- Resolv::IPv6#to_name
181 === constants
182 --- Resolv::IPv6::Regex
183     regular expression for IPv6 address.
185 == Bugs
186 * NIS is not supported.
187 * /etc/nsswitch.conf is not supported.
188 * IPv6 is not supported.
190 =end
192 require 'socket'
193 require 'fcntl'
194 require 'timeout'
195 require 'thread'
197 class Resolv
198   def self.getaddress(name)
199     DefaultResolver.getaddress(name)
200   end
202   def self.getaddresses(name)
203     DefaultResolver.getaddresses(name)
204   end
206   def self.each_address(name, &block)
207     DefaultResolver.each_address(name, &block)
208   end
210   def self.getname(address)
211     DefaultResolver.getname(address)
212   end
214   def self.getnames(address)
215     DefaultResolver.getnames(address)
216   end
218   def self.each_name(address, &proc)
219     DefaultResolver.each_name(address, &proc)
220   end
222   def initialize(resolvers=[Hosts.new, DNS.new])
223     @resolvers = resolvers
224   end
226   def getaddress(name)
227     each_address(name) {|address| return address}
228     raise ResolvError.new("no address for #{name}")
229   end
231   def getaddresses(name)
232     ret = []
233     each_address(name) {|address| ret << address}
234     return ret
235   end
237   def each_address(name)
238     if AddressRegex =~ name
239       yield name
240       return
241     end
242     yielded = false
243     @resolvers.each {|r|
244       r.each_address(name) {|address|
245         yield address.to_s
246         yielded = true
247       }
248       return if yielded
249     }
250   end
252   def getname(address)
253     each_name(address) {|name| return name}
254     raise ResolvError.new("no name for #{address}")
255   end
257   def getnames(address)
258     ret = []
259     each_name(address) {|name| ret << name}
260     return ret
261   end
263   def each_name(address)
264     yielded = false
265     @resolvers.each {|r|
266       r.each_name(address) {|name|
267         yield name.to_s
268         yielded = true
269       }
270       return if yielded
271     }
272   end
274   class ResolvError < StandardError
275   end
277   class ResolvTimeout < TimeoutError
278   end
280   class Hosts
281     if /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
282       require 'win32/resolv'
283       DefaultFileName = Win32::Resolv.get_hosts_path
284     else
285       DefaultFileName = '/etc/hosts'
286     end
288     def initialize(filename = DefaultFileName)
289       @filename = filename
290       @mutex = Mutex.new
291       @initialized = nil
292     end
294     def lazy_initialize
295       @mutex.synchronize {
296         unless @initialized
297           @name2addr = {}
298           @addr2name = {}
299           open(@filename) {|f|
300             f.each {|line|
301               line.sub!(/#.*/, '')
302               addr, hostname, *aliases = line.split(/\s+/)
303               next unless addr
304               addr.untaint
305               hostname.untaint
306               @addr2name[addr] = [] unless @addr2name.include? addr
307               @addr2name[addr] << hostname
308               @addr2name[addr] += aliases
309               @name2addr[hostname] = [] unless @name2addr.include? hostname
310               @name2addr[hostname] << addr
311               aliases.each {|n|
312                 n.untaint
313                 @name2addr[n] = [] unless @name2addr.include? n
314                 @name2addr[n] << addr
315               }
316             }
317           }
318           @name2addr.each {|name, arr| arr.reverse!}
319           @initialized = true
320         end
321       }
322       self
323     end
325     def getaddress(name)
326       each_address(name) {|address| return address}
327       raise ResolvError.new("#{@filename} has no name: #{name}")
328     end
330     def getaddresses(name)
331       ret = []
332       each_address(name) {|address| ret << address}
333       return ret
334     end
336     def each_address(name, &proc)
337       lazy_initialize
338       if @name2addr.include?(name)
339         @name2addr[name].each(&proc)
340       end
341     end
343     def getname(address)
344       each_name(address) {|name| return name}
345       raise ResolvError.new("#{@filename} has no address: #{address}")
346     end
348     def getnames(address)
349       ret = []
350       each_name(address) {|name| ret << name}
351       return ret
352     end
354     def each_name(address, &proc)
355       lazy_initialize
356       if @addr2name.include?(address)
357         @addr2name[address].each(&proc)
358       end
359     end
360   end
362   class DNS
363     # STD0013 (RFC 1035, etc.)
364     # ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters
366     Port = 53
367     UDPSize = 512
369     DNSThreadGroup = ThreadGroup.new
371     def self.open(*args)
372       dns = new(*args)
373       return dns unless block_given?
374       begin
375         yield dns
376       ensure
377         dns.close
378       end
379     end
381     def initialize(config_info=nil)
382       @mutex = Mutex.new
383       @config = Config.new(config_info)
384       @initialized = nil
385     end
387     def lazy_initialize
388       @mutex.synchronize {
389         unless @initialized
390           @config.lazy_initialize
392           if nameserver = @config.single?
393             @requester = Requester::ConnectedUDP.new(nameserver)
394           else
395             @requester = Requester::UnconnectedUDP.new
396           end
398           @initialized = true
399         end
400       }
401       self
402     end
404     def close
405       @mutex.synchronize {
406         if @initialized
407           @requester.close if @requester
408           @requester = nil
409           @initialized = false
410         end
411       }
412     end
414     def getaddress(name)
415       each_address(name) {|address| return address}
416       raise ResolvError.new("DNS result has no information for #{name}")
417     end
419     def getaddresses(name)
420       ret = []
421       each_address(name) {|address| ret << address}
422       return ret
423     end
425     def each_address(name)
426       each_resource(name, Resource::IN::A) {|resource| yield resource.address}
427     end
429     def getname(address)
430       each_name(address) {|name| return name}
431       raise ResolvError.new("DNS result has no information for #{address}")
432     end
434     def getnames(address)
435       ret = []
436       each_name(address) {|name| ret << name}
437       return ret
438     end
440     def each_name(address)
441       case address
442       when Name
443         ptr = address
444       when IPv4::Regex
445         ptr = IPv4.create(address).to_name
446       when IPv6::Regex
447         ptr = IPv6.create(address).to_name
448       else
449         raise ResolvError.new("cannot interpret as address: #{address}")
450       end
451       each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name}
452     end
454     def getresource(name, typeclass)
455       each_resource(name, typeclass) {|resource| return resource}
456       raise ResolvError.new("DNS result has no information for #{name}")
457     end
459     def getresources(name, typeclass)
460       ret = []
461       each_resource(name, typeclass) {|resource| ret << resource}
462       return ret
463     end
465     def each_resource(name, typeclass, &proc)
466       lazy_initialize
467       q = Queue.new
468       senders = {}
469       begin
470         @config.resolv(name) {|candidate, tout, nameserver|
471           msg = Message.new
472           msg.rd = 1
473           msg.add_question(candidate, typeclass)
474           unless sender = senders[[candidate, nameserver]]
475             sender = senders[[candidate, nameserver]] =
476               @requester.sender(msg, candidate, q, nameserver)
477           end
478           sender.send
479           reply = reply_name = nil
480           timeout(tout, ResolvTimeout) { reply, reply_name = q.pop }
481           case reply.rcode
482           when RCode::NoError
483             extract_resources(reply, reply_name, typeclass, &proc)
484             return
485           when RCode::NXDomain
486             raise Config::NXDomain.new(reply_name.to_s)
487           else
488             raise Config::OtherResolvError.new(reply_name.to_s)
489           end
490         }
491       ensure
492         @requester.delete(q)
493       end
494     end
496     def extract_resources(msg, name, typeclass)
497       if typeclass < Resource::ANY
498         n0 = Name.create(name)
499         msg.each_answer {|n, ttl, data|
500           yield data if n0 == n
501         }
502       end
503       yielded = false
504       n0 = Name.create(name)
505       msg.each_answer {|n, ttl, data|
506         if n0 == n
507           case data
508           when typeclass
509             yield data
510             yielded = true
511           when Resource::CNAME
512             n0 = data.name
513           end
514         end
515       }
516       return if yielded
517       msg.each_answer {|n, ttl, data|
518         if n0 == n
519           case data
520           when typeclass
521             yield data
522           end
523         end
524       }
525     end
527     class Requester
528       def initialize
529         @senders = {}
530       end
532       def close
533         thread, sock, @thread, @sock = @thread, @sock
534         begin
535           if thread
536             thread.kill
537             thread.join
538           end
539         ensure
540           sock.close if sock
541         end
542       end
544       def delete(arg)
545         case arg
546         when Sender
547           @senders.delete_if {|k, s| s == arg }
548         when Queue
549           @senders.delete_if {|k, s| s.queue == arg }
550         else
551           raise ArgumentError.new("neither Sender or Queue: #{arg}")
552         end
553       end
555       class Sender
556         def initialize(msg, data, sock, queue)
557           @msg = msg
558           @data = data
559           @sock = sock
560           @queue = queue
561         end
562         attr_reader :queue
564         def recv(msg)
565           @queue.push([msg, @data])
566         end
567       end
569       class UnconnectedUDP < Requester
570         def initialize
571           super()
572           @sock = UDPSocket.new
573           @sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD
574           @id = {}
575           @id.default = -1
576           @thread = Thread.new {
577             DNSThreadGroup.add Thread.current
578             loop {
579               reply, from = @sock.recvfrom(UDPSize)
580               msg = begin
581                 Message.decode(reply)
582               rescue DecodeError
583                 STDERR.print("DNS message decoding error: #{reply.inspect}\n")
584                 next
585               end
586               if s = @senders[[[from[3],from[1]],msg.id]]
587                 s.recv msg
588               else
589                 #STDERR.print("non-handled DNS message: #{msg.inspect} from #{from.inspect}\n")
590               end
591             }
592           }
593         end
595         def sender(msg, data, queue, host, port=Port)
596           service = [host, port]
597           id = Thread.exclusive {
598             @id[service] = (@id[service] + 1) & 0xffff
599           }
600           request = msg.encode
601           request[0,2] = [id].pack('n')
602           return @senders[[service, id]] =
603             Sender.new(request, data, @sock, host, port, queue)
604         end
606         class Sender < Requester::Sender
607           def initialize(msg, data, sock, host, port, queue)
608             super(msg, data, sock, queue)
609             @host = host
610             @port = port
611           end
613           def send
614             @sock.send(@msg, 0, @host, @port)
615           end
616         end
617       end
619       class ConnectedUDP < Requester
620         def initialize(host, port=Port)
621           super()
622           @host = host
623           @port = port
624           @sock = UDPSocket.new(host.index(':') ? Socket::AF_INET6 : Socket::AF_INET)
625           @sock.connect(host, port)
626           @sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD
627           @id = -1
628           @thread = Thread.new {
629             DNSThreadGroup.add Thread.current
630             loop {
631               reply = @sock.recv(UDPSize)
632               msg = begin
633                 Message.decode(reply)
634               rescue DecodeError
635                 STDERR.print("DNS message decoding error: #{reply.inspect}")
636                 next
637               end
638               if s = @senders[msg.id]
639                 s.recv msg
640               else
641                 #STDERR.print("non-handled DNS message: #{msg.inspect}")
642               end
643             }
644           }
645         end
647         def sender(msg, data, queue, host=@host, port=@port)
648           unless host == @host && port == @port
649             raise RequestError.new("host/port don't match: #{host}:#{port}")
650           end
651           id = Thread.exclusive { @id = (@id + 1) & 0xffff }
652           request = msg.encode
653           request[0,2] = [id].pack('n')
654           return @senders[id] = Sender.new(request, data, @sock, queue)
655         end
657         class Sender < Requester::Sender
658           def send
659             @sock.send(@msg, 0)
660           end
661         end
662       end
664       class TCP < Requester
665         def initialize(host, port=Port)
666           super()
667           @host = host
668           @port = port
669           @sock = TCPSocket.new
670           @sock.connect(host, port)
671           @sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD
672           @id = -1
673           @senders = {}
674           @thread = Thread.new {
675             DNSThreadGroup.add Thread.current
676             loop {
677               len = @sock.read(2).unpack('n')
678               reply = @sock.read(len)
679               msg = begin
680                 Message.decode(reply)
681               rescue DecodeError
682                 STDERR.print("DNS message decoding error: #{reply.inspect}")
683                 next
684               end
685               if s = @senders[msg.id]
686                 s.push msg
687               else
688                 #STDERR.print("non-handled DNS message: #{msg.inspect}")
689               end
690             }
691           }
692         end
694         def sender(msg, data, queue, host=@host, port=@port)
695           unless host == @host && port == @port
696             raise RequestError.new("host/port don't match: #{host}:#{port}")
697           end
698           id = Thread.exclusive { @id = (@id + 1) & 0xffff }
699           request = msg.encode
700           request[0,2] = [request.length, id].pack('nn')
701           return @senders[id] = Sender.new(request, data, @sock, queue)
702         end
704         class Sender < Requester::Sender
705           def send
706             @sock.print(@msg)
707             @sock.flush
708           end
709         end
710       end
712       class RequestError < StandardError
713       end
714     end
716     class Config
717       def initialize(config_info=nil)
718         @mutex = Mutex.new
719         @config_info = config_info
720         @initialized = nil
721       end
723       def Config.parse_resolv_conf(filename)
724         nameserver = []
725         search = nil
726         ndots = 1
727         open(filename) {|f|
728           f.each {|line|
729             line.sub!(/[#;].*/, '')
730             keyword, *args = line.split(/\s+/)
731             args.each { |arg|
732               arg.untaint
733             }
734             next unless keyword
735             case keyword
736             when 'nameserver'
737               nameserver += args
738             when 'domain'
739               next if args.empty?
740               search = [args[0]]
741             when 'search'
742               next if args.empty?
743               search = args
744             when 'options'
745               args.each {|arg|
746                 case arg
747                 when /\Andots:(\d+)\z/
748                   ndots = $1.to_i
749                 end
750               }
751             end
752           }
753         }
754         return { :nameserver => nameserver, :search => search, :ndots => ndots }
755       end
757       def Config.default_config_hash(filename="/etc/resolv.conf")
758         if File.exist? filename
759           config_hash = Config.parse_resolv_conf(filename)
760         else
761           if /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
762             search, nameserver = Win32::Resolv.get_resolv_info
763             config_hash = {}
764             config_hash[:nameserver] = nameserver if nameserver
765             config_hash[:search] = [search].flatten if search
766           end
767         end
768         config_hash
769       end
771       def lazy_initialize
772         @mutex.synchronize {
773           unless @initialized
774             @nameserver = []
775             @search = nil
776             @ndots = 1
777             case @config_info
778             when nil
779               config_hash = Config.default_config_hash
780             when String
781               config_hash = Config.parse_resolv_conf(@config_info)
782             when Hash
783               config_hash = @config_info.dup
784               if String === config_hash[:nameserver]
785                 config_hash[:nameserver] = [config_hash[:nameserver]]
786               end
787               if String === config_hash[:search]
788                 config_hash[:search] = [config_hash[:search]]
789               end
790             else
791               raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}")
792             end
793             @nameserver = config_hash[:nameserver] if config_hash.include? :nameserver
794             @search = config_hash[:search] if config_hash.include? :search
795             @ndots = config_hash[:ndots] if config_hash.include? :ndots
797             @nameserver = ['0.0.0.0'] if @nameserver.empty?
798             if @search
799               @search = @search.map {|arg| Label.split(arg) }
800             else
801               hostname = Socket.gethostname
802               if /\./ =~ hostname
803                 @search = [Label.split($')]
804               else
805                 @search = [[]]
806               end
807             end
809             if !@nameserver.kind_of?(Array) ||
810                !@nameserver.all? {|ns| String === ns }
811               raise ArgumentError.new("invalid nameserver config: #{@nameserver.inspect}")
812             end
814             if !@search.kind_of?(Array) ||
815                !@search.all? {|ls| ls.all? {|l| Label::Str === l } }
816               raise ArgumentError.new("invalid search config: #{@search.inspect}")
817             end
819             if !@ndots.kind_of?(Integer)
820               raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}")
821             end
823             @initialized = true
824           end
825         }
826         self
827       end
829       def single?
830         lazy_initialize
831         if @nameserver.length == 1
832           return @nameserver[0]
833         else
834           return nil
835         end
836       end
838       def generate_candidates(name)
839         candidates = nil
840         name = Name.create(name)
841         if name.absolute?
842           candidates = [name]
843         else
844           if @ndots <= name.length - 1
845             candidates = [Name.new(name.to_a)]
846           else
847             candidates = []
848           end
849           candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)})
850         end
851         return candidates
852       end
854       InitialTimeout = 5
856       def generate_timeouts
857         ts = [InitialTimeout]
858         ts << ts[-1] * 2 / @nameserver.length
859         ts << ts[-1] * 2
860         ts << ts[-1] * 2
861         return ts
862       end
864       def resolv(name)
865         candidates = generate_candidates(name)
866         timeouts = generate_timeouts
867         begin
868           candidates.each {|candidate|
869             begin
870               timeouts.each {|tout|
871                 @nameserver.each {|nameserver|
872                   begin
873                     yield candidate, tout, nameserver
874                   rescue ResolvTimeout
875                   end
876                 }
877               }
878               raise ResolvError.new("DNS resolv timeout: #{name}")
879             rescue NXDomain
880             end
881           }
882         rescue ResolvError
883         end
884       end
886       class NXDomain < ResolvError
887       end
889       class OtherResolvError < ResolvError
890       end
891     end
893     module OpCode
894       Query = 0
895       IQuery = 1
896       Status = 2
897       Notify = 4
898       Update = 5
899     end
901     module RCode
902       NoError = 0
903       FormErr = 1
904       ServFail = 2
905       NXDomain = 3
906       NotImp = 4
907       Refused = 5
908       YXDomain = 6
909       YXRRSet = 7
910       NXRRSet = 8
911       NotAuth = 9
912       NotZone = 10
913       BADVERS = 16
914       BADSIG = 16
915       BADKEY = 17
916       BADTIME = 18
917       BADMODE = 19
918       BADNAME = 20
919       BADALG = 21
920     end
922     class DecodeError < StandardError
923     end
925     class EncodeError < StandardError
926     end
928     module Label
929       def self.split(arg)
930         labels = []
931         arg.scan(/[^\.]+/) {labels << Str.new($&)}
932         return labels
933       end
935       class Str
936         def initialize(string)
937           @string = string
938           @downcase = string.downcase
939         end
940         attr_reader :string, :downcase
942         def to_s
943           return @string
944         end
946         def inspect
947           return "#<#{self.class} #{self.to_s}>"
948         end
950         def ==(other)
951           return @downcase == other.downcase
952         end
954         def eql?(other)
955           return self == other
956         end
958         def hash
959           return @downcase.hash
960         end
961       end
962     end
964     class Name
965       def self.create(arg)
966         case arg
967         when Name
968           return arg
969         when String
970           return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
971         else
972           raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
973         end
974       end
976       def initialize(labels, absolute=true)
977         @labels = labels
978         @absolute = absolute
979       end
981       def inspect
982         "#<#{self.class}: #{self.to_s}#{@absolute ? '.' : ''}>"
983       end
985       def absolute?
986         return @absolute
987       end
989       def ==(other)
990         return false unless Name === other
991         return @labels == other.to_a && @absolute == other.absolute?
992       end
993       alias eql? ==
995       # tests subdomain-of relation.
996       #
997       #   domain = Resolv::DNS::Name.create("y.z")
998       #   p Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true
999       #   p Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true
1000       #   p Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false
1001       #   p Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false
1002       #   p Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false
1003       #   p Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false
1004       #
1005       def subdomain_of?(other)
1006         raise ArgumentError, "not a domain name: #{other.inspect}" unless Name === other
1007         return false if @absolute != other.absolute?
1008         other_len = other.length
1009         return false if @labels.length <= other_len
1010         return @labels[-other_len, other_len] == other.to_a
1011       end
1013       def hash
1014         return @labels.hash ^ @absolute.hash
1015       end
1017       def to_a
1018         return @labels
1019       end
1021       def length
1022         return @labels.length
1023       end
1025       def [](i)
1026         return @labels[i]
1027       end
1029       # returns the domain name as a string.
1030       #
1031       # The domain name doesn't have a trailing dot even if the name object is
1032       # absolute.
1033       #
1034       #   p Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z"
1035       #   p Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z"
1036       #
1037       def to_s
1038         return @labels.join('.')
1039       end
1040     end
1042     class Message
1043       @@identifier = -1
1045       def initialize(id = (@@identifier += 1) & 0xffff)
1046         @id = id
1047         @qr = 0
1048         @opcode = 0
1049         @aa = 0
1050         @tc = 0
1051         @rd = 0 # recursion desired
1052         @ra = 0 # recursion available
1053         @rcode = 0
1054         @question = []
1055         @answer = []
1056         @authority = []
1057         @additional = []
1058       end
1060       attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode
1061       attr_reader :question, :answer, :authority, :additional
1063       def ==(other)
1064         return @id == other.id &&
1065                @qr == other.qr &&
1066                @opcode == other.opcode &&
1067                @aa == other.aa &&
1068                @tc == other.tc &&
1069                @rd == other.rd &&
1070                @ra == other.ra &&
1071                @rcode == other.rcode &&
1072                @question == other.question &&
1073                @answer == other.answer &&
1074                @authority == other.authority &&
1075                @additional == other.additional
1076       end
1078       def add_question(name, typeclass)
1079         @question << [Name.create(name), typeclass]
1080       end
1082       def each_question
1083         @question.each {|name, typeclass|
1084           yield name, typeclass
1085         }
1086       end
1088       def add_answer(name, ttl, data)
1089         @answer << [Name.create(name), ttl, data]
1090       end
1092       def each_answer
1093         @answer.each {|name, ttl, data|
1094           yield name, ttl, data
1095         }
1096       end
1098       def add_authority(name, ttl, data)
1099         @authority << [Name.create(name), ttl, data]
1100       end
1102       def each_authority
1103         @authority.each {|name, ttl, data|
1104           yield name, ttl, data
1105         }
1106       end
1108       def add_additional(name, ttl, data)
1109         @additional << [Name.create(name), ttl, data]
1110       end
1112       def each_additional
1113         @additional.each {|name, ttl, data|
1114           yield name, ttl, data
1115         }
1116       end
1118       def each_resource
1119         each_answer {|name, ttl, data| yield name, ttl, data}
1120         each_authority {|name, ttl, data| yield name, ttl, data}
1121         each_additional {|name, ttl, data| yield name, ttl, data}
1122       end
1124       def encode
1125         return MessageEncoder.new {|msg|
1126           msg.put_pack('nnnnnn',
1127             @id,
1128             (@qr & 1) << 15 |
1129             (@opcode & 15) << 11 |
1130             (@aa & 1) << 10 |
1131             (@tc & 1) << 9 |
1132             (@rd & 1) << 8 |
1133             (@ra & 1) << 7 |
1134             (@rcode & 15),
1135             @question.length,
1136             @answer.length,
1137             @authority.length,
1138             @additional.length)
1139           @question.each {|q|
1140             name, typeclass = q
1141             msg.put_name(name)
1142             msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue)
1143           }
1144           [@answer, @authority, @additional].each {|rr|
1145             rr.each {|r|
1146               name, ttl, data = r
1147               msg.put_name(name)
1148               msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl)
1149               msg.put_length16 {data.encode_rdata(msg)}
1150             }
1151           }
1152         }.to_s
1153       end
1155       class MessageEncoder
1156         def initialize
1157           @data = ''
1158           @names = {}
1159           yield self
1160         end
1162         def to_s
1163           return @data
1164         end
1166         def put_bytes(d)
1167           @data << d
1168         end
1170         def put_pack(template, *d)
1171           @data << d.pack(template)
1172         end
1174         def put_length16
1175           length_index = @data.length
1176           @data << "\0\0"
1177           data_start = @data.length
1178           yield
1179           data_end = @data.length
1180           @data[length_index, 2] = [data_end - data_start].pack("n")
1181         end
1183         def put_string(d)
1184           self.put_pack("C", d.length)
1185           @data << d
1186         end
1188         def put_string_list(ds)
1189           ds.each {|d|
1190             self.put_string(d)
1191           }
1192         end
1194         def put_name(d)
1195           put_labels(d.to_a)
1196         end
1198         def put_labels(d)
1199           d.each_index {|i|
1200             domain = d[i..-1]
1201             if idx = @names[domain]
1202               self.put_pack("n", 0xc000 | idx)
1203               return
1204             else
1205               @names[domain] = @data.length
1206               self.put_label(d[i])
1207             end
1208           }
1209           @data << "\0"
1210         end
1212         def put_label(d)
1213           self.put_string(d.string)
1214         end
1215       end
1217       def Message.decode(m)
1218         o = Message.new(0)
1219         MessageDecoder.new(m) {|msg|
1220           id, flag, qdcount, ancount, nscount, arcount =
1221             msg.get_unpack('nnnnnn')
1222           o.id = id
1223           o.qr = (flag >> 15) & 1
1224           o.opcode = (flag >> 11) & 15
1225           o.aa = (flag >> 10) & 1
1226           o.tc = (flag >> 9) & 1
1227           o.rd = (flag >> 8) & 1
1228           o.ra = (flag >> 7) & 1
1229           o.rcode = flag & 15
1230           (1..qdcount).each {
1231             name, typeclass = msg.get_question
1232             o.add_question(name, typeclass)
1233           }
1234           (1..ancount).each {
1235             name, ttl, data = msg.get_rr
1236             o.add_answer(name, ttl, data)
1237           }
1238           (1..nscount).each {
1239             name, ttl, data = msg.get_rr
1240             o.add_authority(name, ttl, data)
1241           }
1242           (1..arcount).each {
1243             name, ttl, data = msg.get_rr
1244             o.add_additional(name, ttl, data)
1245           }
1246         }
1247         return o
1248       end
1250       class MessageDecoder
1251         def initialize(data)
1252           @data = data
1253           @index = 0
1254           @limit = data.length
1255           yield self
1256         end
1258         def get_length16
1259           len, = self.get_unpack('n')
1260           save_limit = @limit
1261           @limit = @index + len
1262           d = yield(len)
1263           if @index < @limit
1264             raise DecodeError.new("junk exists")
1265           elsif @limit < @index
1266             raise DecodeError.new("limit exceeded")
1267           end
1268           @limit = save_limit
1269           return d
1270         end
1272         def get_bytes(len = @limit - @index)
1273           d = @data[@index, len]
1274           @index += len
1275           return d
1276         end
1278         def get_unpack(template)
1279           len = 0
1280           template.each_byte {|byte|
1281             case byte
1282             when ?c, ?C
1283               len += 1
1284             when ?n
1285               len += 2
1286             when ?N
1287               len += 4
1288             else
1289               raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'")
1290             end
1291           }
1292           raise DecodeError.new("limit exceeded") if @limit < @index + len
1293           arr = @data.unpack("@#{@index}#{template}")
1294           @index += len
1295           return arr
1296         end
1298         def get_string
1299           len = @data[@index]
1300           raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len
1301           d = @data[@index + 1, len]
1302           @index += 1 + len
1303           return d
1304         end
1306         def get_string_list
1307           strings = []
1308           while @index < @limit
1309             strings << self.get_string
1310           end
1311           strings
1312         end
1314         def get_name
1315           return Name.new(self.get_labels)
1316         end
1318         def get_labels(limit=nil)
1319           limit = @index if !limit || @index < limit
1320           d = []
1321           while true
1322             case @data[@index]
1323             when 0
1324               @index += 1
1325               return d
1326             when 192..255
1327               idx = self.get_unpack('n')[0] & 0x3fff
1328               if limit <= idx
1329                 raise DecodeError.new("non-backward name pointer")
1330               end
1331               save_index = @index
1332               @index = idx
1333               d += self.get_labels(limit)
1334               @index = save_index
1335               return d
1336             else
1337               d << self.get_label
1338             end
1339           end
1340           return d
1341         end
1343         def get_label
1344           return Label::Str.new(self.get_string)
1345         end
1347         def get_question
1348           name = self.get_name
1349           type, klass = self.get_unpack("nn")
1350           return name, Resource.get_class(type, klass)
1351         end
1353         def get_rr
1354           name = self.get_name
1355           type, klass, ttl = self.get_unpack('nnN')
1356           typeclass = Resource.get_class(type, klass)
1357           return name, ttl, self.get_length16 {typeclass.decode_rdata(self)}
1358         end
1359       end
1360     end
1362     class Query
1363       def encode_rdata(msg)
1364         raise EncodeError.new("#{self.class} is query.") 
1365       end
1367       def self.decode_rdata(msg)
1368         raise DecodeError.new("#{self.class} is query.") 
1369       end
1370     end
1372     class Resource < Query
1373       ClassHash = {}
1375       def encode_rdata(msg)
1376         raise NotImplementedError.new
1377       end
1379       def self.decode_rdata(msg)
1380         raise NotImplementedError.new
1381       end
1383       def ==(other)
1384         return self.class == other.class &&
1385           self.instance_variables == other.instance_variables &&
1386           self.instance_variables.collect {|name| self.instance_eval name} ==
1387             other.instance_variables.collect {|name| other.instance_eval name}
1388       end
1390       def eql?(other)
1391         return self == other
1392       end
1394       def hash
1395         h = 0
1396         self.instance_variables.each {|name|
1397           h ^= self.instance_eval("#{name}.hash")
1398         }
1399         return h
1400       end
1402       def self.get_class(type_value, class_value)
1403         return ClassHash[[type_value, class_value]] ||
1404                Generic.create(type_value, class_value)
1405       end
1407       class Generic < Resource
1408         def initialize(data)
1409           @data = data
1410         end
1411         attr_reader :data
1413         def encode_rdata(msg)
1414           msg.put_bytes(data)
1415         end
1417         def self.decode_rdata(msg)
1418           return self.new(msg.get_bytes)
1419         end
1421         def self.create(type_value, class_value)
1422           c = Class.new(Generic)
1423           c.const_set(:TypeValue, type_value)
1424           c.const_set(:ClassValue, class_value)
1425           Generic.const_set("Type#{type_value}_Class#{class_value}", c)
1426           ClassHash[[type_value, class_value]] = c
1427           return c
1428         end
1429       end
1431       class DomainName < Resource
1432         def initialize(name)
1433           @name = name
1434         end
1435         attr_reader :name
1437         def encode_rdata(msg)
1438           msg.put_name(@name)
1439         end
1441         def self.decode_rdata(msg)
1442           return self.new(msg.get_name)
1443         end
1444       end
1446       # Standard (class generic) RRs
1447       ClassValue = nil
1449       class NS < DomainName
1450         TypeValue = 2
1451       end
1453       class CNAME < DomainName
1454         TypeValue = 5
1455       end
1457       class SOA < Resource
1458         TypeValue = 6
1460         def initialize(mname, rname, serial, refresh, retry_, expire, minimum)
1461           @mname = mname
1462           @rname = rname
1463           @serial = serial
1464           @refresh = refresh
1465           @retry = retry_
1466           @expire = expire
1467           @minimum = minimum
1468         end
1469         attr_reader :mname, :rname, :serial, :refresh, :retry, :expire, :minimum
1471         def encode_rdata(msg)
1472           msg.put_name(@mname)
1473           msg.put_name(@rname)
1474           msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum)
1475         end
1477         def self.decode_rdata(msg)
1478           mname = msg.get_name
1479           rname = msg.get_name
1480           serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN')
1481           return self.new(
1482             mname, rname, serial, refresh, retry_, expire, minimum)
1483         end
1484       end
1486       class PTR < DomainName
1487         TypeValue = 12
1488       end
1490       class HINFO < Resource
1491         TypeValue = 13
1493         def initialize(cpu, os)
1494           @cpu = cpu
1495           @os = os
1496         end
1497         attr_reader :cpu, :os
1499         def encode_rdata(msg)
1500           msg.put_string(@cpu)
1501           msg.put_string(@os)
1502         end
1504         def self.decode_rdata(msg)
1505           cpu = msg.get_string
1506           os = msg.get_string
1507           return self.new(cpu, os)
1508         end
1509       end
1511       class MINFO < Resource
1512         TypeValue = 14
1514         def initialize(rmailbx, emailbx)
1515           @rmailbx = rmailbx
1516           @emailbx = emailbx
1517         end
1518         attr_reader :rmailbx, :emailbx
1520         def encode_rdata(msg)
1521           msg.put_name(@rmailbx)
1522           msg.put_name(@emailbx)
1523         end
1525         def self.decode_rdata(msg)
1526           rmailbx = msg.get_string
1527           emailbx = msg.get_string
1528           return self.new(rmailbx, emailbx)
1529         end
1530       end
1532       class MX < Resource
1533         TypeValue= 15
1535         def initialize(preference, exchange)
1536           @preference = preference
1537           @exchange = exchange
1538         end
1539         attr_reader :preference, :exchange
1541         def encode_rdata(msg)
1542           msg.put_pack('n', @preference)
1543           msg.put_name(@exchange)
1544         end
1546         def self.decode_rdata(msg)
1547           preference, = msg.get_unpack('n')
1548           exchange = msg.get_name
1549           return self.new(preference, exchange)
1550         end
1551       end
1553       class TXT < Resource
1554         TypeValue = 16
1556         def initialize(first_string, *rest_strings)
1557           @strings = [first_string, *rest_strings]
1558         end
1559         attr_reader :strings
1561         def data
1562           @strings[0]
1563         end
1565         def encode_rdata(msg)
1566           msg.put_string_list(@strings)
1567         end
1569         def self.decode_rdata(msg)
1570           strings = msg.get_string_list
1571           return self.new(*strings)
1572         end
1573       end
1575       class ANY < Query
1576         TypeValue = 255
1577       end
1579       ClassInsensitiveTypes = [
1580         NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, ANY
1581       ]
1583       # ARPA Internet specific RRs
1584       module IN
1585         ClassValue = 1
1587         ClassInsensitiveTypes.each {|s|
1588           c = Class.new(s)
1589           c.const_set(:TypeValue, s::TypeValue)
1590           c.const_set(:ClassValue, ClassValue)
1591           ClassHash[[s::TypeValue, ClassValue]] = c
1592           self.const_set(s.name.sub(/.*::/, ''), c)
1593         }
1595         class A < Resource
1596           ClassHash[[TypeValue = 1, ClassValue = ClassValue]] = self
1598           def initialize(address)
1599             @address = IPv4.create(address)
1600           end
1601           attr_reader :address
1603           def encode_rdata(msg)
1604             msg.put_bytes(@address.address)
1605           end
1607           def self.decode_rdata(msg)
1608             return self.new(IPv4.new(msg.get_bytes(4)))
1609           end
1610         end
1612         class WKS < Resource
1613           ClassHash[[TypeValue = 11, ClassValue = ClassValue]] = self
1615           def initialize(address, protocol, bitmap)
1616             @address = IPv4.create(address)
1617             @protocol = protocol
1618             @bitmap = bitmap
1619           end
1620           attr_reader :address, :protocol, :bitmap
1622           def encode_rdata(msg)
1623             msg.put_bytes(@address.address)
1624             msg.put_pack("n", @protocol)
1625             msg.put_bytes(@bitmap)
1626           end
1628           def self.decode_rdata(msg)
1629             address = IPv4.new(msg.get_bytes(4))
1630             protocol, = msg.get_unpack("n")
1631             bitmap = msg.get_bytes
1632             return self.new(address, protocol, bitmap)
1633           end
1634         end
1636         class AAAA < Resource
1637           ClassHash[[TypeValue = 28, ClassValue = ClassValue]] = self
1639           def initialize(address)
1640             @address = IPv6.create(address)
1641           end
1642           attr_reader :address
1644           def encode_rdata(msg)
1645             msg.put_bytes(@address.address)
1646           end
1648           def self.decode_rdata(msg)
1649             return self.new(IPv6.new(msg.get_bytes(16)))
1650           end
1651         end
1653         # SRV resource record defined in RFC 2782
1654         # 
1655         # These records identify the hostname and port that a service is
1656         # available at.
1657         # 
1658         # The format is:
1659         #   _Service._Proto.Name TTL Class SRV Priority Weight Port Target
1660         #
1661         # The fields specific to SRV are defined in RFC 2782 as meaning:
1662         # - +priority+ The priority of this target host.  A client MUST attempt
1663         #   to contact the target host with the lowest-numbered priority it can
1664         #   reach; target hosts with the same priority SHOULD be tried in an
1665         #   order defined by the weight field.  The range is 0-65535.  Note that
1666         #   it is not widely implemented and should be set to zero.
1667         # 
1668         # - +weight+ A server selection mechanism.  The weight field specifies
1669         #   a relative weight for entries with the same priority. Larger weights
1670         #   SHOULD be given a proportionately higher probability of being
1671         #   selected. The range of this number is 0-65535.  Domain administrators
1672         #   SHOULD use Weight 0 when there isn't any server selection to do, to
1673         #   make the RR easier to read for humans (less noisy). Note that it is
1674         #   not widely implemented and should be set to zero.
1675         #
1676         # - +port+  The port on this target host of this service.  The range is 0-
1677         #   65535.
1678         # 
1679         # - +target+ The domain name of the target host. A target of "." means
1680         #   that the service is decidedly not available at this domain.
1681         class SRV < Resource
1682           ClassHash[[TypeValue = 33, ClassValue = ClassValue]] = self
1684           # Create a SRV resource record.
1685           def initialize(priority, weight, port, target)
1686             @priority = priority.to_int
1687             @weight = weight.to_int
1688             @port = port.to_int
1689             @target = Name.create(target)
1690           end
1692           attr_reader :priority, :weight, :port, :target
1694           def encode_rdata(msg)
1695             msg.put_pack("n", @priority)
1696             msg.put_pack("n", @weight)
1697             msg.put_pack("n", @port)
1698             msg.put_name(@target)
1699           end
1701           def self.decode_rdata(msg)
1702             priority, = msg.get_unpack("n")
1703             weight,   = msg.get_unpack("n")
1704             port,     = msg.get_unpack("n")
1705             target    = msg.get_name
1706             return self.new(priority, weight, port, target)
1707           end
1708         end
1710       end
1711     end
1712   end
1714   class IPv4
1715     Regex = /\A(\d+)\.(\d+)\.(\d+)\.(\d+)\z/
1717     def self.create(arg)
1718       case arg
1719       when IPv4
1720         return arg
1721       when Regex
1722         if (0..255) === (a = $1.to_i) &&
1723            (0..255) === (b = $2.to_i) &&
1724            (0..255) === (c = $3.to_i) &&
1725            (0..255) === (d = $4.to_i)
1726           return self.new([a, b, c, d].pack("CCCC"))
1727         else
1728           raise ArgumentError.new("IPv4 address with invalid value: " + arg)
1729         end
1730       else
1731         raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}")
1732       end
1733     end
1735     def initialize(address)
1736       unless address.kind_of?(String) && address.length == 4
1737         raise ArgumentError.new('IPv4 address must be 4 bytes')
1738       end
1739       @address = address
1740     end
1741     attr_reader :address
1743     def to_s
1744       return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC"))
1745     end
1747     def inspect
1748       return "#<#{self.class} #{self.to_s}>"
1749     end
1751     def to_name
1752       return DNS::Name.create(
1753         '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse)
1754     end
1756     def ==(other)
1757       return @address == other.address
1758     end
1760     def eql?(other)
1761       return self == other
1762     end
1764     def hash
1765       return @address.hash
1766     end
1767   end
1769   class IPv6
1770     Regex_8Hex = /\A
1771       (?:[0-9A-Fa-f]{1,4}:){7}
1772          [0-9A-Fa-f]{1,4}
1773       \z/x
1775     Regex_CompressedHex = /\A
1776       ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
1777       ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
1778       \z/x
1780     Regex_6Hex4Dec = /\A
1781       ((?:[0-9A-Fa-f]{1,4}:){6,6})
1782       (\d+)\.(\d+)\.(\d+)\.(\d+)
1783       \z/x
1785     Regex_CompressedHex4Dec = /\A
1786       ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
1787       ((?:[0-9A-Fa-f]{1,4}:)*)
1788       (\d+)\.(\d+)\.(\d+)\.(\d+)
1789       \z/x
1791     Regex = /
1792       (?:#{Regex_8Hex}) |
1793       (?:#{Regex_CompressedHex}) |
1794       (?:#{Regex_6Hex4Dec}) |
1795       (?:#{Regex_CompressedHex4Dec})/x
1797     def self.create(arg)
1798       case arg
1799       when IPv6
1800         return arg
1801       when String
1802         address = ''
1803         if Regex_8Hex =~ arg
1804           arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
1805         elsif Regex_CompressedHex =~ arg
1806           prefix = $1
1807           suffix = $2
1808           a1 = ''
1809           a2 = ''
1810           prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
1811           suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
1812           omitlen = 16 - a1.length - a2.length
1813           address << a1 << "\0" * omitlen << a2
1814         elsif Regex_6Hex4Dec =~ arg
1815           prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i
1816           if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
1817             prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
1818             address << [a, b, c, d].pack('CCCC')
1819           else
1820             raise ArgumentError.new("not numeric IPv6 address: " + arg)
1821           end
1822         elsif Regex_CompressedHex4Dec =~ arg
1823           prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i
1824           if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
1825             a1 = ''
1826             a2 = ''
1827             prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
1828             suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
1829             omitlen = 12 - a1.length - a2.length
1830             address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC')
1831           else
1832             raise ArgumentError.new("not numeric IPv6 address: " + arg)
1833           end
1834         else
1835           raise ArgumentError.new("not numeric IPv6 address: " + arg)
1836         end
1837         return IPv6.new(address)
1838       else
1839         raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}")
1840       end
1841     end
1843     def initialize(address)
1844       unless address.kind_of?(String) && address.length == 16
1845         raise ArgumentError.new('IPv6 address must be 16 bytes')
1846       end
1847       @address = address
1848     end
1849     attr_reader :address
1851     def to_s
1852       address = sprintf("%X:%X:%X:%X:%X:%X:%X:%X", *@address.unpack("nnnnnnnn"))
1853       unless address.sub!(/(^|:)0(:0)+(:|$)/, '::')
1854         address.sub!(/(^|:)0(:|$)/, '::')
1855       end
1856       return address
1857     end
1859     def inspect
1860       return "#<#{self.class} #{self.to_s}>"
1861     end
1863     def to_name
1864       # ip6.arpa should be searched too. [RFC3152]
1865       return DNS::Name.new(
1866         @address.unpack("H32")[0].split(//).reverse + ['ip6', 'int'])
1867     end
1869     def ==(other)
1870       return @address == other.address
1871     end
1873     def eql?(other)
1874       return self == other
1875     end
1877     def hash
1878       return @address.hash
1879     end
1880   end
1882   DefaultResolver = self.new
1883   AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/