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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -2
  3. data/.travis.yml +1 -1
  4. data/README.md +2 -2
  5. data/RELEASE_NOTES.md +16 -0
  6. data/Rakefile +2 -0
  7. data/dnsruby.gemspec +2 -0
  8. data/lib/dnsruby.rb +5 -0
  9. data/lib/dnsruby/bit_mapping.rb +138 -0
  10. data/lib/dnsruby/bitmap.rb +108 -0
  11. data/lib/dnsruby/message/decoder.rb +90 -80
  12. data/lib/dnsruby/message/message.rb +16 -3
  13. data/lib/dnsruby/message/section.rb +3 -3
  14. data/lib/dnsruby/name.rb +2 -2
  15. data/lib/dnsruby/packet_sender.rb +73 -1
  16. data/lib/dnsruby/resolv.rb +56 -42
  17. data/lib/dnsruby/resolver.rb +95 -73
  18. data/lib/dnsruby/resource/GPOS.rb +9 -3
  19. data/lib/dnsruby/resource/HIP.rb +1 -1
  20. data/lib/dnsruby/resource/IN.rb +3 -1
  21. data/lib/dnsruby/resource/NAPTR.rb +2 -2
  22. data/lib/dnsruby/resource/NSEC3.rb +2 -2
  23. data/lib/dnsruby/resource/NXT.rb +302 -0
  24. data/lib/dnsruby/resource/OPT.rb +2 -2
  25. data/lib/dnsruby/resource/TXT.rb +2 -2
  26. data/lib/dnsruby/resource/generic.rb +1 -0
  27. data/lib/dnsruby/resource/type_bitmap.rb +0 -0
  28. data/lib/dnsruby/select_thread.rb +174 -83
  29. data/lib/dnsruby/single_resolver.rb +2 -2
  30. data/lib/dnsruby/version.rb +1 -1
  31. data/lib/dnsruby/zone_reader.rb +19 -9
  32. data/lib/dnsruby/zone_transfer.rb +1 -1
  33. data/test/spec_helper.rb +9 -1
  34. data/test/tc_axfr.rb +17 -4
  35. data/test/tc_gpos.rb +3 -3
  36. data/test/tc_message.rb +7 -1
  37. data/test/tc_nxt.rb +192 -0
  38. data/test/tc_recur.rb +2 -1
  39. data/test/tc_resolv.rb +73 -0
  40. data/test/tc_resolver.rb +22 -4
  41. data/test/tc_rr-opt.rb +9 -8
  42. data/test/tc_rr.rb +22 -0
  43. data/test/tc_single_resolver.rb +15 -15
  44. data/test/tc_soak.rb +154 -65
  45. data/test/tc_soak_base.rb +15 -15
  46. data/test/tc_tcp.rb +1 -1
  47. data/test/tc_tcp_pipelining.rb +203 -0
  48. data/test/tc_zone_reader.rb +73 -0
  49. data/test/test_dnsserver.rb +208 -0
  50. data/test/test_utils.rb +49 -0
  51. data/test/ts_offline.rb +59 -41
  52. data/test/ts_online.rb +92 -63
  53. metadata +40 -3
  54. 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
- def add_answer(rr) #:nodoc: all
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 (other.instance_of?(Message::Section))
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, true)
72
- self.rrsets(nil, true).each {|rrset|
71
+ otherrrsets = other.rrsets(nil)
72
+ self.rrsets(nil).each {|rrset|
73
73
  return false unless otherrrsets.include?(rrset)
74
74
  }
75
75
 
@@ -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([0..9a..fA..F][0..9a..fA..F])/o, pos))
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
- socket = TCPSocket.new(@server, @port, src_address, src_port)
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')
@@ -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
- DefaultResolver.getaddress(name)
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
- DefaultResolver.getaddresses(name)
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
- DefaultResolver.each_address(name, &block)
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
- DefaultResolver.getname(address)
44
+ instance.getname(address)
26
45
  end
27
46
 
28
47
  # Looks up all hostnames of +address+
29
48
  def self.getnames(address)
30
- DefaultResolver.getnames(address)
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
- DefaultResolver.each_name(address, &proc)
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
- each_address(name) {|address| return address}
46
- raise ResolvError.new("no address for #{name}")
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
- ret = []
52
- each_address(name) {|address| ret << address}
53
- return ret
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
- if AddressRegex =~ name
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
- each_name(address) {|name| return name}
75
- raise ResolvError.new("no name for #{address}")
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
- ret = []
81
- each_name(address) {|name| ret << name}
82
- return ret
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
- yielded = false
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
@@ -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; private :create_tsig_options
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