resolv 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+