resolv_fiber 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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