rubysl-resolv 0.0.1 → 1.0.0

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.
@@ -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
+