dnsruby 1.57.0 → 1.58.0

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