source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/resolv.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

File size: 48.9 KB
Line 
1=begin
2= resolv library
3resolv.rb is a resolver library written in Ruby.
4Since it is written in Ruby, it is thread-aware.
5I.e. it can resolv many hostnames concurrently.
6
7It is possible to lookup various resources of DNS using DNS module directly.
8
9== example
10 p Resolv.getaddress("www.ruby-lang.org")
11 p Resolv.getname("210.251.121.214")
12
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 }
17
18== Resolv class
19
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.
26
27 getaddress returns first entry of lookupped addresses.
28 getaddresses returns lookupped addresses as an array.
29 each_address iterates over lookupped addresses.
30
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.
35
36 getname returns first entry of lookupped names.
37 getnames returns lookupped names as an array.
38 each_names iterates over lookupped names.
39
40== Resolv::Hosts class
41hostname resolver using /etc/hosts format.
42
43=== class methods
44--- Resolv::Hosts.new(hosts='/etc/hosts')
45
46=== methods
47--- Resolv::Hosts#getaddress(name)
48--- Resolv::Hosts#getaddresses(name)
49--- Resolv::Hosts#each_address(name) {|address| ...}
50 address lookup methods.
51
52--- Resolv::Hosts#getname(address)
53--- Resolv::Hosts#getnames(address)
54--- Resolv::Hosts#each_name(address) {|name| ...}
55 hostnames lookup methods.
56
57== Resolv::DNS class
58DNS stub resolver.
59
60=== class methods
61--- Resolv::DNS.new(config_info=nil)
62
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.
67
68 Resolv::DNS.new({:nameserver=>["210.251.121.21"], :search=>["ruby-lang.org"], :ndots=>1})
69
70--- Resolv::DNS.open(config_info=nil)
71--- Resolv::DNS.open(config_info=nil) {|dns| ...}
72
73=== methods
74--- Resolv::DNS#close
75
76--- Resolv::DNS#getaddress(name)
77--- Resolv::DNS#getaddresses(name)
78--- Resolv::DNS#each_address(name) {|address| ...}
79 address lookup methods.
80
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.
83
84--- Resolv::DNS#getname(address)
85--- Resolv::DNS#getnames(address)
86--- Resolv::DNS#each_name(address) {|name| ...}
87 hostnames lookup methods.
88
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.
91
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.
97
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
112
113 Lookupped resource is represented as an instance of (a subclass of)
114 Resolv::DNS::Resource.
115 (Resolv::DNS::Resource::IN::A, etc.)
116
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
150
151== Resolv::DNS::Name class
152
153=== class methods
154--- Resolv::DNS::Name.create(name)
155
156=== methods
157--- Resolv::DNS::Name#to_s
158
159== Resolv::DNS::Resource class
160
161== Resolv::IPv4 class
162=== class methods
163--- Resolv::IPv4.create(address)
164
165=== methods
166--- Resolv::IPv4#to_s
167--- Resolv::IPv4#to_name
168
169=== constants
170--- Resolv::IPv4::Regex
171 regular expression for IPv4 address.
172
173== Resolv::IPv6 class
174=== class methods
175--- Resolv::IPv6.create(address)
176
177=== methods
178--- Resolv::IPv6#to_s
179--- Resolv::IPv6#to_name
180
181=== constants
182--- Resolv::IPv6::Regex
183 regular expression for IPv6 address.
184
185== Bugs
186* NIS is not supported.
187* /etc/nsswitch.conf is not supported.
188* IPv6 is not supported.
189
190=end
191
192require 'socket'
193require 'fcntl'
194require 'timeout'
195require 'thread'
196
197class Resolv
198 def self.getaddress(name)
199 DefaultResolver.getaddress(name)
200 end
201
202 def self.getaddresses(name)
203 DefaultResolver.getaddresses(name)
204 end
205
206 def self.each_address(name, &block)
207 DefaultResolver.each_address(name, &block)
208 end
209
210 def self.getname(address)
211 DefaultResolver.getname(address)
212 end
213
214 def self.getnames(address)
215 DefaultResolver.getnames(address)
216 end
217
218 def self.each_name(address, &proc)
219 DefaultResolver.each_name(address, &proc)
220 end
221
222 def initialize(resolvers=[Hosts.new, DNS.new])
223 @resolvers = resolvers
224 end
225
226 def getaddress(name)
227 each_address(name) {|address| return address}
228 raise ResolvError.new("no address for #{name}")
229 end
230
231 def getaddresses(name)
232 ret = []
233 each_address(name) {|address| ret << address}
234 return ret
235 end
236
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
251
252 def getname(address)
253 each_name(address) {|name| return name}
254 raise ResolvError.new("no name for #{address}")
255 end
256
257 def getnames(address)
258 ret = []
259 each_name(address) {|name| ret << name}
260 return ret
261 end
262
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
273
274 class ResolvError < StandardError
275 end
276
277 class ResolvTimeout < TimeoutError
278 end
279
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
287
288 def initialize(filename = DefaultFileName)
289 @filename = filename
290 @mutex = Mutex.new
291 @initialized = nil
292 end
293
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
324
325 def getaddress(name)
326 each_address(name) {|address| return address}
327 raise ResolvError.new("#{@filename} has no name: #{name}")
328 end
329
330 def getaddresses(name)
331 ret = []
332 each_address(name) {|address| ret << address}
333 return ret
334 end
335
336 def each_address(name, &proc)
337 lazy_initialize
338 if @name2addr.include?(name)
339 @name2addr[name].each(&proc)
340 end
341 end
342
343 def getname(address)
344 each_name(address) {|name| return name}
345 raise ResolvError.new("#{@filename} has no address: #{address}")
346 end
347
348 def getnames(address)
349 ret = []
350 each_name(address) {|name| ret << name}
351 return ret
352 end
353
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
361
362 class DNS
363 # STD0013 (RFC 1035, etc.)
364 # ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters
365
366 Port = 53
367 UDPSize = 512
368
369 DNSThreadGroup = ThreadGroup.new
370
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
380
381 def initialize(config_info=nil)
382 @mutex = Mutex.new
383 @config = Config.new(config_info)
384 @initialized = nil
385 end
386
387 def lazy_initialize
388 @mutex.synchronize {
389 unless @initialized
390 @config.lazy_initialize
391
392 if nameserver = @config.single?
393 @requester = Requester::ConnectedUDP.new(nameserver)
394 else
395 @requester = Requester::UnconnectedUDP.new
396 end
397
398 @initialized = true
399 end
400 }
401 self
402 end
403
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
413
414 def getaddress(name)
415 each_address(name) {|address| return address}
416 raise ResolvError.new("DNS result has no information for #{name}")
417 end
418
419 def getaddresses(name)
420 ret = []
421 each_address(name) {|address| ret << address}
422 return ret
423 end
424
425 def each_address(name)
426 each_resource(name, Resource::IN::A) {|resource| yield resource.address}
427 end
428
429 def getname(address)
430 each_name(address) {|name| return name}
431 raise ResolvError.new("DNS result has no information for #{address}")
432 end
433
434 def getnames(address)
435 ret = []
436 each_name(address) {|name| ret << name}
437 return ret
438 end
439
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
453
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
458
459 def getresources(name, typeclass)
460 ret = []
461 each_resource(name, typeclass) {|resource| ret << resource}
462 return ret
463 end
464
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
495
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
526
527 class Requester
528 def initialize
529 @senders = {}
530 end
531
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
543
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
554
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
563
564 def recv(msg)
565 @queue.push([msg, @data])
566 end
567 end
568
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
594
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
605
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
612
613 def send
614 @sock.send(@msg, 0, @host, @port)
615 end
616 end
617 end
618
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
646
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
656
657 class Sender < Requester::Sender
658 def send
659 @sock.send(@msg, 0)
660 end
661 end
662 end
663
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
693
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
703
704 class Sender < Requester::Sender
705 def send
706 @sock.print(@msg)
707 @sock.flush
708 end
709 end
710 end
711
712 class RequestError < StandardError
713 end
714 end
715
716 class Config
717 def initialize(config_info=nil)
718 @mutex = Mutex.new
719 @config_info = config_info
720 @initialized = nil
721 end
722
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
756
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
770
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
796
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
808
809 if [email protected]_of?(Array) ||
810 [email protected]? {|ns| String === ns }
811 raise ArgumentError.new("invalid nameserver config: #{@nameserver.inspect}")
812 end
813
814 if [email protected]_of?(Array) ||
815 [email protected]? {|ls| ls.all? {|l| Label::Str === l } }
816 raise ArgumentError.new("invalid search config: #{@search.inspect}")
817 end
818
819 if [email protected]_of?(Integer)
820 raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}")
821 end
822
823 @initialized = true
824 end
825 }
826 self
827 end
828
829 def single?
830 lazy_initialize
831 if @nameserver.length == 1
832 return @nameserver[0]
833 else
834 return nil
835 end
836 end
837
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
853
854 InitialTimeout = 5
855
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
863
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
885
886 class NXDomain < ResolvError
887 end
888
889 class OtherResolvError < ResolvError
890 end
891 end
892
893 module OpCode
894 Query = 0
895 IQuery = 1
896 Status = 2
897 Notify = 4
898 Update = 5
899 end
900
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
921
922 class DecodeError < StandardError
923 end
924
925 class EncodeError < StandardError
926 end
927
928 module Label
929 def self.split(arg)
930 labels = []
931 arg.scan(/[^\.]+/) {labels << Str.new($&)}
932 return labels
933 end
934
935 class Str
936 def initialize(string)
937 @string = string
938 @downcase = string.downcase
939 end
940 attr_reader :string, :downcase
941
942 def to_s
943 return @string
944 end
945
946 def inspect
947 return "#<#{self.class} #{self.to_s}>"
948 end
949
950 def ==(other)
951 return @downcase == other.downcase
952 end
953
954 def eql?(other)
955 return self == other
956 end
957
958 def hash
959 return @downcase.hash
960 end
961 end
962 end
963
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
975
976 def initialize(labels, absolute=true)
977 @labels = labels
978 @absolute = absolute
979 end
980
981 def inspect
982 "#<#{self.class}: #{self.to_s}#{@absolute ? '.' : ''}>"
983 end
984
985 def absolute?
986 return @absolute
987 end
988
989 def ==(other)
990 return false unless Name === other
991 return @labels == other.to_a && @absolute == other.absolute?
992 end
993 alias eql? ==
994
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
1012
1013 def hash
1014 return @labels.hash ^ @absolute.hash
1015 end
1016
1017 def to_a
1018 return @labels
1019 end
1020
1021 def length
1022 return @labels.length
1023 end
1024
1025 def [](i)
1026 return @labels[i]
1027 end
1028
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
1041
1042 class Message
1043 @@identifier = -1
1044
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
1059
1060 attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode
1061 attr_reader :question, :answer, :authority, :additional
1062
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
1077
1078 def add_question(name, typeclass)
1079 @question << [Name.create(name), typeclass]
1080 end
1081
1082 def each_question
1083 @question.each {|name, typeclass|
1084 yield name, typeclass
1085 }
1086 end
1087
1088 def add_answer(name, ttl, data)
1089 @answer << [Name.create(name), ttl, data]
1090 end
1091
1092 def each_answer
1093 @answer.each {|name, ttl, data|
1094 yield name, ttl, data
1095 }
1096 end
1097
1098 def add_authority(name, ttl, data)
1099 @authority << [Name.create(name), ttl, data]
1100 end
1101
1102 def each_authority
1103 @authority.each {|name, ttl, data|
1104 yield name, ttl, data
1105 }
1106 end
1107
1108 def add_additional(name, ttl, data)
1109 @additional << [Name.create(name), ttl, data]
1110 end
1111
1112 def each_additional
1113 @additional.each {|name, ttl, data|
1114 yield name, ttl, data
1115 }
1116 end
1117
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
1123
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
1154
1155 class MessageEncoder
1156 def initialize
1157 @data = ''
1158 @names = {}
1159 yield self
1160 end
1161
1162 def to_s
1163 return @data
1164 end
1165
1166 def put_bytes(d)
1167 @data << d
1168 end
1169
1170 def put_pack(template, *d)
1171 @data << d.pack(template)
1172 end
1173
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
1182
1183 def put_string(d)
1184 self.put_pack("C", d.length)
1185 @data << d
1186 end
1187
1188 def put_string_list(ds)
1189 ds.each {|d|
1190 self.put_string(d)
1191 }
1192 end
1193
1194 def put_name(d)
1195 put_labels(d.to_a)
1196 end
1197
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
1211
1212 def put_label(d)
1213 self.put_string(d.string)
1214 end
1215 end
1216
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
1249
1250 class MessageDecoder
1251 def initialize(data)
1252 @data = data
1253 @index = 0
1254 @limit = data.length
1255 yield self
1256 end
1257
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
1271
1272 def get_bytes(len = @limit - @index)
1273 d = @data[@index, len]
1274 @index += len
1275 return d
1276 end
1277
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
1297
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
1305
1306 def get_string_list
1307 strings = []
1308 while @index < @limit
1309 strings << self.get_string
1310 end
1311 strings
1312 end
1313
1314 def get_name
1315 return Name.new(self.get_labels)
1316 end
1317
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
1342
1343 def get_label
1344 return Label::Str.new(self.get_string)
1345 end
1346
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
1352
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
1361
1362 class Query
1363 def encode_rdata(msg)
1364 raise EncodeError.new("#{self.class} is query.")
1365 end
1366
1367 def self.decode_rdata(msg)
1368 raise DecodeError.new("#{self.class} is query.")
1369 end
1370 end
1371
1372 class Resource < Query
1373 ClassHash = {}
1374
1375 def encode_rdata(msg)
1376 raise NotImplementedError.new
1377 end
1378
1379 def self.decode_rdata(msg)
1380 raise NotImplementedError.new
1381 end
1382
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
1389
1390 def eql?(other)
1391 return self == other
1392 end
1393
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
1401
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
1406
1407 class Generic < Resource
1408 def initialize(data)
1409 @data = data
1410 end
1411 attr_reader :data
1412
1413 def encode_rdata(msg)
1414 msg.put_bytes(data)
1415 end
1416
1417 def self.decode_rdata(msg)
1418 return self.new(msg.get_bytes)
1419 end
1420
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
1430
1431 class DomainName < Resource
1432 def initialize(name)
1433 @name = name
1434 end
1435 attr_reader :name
1436
1437 def encode_rdata(msg)
1438 msg.put_name(@name)
1439 end
1440
1441 def self.decode_rdata(msg)
1442 return self.new(msg.get_name)
1443 end
1444 end
1445
1446 # Standard (class generic) RRs
1447 ClassValue = nil
1448
1449 class NS < DomainName
1450 TypeValue = 2
1451 end
1452
1453 class CNAME < DomainName
1454 TypeValue = 5
1455 end
1456
1457 class SOA < Resource
1458 TypeValue = 6
1459
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
1470
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
1476
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
1485
1486 class PTR < DomainName
1487 TypeValue = 12
1488 end
1489
1490 class HINFO < Resource
1491 TypeValue = 13
1492
1493 def initialize(cpu, os)
1494 @cpu = cpu
1495 @os = os
1496 end
1497 attr_reader :cpu, :os
1498
1499 def encode_rdata(msg)
1500 msg.put_string(@cpu)
1501 msg.put_string(@os)
1502 end
1503
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
1510
1511 class MINFO < Resource
1512 TypeValue = 14
1513
1514 def initialize(rmailbx, emailbx)
1515 @rmailbx = rmailbx
1516 @emailbx = emailbx
1517 end
1518 attr_reader :rmailbx, :emailbx
1519
1520 def encode_rdata(msg)
1521 msg.put_name(@rmailbx)
1522 msg.put_name(@emailbx)
1523 end
1524
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
1531
1532 class MX < Resource
1533 TypeValue= 15
1534
1535 def initialize(preference, exchange)
1536 @preference = preference
1537 @exchange = exchange
1538 end
1539 attr_reader :preference, :exchange
1540
1541 def encode_rdata(msg)
1542 msg.put_pack('n', @preference)
1543 msg.put_name(@exchange)
1544 end
1545
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
1552
1553 class TXT < Resource
1554 TypeValue = 16
1555
1556 def initialize(first_string, *rest_strings)
1557 @strings = [first_string, *rest_strings]
1558 end
1559 attr_reader :strings
1560
1561 def data
1562 @strings[0]
1563 end
1564
1565 def encode_rdata(msg)
1566 msg.put_string_list(@strings)
1567 end
1568
1569 def self.decode_rdata(msg)
1570 strings = msg.get_string_list
1571 return self.new(*strings)
1572 end
1573 end
1574
1575 class ANY < Query
1576 TypeValue = 255
1577 end
1578
1579 ClassInsensitiveTypes = [
1580 NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, ANY
1581 ]
1582
1583 # ARPA Internet specific RRs
1584 module IN
1585 ClassValue = 1
1586
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 }
1594
1595 class A < Resource
1596 ClassHash[[TypeValue = 1, ClassValue = ClassValue]] = self
1597
1598 def initialize(address)
1599 @address = IPv4.create(address)
1600 end
1601 attr_reader :address
1602
1603 def encode_rdata(msg)
1604 msg.put_bytes(@address.address)
1605 end
1606
1607 def self.decode_rdata(msg)
1608 return self.new(IPv4.new(msg.get_bytes(4)))
1609 end
1610 end
1611
1612 class WKS < Resource
1613 ClassHash[[TypeValue = 11, ClassValue = ClassValue]] = self
1614
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
1621
1622 def encode_rdata(msg)
1623 msg.put_bytes(@address.address)
1624 msg.put_pack("n", @protocol)
1625 msg.put_bytes(@bitmap)
1626 end
1627
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
1635
1636 class AAAA < Resource
1637 ClassHash[[TypeValue = 28, ClassValue = ClassValue]] = self
1638
1639 def initialize(address)
1640 @address = IPv6.create(address)
1641 end
1642 attr_reader :address
1643
1644 def encode_rdata(msg)
1645 msg.put_bytes(@address.address)
1646 end
1647
1648 def self.decode_rdata(msg)
1649 return self.new(IPv6.new(msg.get_bytes(16)))
1650 end
1651 end
1652
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
1683
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
1691
1692 attr_reader :priority, :weight, :port, :target
1693
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
1700
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
1709
1710 end
1711 end
1712 end
1713
1714 class IPv4
1715 Regex = /\A(\d+)\.(\d+)\.(\d+)\.(\d+)\z/
1716
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
1734
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
1742
1743 def to_s
1744 return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC"))
1745 end
1746
1747 def inspect
1748 return "#<#{self.class} #{self.to_s}>"
1749 end
1750
1751 def to_name
1752 return DNS::Name.create(
1753 '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse)
1754 end
1755
1756 def ==(other)
1757 return @address == other.address
1758 end
1759
1760 def eql?(other)
1761 return self == other
1762 end
1763
1764 def hash
1765 return @address.hash
1766 end
1767 end
1768
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
1774
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
1779
1780 Regex_6Hex4Dec = /\A
1781 ((?:[0-9A-Fa-f]{1,4}:){6,6})
1782 (\d+)\.(\d+)\.(\d+)\.(\d+)
1783 \z/x
1784
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
1790
1791 Regex = /
1792 (?:#{Regex_8Hex}) |
1793 (?:#{Regex_CompressedHex}) |
1794 (?:#{Regex_6Hex4Dec}) |
1795 (?:#{Regex_CompressedHex4Dec})/x
1796
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
1842
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
1850
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
1858
1859 def inspect
1860 return "#<#{self.class} #{self.to_s}>"
1861 end
1862
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
1868
1869 def ==(other)
1870 return @address == other.address
1871 end
1872
1873 def eql?(other)
1874 return self == other
1875 end
1876
1877 def hash
1878 return @address.hash
1879 end
1880 end
1881
1882 DefaultResolver = self.new
1883 AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/
1884end
Note: See TracBrowser for help on using the repository browser.