resolv 0.1.0

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