fresolv 0.0.1

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