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