dnsruby 1.57.0 → 1.58.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -2
- data/.travis.yml +1 -1
- data/README.md +2 -2
- data/RELEASE_NOTES.md +16 -0
- data/Rakefile +2 -0
- data/dnsruby.gemspec +2 -0
- data/lib/dnsruby.rb +5 -0
- data/lib/dnsruby/bit_mapping.rb +138 -0
- data/lib/dnsruby/bitmap.rb +108 -0
- data/lib/dnsruby/message/decoder.rb +90 -80
- data/lib/dnsruby/message/message.rb +16 -3
- data/lib/dnsruby/message/section.rb +3 -3
- data/lib/dnsruby/name.rb +2 -2
- data/lib/dnsruby/packet_sender.rb +73 -1
- data/lib/dnsruby/resolv.rb +56 -42
- data/lib/dnsruby/resolver.rb +95 -73
- data/lib/dnsruby/resource/GPOS.rb +9 -3
- data/lib/dnsruby/resource/HIP.rb +1 -1
- data/lib/dnsruby/resource/IN.rb +3 -1
- data/lib/dnsruby/resource/NAPTR.rb +2 -2
- data/lib/dnsruby/resource/NSEC3.rb +2 -2
- data/lib/dnsruby/resource/NXT.rb +302 -0
- data/lib/dnsruby/resource/OPT.rb +2 -2
- data/lib/dnsruby/resource/TXT.rb +2 -2
- data/lib/dnsruby/resource/generic.rb +1 -0
- data/lib/dnsruby/resource/type_bitmap.rb +0 -0
- data/lib/dnsruby/select_thread.rb +174 -83
- data/lib/dnsruby/single_resolver.rb +2 -2
- data/lib/dnsruby/version.rb +1 -1
- data/lib/dnsruby/zone_reader.rb +19 -9
- data/lib/dnsruby/zone_transfer.rb +1 -1
- data/test/spec_helper.rb +9 -1
- data/test/tc_axfr.rb +17 -4
- data/test/tc_gpos.rb +3 -3
- data/test/tc_message.rb +7 -1
- data/test/tc_nxt.rb +192 -0
- data/test/tc_recur.rb +2 -1
- data/test/tc_resolv.rb +73 -0
- data/test/tc_resolver.rb +22 -4
- data/test/tc_rr-opt.rb +9 -8
- data/test/tc_rr.rb +22 -0
- data/test/tc_single_resolver.rb +15 -15
- data/test/tc_soak.rb +154 -65
- data/test/tc_soak_base.rb +15 -15
- data/test/tc_tcp.rb +1 -1
- data/test/tc_tcp_pipelining.rb +203 -0
- data/test/tc_zone_reader.rb +73 -0
- data/test/test_dnsserver.rb +208 -0
- data/test/test_utils.rb +49 -0
- data/test/ts_offline.rb +59 -41
- data/test/ts_online.rb +92 -63
- metadata +40 -3
- data/test/tc_dnsruby.rb +0 -51
@@ -290,14 +290,27 @@ module Dnsruby
|
|
290
290
|
@header.nscount = @authority.length
|
291
291
|
end
|
292
292
|
|
293
|
-
|
294
|
-
|
295
|
-
unless @answer.include?(rr)
|
293
|
+
def _add_answer(rr, force = false)
|
294
|
+
if force || (! @answer.include?(rr))
|
296
295
|
@answer << rr
|
297
296
|
update_counts
|
298
297
|
end
|
298
|
+
end; private :_add_answer
|
299
|
+
|
300
|
+
# Adds an RR to the answer section unless it already occurs.
|
301
|
+
def add_answer(rr) #:nodoc: all
|
302
|
+
_add_answer(rr)
|
299
303
|
end
|
300
304
|
|
305
|
+
# When adding an RR to a Dnsruby::Message, add_answer checks to see if it already occurs,
|
306
|
+
# and, if so, does not add it again. This method adds the record whether or not
|
307
|
+
# it already occurs. This is needed in order to add
|
308
|
+
# a SOA record twice for an AXFR response.
|
309
|
+
def add_answer!(rr)
|
310
|
+
_add_answer(rr, true)
|
311
|
+
end
|
312
|
+
|
313
|
+
|
301
314
|
def each_answer
|
302
315
|
@answer.each {|rec|
|
303
316
|
yield rec
|
@@ -65,11 +65,11 @@ class Section < Array
|
|
65
65
|
end
|
66
66
|
|
67
67
|
def ==(other)
|
68
|
-
return false unless
|
68
|
+
return false unless self.class == other.class
|
69
69
|
return false if other.rrsets(nil, true).length != self.rrsets(nil, true).length
|
70
70
|
|
71
|
-
otherrrsets = other.rrsets(nil
|
72
|
-
self.rrsets(nil
|
71
|
+
otherrrsets = other.rrsets(nil)
|
72
|
+
self.rrsets(nil).each {|rrset|
|
73
73
|
return false unless otherrrsets.include?(rrset)
|
74
74
|
}
|
75
75
|
|
data/lib/dnsruby/name.rb
CHANGED
@@ -329,7 +329,7 @@ module Dnsruby
|
|
329
329
|
if (presentation.index(/\G(\d\d\d)/o, pos))
|
330
330
|
wire=wire+[$1.to_i].pack("C")
|
331
331
|
i=i+3
|
332
|
-
elsif(presentation.index(/\Gx(
|
332
|
+
elsif(presentation.index(/\Gx(\h\h)/o, pos))
|
333
333
|
wire=wire+[$1].pack("H*")
|
334
334
|
i=i+3
|
335
335
|
elsif(presentation.index(/\G\./o, pos))
|
@@ -418,4 +418,4 @@ module Dnsruby
|
|
418
418
|
|
419
419
|
end
|
420
420
|
end
|
421
|
-
end
|
421
|
+
end
|
@@ -16,6 +16,7 @@
|
|
16
16
|
require 'dnsruby/select_thread'
|
17
17
|
require 'ipaddr'
|
18
18
|
# require 'dnsruby/iana_ports'
|
19
|
+
include Socket::Constants
|
19
20
|
module Dnsruby
|
20
21
|
class PacketSender # :nodoc: all
|
21
22
|
@@authoritative_cache = Cache.new
|
@@ -70,6 +71,14 @@ module Dnsruby
|
|
70
71
|
# Defaults to false
|
71
72
|
attr_accessor :use_tcp
|
72
73
|
|
74
|
+
# Reuse tcp connection
|
75
|
+
#
|
76
|
+
# Defaults to false
|
77
|
+
attr_accessor :tcp_pipelining
|
78
|
+
|
79
|
+
# Limit the number of queries per pipeline
|
80
|
+
attr_accessor :tcp_pipelining_max_queries
|
81
|
+
|
73
82
|
# Use UDP only - don't use TCP
|
74
83
|
# For test/debug purposes only
|
75
84
|
# Defaults to false
|
@@ -158,6 +167,8 @@ module Dnsruby
|
|
158
167
|
# * :server
|
159
168
|
# * :port
|
160
169
|
# * :use_tcp
|
170
|
+
# * :tcp_pipelining
|
171
|
+
# * :tcp_pipelining_max_queries
|
161
172
|
# * :no_tcp
|
162
173
|
# * :ignore_truncation
|
163
174
|
# * :src_address
|
@@ -182,6 +193,9 @@ module Dnsruby
|
|
182
193
|
@src_address6 = '::'
|
183
194
|
@src_port = [0]
|
184
195
|
@recurse = true
|
196
|
+
@tcp_pipelining = false
|
197
|
+
@tcp_pipelining_max_queries = :infinite
|
198
|
+
@use_counts = {}
|
185
199
|
|
186
200
|
if (arg==nil)
|
187
201
|
# Get default config
|
@@ -348,6 +362,57 @@ module Dnsruby
|
|
348
362
|
# end
|
349
363
|
end
|
350
364
|
|
365
|
+
# This method returns the current tcp socket for pipelining
|
366
|
+
# If this is the first time the method is called then the socket is bound to
|
367
|
+
# @src_address:@src_port and connected to the remote dns server @server:@port.
|
368
|
+
# If the connection has been closed because of an EOF on recv_nonblock (closed by server)
|
369
|
+
# the function will recreate of the socket (since @pipeline_socket.connect will result in a IOError
|
370
|
+
# exception)
|
371
|
+
# In general, every subsequent call the function will either return the current tcp
|
372
|
+
# pipeline socket or a new connected socket if the current one was closed by the server
|
373
|
+
def tcp_pipeline_socket(src_port)
|
374
|
+
Dnsruby.log.debug("Using tcp_pipeline_socket")
|
375
|
+
sockaddr = Socket.sockaddr_in(@port, @server)
|
376
|
+
|
377
|
+
reuse_pipeline_socket = -> do
|
378
|
+
begin
|
379
|
+
max = @tcp_pipelining_max_queries
|
380
|
+
use_count = @use_counts[@pipeline_socket]
|
381
|
+
if use_count && max != :infinite && use_count >= max
|
382
|
+
#we can't reuse the socket since max is reached
|
383
|
+
@use_counts.delete(@pipeline_socket)
|
384
|
+
@pipeline_socket = nil
|
385
|
+
Dnsruby.log.debug("Max queries per connection attained - creating new socket")
|
386
|
+
else
|
387
|
+
@pipeline_socket.connect(sockaddr)
|
388
|
+
end
|
389
|
+
rescue Errno::EISCONN
|
390
|
+
#already connected, do nothing and reuse!
|
391
|
+
rescue IOError #close by remote host, reconnect
|
392
|
+
@pipeline_socket = nil
|
393
|
+
Dnsruby.log.debug("Connection closed - recreating socket")
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
create_pipeline_socket = -> do
|
398
|
+
@tcp_pipeline_local_port = src_port
|
399
|
+
src_address = @ipv6 ? @src_address6 : @src_address
|
400
|
+
|
401
|
+
@pipeline_socket = Socket.new(AF_INET, SOCK_STREAM, 0)
|
402
|
+
@pipeline_socket.bind(Addrinfo.tcp(src_address, src_port))
|
403
|
+
@pipeline_socket.connect(sockaddr)
|
404
|
+
@use_counts[@pipeline_socket] = 0
|
405
|
+
end
|
406
|
+
|
407
|
+
# Don't combine the following 2 statements; the reuse lambda can set the
|
408
|
+
# socket to nil and if so we'd want to call the create lambda to recreate it.
|
409
|
+
reuse_pipeline_socket.() if @pipeline_socket
|
410
|
+
create_pipeline_socket.() unless @pipeline_socket
|
411
|
+
|
412
|
+
@use_counts[@pipeline_socket] += 1
|
413
|
+
|
414
|
+
@pipeline_socket
|
415
|
+
end
|
351
416
|
|
352
417
|
# This method sends the packet using the built-in pure Ruby event loop, with no dependencies.
|
353
418
|
def send_dnsruby(query_bytes, query, client_query_id, client_queue, use_tcp) #:nodoc: all
|
@@ -367,7 +432,12 @@ module Dnsruby
|
|
367
432
|
src_port = get_next_src_port
|
368
433
|
if (use_tcp)
|
369
434
|
begin
|
370
|
-
|
435
|
+
if (@tcp_pipelining)
|
436
|
+
socket = tcp_pipeline_socket(src_port)
|
437
|
+
src_port = @tcp_pipeline_local_port
|
438
|
+
else
|
439
|
+
socket = TCPSocket.new(@server, @port, src_address, src_port)
|
440
|
+
end
|
371
441
|
rescue Errno::EBADF, Errno::ENETUNREACH => e
|
372
442
|
# Can't create a connection
|
373
443
|
err=IOError.new("TCP connection error to #{@server}:#{@port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp}, exception = #{e.class}, #{e}")
|
@@ -416,6 +486,8 @@ module Dnsruby
|
|
416
486
|
# print "#{Time.now} : Sending packet to #{@server} : #{query.question()[0].qname}, #{query.question()[0].qtype}\n"
|
417
487
|
# Listen for the response before we send the packet (to avoid any race conditions)
|
418
488
|
query_settings = SelectThread::QuerySettings.new(query_bytes, query, @ignore_truncation, client_queue, client_query_id, socket, @server, @port, endtime, udp_packet_size, self)
|
489
|
+
query_settings.is_persistent_socket = @tcp_pipelining if use_tcp
|
490
|
+
query_settings.tcp_pipelining_max_queries = @tcp_pipelining_max_queries if @tcp_pipelining
|
419
491
|
begin
|
420
492
|
if (use_tcp)
|
421
493
|
lenmsg = [query_bytes.length].pack('n')
|
data/lib/dnsruby/resolv.rb
CHANGED
@@ -3,38 +3,60 @@
|
|
3
3
|
# The DNS class may be used to perform more queries. If greater control over the sending
|
4
4
|
# of packets is required, then the Resolver or SingleResolver classes may be used.
|
5
5
|
module Dnsruby
|
6
|
+
|
7
|
+
|
8
|
+
# NOTE! Beware, there is a Ruby library class named Resolv, and you may need to
|
9
|
+
# explicitly specify Dnsruby::Resolv to use the Dnsruby Resolv class,
|
10
|
+
# even if you have include'd Dnsruby.
|
11
|
+
|
6
12
|
class Resolv
|
7
13
|
|
14
|
+
# Address RegExp to use for matching IP addresses
|
15
|
+
ADDRESS_REGEX = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/
|
16
|
+
|
17
|
+
|
18
|
+
# Some class methods require the use of an instance to compute their result.
|
19
|
+
# For this purpose we create a single instance that can be reused.
|
20
|
+
def self.instance
|
21
|
+
@instance ||= self.new
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# Class methods that delegate to instance methods:
|
26
|
+
|
8
27
|
# Looks up the first IP address for +name+
|
9
28
|
def self.getaddress(name)
|
10
|
-
|
29
|
+
instance.getaddress(name)
|
11
30
|
end
|
12
31
|
|
13
32
|
# Looks up all IP addresses for +name+
|
14
33
|
def self.getaddresses(name)
|
15
|
-
|
34
|
+
instance.getaddresses(name)
|
16
35
|
end
|
17
36
|
|
18
37
|
# Iterates over all IP addresses for +name+
|
19
38
|
def self.each_address(name, &block)
|
20
|
-
|
39
|
+
instance.each_address(name, &block)
|
21
40
|
end
|
22
41
|
|
23
42
|
# Looks up the first hostname of +address+
|
24
43
|
def self.getname(address)
|
25
|
-
|
44
|
+
instance.getname(address)
|
26
45
|
end
|
27
46
|
|
28
47
|
# Looks up all hostnames of +address+
|
29
48
|
def self.getnames(address)
|
30
|
-
|
49
|
+
instance.getnames(address)
|
31
50
|
end
|
32
51
|
|
33
52
|
# Iterates over all hostnames of +address+
|
34
53
|
def self.each_name(address, &proc)
|
35
|
-
|
54
|
+
instance.each_name(address, &proc)
|
36
55
|
end
|
37
56
|
|
57
|
+
|
58
|
+
# Instance Methods:
|
59
|
+
|
38
60
|
# Creates a new Resolv using +resolvers+
|
39
61
|
def initialize(resolvers=[Hosts.new, DNS.new])
|
40
62
|
@resolvers = resolvers
|
@@ -42,72 +64,64 @@ class Resolv
|
|
42
64
|
|
43
65
|
# Looks up the first IP address for +name+
|
44
66
|
def getaddress(name)
|
45
|
-
|
46
|
-
|
67
|
+
addresses = getaddresses(name)
|
68
|
+
if addresses.empty?
|
69
|
+
raise ResolvError.new("no address for #{name}")
|
70
|
+
else
|
71
|
+
addresses.first
|
72
|
+
end
|
47
73
|
end
|
48
74
|
|
49
75
|
# Looks up all IP addresses for +name+
|
50
76
|
def getaddresses(name)
|
51
|
-
|
52
|
-
|
53
|
-
|
77
|
+
return [name] if ADDRESS_REGEX.match(name)
|
78
|
+
@resolvers.each do |resolver|
|
79
|
+
addresses = []
|
80
|
+
resolver.each_address(name) { |address| addresses << address }
|
81
|
+
return addresses unless addresses.empty?
|
82
|
+
end
|
83
|
+
[]
|
54
84
|
end
|
55
85
|
|
56
86
|
# Iterates over all IP addresses for +name+
|
57
87
|
def each_address(name)
|
58
|
-
|
59
|
-
yield name
|
60
|
-
return
|
61
|
-
end
|
62
|
-
yielded = false
|
63
|
-
@resolvers.each {|r|
|
64
|
-
r.each_address(name) {|address|
|
65
|
-
yield address.to_s
|
66
|
-
yielded = true
|
67
|
-
}
|
68
|
-
return if yielded
|
69
|
-
}
|
88
|
+
getaddresses(name).each { |address| yield(address)}
|
70
89
|
end
|
71
90
|
|
72
91
|
# Looks up the first hostname of +address+
|
73
92
|
def getname(address)
|
74
|
-
|
75
|
-
|
93
|
+
names = getnames(address)
|
94
|
+
if names.empty?
|
95
|
+
raise ResolvError.new("no name for #{address}")
|
96
|
+
else
|
97
|
+
names.first
|
98
|
+
end
|
76
99
|
end
|
77
100
|
|
78
101
|
# Looks up all hostnames of +address+
|
79
102
|
def getnames(address)
|
80
|
-
|
81
|
-
|
82
|
-
|
103
|
+
@resolvers.each do |resolver|
|
104
|
+
names = []
|
105
|
+
resolver.each_name(address) { |name| names << name }
|
106
|
+
return names unless names.empty?
|
107
|
+
end
|
108
|
+
[]
|
83
109
|
end
|
84
110
|
|
85
111
|
# Iterates over all hostnames of +address+
|
86
112
|
def each_name(address)
|
87
|
-
|
88
|
-
@resolvers.each {|r|
|
89
|
-
r.each_name(address) {|name|
|
90
|
-
yield name.to_s
|
91
|
-
yielded = true
|
92
|
-
}
|
93
|
-
return if yielded
|
94
|
-
}
|
113
|
+
getnames(address).each { |address| yield(address) }
|
95
114
|
end
|
96
115
|
|
97
116
|
|
98
117
|
require 'dnsruby/cache'
|
99
118
|
require 'dnsruby/DNS'
|
100
119
|
require 'dnsruby/hosts'
|
101
|
-
require 'dnsruby/message'
|
120
|
+
require 'dnsruby/message/message'
|
102
121
|
require 'dnsruby/update'
|
103
122
|
require 'dnsruby/zone_transfer'
|
104
123
|
require 'dnsruby/dnssec'
|
105
124
|
require 'dnsruby/zone_reader'
|
106
125
|
|
107
|
-
# Default Resolver to use for Dnsruby class methods
|
108
|
-
DefaultResolver = self.new
|
109
|
-
|
110
|
-
# Address RegExp to use for matching IP addresses
|
111
|
-
AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/
|
112
126
|
end
|
113
127
|
end
|
data/lib/dnsruby/resolver.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# --
|
2
2
|
# Copyright 2007 Nominet UK
|
3
|
-
#
|
3
|
+
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
6
6
|
# You may obtain a copy of the License at
|
7
|
-
#
|
7
|
+
#
|
8
8
|
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
-
#
|
9
|
+
#
|
10
10
|
# Unless required by applicable law or agreed to in writing, software
|
11
11
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
12
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
@@ -23,18 +23,18 @@ module Dnsruby
|
|
23
23
|
# Dnsruby::Resolver is a DNS stub resolver.
|
24
24
|
# This class performs queries with retries across multiple nameservers.
|
25
25
|
# The system configured resolvers are used by default.
|
26
|
-
#
|
26
|
+
#
|
27
27
|
# The retry policy is a combination of the Net::DNS and dnsjava approach, and has the option of :
|
28
28
|
# * A total timeout for the query (defaults to 0, meaning "no total timeout")
|
29
29
|
# * A retransmission system that targets the namervers concurrently once the first query round is
|
30
30
|
# complete, but in which the total time per query round is split between the number of nameservers
|
31
31
|
# targetted for the first round. and total time for query round is doubled for each query round
|
32
|
-
#
|
32
|
+
#
|
33
33
|
# Note that, if a total timeout is specified, then that will apply regardless of the retry policy
|
34
34
|
# (i.e. it may cut retries short).
|
35
|
-
#
|
35
|
+
#
|
36
36
|
# Note also that these timeouts are distinct from the SingleResolver's packet_timeout
|
37
|
-
#
|
37
|
+
#
|
38
38
|
# Timeouts apply to the initial query and response. If DNSSEC validation is to
|
39
39
|
# be performed, then additional queries may be required (these are performed automatically
|
40
40
|
# by Dnsruby). Each additional query will be performed with its own timeouts.
|
@@ -42,31 +42,31 @@ module Dnsruby
|
|
42
42
|
# validation may take several times that long.
|
43
43
|
# (Future versions of Dnsruby may expose finer-grained events for client tracking of
|
44
44
|
# responses and validation)
|
45
|
-
#
|
45
|
+
#
|
46
46
|
# == Methods
|
47
|
-
#
|
47
|
+
#
|
48
48
|
# === Synchronous
|
49
49
|
# These methods raise an exception or return a response message with rcode==NOERROR
|
50
|
-
#
|
50
|
+
#
|
51
51
|
# * Dnsruby::Resolver#send_message(msg)
|
52
52
|
# * Dnsruby::Resolver#query(name [, type [, klass]])
|
53
|
-
#
|
53
|
+
#
|
54
54
|
# There are "!" versions of these two methods that return an array [response, error]
|
55
55
|
# instead of raising an error on failure. They can be called as follows:
|
56
|
-
#
|
56
|
+
#
|
57
57
|
# response, error = resolver.send_message!(...)
|
58
58
|
# response, error = resolver.query!(...)
|
59
|
-
#
|
59
|
+
#
|
60
60
|
# If the request succeeds, response will contain the Dnsruby::Message response
|
61
61
|
# and error will be nil.
|
62
|
-
#
|
62
|
+
#
|
63
63
|
# If the request fails, response will be nil and error will contain the error raised.
|
64
|
-
#
|
64
|
+
#
|
65
65
|
# === Asynchronous
|
66
66
|
# These methods use a response queue to return the response and the error
|
67
|
-
#
|
67
|
+
#
|
68
68
|
# * Dnsruby::Resolver#send_async(msg, response_queue, query_id)
|
69
|
-
#
|
69
|
+
#
|
70
70
|
# == Event Loop
|
71
71
|
# Dnsruby runs a pure Ruby event loop to handle I/O in a single thread.
|
72
72
|
# Support for EventMachine has been deprecated.
|
@@ -75,6 +75,7 @@ module Dnsruby
|
|
75
75
|
DefaultPacketTimeout = 5
|
76
76
|
DefaultRetryTimes = 1
|
77
77
|
DefaultRetryDelay = 5
|
78
|
+
DefaultPipeLiningMaxQueries = 5
|
78
79
|
DefaultPort = 53
|
79
80
|
DefaultDnssec = true
|
80
81
|
AbsoluteMinDnssecUdpSize = 1220
|
@@ -94,6 +95,13 @@ module Dnsruby
|
|
94
95
|
# If use_tcp==true, then ONLY TCP will be used as a transport.
|
95
96
|
attr_reader :use_tcp
|
96
97
|
|
98
|
+
# If tcp_pipelining==true, then we reuse the TCP connection
|
99
|
+
attr_reader :tcp_pipelining
|
100
|
+
|
101
|
+
# How many times (number of messages) to reuse the pipelining connection
|
102
|
+
# before closing, :infinite for infinite number of requests per connection
|
103
|
+
attr_reader :tcp_pipelining_max_queries
|
104
|
+
|
97
105
|
# If no_tcp==true, then ONLY UDP will be used as a transport.
|
98
106
|
# This should not generally be used, but is provided as a debugging aid.
|
99
107
|
attr_reader :no_tcp
|
@@ -171,12 +179,12 @@ module Dnsruby
|
|
171
179
|
# --
|
172
180
|
# @TODO@ add load_balance? i.e. Target nameservers in a random, rather than pre-determined, order?
|
173
181
|
# This is best done when configuring the Resolver, as it will re-order servers based on their response times.
|
174
|
-
#
|
182
|
+
#
|
175
183
|
# ++
|
176
184
|
|
177
185
|
# Query for a name. If a valid Message is received, then it is returned
|
178
186
|
# to the caller. Otherwise an exception (a Dnsruby::ResolvError or Dnsruby::ResolvTimeout) is raised.
|
179
|
-
#
|
187
|
+
#
|
180
188
|
# require 'dnsruby'
|
181
189
|
# res = Dnsruby::Resolver.new
|
182
190
|
# response = res.query('example.com') # defaults to Types.A, Classes.IN
|
@@ -222,11 +230,11 @@ module Dnsruby
|
|
222
230
|
|
223
231
|
# Send a message, and wait for the response. If a valid Message is received, then it is returned
|
224
232
|
# to the caller. Otherwise an exception (a Dnsruby::ResolvError or Dnsruby::ResolvTimeout) is raised.
|
225
|
-
#
|
233
|
+
#
|
226
234
|
# send_async is called internally.
|
227
|
-
#
|
235
|
+
#
|
228
236
|
# example :
|
229
|
-
#
|
237
|
+
#
|
230
238
|
# require 'dnsruby'
|
231
239
|
# include Dnsruby
|
232
240
|
# res = Dnsruby::Resolver.new
|
@@ -302,9 +310,9 @@ module Dnsruby
|
|
302
310
|
# Dnsruby::PacketSender#add_opt_rr(msg)
|
303
311
|
# The return value from this method is the [response, error] tuple. Either of
|
304
312
|
# these values may be nil - it is up to the client to check.
|
305
|
-
#
|
313
|
+
#
|
306
314
|
# example :
|
307
|
-
#
|
315
|
+
#
|
308
316
|
# require 'dnsruby'
|
309
317
|
# include Dnsruby
|
310
318
|
# res = Dnsruby::Resolver.new
|
@@ -328,40 +336,40 @@ module Dnsruby
|
|
328
336
|
|
329
337
|
# Asynchronously send a Message to the server. The send can be done using just
|
330
338
|
# Dnsruby. Support for EventMachine has been deprecated.
|
331
|
-
#
|
339
|
+
#
|
332
340
|
# == Dnsruby pure Ruby event loop :
|
333
|
-
#
|
341
|
+
#
|
334
342
|
# A client_queue is supplied by the client,
|
335
343
|
# along with an optional client_query_id to identify the response. The client_query_id
|
336
344
|
# is generated, if not supplied, and returned to the client.
|
337
345
|
# When the response is known,
|
338
346
|
# a tuple of (query_id, response_message, exception) will be added to the client_queue.
|
339
|
-
#
|
347
|
+
#
|
340
348
|
# The query is sent synchronously in the caller's thread. The select thread is then used to
|
341
349
|
# listen for and process the response (up to pushing it to the client_queue). The client thread
|
342
350
|
# is then used to retrieve the response and deal with it.
|
343
|
-
#
|
351
|
+
#
|
344
352
|
# Takes :
|
345
|
-
#
|
353
|
+
#
|
346
354
|
# * msg - the message to send
|
347
355
|
# * client_queue - a Queue to push the response to, when it arrives
|
348
356
|
# * client_query_id - an optional ID to identify the query to the client
|
349
357
|
# * use_tcp - whether to use only TCP (defaults to SingleResolver.use_tcp)
|
350
|
-
#
|
358
|
+
#
|
351
359
|
# Returns :
|
352
|
-
#
|
360
|
+
#
|
353
361
|
# * client_query_id - to identify the query response to the client. This ID is
|
354
362
|
# generated if it is not passed in by the client
|
355
|
-
#
|
363
|
+
#
|
356
364
|
# === Example invocations :
|
357
|
-
#
|
365
|
+
#
|
358
366
|
# id = res.send_async(msg, queue)
|
359
367
|
# NOT SUPPORTED : id = res.send_async(msg, queue, use_tcp)
|
360
368
|
# id = res.send_async(msg, queue, id)
|
361
369
|
# id = res.send_async(msg, queue, id, use_tcp)
|
362
|
-
#
|
370
|
+
#
|
363
371
|
# === Example code :
|
364
|
-
#
|
372
|
+
#
|
365
373
|
# require 'dnsruby'
|
366
374
|
# res = Dnsruby::Resolver.newsend
|
367
375
|
# query_id = 10 # can be any object you like
|
@@ -378,7 +386,7 @@ module Dnsruby
|
|
378
386
|
# # deal with problem
|
379
387
|
# end
|
380
388
|
# end
|
381
|
-
#
|
389
|
+
#
|
382
390
|
def send_async(msg, client_queue, client_query_id = nil)
|
383
391
|
unless @configured
|
384
392
|
add_config_nameservers
|
@@ -406,8 +414,8 @@ module Dnsruby
|
|
406
414
|
# Create a new Resolver object. If no parameters are passed in, then the default
|
407
415
|
# system configuration will be used. Otherwise, a Hash may be passed in with the
|
408
416
|
# following optional elements :
|
409
|
-
#
|
410
|
-
#
|
417
|
+
#
|
418
|
+
#
|
411
419
|
# * :port
|
412
420
|
# * :use_tcp
|
413
421
|
# * :tsig
|
@@ -424,6 +432,8 @@ module Dnsruby
|
|
424
432
|
# * :retry_times
|
425
433
|
# * :retry_delay
|
426
434
|
# * :do_caching
|
435
|
+
# * :tcp_pipelining
|
436
|
+
# * :tcp_pipelining_max_queries - can be a number or :infinite symbol
|
427
437
|
def initialize(*args)
|
428
438
|
# @TODO@ Should we allow :namesver to be an RRSet of NS records? Would then need to randomly order them?
|
429
439
|
@resolver_ruby = nil
|
@@ -478,6 +488,8 @@ module Dnsruby
|
|
478
488
|
dnssec: @dnssec,
|
479
489
|
use_tcp: @use_tcp,
|
480
490
|
no_tcp: @no_tcp,
|
491
|
+
tcp_pipelining: @tcp_pipelining,
|
492
|
+
tcp_pipelining_max_queries: @tcp_pipelining_max_queries,
|
481
493
|
packet_timeout: @packet_timeout,
|
482
494
|
tsig: @tsig,
|
483
495
|
ignore_truncation: @ignore_truncation,
|
@@ -520,6 +532,8 @@ module Dnsruby
|
|
520
532
|
@do_caching= true
|
521
533
|
@use_tcp = false
|
522
534
|
@no_tcp = false
|
535
|
+
@tcp_pipelining = false
|
536
|
+
@tcp_pipelining_max_queries = DefaultPipeLiningMaxQueries
|
523
537
|
@tsig = nil
|
524
538
|
@ignore_truncation = false
|
525
539
|
@config = Config.new()
|
@@ -557,7 +571,7 @@ module Dnsruby
|
|
557
571
|
end
|
558
572
|
|
559
573
|
def update_internal_res(res)
|
560
|
-
[:port, :use_tcp, :no_tcp, :tsig, :ignore_truncation, :packet_timeout,
|
574
|
+
[:port, :use_tcp, :no_tcp, :tcp_pipelining, :tcp_pipelining_max_queries, :tsig, :ignore_truncation, :packet_timeout,
|
561
575
|
:src_address, :src_address6, :src_port, :recurse,
|
562
576
|
:udp_size, :dnssec].each do |param|
|
563
577
|
|
@@ -589,7 +603,7 @@ module Dnsruby
|
|
589
603
|
# The source port to send queries from
|
590
604
|
# Returns either a single Fixnum or an Array
|
591
605
|
# e.g. '0', or '[60001, 60002, 60007]'
|
592
|
-
#
|
606
|
+
#
|
593
607
|
# Defaults to 0 - random port
|
594
608
|
def src_port
|
595
609
|
@src_port.length == 1 ? @src_port[0] : @src_port
|
@@ -598,11 +612,11 @@ module Dnsruby
|
|
598
612
|
# Can be a single Fixnum or a Range or an Array
|
599
613
|
# If an invalid port is selected (one reserved by
|
600
614
|
# IANA), then an ArgumentError will be raised.
|
601
|
-
#
|
615
|
+
#
|
602
616
|
# res.src_port=0
|
603
617
|
# res.src_port=[60001,60005,60010]
|
604
618
|
# res.src_port=60015..60115
|
605
|
-
#
|
619
|
+
#
|
606
620
|
def src_port=(p)
|
607
621
|
if Resolver.check_port(p)
|
608
622
|
@src_port = Resolver.get_ports_from(p)
|
@@ -617,11 +631,11 @@ module Dnsruby
|
|
617
631
|
# option if it is the only port in the list.
|
618
632
|
# An ArgumentError will be raised if "0" is added to
|
619
633
|
# an existing set of source ports.
|
620
|
-
#
|
634
|
+
#
|
621
635
|
# res.add_src_port(60000)
|
622
636
|
# res.add_src_port([60001,60005,60010])
|
623
637
|
# res.add_src_port(60015..60115)
|
624
|
-
#
|
638
|
+
#
|
625
639
|
def add_src_port(p)
|
626
640
|
if Resolver.check_port(p, @src_port)
|
627
641
|
a = Resolver.get_ports_from(p)
|
@@ -671,6 +685,16 @@ module Dnsruby
|
|
671
685
|
a
|
672
686
|
end
|
673
687
|
|
688
|
+
def tcp_pipelining=(on)
|
689
|
+
@tcp_pipelining = on
|
690
|
+
update
|
691
|
+
end
|
692
|
+
|
693
|
+
def tcp_pipelining_max_queries=(max)
|
694
|
+
@tcp_pipelining_max_queries = max
|
695
|
+
update
|
696
|
+
end
|
697
|
+
|
674
698
|
def use_tcp=(on)
|
675
699
|
@use_tcp = on
|
676
700
|
update
|
@@ -693,23 +717,21 @@ module Dnsruby
|
|
693
717
|
update
|
694
718
|
end
|
695
719
|
|
696
|
-
def create_tsig_options(args)
|
697
|
-
if args.size > 3
|
698
|
-
log_and_raise("Illegal number of arguments (#{args.size}; must be 1, 2, or 3.")
|
699
|
-
end
|
700
|
-
|
701
|
-
options = { type: Types.TSIG, klass: Classes.ANY }
|
702
|
-
if args.length >= 2
|
703
|
-
options.merge!({ name: args[0], key: args[1] })
|
704
|
-
end
|
705
|
-
if args.length == 3
|
706
|
-
options[:algorithm] == args[2]
|
707
|
-
end
|
708
720
|
|
721
|
+
protected
|
722
|
+
def Resolver.create_tsig_options(name, key, algorithm = nil)
|
723
|
+
options = {
|
724
|
+
type: Types.TSIG,
|
725
|
+
klass: Classes.ANY,
|
726
|
+
name: name,
|
727
|
+
key: key
|
728
|
+
}
|
729
|
+
options[:algorithm] = algorithm if algorithm
|
709
730
|
options
|
710
|
-
end
|
731
|
+
end
|
711
732
|
|
712
733
|
|
734
|
+
public
|
713
735
|
def Resolver.get_tsig(args)
|
714
736
|
|
715
737
|
tsig = nil
|
@@ -719,7 +741,7 @@ module Dnsruby
|
|
719
741
|
if args[0].instance_of?(RR::TSIG)
|
720
742
|
tsig = args[0]
|
721
743
|
elsif args[0].instance_of?(Array)
|
722
|
-
tsig = RR.new_from_hash(create_tsig_options(args))
|
744
|
+
tsig = RR.new_from_hash(create_tsig_options(*args[0]))
|
723
745
|
end
|
724
746
|
else
|
725
747
|
# Dnsruby.log.debug{'TSIG signing switched off'}
|
@@ -898,7 +920,7 @@ module Dnsruby
|
|
898
920
|
def generate_timeouts # :nodoc: all
|
899
921
|
# Create the timeouts for the query from the retry_times and retry_delay attributes.
|
900
922
|
# These are created at the same time in case the parameters change during the life of the query.
|
901
|
-
#
|
923
|
+
#
|
902
924
|
# These should be absolute, rather than relative
|
903
925
|
# The first value should be Time.now[
|
904
926
|
@parent.generate_timeouts(Time.now)
|
@@ -917,7 +939,7 @@ module Dnsruby
|
|
917
939
|
end
|
918
940
|
|
919
941
|
# MUST BE CALLED IN A SYNCHRONIZED BLOCK!
|
920
|
-
#
|
942
|
+
#
|
921
943
|
# Send the result back to the client, and close the socket for that query by removing
|
922
944
|
# the query from the select thread.
|
923
945
|
def send_result_and_stop_querying(client_queue, client_query_id, select_queue, msg, error) # :nodoc: all
|
@@ -926,14 +948,14 @@ module Dnsruby
|
|
926
948
|
end
|
927
949
|
|
928
950
|
# MUST BE CALLED IN A SYNCHRONIZED BLOCK!
|
929
|
-
#
|
951
|
+
#
|
930
952
|
# Stops send any more packets for a client-level query
|
931
953
|
def stop_querying(client_query_id) # :nodoc: all
|
932
954
|
@timeouts.delete(client_query_id)
|
933
955
|
end
|
934
956
|
|
935
957
|
# MUST BE CALLED IN A SYNCHRONIZED BLOCK!
|
936
|
-
#
|
958
|
+
#
|
937
959
|
# Sends the result to the client's queue, and removes the queue observer from the select thread
|
938
960
|
def send_result(client_queue, client_query_id, select_queue, msg, error) # :nodoc: all
|
939
961
|
stop_querying(client_query_id) # @TODO@ !
|
@@ -996,35 +1018,35 @@ module Dnsruby
|
|
996
1018
|
# The queue interface is used to separate producer/consumer threads, but we're using it here in one thread.
|
997
1019
|
# It's probably a good idea to create a new "worker thread" to take items from the select thread queue and
|
998
1020
|
# call this method in the worker thread.
|
999
|
-
#
|
1021
|
+
#
|
1000
1022
|
def handle_queue_event(queue, id) # :nodoc: all
|
1001
1023
|
# Time to process a new queue event.
|
1002
1024
|
# If we get a callback for an ID we don't know about, don't worry -
|
1003
1025
|
# just ignore it. It may be for a query we've already completed.
|
1004
|
-
#
|
1026
|
+
#
|
1005
1027
|
# So, get the next response from the queue (presuming there is one!)
|
1006
|
-
#
|
1028
|
+
#
|
1007
1029
|
# @TODO@ Tick could poll the queue and then call this method if needed - no need for observer interface.
|
1008
1030
|
# @TODO@ Currently, tick and handle_queue_event called from select_thread - could have thread chuck events in to tick_queue. But then, clients would have to call in on other thread!
|
1009
|
-
#
|
1031
|
+
#
|
1010
1032
|
# So - two types of response :
|
1011
1033
|
# 1) we've got a coherent response (or error) - stop sending more packets for that query!
|
1012
1034
|
# 2) we've validated the response - it's ready to be sent to the client
|
1013
|
-
#
|
1035
|
+
#
|
1014
1036
|
# so need two more methods :
|
1015
1037
|
# handleValidationResponse : basically calls send_result_and_stop_querying and
|
1016
1038
|
# handleValidationError : does the same as handleValidationResponse, but for errors
|
1017
1039
|
# can leave handleError alone
|
1018
1040
|
# but need to change handleResponse to stop sending, rather than send_result_and_stop_querying.
|
1019
|
-
#
|
1041
|
+
#
|
1020
1042
|
# @TODO@ Also, we could really do with a MaxValidationTimeout - if validation not OK within
|
1021
1043
|
# this time, then raise Timeout (and stop validation)?
|
1022
|
-
#
|
1044
|
+
#
|
1023
1045
|
# @TODO@ Also, should there be some facility to stop validator following same chain
|
1024
1046
|
# concurrently?
|
1025
|
-
#
|
1047
|
+
#
|
1026
1048
|
# @TODO@ Also, should have option to speak only to configured resolvers (not follow authoritative chain)
|
1027
|
-
#
|
1049
|
+
#
|
1028
1050
|
if queue.empty?
|
1029
1051
|
log_and_raise('Severe internal error - Queue empty in handle_queue_event')
|
1030
1052
|
end
|
@@ -1208,8 +1230,8 @@ module Dnsruby
|
|
1208
1230
|
# handle_error_response(queue, event_id, error, response)
|
1209
1231
|
# Or:
|
1210
1232
|
send_result(client_queue, client_query_id, select_queue, response, error)
|
1211
|
-
#
|
1212
|
-
#
|
1233
|
+
#
|
1234
|
+
#
|
1213
1235
|
end
|
1214
1236
|
end
|
1215
1237
|
end
|