lib/resolv.rb
DEFINITIONS
This source file includes following functions.
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.
6
7 It is possible to lookup various resources of DNS using DNS module directly.
8
9 == example
10 Resolv.getaddress("www.ruby-lang.org")
11 Resolv.getname("210.251.121.214")
12
13 dns = Resolv::DNS.new
14 dns.getresources("www.ruby-lang.org", Resolv::DNS::Resource::IN::A).collect {|r| r.address}
15 dns.getresources("ruby-lang.org", Resolv::DNS::Resource::IN::MX).collect {|r| [r.exchange.to_s, r.preference]}
16
17 == Resolv class
18
19 === class methods
20 --- Resolv.getaddress(name)
21 --- Resolv.getaddresses(name)
22 --- Resolv.each_address(name) {|address| ...}
23 They lookups IP addresses of ((|name|)) which represents a hostname
24 as a string by default resolver.
25
26 getaddress returns first entry of lookupped addresses.
27 getaddresses returns lookupped addresses as an array.
28 each_address iterates over lookupped addresses.
29
30 --- Resolv.getname(address)
31 --- Resolv.getnames(address)
32 --- Resolv.each_name(address) {|name| ...}
33 lookups hostnames of ((|address|)) which represents IP address as a string.
34
35 getname returns first entry of lookupped names.
36 getnames returns lookupped names as an array.
37 each_names iterates over lookupped names.
38
39 == Resolv::Hosts class
40 hostname resolver using /etc/hosts format.
41
42 === class methods
43 --- Resolv::Hosts.new(hosts='/etc/hosts')
44
45 === methods
46 --- Resolv::Hosts#getaddress(name)
47 --- Resolv::Hosts#getaddresses(name)
48 --- Resolv::Hosts#each_address(name) {|address| ...}
49 address lookup methods.
50
51 --- Resolv::Hosts#getname(address)
52 --- Resolv::Hosts#getnames(address)
53 --- Resolv::Hosts#each_name(address) {|name| ...}
54 hostnames lookup methods.
55
56 == Resolv::DNS class
57 DNS stub resolver.
58
59 === class methods
60 --- Resolv::DNS.new(resolv_conf='/etc/resolv.conf')
61
62 --- Resolv::DNS.open(resolv_conf='/etc/resolv.conf')
63 --- Resolv::DNS.open(resolv_conf='/etc/resolv.conf') {|dns| ...}
64
65 === methods
66 --- Resolv::DNS#close
67
68 --- Resolv::DNS#getaddress(name)
69 --- Resolv::DNS#getaddresses(name)
70 --- Resolv::DNS#each_address(name) {|address| ...}
71 address lookup methods.
72
73 ((|name|)) must be a instance of Resolv::DNS::Name or String. Lookupped
74 address is represented as an instance of Resolv::IPv4 or Resolv::IPv6.
75
76 --- Resolv::DNS#getname(address)
77 --- Resolv::DNS#getnames(address)
78 --- Resolv::DNS#each_name(address) {|name| ...}
79 hostnames lookup methods.
80
81 ((|address|)) must be a instance of Resolv::IPv4, Resolv::IPv6 or String.
82 Lookupped name is represented as an instance of Resolv::DNS::Name.
83
84 --- Resolv::DNS#getresource(name, typeclass)
85 --- Resolv::DNS#getresources(name, typeclass)
86 --- Resolv::DNS#each_resource(name, typeclass) {|resource| ...}
87 They lookup DNS resources of ((|name|)).
88 ((|name|)) must be a instance of Resolv::Name or String.
89
90 ((|typeclass|)) should be one of follows:
91 * Resolv::DNS::Resource::IN::ANY
92 * Resolv::DNS::Resource::IN::NS
93 * Resolv::DNS::Resource::IN::CNAME
94 * Resolv::DNS::Resource::IN::SOA
95 * Resolv::DNS::Resource::IN::HINFO
96 * Resolv::DNS::Resource::IN::MINFO
97 * Resolv::DNS::Resource::IN::MX
98 * Resolv::DNS::Resource::IN::TXT
99 * Resolv::DNS::Resource::IN::ANY
100 * Resolv::DNS::Resource::IN::A
101 * Resolv::DNS::Resource::IN::WKS
102 * Resolv::DNS::Resource::IN::PTR
103 * Resolv::DNS::Resource::IN::AAAA
104
105 Lookupped resource is represented as an instance of (a subclass of)
106 Resolv::DNS::Resource.
107 (Resolv::DNS::Resource::IN::A, etc.)
108
109 == Resolv::DNS::Resource::IN::NS class
110 --- name
111 == Resolv::DNS::Resource::IN::CNAME class
112 --- name
113 == Resolv::DNS::Resource::IN::SOA class
114 --- mname
115 --- rname
116 --- serial
117 --- refresh
118 --- retry
119 --- expire
120 --- minimum
121 == Resolv::DNS::Resource::IN::HINFO class
122 --- cpu
123 --- os
124 == Resolv::DNS::Resource::IN::MINFO class
125 --- rmailbx
126 --- emailbx
127 == Resolv::DNS::Resource::IN::MX class
128 --- preference
129 --- exchange
130 == Resolv::DNS::Resource::IN::TXT class
131 --- data
132 == Resolv::DNS::Resource::IN::A class
133 --- address
134 == Resolv::DNS::Resource::IN::WKS class
135 --- address
136 --- protocol
137 --- bitmap
138 == Resolv::DNS::Resource::IN::PTR class
139 --- name
140 == Resolv::DNS::Resource::IN::AAAA class
141 --- address
142
143 == Resolv::DNS::Name class
144
145 === class methods
146 --- Resolv::DNS::Name.create(name)
147
148 === methods
149 --- Resolv::DNS::Name#to_s
150
151 == Resolv::DNS::Resource class
152
153 == Resolv::IPv4 class
154 === class methods
155 --- Resolv::IPv4.create(address)
156
157 === methods
158 --- Resolv::IPv4#to_s
159 --- Resolv::IPv4#to_name
160
161 === constants
162 --- Resolv::IPv4::Regex
163 regular expression for IPv4 address.
164
165 == Resolv::IPv6 class
166 === class methods
167 --- Resolv::IPv6.create(address)
168
169 === methods
170 --- Resolv::IPv6#to_s
171 --- Resolv::IPv6#to_name
172
173 === constants
174 --- Resolv::IPv6::Regex
175 regular expression for IPv6 address.
176
177 == Bugs
178 * NIS is not supported.
179 * /etc/nsswitch.conf is not supported.
180 * IPv6 is not supported.
181
182 =end
183
184 require 'socket'
185 require 'fcntl'
186 require 'timeout'
187 require 'thread'
188
189 class Resolv
190 def self.getaddress(name)
191 DefaultResolver.getaddress(name)
192 end
193
194 def self.getaddresses(name)
195 DefaultResolver.getaddresses(name)
196 end
197
198 def self.each_address(name, &block)
199 DefaultResolver.each_address(name, &block)
200 end
201
202 def self.getname(address)
203 DefaultResolver.getname(address)
204 end
205
206 def self.getnames(address)
207 DefaultResolver.getnames(address)
208 end
209
210 def self.each_name(address, &proc)
211 DefaultResolver.each_name(address, &proc)
212 end
213
214 def initialize(resolvers=[Hosts.new, DNS.new])
215 @resolvers = resolvers
216 end
217
218 def getaddress(name)
219 each_address(name) {|address| return address}
220 raise ResolvError.new("no address for #{name}")
221 end
222
223 def getaddresses(name)
224 ret = []
225 each_address(name) {|address| ret << address}
226 return ret
227 end
228
229 def each_address(name)
230 if AddressRegex =~ name
231 yield name
232 return
233 end
234 yielded = false
235 @resolvers.each {|r|
236 r.each_address(name) {|address|
237 yield address.to_s
238 yielded = true
239 }
240 return if yielded
241 }
242 end
243
244 def getname(address)
245 each_name(address) {|name| return name}
246 raise ResolvError.new("no name for #{address}")
247 end
248
249 def getnames(address)
250 ret = []
251 each_name(address) {|name| ret << name}
252 return ret
253 end
254
255 def each_name(address)
256 yielded = false
257 @resolvers.each {|r|
258 r.each_name(address) {|name|
259 yield name.to_s
260 yielded = true
261 }
262 return if yielded
263 }
264 end
265
266 class ResolvError < StandardError
267 end
268
269 class ResolvTimeout < TimeoutError
270 end
271
272 class Hosts
273 DefaultFileName = '/etc/hosts'
274
275 def initialize(filename = DefaultFileName)
276 @filename = filename
277 @mutex = Mutex.new
278 @initialized = nil
279 end
280
281 def lazy_initialize
282 @mutex.synchronize {
283 unless @initialized
284 @name2addr = {}
285 @addr2name = {}
286 open(@filename) {|f|
287 f.each {|line|
288 line.sub!(/#.*/, '')
289 addr, hostname, *aliases = line.split(/\s+/)
290 next unless addr
291 addr.untaint
292 hostname.untaint
293 @addr2name[addr] = [] unless @addr2name.include? addr
294 @addr2name[addr] << hostname
295 @addr2name[addr] += aliases
296 @name2addr[hostname] = [] unless @name2addr.include? hostname
297 @name2addr[hostname] << addr
298 aliases.each {|n|
299 n.untaint
300 @name2addr[n] = [] unless @name2addr.include? n
301 @name2addr[n] << addr
302 }
303 }
304 }
305 @name2addr.each {|name, arr| arr.reverse!}
306 @initialized = true
307 end
308 }
309 end
310
311 def getaddress(name)
312 each_address(name) {|address| return address}
313 raise ResolvError.new("#{@filename} has no name: #{name}")
314 end
315
316 def getaddresses(name)
317 ret = []
318 each_address(name) {|address| ret << address}
319 return ret
320 end
321
322 def each_address(name, &proc)
323 lazy_initialize
324 if @name2addr.include?(name)
325 @name2addr[name].each(&proc)
326 end
327 end
328
329 def getname(address)
330 each_name(address) {|name| return name}
331 raise ResolvError.new("#{@filename} has no address: #{address}")
332 end
333
334 def getnames(address)
335 ret = []
336 each_name(address) {|name| ret << name}
337 return ret
338 end
339
340 def each_name(address, &proc)
341 lazy_initialize
342 if @addr2name.include?(address)
343 @addr2name[address].each(&proc)
344 end
345 end
346 end
347
348 class DNS
349 # STD0013 (RFC 1035, etc.)
350 # ftp:
351
352 Port = 53
353 UDPSize = 512
354
355 DNSThreadGroup = ThreadGroup.new
356
357 def self.open(*args)
358 dns = new(*args)
359 return dns unless block_given?
360 begin
361 yield dns
362 ensure
363 dns.close
364 end
365 end
366
367 def initialize(config="/etc/resolv.conf")
368 @mutex = Mutex.new
369 @config = Config.new(config)
370 @initialized = nil
371 end
372
373 def lazy_initialize
374 @mutex.synchronize {
375 unless @initialized
376 @config.lazy_initialize
377
378 if nameserver = @config.single?
379 @requester = Requester::ConnectedUDP.new(nameserver)
380 else
381 @requester = Requester::UnconnectedUDP.new
382 end
383
384 @initialized = true
385 end
386 }
387 end
388
389 def close
390 @mutex.synchronize {
391 if @initialized
392 @requester.close if @requester
393 @requester = nil
394 @initialized = false
395 end
396 }
397 end
398
399 def getaddress(name)
400 each_address(name) {|address| return address}
401 raise ResolvError.new("DNS result has no information for #{name}")
402 end
403
404 def getaddresses(name)
405 ret = []
406 each_address(name) {|address| ret << address}
407 return ret
408 end
409
410 def each_address(name)
411 each_resource(name, Resource::IN::A) {|resource| yield resource.address}
412 end
413
414 def getname(address)
415 each_name(address) {|name| return name}
416 raise ResolvError.new("DNS result has no information for #{address}")
417 end
418
419 def getnames(address)
420 ret = []
421 each_name(address) {|name| ret << name}
422 return ret
423 end
424
425 def each_name(address)
426 case address
427 when Name
428 ptr = address
429 when IPv4::Regex
430 ptr = IPv4.create(address).to_name
431 when IPv6::Regex
432 ptr = IPv6.create(address).to_name
433 else
434 raise ResolvError.new("cannot interpret as address: #{address}")
435 end
436 each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name}
437 end
438
439 def getresource(name, typeclass)
440 each_resource(name, typeclass) {|resource| return resource}
441 raise ResolvError.new("DNS result has no information for #{name}")
442 end
443
444 def getresources(name, typeclass)
445 ret = []
446 each_resource(name, typeclass) {|resource| ret << resource}
447 return ret
448 end
449
450 def each_resource(name, typeclass, &proc)
451 lazy_initialize
452 q = Queue.new
453 senders = {}
454 begin
455 @config.resolv(name) {|candidate, tout, nameserver|
456 msg = Message.new
457 msg.rd = 1
458 msg.add_question(candidate, typeclass)
459 unless sender = senders[[candidate, nameserver]]
460 sender = senders[[candidate, nameserver]] =
461 @requester.sender(msg, candidate, q, nameserver)
462 end
463 sender.send
464 reply = reply_name = nil
465 timeout(tout, ResolvTimeout) { reply, reply_name = q.pop }
466 case reply.rcode
467 when RCode::NoError
468 extract_resources(reply, reply_name, typeclass, &proc)
469 return
470 when RCode::NXDomain
471 raise Config::NXDomain.new(reply_name.to_s)
472 else
473 raise Config::OtherResolvError.new(reply_name.to_s)
474 end
475 }
476 ensure
477 @requester.delete(q)
478 end
479 end
480
481 def extract_resources(msg, name, typeclass)
482 if typeclass < Resource::ANY
483 n0 = Name.create(name)
484 msg.each_answer {|n, ttl, data|
485 yield data if n0 == n
486 }
487 end
488 yielded = false
489 n0 = Name.create(name)
490 msg.each_answer {|n, ttl, data|
491 if n0 == n
492 case data
493 when typeclass
494 yield data
495 yielded = true
496 when Resource::CNAME
497 n0 = data.name
498 end
499 end
500 }
501 return if yielded
502 msg.each_answer {|n, ttl, data|
503 if n0 == n
504 case data
505 when typeclass
506 yield data
507 end
508 end
509 }
510 end
511
512 class Requester
513 def initialize
514 @senders = {}
515 end
516
517 def close
518 thread, sock, @thread, @sock = @thread, @sock
519 begin
520 if thread
521 thread.kill
522 thread.join
523 end
524 ensure
525 sock.close if sock
526 end
527 end
528
529 def delete(arg)
530 case arg
531 when Sender
532 @senders.delete_if {|k, s| s == arg }
533 when Queue
534 @senders.delete_if {|k, s| s.queue == arg }
535 else
536 raise ArgumentError.new("neither Sender or Queue: #{arg}")
537 end
538 end
539
540 class Sender
541 def initialize(msg, data, sock, queue)
542 @msg = msg
543 @data = data
544 @sock = sock
545 @queue = queue
546 end
547 attr_reader :queue
548
549 def recv(msg)
550 @queue.push([msg, @data])
551 end
552 end
553
554 class UnconnectedUDP < Requester
555 def initialize
556 super()
557 @sock = UDPSocket.new
558 @sock.fcntl(Fcntl::F_SETFD, 1)
559 @id = {}
560 @id.default = -1
561 @thread = Thread.new {
562 DNSThreadGroup.add Thread.current
563 loop {
564 reply, from = @sock.recvfrom(UDPSize)
565 msg = begin
566 Message.decode(reply)
567 rescue DecodeError
568 STDERR.print("DNS message decoding error: #{reply.inspect}\n")
569 next
570 end
571 if s = @senders[[[from[3],from[1]],msg.id]]
572 s.recv msg
573 else
574 #STDERR.print("non-handled DNS message: #{msg.inspect} from #{from.inspect}\n")
575 end
576 }
577 }
578 end
579
580 def sender(msg, data, queue, host, port=Port)
581 service = [host, port]
582 id = Thread.exclusive {
583 @id[service] = (@id[service] + 1) & 0xffff
584 }
585 request = msg.encode
586 request[0,2] = [id].pack('n')
587 return @senders[[service, id]] =
588 Sender.new(request, data, @sock, host, port, queue)
589 end
590
591 class Sender < Requester::Sender
592 def initialize(msg, data, sock, host, port, queue)
593 super(msg, data, sock, queue)
594 @host = host
595 @port = port
596 end
597
598 def send
599 @sock.send(@msg, 0, @host, @port)
600 end
601 end
602 end
603
604 class ConnectedUDP < Requester
605 def initialize(host, port=Port)
606 super()
607 @host = host
608 @port = port
609 @sock = UDPSocket.new
610 @sock.connect(host, port)
611 @sock.fcntl(Fcntl::F_SETFD, 1)
612 @id = -1
613 @thread = Thread.new {
614 DNSThreadGroup.add Thread.current
615 loop {
616 reply = @sock.recv(UDPSize)
617 msg = begin
618 Message.decode(reply)
619 rescue DecodeError
620 STDERR.print("DNS message decoding error: #{reply.inspect}")
621 next
622 end
623 if s = @senders[msg.id]
624 s.recv msg
625 else
626 #STDERR.print("non-handled DNS message: #{msg.inspect}")
627 end
628 }
629 }
630 end
631
632 def sender(msg, data, queue, host=@host, port=@port)
633 unless host == @host && port == @port
634 raise RequestError.new("host/port don't match: #{host}:#{port}")
635 end
636 id = Thread.exclusive { @id = (@id + 1) & 0xffff }
637 request = msg.encode
638 request[0,2] = [id].pack('n')
639 return @senders[id] = Sender.new(request, data, @sock, queue)
640 end
641
642 class Sender < Requester::Sender
643 def send
644 @sock.send(@msg, 0)
645 end
646 end
647 end
648
649 class TCP < Requester
650 def initialize(host, port=Port)
651 super()
652 @host = host
653 @port = port
654 @sock = TCPSocket.new
655 @sock.connect(host, port)
656 @sock.fcntl(Fcntl::F_SETFD, 1)
657 @id = -1
658 @senders = {}
659 @thread = Thread.new {
660 DNSThreadGroup.add Thread.current
661 loop {
662 len = @sock.read(2).unpack('n')
663 reply = @sock.read(len)
664 msg = begin
665 Message.decode(reply)
666 rescue DecodeError
667 STDERR.print("DNS message decoding error: #{reply.inspect}")
668 next
669 end
670 if s = @senders[msg.id]
671 s.push msg
672 else
673 #STDERR.print("non-handled DNS message: #{msg.inspect}")
674 end
675 }
676 }
677 end
678
679 def sender(msg, data, queue, host=@host, port=@port)
680 unless host == @host && port == @port
681 raise RequestError.new("host/port don't match: #{host}:#{port}")
682 end
683 id = Thread.exclusive { @id = (@id + 1) & 0xffff }
684 request = msg.encode
685 request[0,2] = [request.length, id].pack('nn')
686 return @senders[id] = Sender.new(request, data, @sock, queue)
687 end
688
689 class Sender < Requester::Sender
690 def send
691 @sock.print(@msg)
692 @sock.flush
693 end
694 end
695 end
696
697 class RequestError < StandardError
698 end
699 end
700
701 class Config
702 def initialize(filename="/etc/resolv.conf")
703 @mutex = Mutex.new
704 @filename = filename
705 @initialized = nil
706 end
707
708 def lazy_initialize
709 @mutex.synchronize {
710 unless @initialized
711 @nameserver = []
712 @search = nil
713 @ndots = 1
714 begin
715 open(@filename) {|f|
716 f.each {|line|
717 line.sub!(/[#;].*/, '')
718 keyword, *args = line.split(/\s+/)
719 args.each { |arg|
720 arg.untaint
721 }
722 next unless keyword
723 case keyword
724 when 'nameserver'
725 @nameserver += args
726 when 'domain'
727 @search = [Label.split(args[0])]
728 when 'search'
729 @search = args.map {|arg| Label.split(arg)}
730 end
731 }
732 }
733 rescue Errno::ENOENT
734 end
735
736 @nameserver = ['0.0.0.0'] if @nameserver.empty?
737 unless @search
738 hostname = Socket.gethostname
739 if /\./ =~ hostname
740 @search = [Label.split($')]
741 else
742 @search = [[]]
743 end
744 end
745 @initialized = true
746 end
747 }
748 end
749
750 def single?
751 lazy_initialize
752 if @nameserver.length == 1
753 return @nameserver[0]
754 else
755 return nil
756 end
757 end
758
759 def generate_candidates(name)
760 candidates = nil
761 name = Name.create(name)
762 if name.absolute?
763 candidates = [name]
764 else
765 if @ndots <= name.length - 1
766 candidates = [Name.new(name.to_a)]
767 else
768 candidates = []
769 end
770 candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)})
771 end
772 return candidates
773 end
774
775 InitialTimeout = 5
776
777 def generate_timeouts
778 ts = [InitialTimeout]
779 ts << ts[-1] * 2 / @nameserver.length
780 ts << ts[-1] * 2
781 ts << ts[-1] * 2
782 return ts
783 end
784
785 def resolv(name)
786 candidates = generate_candidates(name)
787 timeouts = generate_timeouts
788 begin
789 candidates.each {|candidate|
790 begin
791 timeouts.each {|tout|
792 @nameserver.each {|nameserver|
793 begin
794 yield candidate, tout, nameserver
795 rescue ResolvTimeout
796 end
797 }
798 }
799 raise ResolvError.new("DNS resolv timeout: #{name}")
800 rescue NXDomain
801 end
802 }
803 rescue OtherResolvError
804 raise ResolvError.new("DNS error: #{$!.message}")
805 end
806 raise ResolvError.new("DNS resolv error: #{name}")
807 end
808
809 class NXDomain < ResolvError
810 end
811
812 class OtherResolvError < ResolvError
813 end
814 end
815
816 module OpCode
817 Query = 0
818 IQuery = 1
819 Status = 2
820 Notify = 4
821 Update = 5
822 end
823
824 module RCode
825 NoError = 0
826 FormErr = 1
827 ServFail = 2
828 NXDomain = 3
829 NotImp = 4
830 Refused = 5
831 YXDomain = 6
832 YXRRSet = 7
833 NXRRSet = 8
834 NotAuth = 9
835 NotZone = 10
836 BADVERS = 16
837 BADSIG = 16
838 BADKEY = 17
839 BADTIME = 18
840 BADMODE = 19
841 BADNAME = 20
842 BADALG = 21
843 end
844
845 class DecodeError < StandardError
846 end
847
848 class EncodeError < StandardError
849 end
850
851 module Label
852 def self.split(arg)
853 labels = []
854 arg.scan(/[^\.]+/) {labels << Str.new($&)}
855 return labels
856 end
857
858 class Str
859 def initialize(string)
860 @string = string
861 @downcase = string.downcase
862 end
863 attr_reader :string, :downcase
864
865 def to_s
866 return @string
867 end
868
869 def inspect
870 return "#<#{self.class} #{self.to_s}>"
871 end
872
873 def ==(other)
874 return @downcase == other.downcase
875 end
876
877 def eql?(other)
878 return self == other
879 end
880
881 def hash
882 return @downcase.hash
883 end
884 end
885 end
886
887 class Name
888 def self.create(arg)
889 case arg
890 when Name
891 return arg
892 when String
893 return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
894 else
895 raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
896 end
897 end
898
899 def initialize(labels, absolute=true)
900 @labels = labels
901 @absolute = absolute
902 end
903
904 def absolute?
905 return @absolute
906 end
907
908 def ==(other)
909 return @labels == other.to_a && @absolute == other.absolute?
910 end
911 alias eql? ==
912
913 def hash
914 return @labels.hash ^ @absolute.hash
915 end
916
917 def to_a
918 return @labels
919 end
920
921 def length
922 return @labels.length
923 end
924
925 def [](i)
926 return @labels[i]
927 end
928
929 def to_s
930 return @labels.join('.')
931 end
932 end
933
934 class Message
935 @@identifier = -1
936
937 def initialize(id = (@@identifier += 1) & 0xffff)
938 @id = id
939 @qr = 0
940 @opcode = 0
941 @aa = 0
942 @tc = 0
943 @rd = 0 # recursion desired
944 @ra = 0 # recursion available
945 @rcode = 0
946 @question = []
947 @answer = []
948 @authority = []
949 @additional = []
950 end
951
952 attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode
953 attr_reader :question, :answer, :authority, :additional
954
955 def ==(other)
956 return @id == other.id &&
957 @qr == other.qr &&
958 @opcode == other.opcode &&
959 @aa == other.aa &&
960 @tc == other.tc &&
961 @rd == other.rd &&
962 @ra == other.ra &&
963 @rcode == other.rcode &&
964 @question == other.question &&
965 @answer == other.answer &&
966 @authority == other.authority &&
967 @additional == other.additional
968 end
969
970 def add_question(name, typeclass)
971 @question << [Name.create(name), typeclass]
972 end
973
974 def each_question
975 @question.each {|name, typeclass|
976 yield name, typeclass
977 }
978 end
979
980 def add_answer(name, ttl, data)
981 @answer << [Name.create(name), ttl, data]
982 end
983
984 def each_answer
985 @answer.each {|name, ttl, data|
986 yield name, ttl, data
987 }
988 end
989
990 def add_authority(name, ttl, data)
991 @authority << [Name.create(name), ttl, data]
992 end
993
994 def each_authority
995 @authority.each {|name, ttl, data|
996 yield name, ttl, data
997 }
998 end
999
1000 def add_additional(name, ttl, data)
1001 @additional << [Name.create(name), ttl, data]
1002 end
1003
1004 def each_additional
1005 @additional.each {|name, ttl, data|
1006 yield name, ttl, data
1007 }
1008 end
1009
1010 def each_resource
1011 each_answer {|name, ttl, data| yield name, ttl, data}
1012 each_authority {|name, ttl, data| yield name, ttl, data}
1013 each_additional {|name, ttl, data| yield name, ttl, data}
1014 end
1015
1016 def encode
1017 return MessageEncoder.new {|msg|
1018 msg.put_pack('nnnnnn',
1019 @id,
1020 (@qr & 1) << 15 |
1021 (@opcode & 15) << 11 |
1022 (@aa & 1) << 10 |
1023 (@tc & 1) << 9 |
1024 (@rd & 1) << 8 |
1025 (@ra & 1) << 7 |
1026 (@rcode & 15),
1027 @question.length,
1028 @answer.length,
1029 @authority.length,
1030 @additional.length)
1031 @question.each {|q|
1032 name, typeclass = q
1033 msg.put_name(name)
1034 msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue)
1035 }
1036 [@answer, @authority, @additional].each {|rr|
1037 rr.each {|r|
1038 name, ttl, data = r
1039 msg.put_name(name)
1040 msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl)
1041 msg.put_length16 {data.encode_rdata(msg)}
1042 }
1043 }
1044 }.to_s
1045 end
1046
1047 class MessageEncoder
1048 def initialize
1049 @data = ''
1050 @names = {}
1051 yield self
1052 end
1053
1054 def to_s
1055 return @data
1056 end
1057
1058 def put_bytes(d)
1059 @data << d
1060 end
1061
1062 def put_pack(template, *d)
1063 @data << d.pack(template)
1064 end
1065
1066 def put_length16
1067 length_index = @data.length
1068 @data << "\0\0"
1069 data_start = @data.length
1070 yield
1071 data_end = @data.length
1072 @data[length_index, 2] = [data_end - data_start].pack("n")
1073 end
1074
1075 def put_string(d)
1076 self.put_pack("C", d.length)
1077 @data << d
1078 end
1079
1080 def put_name(d)
1081 put_labels(d.to_a)
1082 end
1083
1084 def put_labels(d)
1085 d.each_index {|i|
1086 domain = d[i..-1]
1087 if idx = @names[domain]
1088 self.put_pack("n", 0xc000 | idx)
1089 return
1090 else
1091 @names[domain] = @data.length
1092 self.put_label(d[i])
1093 end
1094 }
1095 @data << "\0"
1096 end
1097
1098 def put_label(d)
1099 self.put_string(d.string)
1100 end
1101 end
1102
1103 def Message.decode(m)
1104 o = Message.new(0)
1105 MessageDecoder.new(m) {|msg|
1106 id, flag, qdcount, ancount, nscount, arcount =
1107 msg.get_unpack('nnnnnn')
1108 o.id = id
1109 o.qr = (flag >> 15) & 1
1110 o.opcode = (flag >> 11) & 15
1111 o.aa = (flag >> 10) & 1
1112 o.tc = (flag >> 9) & 1
1113 o.rd = (flag >> 8) & 1
1114 o.ra = (flag >> 7) & 1
1115 o.rcode = flag & 15
1116 (1..qdcount).each {
1117 name, typeclass = msg.get_question
1118 o.add_question(name, typeclass)
1119 }
1120 (1..ancount).each {
1121 name, ttl, data = msg.get_rr
1122 o.add_answer(name, ttl, data)
1123 }
1124 (1..nscount).each {
1125 name, ttl, data = msg.get_rr
1126 o.add_authority(name, ttl, data)
1127 }
1128 (1..arcount).each {
1129 name, ttl, data = msg.get_rr
1130 o.add_additional(name, ttl, data)
1131 }
1132 }
1133 return o
1134 end
1135
1136 class MessageDecoder
1137 def initialize(data)
1138 @data = data
1139 @index = 0
1140 @limit = data.length
1141 yield self
1142 end
1143
1144 def get_length16
1145 len, = self.get_unpack('n')
1146 save_limit = @limit
1147 @limit = @index + len
1148 d = yield len
1149 if @index < @limit
1150 raise DecodeError.new("junk exist")
1151 elsif @limit < @index
1152 raise DecodeError.new("limit exceed")
1153 end
1154 @limit = save_limit
1155 return d
1156 end
1157
1158 def get_bytes(len = @limit - @index)
1159 d = @data[@index, len]
1160 @index += len
1161 return d
1162 end
1163
1164 def get_unpack(template)
1165 len = 0
1166 template.each_byte {|byte|
1167 case byte
1168 when ?c, ?C
1169 len += 1
1170 when ?n
1171 len += 2
1172 when ?N
1173 len += 4
1174 else
1175 raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'")
1176 end
1177 }
1178 raise DecodeError.new("limit exceed") if @limit < @index + len
1179 arr = @data.unpack("@#{@index}#{template}")
1180 @index += len
1181 return arr
1182 end
1183
1184 def get_string
1185 len = @data[@index]
1186 raise DecodeError.new("limit exceed") if @limit < @index + 1 + len
1187 d = @data[@index + 1, len]
1188 @index += 1 + len
1189 return d
1190 end
1191
1192 def get_name
1193 return Name.new(self.get_labels)
1194 end
1195
1196 def get_labels(limit=nil)
1197 limit = @index if !limit || @index < limit
1198 d = []
1199 while true
1200 case @data[@index]
1201 when 0
1202 @index += 1
1203 return d
1204 when 192..255
1205 idx = self.get_unpack('n')[0] & 0x3fff
1206 if limit <= idx
1207 raise DecodeError.new("non-backward name pointer")
1208 end
1209 save_index = @index
1210 @index = idx
1211 d += self.get_labels(limit)
1212 @index = save_index
1213 return d
1214 else
1215 d << self.get_label
1216 end
1217 end
1218 return d
1219 end
1220
1221 def get_label
1222 return Label::Str.new(self.get_string)
1223 end
1224
1225 def get_question
1226 name = self.get_name
1227 type, klass = self.get_unpack("nn")
1228 return name, Resource.get_class(type, klass)
1229 end
1230
1231 def get_rr
1232 name = self.get_name
1233 type, klass, ttl = self.get_unpack('nnN')
1234 typeclass = Resource.get_class(type, klass)
1235 return name, ttl, self.get_length16 {typeclass.decode_rdata(self)}
1236 end
1237 end
1238 end
1239
1240 class Query
1241 def encode_rdata(msg)
1242 raise EncodeError.new("#{self.type} is query.")
1243 end
1244
1245 def self.decode_rdata(msg)
1246 raise DecodeError.new("#{self.type} is query.")
1247 end
1248 end
1249
1250 class Resource < Query
1251 ClassHash = {}
1252
1253 def encode_rdata(msg)
1254 raise NotImplementedError.new
1255 end
1256
1257 def self.decode_rdata(msg)
1258 raise NotImplementedError.new
1259 end
1260
1261 def ==(other)
1262 return self.type == other.type &&
1263 self.instance_variables == other.instance_variables &&
1264 self.instance_variables.collect {|name| self.instance_eval name} ==
1265 other.instance_variables.collect {|name| other.instance_eval name}
1266 end
1267
1268 def eql?(other)
1269 return self == other
1270 end
1271
1272 def hash
1273 h = 0
1274 self.instance_variables.each {|name|
1275 h += self.instance_eval("#{name}.hash")
1276 }
1277 return h
1278 end
1279
1280 def self.get_class(type_value, class_value)
1281 return ClassHash[[type_value, class_value]] ||
1282 Generic.create(type_value, class_value)
1283 end
1284
1285 class Generic < Resource
1286 def initialize(data)
1287 @data = data
1288 end
1289 attr_reader :data
1290
1291 def encode_rdata(msg)
1292 msg.put_bytes(data)
1293 end
1294
1295 def self.decode_rdata(msg)
1296 return self.new(msg.get_bytes)
1297 end
1298
1299 def self.create(type_value, class_value)
1300 c = Class.new(Generic)
1301 c.const_set(:TypeValue, type_value)
1302 c.const_set(:ClassValue, class_value)
1303 Generic.const_set("Type#{type_value}_Class#{class_value}", c)
1304 ClassHash[[type_value, class_value]] = c
1305 return c
1306 end
1307 end
1308
1309 class DomainName < Resource
1310 def initialize(name)
1311 @name = name
1312 end
1313 attr_reader :name
1314
1315 def encode_rdata(msg)
1316 msg.put_name(@name)
1317 end
1318
1319 def self.decode_rdata(msg)
1320 return self.new(msg.get_name)
1321 end
1322 end
1323
1324 # Standard (class generic) RRs
1325 ClassValue = nil
1326
1327 class NS < DomainName
1328 TypeValue = 2
1329 end
1330
1331 class CNAME < DomainName
1332 TypeValue = 5
1333 end
1334
1335 class SOA < Resource
1336 TypeValue = 6
1337
1338 def initialize(mname, rname, serial, refresh, retry_, expire, minimum)
1339 @mname = mname
1340 @rname = rname
1341 @serial = serial
1342 @refresh = refresh
1343 @retry = retry_
1344 @expire = expire
1345 @minimum = minimum
1346 end
1347 attr_reader :mname, :rname, :serial, :refresh, :retry, :expire, :minimum
1348
1349 def encode_rdata(msg)
1350 msg.put_name(@mname)
1351 msg.put_name(@rname)
1352 msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum)
1353 end
1354
1355 def self.decode_rdata(msg)
1356 mname = msg.get_name
1357 rname = msg.get_name
1358 serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN')
1359 return self.new(
1360 mname, rname, serial, refresh, retry_, expire, minimum)
1361 end
1362 end
1363
1364 class PTR < DomainName
1365 TypeValue = 12
1366 end
1367
1368 class HINFO < Resource
1369 TypeValue = 13
1370
1371 def initialize(cpu, os)
1372 @cpu = cpu
1373 @os = os
1374 end
1375 attr_reader :cpu, :os
1376
1377 def encode_rdata(msg)
1378 msg.put_string(@cpu)
1379 msg.put_string(@os)
1380 end
1381
1382 def self.decode_rdata(msg)
1383 cpu = msg.get_string
1384 os = msg.get_string
1385 return self.new(cpu, os)
1386 end
1387 end
1388
1389 class MINFO < Resource
1390 TypeValue = 14
1391
1392 def initialize(rmailbx, emailbx)
1393 @rmailbx = rmailbx
1394 @emailbx = emailbx
1395 end
1396 attr_reader :rmailbx, :emailbx
1397
1398 def encode_rdata(msg)
1399 msg.put_name(@rmailbx)
1400 msg.put_name(@emailbx)
1401 end
1402
1403 def self.decode_rdata(msg)
1404 rmailbx = msg.get_string
1405 emailbx = msg.get_string
1406 return self.new(rmailbx, emailbx)
1407 end
1408 end
1409
1410 class MX < Resource
1411 TypeValue= 15
1412
1413 def initialize(preference, exchange)
1414 @preference = preference
1415 @exchange = exchange
1416 end
1417 attr_reader :preference, :exchange
1418
1419 def encode_rdata(msg)
1420 msg.put_pack('n', @preference)
1421 msg.put_name(@exchange)
1422 end
1423
1424 def self.decode_rdata(msg)
1425 preference, = msg.get_unpack('n')
1426 exchange = msg.get_name
1427 return self.new(preference, exchange)
1428 end
1429 end
1430
1431 class TXT < Resource
1432 TypeValue = 16
1433
1434 def initialize(data)
1435 @data = data
1436 end
1437 attr_reader :data
1438
1439 def encode_rdata(msg)
1440 msg.put_string(@data)
1441 end
1442
1443 def self.decode_rdata(msg)
1444 data = msg.get_string
1445 return self.new(data)
1446 end
1447 end
1448
1449 class ANY < Query
1450 TypeValue = 255
1451 end
1452
1453 ClassInsensitiveTypes = [
1454 NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, ANY
1455 ]
1456
1457 # ARPA Internet specific RRs
1458 module IN
1459 ClassValue = 1
1460
1461 ClassInsensitiveTypes.each {|s|
1462 c = Class.new(s)
1463 c.const_set(:TypeValue, s::TypeValue)
1464 c.const_set(:ClassValue, ClassValue)
1465 ClassHash[[s::TypeValue, ClassValue]] = c
1466 self.const_set(s.name.sub(/.*::/, ''), c)
1467 }
1468
1469 class A < Resource
1470 ClassHash[[TypeValue = 1, ClassValue = ClassValue]] = self
1471
1472 def initialize(address)
1473 @address = IPv4.create(address)
1474 end
1475 attr_reader :address
1476
1477 def encode_rdata(msg)
1478 msg.put_bytes(@address.address)
1479 end
1480
1481 def self.decode_rdata(msg)
1482 return self.new(IPv4.new(msg.get_bytes(4)))
1483 end
1484 end
1485
1486 class WKS < Resource
1487 ClassHash[[TypeValue = 11, ClassValue = ClassValue]] = self
1488
1489 def initialize(address, protocol, bitmap)
1490 @address = IPv4.create(address)
1491 @protocol = protocol
1492 @bitmap = bitmap
1493 end
1494 attr_reader :address, :protocol, :bitmap
1495
1496 def encode_rdata(msg)
1497 msg.put_bytes(@address.address)
1498 msg.put_pack("n", @protocol)
1499 msg.put_bytes(@bitmap)
1500 end
1501
1502 def self.decode_rdata(msg)
1503 address = IPv4.new(msg.get_bytes(4))
1504 protocol, = msg.get_unpack("n")
1505 bitmap = msg.get_bytes
1506 return self.new(address, protocol, bitmap)
1507 end
1508 end
1509
1510 class AAAA < Resource
1511 ClassHash[[TypeValue = 28, ClassValue = ClassValue]] = self
1512
1513 def initialize(address)
1514 @address = IPv6.create(address)
1515 end
1516 attr_reader :address
1517
1518 def encode_rdata(msg)
1519 msg.put_bytes(@address.address)
1520 end
1521
1522 def self.decode_rdata(msg)
1523 return self.new(IPv6.new(msg.get_bytes(16)))
1524 end
1525 end
1526 end
1527 end
1528 end
1529
1530 class IPv4
1531 Regex = /\A(\d+)\.(\d+)\.(\d+)\.(\d+)\z/
1532
1533 def self.create(arg)
1534 case arg
1535 when IPv4
1536 return arg
1537 when Regex
1538 if (0..255) === (a = $1.to_i) &&
1539 (0..255) === (b = $2.to_i) &&
1540 (0..255) === (c = $3.to_i) &&
1541 (0..255) === (d = $4.to_i)
1542 return self.new([a, b, c, d].pack("CCCC"))
1543 else
1544 raise ArgumentError.new("IPv4 address with invalid value: " + arg)
1545 end
1546 else
1547 raise ArgumentError.new("cannot interprete as IPv4 address: #{arg.inspect}")
1548 end
1549 end
1550
1551 def initialize(address)
1552 unless address.kind_of?(String) && address.length == 4
1553 raise ArgumentError.new('IPv4 address muse be 4 bytes')
1554 end
1555 @address = address
1556 end
1557 attr_reader :address
1558
1559 def to_s
1560 return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC"))
1561 end
1562
1563 def inspect
1564 return "#<#{self.class} #{self.to_s}>"
1565 end
1566
1567 def to_name
1568 return DNS::Name.create(
1569 '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse)
1570 end
1571
1572 def ==(other)
1573 return @address == other.address
1574 end
1575
1576 def eql?(other)
1577 return self == other
1578 end
1579
1580 def hash
1581 return @address.hash
1582 end
1583 end
1584
1585 class IPv6
1586 Regex_8Hex = /\A
1587 (?:[0-9A-Fa-f]{1,4}:){7}
1588 [0-9A-Fa-f]{1,4}
1589 \z/x
1590
1591 Regex_CompressedHex = /\A
1592 ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
1593 ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
1594 \z/x
1595
1596 Regex_6Hex4Dec = /\A
1597 ((?:[0-9A-Fa-f]{1,4}:){6,6})
1598 (\d+)\.(\d+)\.(\d+)\.(\d+)
1599 \z/x
1600
1601 Regex_CompressedHex4Dec = /\A
1602 ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
1603 ((?:[0-9A-Fa-f]{1,4}:)*)
1604 (\d+)\.(\d+)\.(\d+)\.(\d+)
1605 \z/x
1606
1607 Regex = /
1608 (?:#{Regex_8Hex.source}) |
1609 (?:#{Regex_CompressedHex.source}) |
1610 (?:#{Regex_6Hex4Dec.source}) |
1611 (?:#{Regex_CompressedHex4Dec.source})/x
1612
1613 def self.create(arg)
1614 case arg
1615 when IPv6
1616 return arg
1617 when String
1618 address = ''
1619 if Regex_8Hex =~ arg
1620 arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
1621 elsif Regex_CompressedHex =~ arg
1622 prefix = $1
1623 suffix = $2
1624 a1 = ''
1625 a2 = ''
1626 prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
1627 suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
1628 omitlen = 16 - a1.length - a2.length
1629 address << a1 << "\0" * omitlen << a2
1630 elsif Regex_6Hex4Dec =~ arg
1631 prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i
1632 if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
1633 prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
1634 address << [a, b, c, d].pack('CCCC')
1635 else
1636 raise ArgumentError.new("not numeric IPv6 address: " + arg)
1637 end
1638 elsif Regex_CompressedHex4Dec =~ arg
1639 prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i
1640 if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
1641 a1 = ''
1642 a2 = ''
1643 prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
1644 suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
1645 omitlen = 12 - a1.length - a2.length
1646 address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC')
1647 else
1648 raise ArgumentError.new("not numeric IPv6 address: " + arg)
1649 end
1650 else
1651 raise ArgumentError.new("not numeric IPv6 address: " + arg)
1652 end
1653 return IPv6.new(address)
1654 else
1655 raise ArgumentError.new("cannot interprete as IPv6 address: #{arg.inspect}")
1656 end
1657 end
1658
1659 def initialize(address)
1660 unless address.kind_of?(String) && address.length == 16
1661 raise ArgumentError.new('IPv6 address muse be 16 bytes')
1662 end
1663 @address = address
1664 end
1665 attr_reader :address
1666
1667 def to_s
1668 address = sprintf("%X:%X:%X:%X:%X:%X:%X:%X", *@address.unpack("nnnnnnnn"))
1669 unless address.sub!(/(^|:)0(:0)+(:|$)/, '::')
1670 address.sub!(/(^|:)0(:|$)/, '::')
1671 end
1672 return address
1673 end
1674
1675 def inspect
1676 return "#<#{self.class} #{self.to_s}>"
1677 end
1678
1679 def to_name
1680 # ip6.arpa should be searched too. [RFC3152]
1681 return DNS::Name.new(
1682 @address.unpack("H32")[0].split(
1683 end
1684
1685 def ==(other)
1686 return @address == other.address
1687 end
1688
1689 def eql?(other)
1690 return self == other
1691 end
1692
1693 def hash
1694 return @address.hash
1695 end
1696 end
1697
1698 DefaultResolver = self.new
1699 AddressRegex = /(?:#{IPv4::Regex.source})|(?:#{IPv6::Regex.source})/
1700 end