fresolv 0.0.1

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