rubysl-resolv 0.0.1 → 1.0.0

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