logstash-core 7.0.1-java → 7.1.0-java

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