resolv_fiber 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2930 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'timeout'
5
+ require 'io/wait'
6
+
7
+ begin
8
+ require 'securerandom'
9
+ rescue LoadError
10
+ end
11
+
12
+ # Resolv is a thread-aware DNS resolver library written in Ruby. Resolv can
13
+ # handle multiple DNS requests concurrently without blocking the entire Ruby
14
+ # interpreter.
15
+ #
16
+ # See also resolv-replace.rb to replace the libc resolver with Resolv.
17
+ #
18
+ # Resolv can look up various DNS resources using the DNS module directly.
19
+ #
20
+ # Examples:
21
+ #
22
+ # p Resolv.getaddress "www.ruby-lang.org"
23
+ # p Resolv.getname "210.251.121.214"
24
+ #
25
+ # Resolv::DNS.open do |dns|
26
+ # ress = dns.getresources "www.ruby-lang.org", Resolv::DNS::Resource::IN::A
27
+ # p ress.map(&:address)
28
+ # ress = dns.getresources "ruby-lang.org", Resolv::DNS::Resource::IN::MX
29
+ # p ress.map { |r| [r.exchange.to_s, r.preference] }
30
+ # end
31
+ #
32
+ #
33
+ # == Bugs
34
+ #
35
+ # * NIS is not supported.
36
+ # * /etc/nsswitch.conf is not supported.
37
+ class ResolvFiber
38
+
39
+ VERSION = "0.0.1"
40
+
41
+ def self.getaddresses_fiber(hostname)
42
+ Fiber.new do
43
+ ResolvFiber.getaddresses(hostname)
44
+ end
45
+ end
46
+
47
+ ##
48
+ # Looks up the first IP address for +name+.
49
+
50
+ def self.getaddress(name)
51
+ DefaultResolver.getaddress(name)
52
+ end
53
+
54
+ ##
55
+ # Looks up all IP address for +name+.
56
+
57
+ def self.getaddresses(name)
58
+ DefaultResolver.getaddresses(name)
59
+ end
60
+
61
+ ##
62
+ # Iterates over all IP addresses for +name+.
63
+
64
+ def self.each_address(name, &block)
65
+ DefaultResolver.each_address(name, &block)
66
+ end
67
+
68
+ ##
69
+ # Looks up the hostname of +address+.
70
+
71
+ def self.getname(address)
72
+ DefaultResolver.getname(address)
73
+ end
74
+
75
+ ##
76
+ # Looks up all hostnames for +address+.
77
+
78
+ def self.getnames(address)
79
+ DefaultResolver.getnames(address)
80
+ end
81
+
82
+ ##
83
+ # Iterates over all hostnames for +address+.
84
+
85
+ def self.each_name(address, &proc)
86
+ DefaultResolver.each_name(address, &proc)
87
+ end
88
+
89
+ ##
90
+ # Creates a new Resolv using +resolvers+.
91
+
92
+ def initialize(resolvers=[Hosts.new, DNS.new])
93
+ @resolvers = resolvers
94
+ end
95
+
96
+ ##
97
+ # Looks up the first IP address for +name+.
98
+
99
+ def getaddress(name)
100
+ each_address(name) {|address| return address}
101
+ raise ResolvError.new("no address for #{name}")
102
+ end
103
+
104
+ ##
105
+ # Looks up all IP address for +name+.
106
+
107
+ def getaddresses(name)
108
+ ret = []
109
+ each_address(name) {|address| ret << address}
110
+ return ret
111
+ end
112
+
113
+ ##
114
+ # Iterates over all IP addresses for +name+.
115
+
116
+ def each_address(name)
117
+ if AddressRegex =~ name
118
+ yield name
119
+ return
120
+ end
121
+ yielded = false
122
+ @resolvers.each {|r|
123
+ r.each_address(name) {|address|
124
+ yield address.to_s
125
+ yielded = true
126
+ }
127
+ return if yielded
128
+ }
129
+ end
130
+
131
+ ##
132
+ # Looks up the hostname of +address+.
133
+
134
+ def getname(address)
135
+ each_name(address) {|name| return name}
136
+ raise ResolvError.new("no name for #{address}")
137
+ end
138
+
139
+ ##
140
+ # Looks up all hostnames for +address+.
141
+
142
+ def getnames(address)
143
+ ret = []
144
+ each_name(address) {|name| ret << name}
145
+ return ret
146
+ end
147
+
148
+ ##
149
+ # Iterates over all hostnames for +address+.
150
+
151
+ def each_name(address)
152
+ yielded = false
153
+ @resolvers.each {|r|
154
+ r.each_name(address) {|name|
155
+ yield name.to_s
156
+ yielded = true
157
+ }
158
+ return if yielded
159
+ }
160
+ end
161
+
162
+ ##
163
+ # Indicates a failure to resolve a name or address.
164
+
165
+ class ResolvError < StandardError; end
166
+
167
+ ##
168
+ # Indicates a timeout resolving a name or address.
169
+
170
+ class ResolvTimeout < Timeout::Error; end
171
+
172
+ ##
173
+ # Resolv::Hosts is a hostname resolver that uses the system hosts file.
174
+
175
+ class Hosts
176
+ if /mswin|mingw|cygwin/ =~ RUBY_PLATFORM and
177
+ begin
178
+ require 'win32/resolv'
179
+ DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL
180
+ rescue LoadError
181
+ end
182
+ end
183
+ DefaultFileName ||= '/etc/hosts'
184
+
185
+ ##
186
+ # Creates a new Resolv::Hosts, using +filename+ for its data source.
187
+
188
+ def initialize(filename = DefaultFileName)
189
+ @filename = filename
190
+ @mutex = Thread::Mutex.new
191
+ @initialized = nil
192
+ end
193
+
194
+ def lazy_initialize # :nodoc:
195
+ @mutex.synchronize {
196
+ unless @initialized
197
+ @name2addr = {}
198
+ @addr2name = {}
199
+ File.open(@filename, 'rb') {|f|
200
+ f.each {|line|
201
+ line.sub!(/#.*/, '')
202
+ addr, hostname, *aliases = line.split(/\s+/)
203
+ next unless addr
204
+ @addr2name[addr] = [] unless @addr2name.include? addr
205
+ @addr2name[addr] << hostname
206
+ @addr2name[addr].concat(aliases)
207
+ @name2addr[hostname] = [] unless @name2addr.include? hostname
208
+ @name2addr[hostname] << addr
209
+ aliases.each {|n|
210
+ @name2addr[n] = [] unless @name2addr.include? n
211
+ @name2addr[n] << addr
212
+ }
213
+ }
214
+ }
215
+ @name2addr.each {|name, arr| arr.reverse!}
216
+ @initialized = true
217
+ end
218
+ }
219
+ self
220
+ end
221
+
222
+ ##
223
+ # Gets the IP address of +name+ from the hosts file.
224
+
225
+ def getaddress(name)
226
+ each_address(name) {|address| return address}
227
+ raise ResolvError.new("#{@filename} has no name: #{name}")
228
+ end
229
+
230
+ ##
231
+ # Gets all IP addresses for +name+ from the hosts file.
232
+
233
+ def getaddresses(name)
234
+ ret = []
235
+ each_address(name) {|address| ret << address}
236
+ return ret
237
+ end
238
+
239
+ ##
240
+ # Iterates over all IP addresses for +name+ retrieved from the hosts file.
241
+
242
+ def each_address(name, &proc)
243
+ lazy_initialize
244
+ @name2addr[name]&.each(&proc)
245
+ end
246
+
247
+ ##
248
+ # Gets the hostname of +address+ from the hosts file.
249
+
250
+ def getname(address)
251
+ each_name(address) {|name| return name}
252
+ raise ResolvError.new("#{@filename} has no address: #{address}")
253
+ end
254
+
255
+ ##
256
+ # Gets all hostnames for +address+ from the hosts file.
257
+
258
+ def getnames(address)
259
+ ret = []
260
+ each_name(address) {|name| ret << name}
261
+ return ret
262
+ end
263
+
264
+ ##
265
+ # Iterates over all hostnames for +address+ retrieved from the hosts file.
266
+
267
+ def each_name(address, &proc)
268
+ lazy_initialize
269
+ @addr2name[address]&.each(&proc)
270
+ end
271
+ end
272
+
273
+ ##
274
+ # Resolv::DNS is a DNS stub resolver.
275
+ #
276
+ # Information taken from the following places:
277
+ #
278
+ # * STD0013
279
+ # * RFC 1035
280
+ # * ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters
281
+ # * etc.
282
+
283
+ class DNS
284
+
285
+ ##
286
+ # Default DNS Port
287
+
288
+ Port = 53
289
+
290
+ ##
291
+ # Default DNS UDP packet size
292
+
293
+ UDPSize = 512
294
+
295
+ ##
296
+ # Creates a new DNS resolver. See Resolv::DNS.new for argument details.
297
+ #
298
+ # Yields the created DNS resolver to the block, if given, otherwise
299
+ # returns it.
300
+
301
+ def self.open(*args)
302
+ dns = new(*args)
303
+ return dns unless block_given?
304
+ begin
305
+ yield dns
306
+ ensure
307
+ dns.close
308
+ end
309
+ end
310
+
311
+ ##
312
+ # Creates a new DNS resolver.
313
+ #
314
+ # +config_info+ can be:
315
+ #
316
+ # nil:: Uses /etc/resolv.conf.
317
+ # String:: Path to a file using /etc/resolv.conf's format.
318
+ # Hash:: Must contain :nameserver, :search and :ndots keys.
319
+ # :nameserver_port can be used to specify port number of nameserver address.
320
+ #
321
+ # The value of :nameserver should be an address string or
322
+ # an array of address strings.
323
+ # - :nameserver => '8.8.8.8'
324
+ # - :nameserver => ['8.8.8.8', '8.8.4.4']
325
+ #
326
+ # The value of :nameserver_port should be an array of
327
+ # pair of nameserver address and port number.
328
+ # - :nameserver_port => [['8.8.8.8', 53], ['8.8.4.4', 53]]
329
+ #
330
+ # Example:
331
+ #
332
+ # Resolv::DNS.new(:nameserver => ['210.251.121.21'],
333
+ # :search => ['ruby-lang.org'],
334
+ # :ndots => 1)
335
+
336
+ def initialize(config_info=nil)
337
+ @mutex = Thread::Mutex.new
338
+ @config = Config.new(config_info)
339
+ @initialized = nil
340
+ end
341
+
342
+ # Sets the resolver timeouts. This may be a single positive number
343
+ # or an array of positive numbers representing timeouts in seconds.
344
+ # If an array is specified, a DNS request will retry and wait for
345
+ # each successive interval in the array until a successful response
346
+ # is received. Specifying +nil+ reverts to the default timeouts:
347
+ # [ 5, second = 5 * 2 / nameserver_count, 2 * second, 4 * second ]
348
+ #
349
+ # Example:
350
+ #
351
+ # dns.timeouts = 3
352
+ #
353
+ def timeouts=(values)
354
+ @config.timeouts = values
355
+ end
356
+
357
+ def lazy_initialize # :nodoc:
358
+ @mutex.synchronize {
359
+ unless @initialized
360
+ @config.lazy_initialize
361
+ @initialized = true
362
+ end
363
+ }
364
+ self
365
+ end
366
+
367
+ ##
368
+ # Closes the DNS resolver.
369
+
370
+ def close
371
+ @mutex.synchronize {
372
+ if @initialized
373
+ @initialized = false
374
+ end
375
+ }
376
+ end
377
+
378
+ ##
379
+ # Gets the IP address of +name+ from the DNS resolver.
380
+ #
381
+ # +name+ can be a Resolv::DNS::Name or a String. Retrieved address will
382
+ # be a Resolv::IPv4 or Resolv::IPv6
383
+
384
+ def getaddress(name)
385
+ each_address(name) {|address| return address}
386
+ raise ResolvError.new("DNS result has no information for #{name}")
387
+ end
388
+
389
+ ##
390
+ # Gets all IP addresses for +name+ from the DNS resolver.
391
+ #
392
+ # +name+ can be a Resolv::DNS::Name or a String. Retrieved addresses will
393
+ # be a Resolv::IPv4 or Resolv::IPv6
394
+
395
+ def getaddresses(name)
396
+ ret = []
397
+ each_address(name) {|address| ret << address}
398
+ return ret
399
+ end
400
+
401
+ ##
402
+ # Iterates over all IP addresses for +name+ retrieved from the DNS
403
+ # resolver.
404
+ #
405
+ # +name+ can be a Resolv::DNS::Name or a String. Retrieved addresses will
406
+ # be a Resolv::IPv4 or Resolv::IPv6
407
+
408
+ def each_address(name)
409
+ each_resource(name, Resource::IN::A) {|resource| yield resource.address}
410
+ if use_ipv6?
411
+ each_resource(name, Resource::IN::AAAA) {|resource| yield resource.address}
412
+ end
413
+ end
414
+
415
+ def use_ipv6? # :nodoc:
416
+ # This is a patch on the bug which has not fixed in the upstream "ruby/resolv" gem.
417
+ # ref. https://bugs.ruby-lang.org/issues/14922
418
+ true
419
+
420
+ # begin
421
+ # list = Socket.ip_address_list
422
+ # rescue NotImplementedError
423
+ # return true
424
+ # end
425
+ # list.any? {|a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? }
426
+ end
427
+ private :use_ipv6?
428
+
429
+ ##
430
+ # Gets the hostname for +address+ from the DNS resolver.
431
+ #
432
+ # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved
433
+ # name will be a Resolv::DNS::Name.
434
+
435
+ def getname(address)
436
+ each_name(address) {|name| return name}
437
+ raise ResolvError.new("DNS result has no information for #{address}")
438
+ end
439
+
440
+ ##
441
+ # Gets all hostnames for +address+ from the DNS resolver.
442
+ #
443
+ # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved
444
+ # names will be Resolv::DNS::Name instances.
445
+
446
+ def getnames(address)
447
+ ret = []
448
+ each_name(address) {|name| ret << name}
449
+ return ret
450
+ end
451
+
452
+ ##
453
+ # Iterates over all hostnames for +address+ retrieved from the DNS
454
+ # resolver.
455
+ #
456
+ # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved
457
+ # names will be Resolv::DNS::Name instances.
458
+
459
+ def each_name(address)
460
+ case address
461
+ when Name
462
+ ptr = address
463
+ when IPv4, IPv6
464
+ ptr = address.to_name
465
+ when IPv4::Regex
466
+ ptr = IPv4.create(address).to_name
467
+ when IPv6::Regex
468
+ ptr = IPv6.create(address).to_name
469
+ else
470
+ raise ResolvError.new("cannot interpret as address: #{address}")
471
+ end
472
+ each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name}
473
+ end
474
+
475
+ ##
476
+ # Look up the +typeclass+ DNS resource of +name+.
477
+ #
478
+ # +name+ must be a Resolv::DNS::Name or a String.
479
+ #
480
+ # +typeclass+ should be one of the following:
481
+ #
482
+ # * Resolv::DNS::Resource::IN::A
483
+ # * Resolv::DNS::Resource::IN::AAAA
484
+ # * Resolv::DNS::Resource::IN::ANY
485
+ # * Resolv::DNS::Resource::IN::CNAME
486
+ # * Resolv::DNS::Resource::IN::HINFO
487
+ # * Resolv::DNS::Resource::IN::MINFO
488
+ # * Resolv::DNS::Resource::IN::MX
489
+ # * Resolv::DNS::Resource::IN::NS
490
+ # * Resolv::DNS::Resource::IN::PTR
491
+ # * Resolv::DNS::Resource::IN::SOA
492
+ # * Resolv::DNS::Resource::IN::TXT
493
+ # * Resolv::DNS::Resource::IN::WKS
494
+ #
495
+ # Returned resource is represented as a Resolv::DNS::Resource instance,
496
+ # i.e. Resolv::DNS::Resource::IN::A.
497
+
498
+ def getresource(name, typeclass)
499
+ each_resource(name, typeclass) {|resource| return resource}
500
+ raise ResolvError.new("DNS result has no information for #{name}")
501
+ end
502
+
503
+ ##
504
+ # Looks up all +typeclass+ DNS resources for +name+. See #getresource for
505
+ # argument details.
506
+
507
+ def getresources(name, typeclass)
508
+ ret = []
509
+ each_resource(name, typeclass) {|resource| ret << resource}
510
+ return ret
511
+ end
512
+
513
+ ##
514
+ # Iterates over all +typeclass+ DNS resources for +name+. See
515
+ # #getresource for argument details.
516
+
517
+ def each_resource(name, typeclass, &proc)
518
+ fetch_resource(name, typeclass) {|reply, reply_name|
519
+ extract_resources(reply, reply_name, typeclass, &proc)
520
+ }
521
+ end
522
+
523
+ def fetch_resource(name, typeclass)
524
+ lazy_initialize
525
+ begin
526
+ requester = make_udp_requester
527
+ rescue Errno::EACCES
528
+ # fall back to TCP
529
+ end
530
+ senders = {}
531
+ begin
532
+ @config.resolv(name) {|candidate, tout, nameserver, port|
533
+ requester ||= make_tcp_requester(nameserver, port)
534
+ msg = Message.new
535
+ msg.rd = 1
536
+ msg.add_question(candidate, typeclass)
537
+ unless sender = senders[[candidate, nameserver, port]]
538
+ sender = requester.sender(msg, candidate, nameserver, port)
539
+ next if !sender
540
+ senders[[candidate, nameserver, port]] = sender
541
+ end
542
+ reply, reply_name = requester.request(sender, tout)
543
+ case reply.rcode
544
+ when RCode::NoError
545
+ if reply.tc == 1 and not Requester::TCP === requester
546
+ requester.close
547
+ # Retry via TCP:
548
+ requester = make_tcp_requester(nameserver, port)
549
+ senders = {}
550
+ # This will use TCP for all remaining candidates (assuming the
551
+ # current candidate does not already respond successfully via
552
+ # TCP). This makes sense because we already know the full
553
+ # response will not fit in an untruncated UDP packet.
554
+ redo
555
+ else
556
+ yield(reply, reply_name)
557
+ end
558
+ return
559
+ when RCode::NXDomain
560
+ raise Config::NXDomain.new(reply_name.to_s)
561
+ else
562
+ raise Config::OtherResolvError.new(reply_name.to_s)
563
+ end
564
+ }
565
+ ensure
566
+ requester&.close
567
+ end
568
+ end
569
+
570
+ def make_udp_requester # :nodoc:
571
+ nameserver_port = @config.nameserver_port
572
+ if nameserver_port.length == 1
573
+ Requester::ConnectedUDP.new(*nameserver_port[0])
574
+ else
575
+ Requester::UnconnectedUDP.new(*nameserver_port)
576
+ end
577
+ end
578
+
579
+ def make_tcp_requester(host, port) # :nodoc:
580
+ return Requester::TCP.new(host, port)
581
+ end
582
+
583
+ def extract_resources(msg, name, typeclass) # :nodoc:
584
+ if typeclass < Resource::ANY
585
+ n0 = Name.create(name)
586
+ msg.each_resource {|n, ttl, data|
587
+ yield data if n0 == n
588
+ }
589
+ end
590
+ yielded = false
591
+ n0 = Name.create(name)
592
+ msg.each_resource {|n, ttl, data|
593
+ if n0 == n
594
+ case data
595
+ when typeclass
596
+ yield data
597
+ yielded = true
598
+ when Resource::CNAME
599
+ n0 = data.name
600
+ end
601
+ end
602
+ }
603
+ return if yielded
604
+ msg.each_resource {|n, ttl, data|
605
+ if n0 == n
606
+ case data
607
+ when typeclass
608
+ yield data
609
+ end
610
+ end
611
+ }
612
+ end
613
+
614
+ if defined? SecureRandom
615
+ def self.random(arg) # :nodoc:
616
+ begin
617
+ SecureRandom.random_number(arg)
618
+ rescue NotImplementedError
619
+ rand(arg)
620
+ end
621
+ end
622
+ else
623
+ def self.random(arg) # :nodoc:
624
+ rand(arg)
625
+ end
626
+ end
627
+
628
+ RequestID = {} # :nodoc:
629
+ RequestIDMutex = Thread::Mutex.new # :nodoc:
630
+
631
+ def self.allocate_request_id(host, port) # :nodoc:
632
+ id = nil
633
+ RequestIDMutex.synchronize {
634
+ h = (RequestID[[host, port]] ||= {})
635
+ begin
636
+ id = random(0x0000..0xffff)
637
+ end while h[id]
638
+ h[id] = true
639
+ }
640
+ id
641
+ end
642
+
643
+ def self.free_request_id(host, port, id) # :nodoc:
644
+ RequestIDMutex.synchronize {
645
+ key = [host, port]
646
+ if h = RequestID[key]
647
+ h.delete id
648
+ if h.empty?
649
+ RequestID.delete key
650
+ end
651
+ end
652
+ }
653
+ end
654
+
655
+ def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc:
656
+ begin
657
+ port = random(1024..65535)
658
+ udpsock.bind(bind_host, port)
659
+ rescue Errno::EADDRINUSE, # POSIX
660
+ Errno::EACCES, # SunOS: See PRIV_SYS_NFS in privileges(5)
661
+ Errno::EPERM # FreeBSD: security.mac.portacl.port_high is configurable. See mac_portacl(4).
662
+ retry
663
+ end
664
+ end
665
+
666
+ class Requester # :nodoc:
667
+ def initialize
668
+ @senders = {}
669
+ @socks = nil
670
+ end
671
+
672
+ def request(sender, tout)
673
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
674
+ timelimit = start + tout
675
+ begin
676
+ sender.send
677
+ rescue Errno::EHOSTUNREACH, # multi-homed IPv6 may generate this
678
+ Errno::ENETUNREACH
679
+ raise ResolvTimeout
680
+ end
681
+
682
+ while true
683
+ before_select = Process.clock_gettime(Process::CLOCK_MONOTONIC)
684
+ timeout = timelimit - before_select
685
+ if timeout <= 0
686
+ raise ResolvTimeout
687
+ end
688
+
689
+ Fiber.yield @socks, timelimit
690
+ # Here, when this fiber is resumed, at least one of @socks is ready or it timed out.
691
+
692
+ # `wait_readable` cannot be used here because it uses FiberScheduler#io_wait internally.
693
+ select_result = IO.select(@socks, nil, nil, 0)
694
+
695
+ # Below is the original implementation of resolv gem.
696
+ #
697
+ # if @socks.size == 1
698
+ # select_result = @socks[0].wait_readable(timeout) ? [ @socks ] : nil
699
+ # else
700
+ # select_result = IO.select(@socks, nil, nil, timeout)
701
+ # end
702
+ if !select_result
703
+ after_select = Process.clock_gettime(Process::CLOCK_MONOTONIC)
704
+ next if after_select < timelimit
705
+ raise ResolvTimeout
706
+ end
707
+ begin
708
+ reply, from = recv_reply(select_result[0])
709
+ rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD
710
+ Errno::ECONNRESET # Windows
711
+ # No name server running on the server?
712
+ # Don't wait anymore.
713
+ raise ResolvTimeout
714
+ end
715
+ begin
716
+ msg = Message.decode(reply)
717
+ rescue DecodeError
718
+ next # broken DNS message ignored
719
+ end
720
+ if sender == sender_for(from, msg)
721
+ break
722
+ else
723
+ # unexpected DNS message ignored
724
+ end
725
+ end
726
+ return msg, sender.data
727
+ end
728
+
729
+ def sender_for(addr, msg)
730
+ @senders[[addr,msg.id]]
731
+ end
732
+
733
+ def close
734
+ socks = @socks
735
+ @socks = nil
736
+ socks&.each(&:close)
737
+ end
738
+
739
+ class Sender # :nodoc:
740
+ def initialize(msg, data, sock)
741
+ @msg = msg
742
+ @data = data
743
+ @sock = sock
744
+ end
745
+ end
746
+
747
+ class UnconnectedUDP < Requester # :nodoc:
748
+ def initialize(*nameserver_port)
749
+ super()
750
+ @nameserver_port = nameserver_port
751
+ @initialized = false
752
+ @mutex = Thread::Mutex.new
753
+ end
754
+
755
+ def lazy_initialize
756
+ @mutex.synchronize {
757
+ next if @initialized
758
+ @initialized = true
759
+ @socks_hash = {}
760
+ @socks = []
761
+ @nameserver_port.each {|host, port|
762
+ if host.index(':')
763
+ bind_host = "::"
764
+ af = Socket::AF_INET6
765
+ else
766
+ bind_host = "0.0.0.0"
767
+ af = Socket::AF_INET
768
+ end
769
+ next if @socks_hash[bind_host]
770
+ begin
771
+ sock = UDPSocket.new(af)
772
+ rescue Errno::EAFNOSUPPORT
773
+ next # The kernel doesn't support the address family.
774
+ end
775
+ @socks << sock
776
+ @socks_hash[bind_host] = sock
777
+ sock.do_not_reverse_lookup = true
778
+ DNS.bind_random_port(sock, bind_host)
779
+ }
780
+ }
781
+ self
782
+ end
783
+
784
+ def recv_reply(readable_socks)
785
+ lazy_initialize
786
+ reply, from = readable_socks[0].recvfrom(UDPSize)
787
+ return reply, [from[3],from[1]]
788
+ end
789
+
790
+ def sender(msg, data, host, port=Port)
791
+ host = Addrinfo.ip(host).ip_address
792
+ lazy_initialize
793
+ sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"]
794
+ return nil if !sock
795
+ service = [host, port]
796
+ id = DNS.allocate_request_id(host, port)
797
+ request = msg.encode
798
+ request[0,2] = [id].pack('n')
799
+ return @senders[[service, id]] =
800
+ Sender.new(request, data, sock, host, port)
801
+ end
802
+
803
+ def close
804
+ @mutex.synchronize {
805
+ if @initialized
806
+ super
807
+ @senders.each_key {|service, id|
808
+ DNS.free_request_id(service[0], service[1], id)
809
+ }
810
+ @initialized = false
811
+ end
812
+ }
813
+ end
814
+
815
+ class Sender < Requester::Sender # :nodoc:
816
+ def initialize(msg, data, sock, host, port)
817
+ super(msg, data, sock)
818
+ @host = host
819
+ @port = port
820
+ end
821
+ attr_reader :data
822
+
823
+ def send
824
+ raise "@sock is nil." if @sock.nil?
825
+ @sock.send(@msg, 0, @host, @port)
826
+ end
827
+ end
828
+ end
829
+
830
+ class ConnectedUDP < Requester # :nodoc:
831
+ def initialize(host, port=Port)
832
+ super()
833
+ @host = host
834
+ @port = port
835
+ @mutex = Thread::Mutex.new
836
+ @initialized = false
837
+ end
838
+
839
+ def lazy_initialize
840
+ @mutex.synchronize {
841
+ next if @initialized
842
+ @initialized = true
843
+ is_ipv6 = @host.index(':')
844
+ sock = UDPSocket.new(is_ipv6 ? Socket::AF_INET6 : Socket::AF_INET)
845
+ @socks = [sock]
846
+ sock.do_not_reverse_lookup = true
847
+ DNS.bind_random_port(sock, is_ipv6 ? "::" : "0.0.0.0")
848
+ sock.connect(@host, @port)
849
+ }
850
+ self
851
+ end
852
+
853
+ def recv_reply(readable_socks)
854
+ lazy_initialize
855
+ reply = readable_socks[0].recv(UDPSize)
856
+ return reply, nil
857
+ end
858
+
859
+ def sender(msg, data, host=@host, port=@port)
860
+ lazy_initialize
861
+ unless host == @host && port == @port
862
+ raise RequestError.new("host/port don't match: #{host}:#{port}")
863
+ end
864
+ id = DNS.allocate_request_id(@host, @port)
865
+ request = msg.encode
866
+ request[0,2] = [id].pack('n')
867
+ return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
868
+ end
869
+
870
+ def close
871
+ @mutex.synchronize do
872
+ if @initialized
873
+ super
874
+ @senders.each_key {|from, id|
875
+ DNS.free_request_id(@host, @port, id)
876
+ }
877
+ @initialized = false
878
+ end
879
+ end
880
+ end
881
+
882
+ class Sender < Requester::Sender # :nodoc:
883
+ def send
884
+ raise "@sock is nil." if @sock.nil?
885
+ @sock.send(@msg, 0)
886
+ end
887
+ attr_reader :data
888
+ end
889
+ end
890
+
891
+ class MDNSOneShot < UnconnectedUDP # :nodoc:
892
+ def sender(msg, data, host, port=Port)
893
+ lazy_initialize
894
+ id = DNS.allocate_request_id(host, port)
895
+ request = msg.encode
896
+ request[0,2] = [id].pack('n')
897
+ sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"]
898
+ return @senders[id] =
899
+ UnconnectedUDP::Sender.new(request, data, sock, host, port)
900
+ end
901
+
902
+ def sender_for(addr, msg)
903
+ lazy_initialize
904
+ @senders[msg.id]
905
+ end
906
+ end
907
+
908
+ class TCP < Requester # :nodoc:
909
+ def initialize(host, port=Port)
910
+ super()
911
+ @host = host
912
+ @port = port
913
+ sock = TCPSocket.new(@host, @port)
914
+ @socks = [sock]
915
+ @senders = {}
916
+ end
917
+
918
+ def recv_reply(readable_socks)
919
+ len = readable_socks[0].read(2).unpack('n')[0]
920
+ reply = @socks[0].read(len)
921
+ return reply, nil
922
+ end
923
+
924
+ def sender(msg, data, host=@host, port=@port)
925
+ unless host == @host && port == @port
926
+ raise RequestError.new("host/port don't match: #{host}:#{port}")
927
+ end
928
+ id = DNS.allocate_request_id(@host, @port)
929
+ request = msg.encode
930
+ request[0,2] = [request.length, id].pack('nn')
931
+ return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
932
+ end
933
+
934
+ class Sender < Requester::Sender # :nodoc:
935
+ def send
936
+ @sock.print(@msg)
937
+ @sock.flush
938
+ end
939
+ attr_reader :data
940
+ end
941
+
942
+ def close
943
+ super
944
+ @senders.each_key {|from,id|
945
+ DNS.free_request_id(@host, @port, id)
946
+ }
947
+ end
948
+ end
949
+
950
+ ##
951
+ # Indicates a problem with the DNS request.
952
+
953
+ class RequestError < StandardError
954
+ end
955
+ end
956
+
957
+ class Config # :nodoc:
958
+ def initialize(config_info=nil)
959
+ @mutex = Thread::Mutex.new
960
+ @config_info = config_info
961
+ @initialized = nil
962
+ @timeouts = nil
963
+ end
964
+
965
+ def timeouts=(values)
966
+ if values
967
+ values = Array(values)
968
+ values.each do |t|
969
+ Numeric === t or raise ArgumentError, "#{t.inspect} is not numeric"
970
+ t > 0.0 or raise ArgumentError, "timeout=#{t} must be positive"
971
+ end
972
+ @timeouts = values
973
+ else
974
+ @timeouts = nil
975
+ end
976
+ end
977
+
978
+ def Config.parse_resolv_conf(filename)
979
+ nameserver = []
980
+ search = nil
981
+ ndots = 1
982
+ File.open(filename, 'rb') {|f|
983
+ f.each {|line|
984
+ line.sub!(/[#;].*/, '')
985
+ keyword, *args = line.split(/\s+/)
986
+ next unless keyword
987
+ case keyword
988
+ when 'nameserver'
989
+ nameserver.concat(args)
990
+ when 'domain'
991
+ next if args.empty?
992
+ search = [args[0]]
993
+ when 'search'
994
+ next if args.empty?
995
+ search = args
996
+ when 'options'
997
+ args.each {|arg|
998
+ case arg
999
+ when /\Andots:(\d+)\z/
1000
+ ndots = $1.to_i
1001
+ end
1002
+ }
1003
+ end
1004
+ }
1005
+ }
1006
+ return { :nameserver => nameserver, :search => search, :ndots => ndots }
1007
+ end
1008
+
1009
+ def Config.default_config_hash(filename="/etc/resolv.conf")
1010
+ if File.exist? filename
1011
+ config_hash = Config.parse_resolv_conf(filename)
1012
+ else
1013
+ if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
1014
+ require 'win32/resolv'
1015
+ search, nameserver = Win32::Resolv.get_resolv_info
1016
+ config_hash = {}
1017
+ config_hash[:nameserver] = nameserver if nameserver
1018
+ config_hash[:search] = [search].flatten if search
1019
+ end
1020
+ end
1021
+ config_hash || {}
1022
+ end
1023
+
1024
+ def lazy_initialize
1025
+ @mutex.synchronize {
1026
+ unless @initialized
1027
+ @nameserver_port = []
1028
+ @search = nil
1029
+ @ndots = 1
1030
+ case @config_info
1031
+ when nil
1032
+ config_hash = Config.default_config_hash
1033
+ when String
1034
+ config_hash = Config.parse_resolv_conf(@config_info)
1035
+ when Hash
1036
+ config_hash = @config_info.dup
1037
+ if String === config_hash[:nameserver]
1038
+ config_hash[:nameserver] = [config_hash[:nameserver]]
1039
+ end
1040
+ if String === config_hash[:search]
1041
+ config_hash[:search] = [config_hash[:search]]
1042
+ end
1043
+ else
1044
+ raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}")
1045
+ end
1046
+ if config_hash.include? :nameserver
1047
+ @nameserver_port = config_hash[:nameserver].map {|ns| [ns, Port] }
1048
+ end
1049
+ if config_hash.include? :nameserver_port
1050
+ @nameserver_port = config_hash[:nameserver_port].map {|ns, port| [ns, (port || Port)] }
1051
+ end
1052
+ @search = config_hash[:search] if config_hash.include? :search
1053
+ @ndots = config_hash[:ndots] if config_hash.include? :ndots
1054
+
1055
+ if @nameserver_port.empty?
1056
+ @nameserver_port << ['0.0.0.0', Port]
1057
+ end
1058
+ if @search
1059
+ @search = @search.map {|arg| Label.split(arg) }
1060
+ else
1061
+ hostname = Socket.gethostname
1062
+ if /\./ =~ hostname
1063
+ @search = [Label.split($')]
1064
+ else
1065
+ @search = [[]]
1066
+ end
1067
+ end
1068
+
1069
+ if !@nameserver_port.kind_of?(Array) ||
1070
+ @nameserver_port.any? {|ns_port|
1071
+ !(Array === ns_port) ||
1072
+ ns_port.length != 2
1073
+ !(String === ns_port[0]) ||
1074
+ !(Integer === ns_port[1])
1075
+ }
1076
+ raise ArgumentError.new("invalid nameserver config: #{@nameserver_port.inspect}")
1077
+ end
1078
+
1079
+ if !@search.kind_of?(Array) ||
1080
+ !@search.all? {|ls| ls.all? {|l| Label::Str === l } }
1081
+ raise ArgumentError.new("invalid search config: #{@search.inspect}")
1082
+ end
1083
+
1084
+ if !@ndots.kind_of?(Integer)
1085
+ raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}")
1086
+ end
1087
+
1088
+ @initialized = true
1089
+ end
1090
+ }
1091
+ self
1092
+ end
1093
+
1094
+ def single?
1095
+ lazy_initialize
1096
+ if @nameserver_port.length == 1
1097
+ return @nameserver_port[0]
1098
+ else
1099
+ return nil
1100
+ end
1101
+ end
1102
+
1103
+ def nameserver_port
1104
+ @nameserver_port
1105
+ end
1106
+
1107
+ def generate_candidates(name)
1108
+ candidates = nil
1109
+ name = Name.create(name)
1110
+ if name.absolute?
1111
+ candidates = [name]
1112
+ else
1113
+ if @ndots <= name.length - 1
1114
+ candidates = [Name.new(name.to_a)]
1115
+ else
1116
+ candidates = []
1117
+ end
1118
+ candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)})
1119
+ fname = Name.create("#{name}.")
1120
+ if !candidates.include?(fname)
1121
+ candidates << fname
1122
+ end
1123
+ end
1124
+ return candidates
1125
+ end
1126
+
1127
+ InitialTimeout = 5
1128
+
1129
+ def generate_timeouts
1130
+ ts = [InitialTimeout]
1131
+ ts << ts[-1] * 2 / @nameserver_port.length
1132
+ ts << ts[-1] * 2
1133
+ ts << ts[-1] * 2
1134
+ return ts
1135
+ end
1136
+
1137
+ def resolv(name)
1138
+ candidates = generate_candidates(name)
1139
+ timeouts = @timeouts || generate_timeouts
1140
+ begin
1141
+ candidates.each {|candidate|
1142
+ begin
1143
+ timeouts.each {|tout|
1144
+ @nameserver_port.each {|nameserver, port|
1145
+ begin
1146
+ yield candidate, tout, nameserver, port
1147
+ rescue ResolvTimeout
1148
+ end
1149
+ }
1150
+ }
1151
+ raise ResolvError.new("DNS resolv timeout: #{name}")
1152
+ rescue NXDomain
1153
+ end
1154
+ }
1155
+ rescue ResolvError
1156
+ end
1157
+ end
1158
+
1159
+ ##
1160
+ # Indicates no such domain was found.
1161
+
1162
+ class NXDomain < ResolvError
1163
+ end
1164
+
1165
+ ##
1166
+ # Indicates some other unhandled resolver error was encountered.
1167
+
1168
+ class OtherResolvError < ResolvError
1169
+ end
1170
+ end
1171
+
1172
+ module OpCode # :nodoc:
1173
+ Query = 0
1174
+ IQuery = 1
1175
+ Status = 2
1176
+ Notify = 4
1177
+ Update = 5
1178
+ end
1179
+
1180
+ module RCode # :nodoc:
1181
+ NoError = 0
1182
+ FormErr = 1
1183
+ ServFail = 2
1184
+ NXDomain = 3
1185
+ NotImp = 4
1186
+ Refused = 5
1187
+ YXDomain = 6
1188
+ YXRRSet = 7
1189
+ NXRRSet = 8
1190
+ NotAuth = 9
1191
+ NotZone = 10
1192
+ BADVERS = 16
1193
+ BADSIG = 16
1194
+ BADKEY = 17
1195
+ BADTIME = 18
1196
+ BADMODE = 19
1197
+ BADNAME = 20
1198
+ BADALG = 21
1199
+ end
1200
+
1201
+ ##
1202
+ # Indicates that the DNS response was unable to be decoded.
1203
+
1204
+ class DecodeError < StandardError
1205
+ end
1206
+
1207
+ ##
1208
+ # Indicates that the DNS request was unable to be encoded.
1209
+
1210
+ class EncodeError < StandardError
1211
+ end
1212
+
1213
+ module Label # :nodoc:
1214
+ def self.split(arg)
1215
+ labels = []
1216
+ arg.scan(/[^\.]+/) {labels << Str.new($&)}
1217
+ return labels
1218
+ end
1219
+
1220
+ class Str # :nodoc:
1221
+ def initialize(string)
1222
+ @string = string
1223
+ # case insensivity of DNS labels doesn't apply non-ASCII characters. [RFC 4343]
1224
+ # This assumes @string is given in ASCII compatible encoding.
1225
+ @downcase = string.b.downcase
1226
+ end
1227
+ attr_reader :string, :downcase
1228
+
1229
+ def to_s
1230
+ return @string
1231
+ end
1232
+
1233
+ def inspect
1234
+ return "#<#{self.class} #{self}>"
1235
+ end
1236
+
1237
+ def ==(other)
1238
+ return self.class == other.class && @downcase == other.downcase
1239
+ end
1240
+
1241
+ def eql?(other)
1242
+ return self == other
1243
+ end
1244
+
1245
+ def hash
1246
+ return @downcase.hash
1247
+ end
1248
+ end
1249
+ end
1250
+
1251
+ ##
1252
+ # A representation of a DNS name.
1253
+
1254
+ class Name
1255
+
1256
+ ##
1257
+ # Creates a new DNS name from +arg+. +arg+ can be:
1258
+ #
1259
+ # Name:: returns +arg+.
1260
+ # String:: Creates a new Name.
1261
+
1262
+ def self.create(arg)
1263
+ case arg
1264
+ when Name
1265
+ return arg
1266
+ when String
1267
+ return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
1268
+ else
1269
+ raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
1270
+ end
1271
+ end
1272
+
1273
+ def initialize(labels, absolute=true) # :nodoc:
1274
+ labels = labels.map {|label|
1275
+ case label
1276
+ when String then Label::Str.new(label)
1277
+ when Label::Str then label
1278
+ else
1279
+ raise ArgumentError, "unexpected label: #{label.inspect}"
1280
+ end
1281
+ }
1282
+ @labels = labels
1283
+ @absolute = absolute
1284
+ end
1285
+
1286
+ def inspect # :nodoc:
1287
+ "#<#{self.class}: #{self}#{@absolute ? '.' : ''}>"
1288
+ end
1289
+
1290
+ ##
1291
+ # True if this name is absolute.
1292
+
1293
+ def absolute?
1294
+ return @absolute
1295
+ end
1296
+
1297
+ def ==(other) # :nodoc:
1298
+ return false unless Name === other
1299
+ return false unless @absolute == other.absolute?
1300
+ return @labels == other.to_a
1301
+ end
1302
+
1303
+ alias eql? == # :nodoc:
1304
+
1305
+ ##
1306
+ # Returns true if +other+ is a subdomain.
1307
+ #
1308
+ # Example:
1309
+ #
1310
+ # domain = Resolv::DNS::Name.create("y.z")
1311
+ # p Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true
1312
+ # p Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true
1313
+ # p Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false
1314
+ # p Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false
1315
+ # p Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false
1316
+ # p Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false
1317
+ #
1318
+
1319
+ def subdomain_of?(other)
1320
+ raise ArgumentError, "not a domain name: #{other.inspect}" unless Name === other
1321
+ return false if @absolute != other.absolute?
1322
+ other_len = other.length
1323
+ return false if @labels.length <= other_len
1324
+ return @labels[-other_len, other_len] == other.to_a
1325
+ end
1326
+
1327
+ def hash # :nodoc:
1328
+ return @labels.hash ^ @absolute.hash
1329
+ end
1330
+
1331
+ def to_a # :nodoc:
1332
+ return @labels
1333
+ end
1334
+
1335
+ def length # :nodoc:
1336
+ return @labels.length
1337
+ end
1338
+
1339
+ def [](i) # :nodoc:
1340
+ return @labels[i]
1341
+ end
1342
+
1343
+ ##
1344
+ # returns the domain name as a string.
1345
+ #
1346
+ # The domain name doesn't have a trailing dot even if the name object is
1347
+ # absolute.
1348
+ #
1349
+ # Example:
1350
+ #
1351
+ # p Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z"
1352
+ # p Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z"
1353
+
1354
+ def to_s
1355
+ return @labels.join('.')
1356
+ end
1357
+ end
1358
+
1359
+ class Message # :nodoc:
1360
+ @@identifier = -1
1361
+
1362
+ def initialize(id = (@@identifier += 1) & 0xffff)
1363
+ @id = id
1364
+ @qr = 0
1365
+ @opcode = 0
1366
+ @aa = 0
1367
+ @tc = 0
1368
+ @rd = 0 # recursion desired
1369
+ @ra = 0 # recursion available
1370
+ @rcode = 0
1371
+ @question = []
1372
+ @answer = []
1373
+ @authority = []
1374
+ @additional = []
1375
+ end
1376
+
1377
+ attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode
1378
+ attr_reader :question, :answer, :authority, :additional
1379
+
1380
+ def ==(other)
1381
+ return @id == other.id &&
1382
+ @qr == other.qr &&
1383
+ @opcode == other.opcode &&
1384
+ @aa == other.aa &&
1385
+ @tc == other.tc &&
1386
+ @rd == other.rd &&
1387
+ @ra == other.ra &&
1388
+ @rcode == other.rcode &&
1389
+ @question == other.question &&
1390
+ @answer == other.answer &&
1391
+ @authority == other.authority &&
1392
+ @additional == other.additional
1393
+ end
1394
+
1395
+ def add_question(name, typeclass)
1396
+ @question << [Name.create(name), typeclass]
1397
+ end
1398
+
1399
+ def each_question
1400
+ @question.each {|name, typeclass|
1401
+ yield name, typeclass
1402
+ }
1403
+ end
1404
+
1405
+ def add_answer(name, ttl, data)
1406
+ @answer << [Name.create(name), ttl, data]
1407
+ end
1408
+
1409
+ def each_answer
1410
+ @answer.each {|name, ttl, data|
1411
+ yield name, ttl, data
1412
+ }
1413
+ end
1414
+
1415
+ def add_authority(name, ttl, data)
1416
+ @authority << [Name.create(name), ttl, data]
1417
+ end
1418
+
1419
+ def each_authority
1420
+ @authority.each {|name, ttl, data|
1421
+ yield name, ttl, data
1422
+ }
1423
+ end
1424
+
1425
+ def add_additional(name, ttl, data)
1426
+ @additional << [Name.create(name), ttl, data]
1427
+ end
1428
+
1429
+ def each_additional
1430
+ @additional.each {|name, ttl, data|
1431
+ yield name, ttl, data
1432
+ }
1433
+ end
1434
+
1435
+ def each_resource
1436
+ each_answer {|name, ttl, data| yield name, ttl, data}
1437
+ each_authority {|name, ttl, data| yield name, ttl, data}
1438
+ each_additional {|name, ttl, data| yield name, ttl, data}
1439
+ end
1440
+
1441
+ def encode
1442
+ return MessageEncoder.new {|msg|
1443
+ msg.put_pack('nnnnnn',
1444
+ @id,
1445
+ (@qr & 1) << 15 |
1446
+ (@opcode & 15) << 11 |
1447
+ (@aa & 1) << 10 |
1448
+ (@tc & 1) << 9 |
1449
+ (@rd & 1) << 8 |
1450
+ (@ra & 1) << 7 |
1451
+ (@rcode & 15),
1452
+ @question.length,
1453
+ @answer.length,
1454
+ @authority.length,
1455
+ @additional.length)
1456
+ @question.each {|q|
1457
+ name, typeclass = q
1458
+ msg.put_name(name)
1459
+ msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue)
1460
+ }
1461
+ [@answer, @authority, @additional].each {|rr|
1462
+ rr.each {|r|
1463
+ name, ttl, data = r
1464
+ msg.put_name(name)
1465
+ msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl)
1466
+ msg.put_length16 {data.encode_rdata(msg)}
1467
+ }
1468
+ }
1469
+ }.to_s
1470
+ end
1471
+
1472
+ class MessageEncoder # :nodoc:
1473
+ def initialize
1474
+ @data = ''.dup
1475
+ @names = {}
1476
+ yield self
1477
+ end
1478
+
1479
+ def to_s
1480
+ return @data
1481
+ end
1482
+
1483
+ def put_bytes(d)
1484
+ @data << d
1485
+ end
1486
+
1487
+ def put_pack(template, *d)
1488
+ @data << d.pack(template)
1489
+ end
1490
+
1491
+ def put_length16
1492
+ length_index = @data.length
1493
+ @data << "\0\0"
1494
+ data_start = @data.length
1495
+ yield
1496
+ data_end = @data.length
1497
+ @data[length_index, 2] = [data_end - data_start].pack("n")
1498
+ end
1499
+
1500
+ def put_string(d)
1501
+ self.put_pack("C", d.length)
1502
+ @data << d
1503
+ end
1504
+
1505
+ def put_string_list(ds)
1506
+ ds.each {|d|
1507
+ self.put_string(d)
1508
+ }
1509
+ end
1510
+
1511
+ def put_name(d, compress: true)
1512
+ put_labels(d.to_a, compress: compress)
1513
+ end
1514
+
1515
+ def put_labels(d, compress: true)
1516
+ d.each_index {|i|
1517
+ domain = d[i..-1]
1518
+ if compress && idx = @names[domain]
1519
+ self.put_pack("n", 0xc000 | idx)
1520
+ return
1521
+ else
1522
+ if @data.length < 0x4000
1523
+ @names[domain] = @data.length
1524
+ end
1525
+ self.put_label(d[i])
1526
+ end
1527
+ }
1528
+ @data << "\0"
1529
+ end
1530
+
1531
+ def put_label(d)
1532
+ self.put_string(d.to_s)
1533
+ end
1534
+ end
1535
+
1536
+ def Message.decode(m)
1537
+ o = Message.new(0)
1538
+ MessageDecoder.new(m) {|msg|
1539
+ id, flag, qdcount, ancount, nscount, arcount =
1540
+ msg.get_unpack('nnnnnn')
1541
+ o.id = id
1542
+ o.qr = (flag >> 15) & 1
1543
+ o.opcode = (flag >> 11) & 15
1544
+ o.aa = (flag >> 10) & 1
1545
+ o.tc = (flag >> 9) & 1
1546
+ o.rd = (flag >> 8) & 1
1547
+ o.ra = (flag >> 7) & 1
1548
+ o.rcode = flag & 15
1549
+ (1..qdcount).each {
1550
+ name, typeclass = msg.get_question
1551
+ o.add_question(name, typeclass)
1552
+ }
1553
+ (1..ancount).each {
1554
+ name, ttl, data = msg.get_rr
1555
+ o.add_answer(name, ttl, data)
1556
+ }
1557
+ (1..nscount).each {
1558
+ name, ttl, data = msg.get_rr
1559
+ o.add_authority(name, ttl, data)
1560
+ }
1561
+ (1..arcount).each {
1562
+ name, ttl, data = msg.get_rr
1563
+ o.add_additional(name, ttl, data)
1564
+ }
1565
+ }
1566
+ return o
1567
+ end
1568
+
1569
+ class MessageDecoder # :nodoc:
1570
+ def initialize(data)
1571
+ @data = data
1572
+ @index = 0
1573
+ @limit = data.bytesize
1574
+ yield self
1575
+ end
1576
+
1577
+ def inspect
1578
+ "\#<#{self.class}: #{@data.byteslice(0, @index).inspect} #{@data.byteslice(@index..-1).inspect}>"
1579
+ end
1580
+
1581
+ def get_length16
1582
+ len, = self.get_unpack('n')
1583
+ save_limit = @limit
1584
+ @limit = @index + len
1585
+ d = yield(len)
1586
+ if @index < @limit
1587
+ raise DecodeError.new("junk exists")
1588
+ elsif @limit < @index
1589
+ raise DecodeError.new("limit exceeded")
1590
+ end
1591
+ @limit = save_limit
1592
+ return d
1593
+ end
1594
+
1595
+ def get_bytes(len = @limit - @index)
1596
+ raise DecodeError.new("limit exceeded") if @limit < @index + len
1597
+ d = @data.byteslice(@index, len)
1598
+ @index += len
1599
+ return d
1600
+ end
1601
+
1602
+ def get_unpack(template)
1603
+ len = 0
1604
+ template.each_byte {|byte|
1605
+ byte = "%c" % byte
1606
+ case byte
1607
+ when ?c, ?C
1608
+ len += 1
1609
+ when ?n
1610
+ len += 2
1611
+ when ?N
1612
+ len += 4
1613
+ else
1614
+ raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'")
1615
+ end
1616
+ }
1617
+ raise DecodeError.new("limit exceeded") if @limit < @index + len
1618
+ arr = @data.unpack("@#{@index}#{template}")
1619
+ @index += len
1620
+ return arr
1621
+ end
1622
+
1623
+ def get_string
1624
+ raise DecodeError.new("limit exceeded") if @limit <= @index
1625
+ len = @data.getbyte(@index)
1626
+ raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len
1627
+ d = @data.byteslice(@index + 1, len)
1628
+ @index += 1 + len
1629
+ return d
1630
+ end
1631
+
1632
+ def get_string_list
1633
+ strings = []
1634
+ while @index < @limit
1635
+ strings << self.get_string
1636
+ end
1637
+ strings
1638
+ end
1639
+
1640
+ def get_name
1641
+ return Name.new(self.get_labels)
1642
+ end
1643
+
1644
+ def get_labels
1645
+ prev_index = @index
1646
+ save_index = nil
1647
+ d = []
1648
+ while true
1649
+ raise DecodeError.new("limit exceeded") if @limit <= @index
1650
+ case @data.getbyte(@index)
1651
+ when 0
1652
+ @index += 1
1653
+ if save_index
1654
+ @index = save_index
1655
+ end
1656
+ return d
1657
+ when 192..255
1658
+ idx = self.get_unpack('n')[0] & 0x3fff
1659
+ if prev_index <= idx
1660
+ raise DecodeError.new("non-backward name pointer")
1661
+ end
1662
+ prev_index = idx
1663
+ if !save_index
1664
+ save_index = @index
1665
+ end
1666
+ @index = idx
1667
+ else
1668
+ d << self.get_label
1669
+ end
1670
+ end
1671
+ end
1672
+
1673
+ def get_label
1674
+ return Label::Str.new(self.get_string)
1675
+ end
1676
+
1677
+ def get_question
1678
+ name = self.get_name
1679
+ type, klass = self.get_unpack("nn")
1680
+ return name, Resource.get_class(type, klass)
1681
+ end
1682
+
1683
+ def get_rr
1684
+ name = self.get_name
1685
+ type, klass, ttl = self.get_unpack('nnN')
1686
+ typeclass = Resource.get_class(type, klass)
1687
+ res = self.get_length16 do
1688
+ begin
1689
+ typeclass.decode_rdata self
1690
+ rescue => e
1691
+ raise DecodeError, e.message, e.backtrace
1692
+ end
1693
+ end
1694
+ res.instance_variable_set :@ttl, ttl
1695
+ return name, ttl, res
1696
+ end
1697
+ end
1698
+ end
1699
+
1700
+ ##
1701
+ # A DNS query abstract class.
1702
+
1703
+ class Query
1704
+ def encode_rdata(msg) # :nodoc:
1705
+ raise EncodeError.new("#{self.class} is query.")
1706
+ end
1707
+
1708
+ def self.decode_rdata(msg) # :nodoc:
1709
+ raise DecodeError.new("#{self.class} is query.")
1710
+ end
1711
+ end
1712
+
1713
+ ##
1714
+ # A DNS resource abstract class.
1715
+
1716
+ class Resource < Query
1717
+
1718
+ ##
1719
+ # Remaining Time To Live for this Resource.
1720
+
1721
+ attr_reader :ttl
1722
+
1723
+ ClassHash = {} # :nodoc:
1724
+
1725
+ def encode_rdata(msg) # :nodoc:
1726
+ raise NotImplementedError.new
1727
+ end
1728
+
1729
+ def self.decode_rdata(msg) # :nodoc:
1730
+ raise NotImplementedError.new
1731
+ end
1732
+
1733
+ def ==(other) # :nodoc:
1734
+ return false unless self.class == other.class
1735
+ s_ivars = self.instance_variables
1736
+ s_ivars.sort!
1737
+ s_ivars.delete :@ttl
1738
+ o_ivars = other.instance_variables
1739
+ o_ivars.sort!
1740
+ o_ivars.delete :@ttl
1741
+ return s_ivars == o_ivars &&
1742
+ s_ivars.collect {|name| self.instance_variable_get name} ==
1743
+ o_ivars.collect {|name| other.instance_variable_get name}
1744
+ end
1745
+
1746
+ def eql?(other) # :nodoc:
1747
+ return self == other
1748
+ end
1749
+
1750
+ def hash # :nodoc:
1751
+ h = 0
1752
+ vars = self.instance_variables
1753
+ vars.delete :@ttl
1754
+ vars.each {|name|
1755
+ h ^= self.instance_variable_get(name).hash
1756
+ }
1757
+ return h
1758
+ end
1759
+
1760
+ def self.get_class(type_value, class_value) # :nodoc:
1761
+ return ClassHash[[type_value, class_value]] ||
1762
+ Generic.create(type_value, class_value)
1763
+ end
1764
+
1765
+ ##
1766
+ # A generic resource abstract class.
1767
+
1768
+ class Generic < Resource
1769
+
1770
+ ##
1771
+ # Creates a new generic resource.
1772
+
1773
+ def initialize(data)
1774
+ @data = data
1775
+ end
1776
+
1777
+ ##
1778
+ # Data for this generic resource.
1779
+
1780
+ attr_reader :data
1781
+
1782
+ def encode_rdata(msg) # :nodoc:
1783
+ msg.put_bytes(data)
1784
+ end
1785
+
1786
+ def self.decode_rdata(msg) # :nodoc:
1787
+ return self.new(msg.get_bytes)
1788
+ end
1789
+
1790
+ def self.create(type_value, class_value) # :nodoc:
1791
+ c = Class.new(Generic)
1792
+ c.const_set(:TypeValue, type_value)
1793
+ c.const_set(:ClassValue, class_value)
1794
+ Generic.const_set("Type#{type_value}_Class#{class_value}", c)
1795
+ ClassHash[[type_value, class_value]] = c
1796
+ return c
1797
+ end
1798
+ end
1799
+
1800
+ ##
1801
+ # Domain Name resource abstract class.
1802
+
1803
+ class DomainName < Resource
1804
+
1805
+ ##
1806
+ # Creates a new DomainName from +name+.
1807
+
1808
+ def initialize(name)
1809
+ @name = name
1810
+ end
1811
+
1812
+ ##
1813
+ # The name of this DomainName.
1814
+
1815
+ attr_reader :name
1816
+
1817
+ def encode_rdata(msg) # :nodoc:
1818
+ msg.put_name(@name)
1819
+ end
1820
+
1821
+ def self.decode_rdata(msg) # :nodoc:
1822
+ return self.new(msg.get_name)
1823
+ end
1824
+ end
1825
+
1826
+ # Standard (class generic) RRs
1827
+
1828
+ ClassValue = nil # :nodoc:
1829
+
1830
+ ##
1831
+ # An authoritative name server.
1832
+
1833
+ class NS < DomainName
1834
+ TypeValue = 2 # :nodoc:
1835
+ end
1836
+
1837
+ ##
1838
+ # The canonical name for an alias.
1839
+
1840
+ class CNAME < DomainName
1841
+ TypeValue = 5 # :nodoc:
1842
+ end
1843
+
1844
+ ##
1845
+ # Start Of Authority resource.
1846
+
1847
+ class SOA < Resource
1848
+
1849
+ TypeValue = 6 # :nodoc:
1850
+
1851
+ ##
1852
+ # Creates a new SOA record. See the attr documentation for the
1853
+ # details of each argument.
1854
+
1855
+ def initialize(mname, rname, serial, refresh, retry_, expire, minimum)
1856
+ @mname = mname
1857
+ @rname = rname
1858
+ @serial = serial
1859
+ @refresh = refresh
1860
+ @retry = retry_
1861
+ @expire = expire
1862
+ @minimum = minimum
1863
+ end
1864
+
1865
+ ##
1866
+ # Name of the host where the master zone file for this zone resides.
1867
+
1868
+ attr_reader :mname
1869
+
1870
+ ##
1871
+ # The person responsible for this domain name.
1872
+
1873
+ attr_reader :rname
1874
+
1875
+ ##
1876
+ # The version number of the zone file.
1877
+
1878
+ attr_reader :serial
1879
+
1880
+ ##
1881
+ # How often, in seconds, a secondary name server is to check for
1882
+ # updates from the primary name server.
1883
+
1884
+ attr_reader :refresh
1885
+
1886
+ ##
1887
+ # How often, in seconds, a secondary name server is to retry after a
1888
+ # failure to check for a refresh.
1889
+
1890
+ attr_reader :retry
1891
+
1892
+ ##
1893
+ # Time in seconds that a secondary name server is to use the data
1894
+ # before refreshing from the primary name server.
1895
+
1896
+ attr_reader :expire
1897
+
1898
+ ##
1899
+ # The minimum number of seconds to be used for TTL values in RRs.
1900
+
1901
+ attr_reader :minimum
1902
+
1903
+ def encode_rdata(msg) # :nodoc:
1904
+ msg.put_name(@mname)
1905
+ msg.put_name(@rname)
1906
+ msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum)
1907
+ end
1908
+
1909
+ def self.decode_rdata(msg) # :nodoc:
1910
+ mname = msg.get_name
1911
+ rname = msg.get_name
1912
+ serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN')
1913
+ return self.new(
1914
+ mname, rname, serial, refresh, retry_, expire, minimum)
1915
+ end
1916
+ end
1917
+
1918
+ ##
1919
+ # A Pointer to another DNS name.
1920
+
1921
+ class PTR < DomainName
1922
+ TypeValue = 12 # :nodoc:
1923
+ end
1924
+
1925
+ ##
1926
+ # Host Information resource.
1927
+
1928
+ class HINFO < Resource
1929
+
1930
+ TypeValue = 13 # :nodoc:
1931
+
1932
+ ##
1933
+ # Creates a new HINFO running +os+ on +cpu+.
1934
+
1935
+ def initialize(cpu, os)
1936
+ @cpu = cpu
1937
+ @os = os
1938
+ end
1939
+
1940
+ ##
1941
+ # CPU architecture for this resource.
1942
+
1943
+ attr_reader :cpu
1944
+
1945
+ ##
1946
+ # Operating system for this resource.
1947
+
1948
+ attr_reader :os
1949
+
1950
+ def encode_rdata(msg) # :nodoc:
1951
+ msg.put_string(@cpu)
1952
+ msg.put_string(@os)
1953
+ end
1954
+
1955
+ def self.decode_rdata(msg) # :nodoc:
1956
+ cpu = msg.get_string
1957
+ os = msg.get_string
1958
+ return self.new(cpu, os)
1959
+ end
1960
+ end
1961
+
1962
+ ##
1963
+ # Mailing list or mailbox information.
1964
+
1965
+ class MINFO < Resource
1966
+
1967
+ TypeValue = 14 # :nodoc:
1968
+
1969
+ def initialize(rmailbx, emailbx)
1970
+ @rmailbx = rmailbx
1971
+ @emailbx = emailbx
1972
+ end
1973
+
1974
+ ##
1975
+ # Domain name responsible for this mail list or mailbox.
1976
+
1977
+ attr_reader :rmailbx
1978
+
1979
+ ##
1980
+ # Mailbox to use for error messages related to the mail list or mailbox.
1981
+
1982
+ attr_reader :emailbx
1983
+
1984
+ def encode_rdata(msg) # :nodoc:
1985
+ msg.put_name(@rmailbx)
1986
+ msg.put_name(@emailbx)
1987
+ end
1988
+
1989
+ def self.decode_rdata(msg) # :nodoc:
1990
+ rmailbx = msg.get_string
1991
+ emailbx = msg.get_string
1992
+ return self.new(rmailbx, emailbx)
1993
+ end
1994
+ end
1995
+
1996
+ ##
1997
+ # Mail Exchanger resource.
1998
+
1999
+ class MX < Resource
2000
+
2001
+ TypeValue= 15 # :nodoc:
2002
+
2003
+ ##
2004
+ # Creates a new MX record with +preference+, accepting mail at
2005
+ # +exchange+.
2006
+
2007
+ def initialize(preference, exchange)
2008
+ @preference = preference
2009
+ @exchange = exchange
2010
+ end
2011
+
2012
+ ##
2013
+ # The preference for this MX.
2014
+
2015
+ attr_reader :preference
2016
+
2017
+ ##
2018
+ # The host of this MX.
2019
+
2020
+ attr_reader :exchange
2021
+
2022
+ def encode_rdata(msg) # :nodoc:
2023
+ msg.put_pack('n', @preference)
2024
+ msg.put_name(@exchange)
2025
+ end
2026
+
2027
+ def self.decode_rdata(msg) # :nodoc:
2028
+ preference, = msg.get_unpack('n')
2029
+ exchange = msg.get_name
2030
+ return self.new(preference, exchange)
2031
+ end
2032
+ end
2033
+
2034
+ ##
2035
+ # Unstructured text resource.
2036
+
2037
+ class TXT < Resource
2038
+
2039
+ TypeValue = 16 # :nodoc:
2040
+
2041
+ def initialize(first_string, *rest_strings)
2042
+ @strings = [first_string, *rest_strings]
2043
+ end
2044
+
2045
+ ##
2046
+ # Returns an Array of Strings for this TXT record.
2047
+
2048
+ attr_reader :strings
2049
+
2050
+ ##
2051
+ # Returns the concatenated string from +strings+.
2052
+
2053
+ def data
2054
+ @strings.join("")
2055
+ end
2056
+
2057
+ def encode_rdata(msg) # :nodoc:
2058
+ msg.put_string_list(@strings)
2059
+ end
2060
+
2061
+ def self.decode_rdata(msg) # :nodoc:
2062
+ strings = msg.get_string_list
2063
+ return self.new(*strings)
2064
+ end
2065
+ end
2066
+
2067
+ ##
2068
+ # Location resource
2069
+
2070
+ class LOC < Resource
2071
+
2072
+ TypeValue = 29 # :nodoc:
2073
+
2074
+ def initialize(version, ssize, hprecision, vprecision, latitude, longitude, altitude)
2075
+ @version = version
2076
+ @ssize = Resolv::LOC::Size.create(ssize)
2077
+ @hprecision = Resolv::LOC::Size.create(hprecision)
2078
+ @vprecision = Resolv::LOC::Size.create(vprecision)
2079
+ @latitude = Resolv::LOC::Coord.create(latitude)
2080
+ @longitude = Resolv::LOC::Coord.create(longitude)
2081
+ @altitude = Resolv::LOC::Alt.create(altitude)
2082
+ end
2083
+
2084
+ ##
2085
+ # Returns the version value for this LOC record which should always be 00
2086
+
2087
+ attr_reader :version
2088
+
2089
+ ##
2090
+ # The spherical size of this LOC
2091
+ # in meters using scientific notation as 2 integers of XeY
2092
+
2093
+ attr_reader :ssize
2094
+
2095
+ ##
2096
+ # The horizontal precision using ssize type values
2097
+ # in meters using scientific notation as 2 integers of XeY
2098
+ # for precision use value/2 e.g. 2m = +/-1m
2099
+
2100
+ attr_reader :hprecision
2101
+
2102
+ ##
2103
+ # The vertical precision using ssize type values
2104
+ # in meters using scientific notation as 2 integers of XeY
2105
+ # for precision use value/2 e.g. 2m = +/-1m
2106
+
2107
+ attr_reader :vprecision
2108
+
2109
+ ##
2110
+ # The latitude for this LOC where 2**31 is the equator
2111
+ # in thousandths of an arc second as an unsigned 32bit integer
2112
+
2113
+ attr_reader :latitude
2114
+
2115
+ ##
2116
+ # The longitude for this LOC where 2**31 is the prime meridian
2117
+ # in thousandths of an arc second as an unsigned 32bit integer
2118
+
2119
+ attr_reader :longitude
2120
+
2121
+ ##
2122
+ # The altitude of the LOC above a reference sphere whose surface sits 100km below the WGS84 spheroid
2123
+ # in centimeters as an unsigned 32bit integer
2124
+
2125
+ attr_reader :altitude
2126
+
2127
+
2128
+ def encode_rdata(msg) # :nodoc:
2129
+ msg.put_bytes(@version)
2130
+ msg.put_bytes(@ssize.scalar)
2131
+ msg.put_bytes(@hprecision.scalar)
2132
+ msg.put_bytes(@vprecision.scalar)
2133
+ msg.put_bytes(@latitude.coordinates)
2134
+ msg.put_bytes(@longitude.coordinates)
2135
+ msg.put_bytes(@altitude.altitude)
2136
+ end
2137
+
2138
+ def self.decode_rdata(msg) # :nodoc:
2139
+ version = msg.get_bytes(1)
2140
+ ssize = msg.get_bytes(1)
2141
+ hprecision = msg.get_bytes(1)
2142
+ vprecision = msg.get_bytes(1)
2143
+ latitude = msg.get_bytes(4)
2144
+ longitude = msg.get_bytes(4)
2145
+ altitude = msg.get_bytes(4)
2146
+ return self.new(
2147
+ version,
2148
+ Resolv::LOC::Size.new(ssize),
2149
+ Resolv::LOC::Size.new(hprecision),
2150
+ Resolv::LOC::Size.new(vprecision),
2151
+ Resolv::LOC::Coord.new(latitude,"lat"),
2152
+ Resolv::LOC::Coord.new(longitude,"lon"),
2153
+ Resolv::LOC::Alt.new(altitude)
2154
+ )
2155
+ end
2156
+ end
2157
+
2158
+ ##
2159
+ # A Query type requesting any RR.
2160
+
2161
+ class ANY < Query
2162
+ TypeValue = 255 # :nodoc:
2163
+ end
2164
+
2165
+ ClassInsensitiveTypes = [ # :nodoc:
2166
+ NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, LOC, ANY
2167
+ ]
2168
+
2169
+ ##
2170
+ # module IN contains ARPA Internet specific RRs.
2171
+
2172
+ module IN
2173
+
2174
+ ClassValue = 1 # :nodoc:
2175
+
2176
+ ClassInsensitiveTypes.each {|s|
2177
+ c = Class.new(s)
2178
+ c.const_set(:TypeValue, s::TypeValue)
2179
+ c.const_set(:ClassValue, ClassValue)
2180
+ ClassHash[[s::TypeValue, ClassValue]] = c
2181
+ self.const_set(s.name.sub(/.*::/, ''), c)
2182
+ }
2183
+
2184
+ ##
2185
+ # IPv4 Address resource
2186
+
2187
+ class A < Resource
2188
+ TypeValue = 1
2189
+ ClassValue = IN::ClassValue
2190
+ ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
2191
+
2192
+ ##
2193
+ # Creates a new A for +address+.
2194
+
2195
+ def initialize(address)
2196
+ @address = IPv4.create(address)
2197
+ end
2198
+
2199
+ ##
2200
+ # The Resolv::IPv4 address for this A.
2201
+
2202
+ attr_reader :address
2203
+
2204
+ def encode_rdata(msg) # :nodoc:
2205
+ msg.put_bytes(@address.address)
2206
+ end
2207
+
2208
+ def self.decode_rdata(msg) # :nodoc:
2209
+ return self.new(IPv4.new(msg.get_bytes(4)))
2210
+ end
2211
+ end
2212
+
2213
+ ##
2214
+ # Well Known Service resource.
2215
+
2216
+ class WKS < Resource
2217
+ TypeValue = 11
2218
+ ClassValue = IN::ClassValue
2219
+ ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
2220
+
2221
+ def initialize(address, protocol, bitmap)
2222
+ @address = IPv4.create(address)
2223
+ @protocol = protocol
2224
+ @bitmap = bitmap
2225
+ end
2226
+
2227
+ ##
2228
+ # The host these services run on.
2229
+
2230
+ attr_reader :address
2231
+
2232
+ ##
2233
+ # IP protocol number for these services.
2234
+
2235
+ attr_reader :protocol
2236
+
2237
+ ##
2238
+ # A bit map of enabled services on this host.
2239
+ #
2240
+ # If protocol is 6 (TCP) then the 26th bit corresponds to the SMTP
2241
+ # service (port 25). If this bit is set, then an SMTP server should
2242
+ # be listening on TCP port 25; if zero, SMTP service is not
2243
+ # supported.
2244
+
2245
+ attr_reader :bitmap
2246
+
2247
+ def encode_rdata(msg) # :nodoc:
2248
+ msg.put_bytes(@address.address)
2249
+ msg.put_pack("n", @protocol)
2250
+ msg.put_bytes(@bitmap)
2251
+ end
2252
+
2253
+ def self.decode_rdata(msg) # :nodoc:
2254
+ address = IPv4.new(msg.get_bytes(4))
2255
+ protocol, = msg.get_unpack("n")
2256
+ bitmap = msg.get_bytes
2257
+ return self.new(address, protocol, bitmap)
2258
+ end
2259
+ end
2260
+
2261
+ ##
2262
+ # An IPv6 address record.
2263
+
2264
+ class AAAA < Resource
2265
+ TypeValue = 28
2266
+ ClassValue = IN::ClassValue
2267
+ ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
2268
+
2269
+ ##
2270
+ # Creates a new AAAA for +address+.
2271
+
2272
+ def initialize(address)
2273
+ @address = IPv6.create(address)
2274
+ end
2275
+
2276
+ ##
2277
+ # The Resolv::IPv6 address for this AAAA.
2278
+
2279
+ attr_reader :address
2280
+
2281
+ def encode_rdata(msg) # :nodoc:
2282
+ msg.put_bytes(@address.address)
2283
+ end
2284
+
2285
+ def self.decode_rdata(msg) # :nodoc:
2286
+ return self.new(IPv6.new(msg.get_bytes(16)))
2287
+ end
2288
+ end
2289
+
2290
+ ##
2291
+ # SRV resource record defined in RFC 2782
2292
+ #
2293
+ # These records identify the hostname and port that a service is
2294
+ # available at.
2295
+
2296
+ class SRV < Resource
2297
+ TypeValue = 33
2298
+ ClassValue = IN::ClassValue
2299
+ ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
2300
+
2301
+ # Create a SRV resource record.
2302
+ #
2303
+ # See the documentation for #priority, #weight, #port and #target
2304
+ # for +priority+, +weight+, +port and +target+ respectively.
2305
+
2306
+ def initialize(priority, weight, port, target)
2307
+ @priority = priority.to_int
2308
+ @weight = weight.to_int
2309
+ @port = port.to_int
2310
+ @target = Name.create(target)
2311
+ end
2312
+
2313
+ # The priority of this target host.
2314
+ #
2315
+ # A client MUST attempt to contact the target host with the
2316
+ # lowest-numbered priority it can reach; target hosts with the same
2317
+ # priority SHOULD be tried in an order defined by the weight field.
2318
+ # The range is 0-65535. Note that it is not widely implemented and
2319
+ # should be set to zero.
2320
+
2321
+ attr_reader :priority
2322
+
2323
+ # A server selection mechanism.
2324
+ #
2325
+ # The weight field specifies a relative weight for entries with the
2326
+ # same priority. Larger weights SHOULD be given a proportionately
2327
+ # higher probability of being selected. The range of this number is
2328
+ # 0-65535. Domain administrators SHOULD use Weight 0 when there
2329
+ # isn't any server selection to do, to make the RR easier to read
2330
+ # for humans (less noisy). Note that it is not widely implemented
2331
+ # and should be set to zero.
2332
+
2333
+ attr_reader :weight
2334
+
2335
+ # The port on this target host of this service.
2336
+ #
2337
+ # The range is 0-65535.
2338
+
2339
+ attr_reader :port
2340
+
2341
+ # The domain name of the target host.
2342
+ #
2343
+ # A target of "." means that the service is decidedly not available
2344
+ # at this domain.
2345
+
2346
+ attr_reader :target
2347
+
2348
+ def encode_rdata(msg) # :nodoc:
2349
+ msg.put_pack("n", @priority)
2350
+ msg.put_pack("n", @weight)
2351
+ msg.put_pack("n", @port)
2352
+ msg.put_name(@target, compress: false)
2353
+ end
2354
+
2355
+ def self.decode_rdata(msg) # :nodoc:
2356
+ priority, = msg.get_unpack("n")
2357
+ weight, = msg.get_unpack("n")
2358
+ port, = msg.get_unpack("n")
2359
+ target = msg.get_name
2360
+ return self.new(priority, weight, port, target)
2361
+ end
2362
+ end
2363
+ end
2364
+ end
2365
+ end
2366
+
2367
+ ##
2368
+ # A Resolv::DNS IPv4 address.
2369
+
2370
+ class IPv4
2371
+
2372
+ ##
2373
+ # Regular expression IPv4 addresses must match.
2374
+
2375
+ Regex256 = /0
2376
+ |1(?:[0-9][0-9]?)?
2377
+ |2(?:[0-4][0-9]?|5[0-5]?|[6-9])?
2378
+ |[3-9][0-9]?/x
2379
+ Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/
2380
+
2381
+ def self.create(arg)
2382
+ case arg
2383
+ when IPv4
2384
+ return arg
2385
+ when Regex
2386
+ if (0..255) === (a = $1.to_i) &&
2387
+ (0..255) === (b = $2.to_i) &&
2388
+ (0..255) === (c = $3.to_i) &&
2389
+ (0..255) === (d = $4.to_i)
2390
+ return self.new([a, b, c, d].pack("CCCC"))
2391
+ else
2392
+ raise ArgumentError.new("IPv4 address with invalid value: " + arg)
2393
+ end
2394
+ else
2395
+ raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}")
2396
+ end
2397
+ end
2398
+
2399
+ def initialize(address) # :nodoc:
2400
+ unless address.kind_of?(String)
2401
+ raise ArgumentError, 'IPv4 address must be a string'
2402
+ end
2403
+ unless address.length == 4
2404
+ raise ArgumentError, "IPv4 address expects 4 bytes but #{address.length} bytes"
2405
+ end
2406
+ @address = address
2407
+ end
2408
+
2409
+ ##
2410
+ # A String representation of this IPv4 address.
2411
+
2412
+ ##
2413
+ # The raw IPv4 address as a String.
2414
+
2415
+ attr_reader :address
2416
+
2417
+ def to_s # :nodoc:
2418
+ return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC"))
2419
+ end
2420
+
2421
+ def inspect # :nodoc:
2422
+ return "#<#{self.class} #{self}>"
2423
+ end
2424
+
2425
+ ##
2426
+ # Turns this IPv4 address into a Resolv::DNS::Name.
2427
+
2428
+ def to_name
2429
+ return DNS::Name.create(
2430
+ '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse)
2431
+ end
2432
+
2433
+ def ==(other) # :nodoc:
2434
+ return @address == other.address
2435
+ end
2436
+
2437
+ def eql?(other) # :nodoc:
2438
+ return self == other
2439
+ end
2440
+
2441
+ def hash # :nodoc:
2442
+ return @address.hash
2443
+ end
2444
+ end
2445
+
2446
+ ##
2447
+ # A Resolv::DNS IPv6 address.
2448
+
2449
+ class IPv6
2450
+
2451
+ ##
2452
+ # IPv6 address format a:b:c:d:e:f:g:h
2453
+ Regex_8Hex = /\A
2454
+ (?:[0-9A-Fa-f]{1,4}:){7}
2455
+ [0-9A-Fa-f]{1,4}
2456
+ \z/x
2457
+
2458
+ ##
2459
+ # Compressed IPv6 address format a::b
2460
+
2461
+ Regex_CompressedHex = /\A
2462
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
2463
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
2464
+ \z/x
2465
+
2466
+ ##
2467
+ # IPv4 mapped IPv6 address format a:b:c:d:e:f:w.x.y.z
2468
+
2469
+ Regex_6Hex4Dec = /\A
2470
+ ((?:[0-9A-Fa-f]{1,4}:){6,6})
2471
+ (\d+)\.(\d+)\.(\d+)\.(\d+)
2472
+ \z/x
2473
+
2474
+ ##
2475
+ # Compressed IPv4 mapped IPv6 address format a::b:w.x.y.z
2476
+
2477
+ Regex_CompressedHex4Dec = /\A
2478
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
2479
+ ((?:[0-9A-Fa-f]{1,4}:)*)
2480
+ (\d+)\.(\d+)\.(\d+)\.(\d+)
2481
+ \z/x
2482
+
2483
+ ##
2484
+ # IPv6 link local address format fe80:b:c:d:e:f:g:h%em1
2485
+ Regex_8HexLinkLocal = /\A
2486
+ [Ff][Ee]80
2487
+ (?::[0-9A-Fa-f]{1,4}){7}
2488
+ %[-0-9A-Za-z._~]+
2489
+ \z/x
2490
+
2491
+ ##
2492
+ # Compressed IPv6 link local address format fe80::b%em1
2493
+
2494
+ Regex_CompressedHexLinkLocal = /\A
2495
+ [Ff][Ee]80:
2496
+ (?:
2497
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
2498
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
2499
+ |
2500
+ :((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
2501
+ )?
2502
+ :[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+
2503
+ \z/x
2504
+
2505
+ ##
2506
+ # A composite IPv6 address Regexp.
2507
+
2508
+ Regex = /
2509
+ (?:#{Regex_8Hex}) |
2510
+ (?:#{Regex_CompressedHex}) |
2511
+ (?:#{Regex_6Hex4Dec}) |
2512
+ (?:#{Regex_CompressedHex4Dec}) |
2513
+ (?:#{Regex_8HexLinkLocal}) |
2514
+ (?:#{Regex_CompressedHexLinkLocal})
2515
+ /x
2516
+
2517
+ ##
2518
+ # Creates a new IPv6 address from +arg+ which may be:
2519
+ #
2520
+ # IPv6:: returns +arg+.
2521
+ # String:: +arg+ must match one of the IPv6::Regex* constants
2522
+
2523
+ def self.create(arg)
2524
+ case arg
2525
+ when IPv6
2526
+ return arg
2527
+ when String
2528
+ address = ''.b
2529
+ if Regex_8Hex =~ arg
2530
+ arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
2531
+ elsif Regex_CompressedHex =~ arg
2532
+ prefix = $1
2533
+ suffix = $2
2534
+ a1 = ''.b
2535
+ a2 = ''.b
2536
+ prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
2537
+ suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
2538
+ omitlen = 16 - a1.length - a2.length
2539
+ address << a1 << "\0" * omitlen << a2
2540
+ elsif Regex_6Hex4Dec =~ arg
2541
+ prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i
2542
+ if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
2543
+ prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
2544
+ address << [a, b, c, d].pack('CCCC')
2545
+ else
2546
+ raise ArgumentError.new("not numeric IPv6 address: " + arg)
2547
+ end
2548
+ elsif Regex_CompressedHex4Dec =~ arg
2549
+ prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i
2550
+ if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
2551
+ a1 = ''.b
2552
+ a2 = ''.b
2553
+ prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
2554
+ suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
2555
+ omitlen = 12 - a1.length - a2.length
2556
+ address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC')
2557
+ else
2558
+ raise ArgumentError.new("not numeric IPv6 address: " + arg)
2559
+ end
2560
+ else
2561
+ raise ArgumentError.new("not numeric IPv6 address: " + arg)
2562
+ end
2563
+ return IPv6.new(address)
2564
+ else
2565
+ raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}")
2566
+ end
2567
+ end
2568
+
2569
+ def initialize(address) # :nodoc:
2570
+ unless address.kind_of?(String) && address.length == 16
2571
+ raise ArgumentError.new('IPv6 address must be 16 bytes')
2572
+ end
2573
+ @address = address
2574
+ end
2575
+
2576
+ ##
2577
+ # The raw IPv6 address as a String.
2578
+
2579
+ attr_reader :address
2580
+
2581
+ def to_s # :nodoc:
2582
+ address = sprintf("%x:%x:%x:%x:%x:%x:%x:%x", *@address.unpack("nnnnnnnn"))
2583
+ unless address.sub!(/(^|:)0(:0)+(:|$)/, '::')
2584
+ address.sub!(/(^|:)0(:|$)/, '::')
2585
+ end
2586
+ return address
2587
+ end
2588
+
2589
+ def inspect # :nodoc:
2590
+ return "#<#{self.class} #{self}>"
2591
+ end
2592
+
2593
+ ##
2594
+ # Turns this IPv6 address into a Resolv::DNS::Name.
2595
+ #--
2596
+ # ip6.arpa should be searched too. [RFC3152]
2597
+
2598
+ def to_name
2599
+ return DNS::Name.new(
2600
+ @address.unpack("H32")[0].split(//).reverse + ['ip6', 'arpa'])
2601
+ end
2602
+
2603
+ def ==(other) # :nodoc:
2604
+ return @address == other.address
2605
+ end
2606
+
2607
+ def eql?(other) # :nodoc:
2608
+ return self == other
2609
+ end
2610
+
2611
+ def hash # :nodoc:
2612
+ return @address.hash
2613
+ end
2614
+ end
2615
+
2616
+ ##
2617
+ # Resolv::MDNS is a one-shot Multicast DNS (mDNS) resolver. It blindly
2618
+ # makes queries to the mDNS addresses without understanding anything about
2619
+ # multicast ports.
2620
+ #
2621
+ # Information taken form the following places:
2622
+ #
2623
+ # * RFC 6762
2624
+
2625
+ class MDNS < DNS
2626
+
2627
+ ##
2628
+ # Default mDNS Port
2629
+
2630
+ Port = 5353
2631
+
2632
+ ##
2633
+ # Default IPv4 mDNS address
2634
+
2635
+ AddressV4 = '224.0.0.251'
2636
+
2637
+ ##
2638
+ # Default IPv6 mDNS address
2639
+
2640
+ AddressV6 = 'ff02::fb'
2641
+
2642
+ ##
2643
+ # Default mDNS addresses
2644
+
2645
+ Addresses = [
2646
+ [AddressV4, Port],
2647
+ [AddressV6, Port],
2648
+ ]
2649
+
2650
+ ##
2651
+ # Creates a new one-shot Multicast DNS (mDNS) resolver.
2652
+ #
2653
+ # +config_info+ can be:
2654
+ #
2655
+ # nil::
2656
+ # Uses the default mDNS addresses
2657
+ #
2658
+ # Hash::
2659
+ # Must contain :nameserver or :nameserver_port like
2660
+ # Resolv::DNS#initialize.
2661
+
2662
+ def initialize(config_info=nil)
2663
+ if config_info then
2664
+ super({ nameserver_port: Addresses }.merge(config_info))
2665
+ else
2666
+ super(nameserver_port: Addresses)
2667
+ end
2668
+ end
2669
+
2670
+ ##
2671
+ # Iterates over all IP addresses for +name+ retrieved from the mDNS
2672
+ # resolver, provided name ends with "local". If the name does not end in
2673
+ # "local" no records will be returned.
2674
+ #
2675
+ # +name+ can be a Resolv::DNS::Name or a String. Retrieved addresses will
2676
+ # be a Resolv::IPv4 or Resolv::IPv6
2677
+
2678
+ def each_address(name)
2679
+ name = Resolv::DNS::Name.create(name)
2680
+
2681
+ return unless name[-1].to_s == 'local'
2682
+
2683
+ super(name)
2684
+ end
2685
+
2686
+ def make_udp_requester # :nodoc:
2687
+ nameserver_port = @config.nameserver_port
2688
+ Requester::MDNSOneShot.new(*nameserver_port)
2689
+ end
2690
+
2691
+ end
2692
+
2693
+ module LOC
2694
+
2695
+ ##
2696
+ # A Resolv::LOC::Size
2697
+
2698
+ class Size
2699
+
2700
+ Regex = /^(\d+\.*\d*)[m]$/
2701
+
2702
+ ##
2703
+ # Creates a new LOC::Size from +arg+ which may be:
2704
+ #
2705
+ # LOC::Size:: returns +arg+.
2706
+ # String:: +arg+ must match the LOC::Size::Regex constant
2707
+
2708
+ def self.create(arg)
2709
+ case arg
2710
+ when Size
2711
+ return arg
2712
+ when String
2713
+ scalar = ''
2714
+ if Regex =~ arg
2715
+ scalar = [(($1.to_f*(1e2)).to_i.to_s[0].to_i*(2**4)+(($1.to_f*(1e2)).to_i.to_s.length-1))].pack("C")
2716
+ else
2717
+ raise ArgumentError.new("not a properly formed Size string: " + arg)
2718
+ end
2719
+ return Size.new(scalar)
2720
+ else
2721
+ raise ArgumentError.new("cannot interpret as Size: #{arg.inspect}")
2722
+ end
2723
+ end
2724
+
2725
+ def initialize(scalar)
2726
+ @scalar = scalar
2727
+ end
2728
+
2729
+ ##
2730
+ # The raw size
2731
+
2732
+ attr_reader :scalar
2733
+
2734
+ def to_s # :nodoc:
2735
+ s = @scalar.unpack("H2").join.to_s
2736
+ return ((s[0].to_i)*(10**(s[1].to_i-2))).to_s << "m"
2737
+ end
2738
+
2739
+ def inspect # :nodoc:
2740
+ return "#<#{self.class} #{self}>"
2741
+ end
2742
+
2743
+ def ==(other) # :nodoc:
2744
+ return @scalar == other.scalar
2745
+ end
2746
+
2747
+ def eql?(other) # :nodoc:
2748
+ return self == other
2749
+ end
2750
+
2751
+ def hash # :nodoc:
2752
+ return @scalar.hash
2753
+ end
2754
+
2755
+ end
2756
+
2757
+ ##
2758
+ # A Resolv::LOC::Coord
2759
+
2760
+ class Coord
2761
+
2762
+ Regex = /^(\d+)\s(\d+)\s(\d+\.\d+)\s([NESW])$/
2763
+
2764
+ ##
2765
+ # Creates a new LOC::Coord from +arg+ which may be:
2766
+ #
2767
+ # LOC::Coord:: returns +arg+.
2768
+ # String:: +arg+ must match the LOC::Coord::Regex constant
2769
+
2770
+ def self.create(arg)
2771
+ case arg
2772
+ when Coord
2773
+ return arg
2774
+ when String
2775
+ coordinates = ''
2776
+ if Regex =~ arg && $1.to_f < 180
2777
+ m = $~
2778
+ hemi = (m[4][/[NE]/]) || (m[4][/[SW]/]) ? 1 : -1
2779
+ coordinates = [ ((m[1].to_i*(36e5)) + (m[2].to_i*(6e4)) +
2780
+ (m[3].to_f*(1e3))) * hemi+(2**31) ].pack("N")
2781
+ orientation = m[4][/[NS]/] ? 'lat' : 'lon'
2782
+ else
2783
+ raise ArgumentError.new("not a properly formed Coord string: " + arg)
2784
+ end
2785
+ return Coord.new(coordinates,orientation)
2786
+ else
2787
+ raise ArgumentError.new("cannot interpret as Coord: #{arg.inspect}")
2788
+ end
2789
+ end
2790
+
2791
+ def initialize(coordinates,orientation)
2792
+ unless coordinates.kind_of?(String)
2793
+ raise ArgumentError.new("Coord must be a 32bit unsigned integer in hex format: #{coordinates.inspect}")
2794
+ end
2795
+ unless orientation.kind_of?(String) && orientation[/^lon$|^lat$/]
2796
+ raise ArgumentError.new('Coord expects orientation to be a String argument of "lat" or "lon"')
2797
+ end
2798
+ @coordinates = coordinates
2799
+ @orientation = orientation
2800
+ end
2801
+
2802
+ ##
2803
+ # The raw coordinates
2804
+
2805
+ attr_reader :coordinates
2806
+
2807
+ ## The orientation of the hemisphere as 'lat' or 'lon'
2808
+
2809
+ attr_reader :orientation
2810
+
2811
+ def to_s # :nodoc:
2812
+ c = @coordinates.unpack("N").join.to_i
2813
+ val = (c - (2**31)).abs
2814
+ fracsecs = (val % 1e3).to_i.to_s
2815
+ val = val / 1e3
2816
+ secs = (val % 60).to_i.to_s
2817
+ val = val / 60
2818
+ mins = (val % 60).to_i.to_s
2819
+ degs = (val / 60).to_i.to_s
2820
+ posi = (c >= 2**31)
2821
+ case posi
2822
+ when true
2823
+ hemi = @orientation[/^lat$/] ? "N" : "E"
2824
+ else
2825
+ hemi = @orientation[/^lon$/] ? "W" : "S"
2826
+ end
2827
+ return degs << " " << mins << " " << secs << "." << fracsecs << " " << hemi
2828
+ end
2829
+
2830
+ def inspect # :nodoc:
2831
+ return "#<#{self.class} #{self}>"
2832
+ end
2833
+
2834
+ def ==(other) # :nodoc:
2835
+ return @coordinates == other.coordinates
2836
+ end
2837
+
2838
+ def eql?(other) # :nodoc:
2839
+ return self == other
2840
+ end
2841
+
2842
+ def hash # :nodoc:
2843
+ return @coordinates.hash
2844
+ end
2845
+
2846
+ end
2847
+
2848
+ ##
2849
+ # A Resolv::LOC::Alt
2850
+
2851
+ class Alt
2852
+
2853
+ Regex = /^([+-]*\d+\.*\d*)[m]$/
2854
+
2855
+ ##
2856
+ # Creates a new LOC::Alt from +arg+ which may be:
2857
+ #
2858
+ # LOC::Alt:: returns +arg+.
2859
+ # String:: +arg+ must match the LOC::Alt::Regex constant
2860
+
2861
+ def self.create(arg)
2862
+ case arg
2863
+ when Alt
2864
+ return arg
2865
+ when String
2866
+ altitude = ''
2867
+ if Regex =~ arg
2868
+ altitude = [($1.to_f*(1e2))+(1e7)].pack("N")
2869
+ else
2870
+ raise ArgumentError.new("not a properly formed Alt string: " + arg)
2871
+ end
2872
+ return Alt.new(altitude)
2873
+ else
2874
+ raise ArgumentError.new("cannot interpret as Alt: #{arg.inspect}")
2875
+ end
2876
+ end
2877
+
2878
+ def initialize(altitude)
2879
+ @altitude = altitude
2880
+ end
2881
+
2882
+ ##
2883
+ # The raw altitude
2884
+
2885
+ attr_reader :altitude
2886
+
2887
+ def to_s # :nodoc:
2888
+ a = @altitude.unpack("N").join.to_i
2889
+ return ((a.to_f/1e2)-1e5).to_s + "m"
2890
+ end
2891
+
2892
+ def inspect # :nodoc:
2893
+ return "#<#{self.class} #{self}>"
2894
+ end
2895
+
2896
+ def ==(other) # :nodoc:
2897
+ return @altitude == other.altitude
2898
+ end
2899
+
2900
+ def eql?(other) # :nodoc:
2901
+ return self == other
2902
+ end
2903
+
2904
+ def hash # :nodoc:
2905
+ return @altitude.hash
2906
+ end
2907
+
2908
+ end
2909
+
2910
+ end
2911
+
2912
+ ##
2913
+ # Default resolver to use for Resolv class methods.
2914
+
2915
+ DefaultResolver = self.new
2916
+
2917
+ ##
2918
+ # Replaces the resolvers in the default resolver with +new_resolvers+. This
2919
+ # allows resolvers to be changed for resolv-replace.
2920
+
2921
+ def DefaultResolver.replace_resolvers new_resolvers
2922
+ @resolvers = new_resolvers
2923
+ end
2924
+
2925
+ ##
2926
+ # Address Regexp to use for matching IP addresses.
2927
+
2928
+ AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/
2929
+
2930
+ end