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
@@ -113,14 +113,15 @@ class TestRrOpt < Minitest::Test
113
113
  assert(p.additional()[0].klass.code == 4096)
114
114
  end
115
115
 
116
- def test_large_packet
117
- # Query TXT for overflow.dnsruby.validation-test-servers.nominet.org.uk
118
- # with a large udp_size
119
- res = SingleResolver.new
120
- res.udp_size = 4096
121
- ret = res.query("overflow.dnsruby.validation-test-servers.nominet.org.uk", Types.TXT)
122
- assert(ret.rcode == RCode.NoError)
123
- end
116
+ # Sadly Nominet no longer host these servers :-(
117
+ # def test_large_packet
118
+ # # Query TXT for overflow.dnsruby.validation-test-servers.nominet.org.uk
119
+ # # with a large udp_size
120
+ # res = SingleResolver.new
121
+ # res.udp_size = 4096
122
+ # ret = res.query("overflow.dnsruby.validation-test-servers.nominet.org.uk", Types.TXT)
123
+ # assert(ret.rcode == RCode.NoError)
124
+ # end
124
125
 
125
126
  def test_decode_opt
126
127
  # Create an OPT RR
@@ -328,4 +328,26 @@ class TestRR < Minitest::Test
328
328
  assert_equal a1.hash, a2.hash
329
329
  assert_equal a1.hash, a3.hash
330
330
  end
331
+
332
+ def _test_duplicate_answer(method_as_symbol)
333
+ expected_count = case method_as_symbol
334
+ when :add_answer
335
+ 1
336
+ when :add_answer!
337
+ 2
338
+ end
339
+
340
+ rr = RR.new_from_string 'techhumans.com. 1111 IN A 69.89.31.97'
341
+ message = Message.new
342
+ 2.times { message.send(method_as_symbol, rr) }
343
+ assert_equal(expected_count, message.header.ancount)
344
+ end
345
+
346
+ def test_add_dup_answer_no_force
347
+ _test_duplicate_answer(:add_answer)
348
+ end
349
+
350
+ def test_add_dup_answer_force
351
+ _test_duplicate_answer(:add_answer!)
352
+ end
331
353
  end
@@ -28,30 +28,30 @@ class TestSingleResolver < Minitest::Test
28
28
  Rrs = [
29
29
  {
30
30
  :type => Types.A,
31
- :name => 'a.t.dnsruby.validation-test-servers.nominet.org.uk',
31
+ :name => 'a.t.net-dns.org',
32
32
  :address => '10.0.1.128'
33
33
  },
34
34
  {
35
35
  :type => Types::MX,
36
- :name => 'mx.t.dnsruby.validation-test-servers.nominet.org.uk',
37
- :exchange => 'a.t.dnsruby.validation-test-servers.nominet.org.uk',
36
+ :name => 'mx.t.net-dns.org',
37
+ :exchange => 'a.t.net-dns.org',
38
38
  :preference => 10
39
39
  },
40
40
  {
41
41
  :type => 'CNAME',
42
- :name => 'cname.t.dnsruby.validation-test-servers.nominet.org.uk',
43
- :domainname => 'a.t.dnsruby.validation-test-servers.nominet.org.uk'
42
+ :name => 'cname.t.net-dns.org',
43
+ :domainname => 'a.t.net-dns.org'
44
44
  },
45
45
  {
46
46
  :type => Types.TXT,
47
- :name => 'txt.t.dnsruby.validation-test-servers.nominet.org.uk',
47
+ :name => 'txt.t.net-dns.org',
48
48
  :strings => ['Net-DNS']
49
49
  }
50
50
  ]
51
51
 
52
52
  def test_simple
53
53
  res = SingleResolver.new()
54
- m = res.query("a.t.dnsruby.validation-test-servers.nominet.org.uk")
54
+ m = res.query("ns1.google.com.")
55
55
  end
56
56
 
57
57
  def test_timeout
@@ -69,7 +69,7 @@ class TestSingleResolver < Minitest::Test
69
69
  res.port = port
70
70
  res.packet_timeout=1
71
71
  start_time = Time.now.to_i
72
- m = res.query("a.t.dnsruby.validation-test-servers.nominet.org.uk")
72
+ m = res.query("a.t.net-dns.org")
73
73
  fail "Got response when should have got none"
74
74
  rescue ResolvTimeout
75
75
  stop_time = Time.now.to_i
@@ -85,7 +85,7 @@ class TestSingleResolver < Minitest::Test
85
85
  res.packet_timeout=1
86
86
  start_time = Time.now.to_i
87
87
  # TheLog.level = Logger::DEBUG
88
- m = res.query("a.t.dnsruby.validation-test-servers.nominet.org.uk")
88
+ m = res.query("a.t.net-dns.org")
89
89
  fail "TCP timeouts"
90
90
  rescue ResolvTimeout
91
91
  # print "Got Timeout for TCP\n"
@@ -112,7 +112,7 @@ class TestSingleResolver < Minitest::Test
112
112
  res.port = port
113
113
  res.packet_timeout=1
114
114
  q = Queue.new
115
- msg = Message.new("a.t.dnsruby.validation-test-servers.nominet.org.uk")
115
+ msg = Message.new("a.t.net-dns.org")
116
116
  res.send_async(msg, q, msg)
117
117
  id,ret, error = q.pop
118
118
  assert(id==msg)
@@ -141,7 +141,7 @@ class TestSingleResolver < Minitest::Test
141
141
 
142
142
  assert(packet, "Got an answer for #{data[:name]} IN #{data[:type]}")
143
143
  assert_equal(1, packet.header.qdcount, 'Only one question')
144
- assert_equal(1, packet.header.ancount, 'Got single answer')
144
+ assert_equal(1, packet.header.ancount, "Got single answer (for question #{data[:name]}")
145
145
 
146
146
  question = (packet.question)[0]
147
147
  answer = (packet.answer)[0]
@@ -184,11 +184,11 @@ class TestSingleResolver < Minitest::Test
184
184
  def test_res_config
185
185
  res = Dnsruby::SingleResolver.new
186
186
 
187
- res.server=('a.t.dnsruby.validation-test-servers.nominet.org.uk')
187
+ res.server=('a.t.net-dns.org')
188
188
  ip = res.server
189
189
  assert_equal('10.0.1.128', ip.to_s, 'nameserver() looks up IP.')
190
190
 
191
- res.server=('cname.t.dnsruby.validation-test-servers.nominet.org.uk')
191
+ res.server=('cname.t.net-dns.org')
192
192
  ip = res.server
193
193
  assert_equal('10.0.1.128', ip.to_s, 'nameserver() looks up cname.')
194
194
  end
@@ -196,9 +196,9 @@ class TestSingleResolver < Minitest::Test
196
196
  def test_truncated_response
197
197
  res = SingleResolver.new
198
198
  # print "Dnssec = #{res.dnssec}\n"
199
- res.server=('ns0.validation-test-servers.nominet.org.uk')
199
+ # res.server=('ns0.validation-test-servers.nominet.org.uk')
200
200
  res.packet_timeout = 15
201
- m = res.query("overflow.dnsruby.validation-test-servers.nominet.org.uk", 'txt')
201
+ m = res.query("overflow.net-dns.org", 'txt')
202
202
  assert(m.header.ancount == 61, "61 answer records expected, got #{m.header.ancount}")
203
203
  assert(!m.header.tc, "Message was truncated!")
204
204
  end
@@ -16,24 +16,76 @@
16
16
 
17
17
  require_relative 'spec_helper'
18
18
 
19
- begin
20
- require 'test/tc_single_resolver'
21
- rescue LoadError
22
- require 'tc_single_resolver'
23
- end
24
- begin
25
- require 'test/tc_soak_base'
26
- rescue LoadError
27
- require 'tc_soak_base'
28
- end
19
+ # require_relative 'tc_single_resolver'
20
+ require_relative 'tc_soak_base'
21
+ require_relative 'test_dnsserver'
22
+
29
23
  include Dnsruby
24
+
30
25
  # This class tries to soak test the Dnsruby library.
31
26
  # It can't do this very well, owing to the small number of sockets allowed to be open simultaneously.
32
27
  # @TODO@ Future versions of dnsruby will allow random streaming over a fixed number of (cycling) random sockets,
33
28
  # so this test can be beefed up considerably at that point.
34
29
  # @todo@ A test DNS server running on localhost is really needed here
30
+
31
+ class MyServer < RubyDNS::Server
32
+
33
+ IP = "127.0.0.1"
34
+ PORT = 53927
35
+
36
+ @@stats = Stats.new
37
+
38
+ def self.stats
39
+ @@stats
40
+ end
41
+
42
+ def process(name, resource_class, transaction)
43
+ transaction.respond!("93.184.216.34", { resource_class: Resolv::DNS::Resource::IN::A })
44
+ Celluloid.logger.debug "got message"
45
+ end
46
+ end
47
+
48
+ class PipeliningServer < MyServer
49
+ def run
50
+ fire(:setup)
51
+
52
+ link NioTcpPipeliningHandler.new(self, IP, PORT, 5) #5 max request
53
+ link RubyDNS::UDPHandler.new(self, IP, PORT)
54
+
55
+ fire(:start)
56
+ end
57
+ end
58
+
35
59
  class TestSingleResolverSoak < Minitest::Test
36
60
 
61
+ IP = MyServer::IP
62
+ PORT = MyServer::PORT
63
+
64
+ def initialize(arg)
65
+ super(arg)
66
+ self.class.init
67
+ end
68
+
69
+ def self.init
70
+ unless @initialized
71
+ Celluloid.boot
72
+ # By default, Celluloid logs output to console. Use Dnsruby.log instead.
73
+ Celluloid.logger = Dnsruby.log
74
+ @initialized = true
75
+ end
76
+ end
77
+
78
+ SINGLE_RESOLVER_QUERY_TIMES = 63
79
+
80
+ def setup
81
+ # Instantiate a new server
82
+ # For each query respond with 93.184.216.34
83
+
84
+ @@supervisor ||= RubyDNS::run_server(asynchronous: true,
85
+ server_class: PipeliningServer)
86
+
87
+ end
88
+
37
89
  def test_many_asynchronous_queries_one_single_resolver
38
90
  run_many_asynch_queries_test_single_res(1)
39
91
  end
@@ -42,58 +94,87 @@ class TestSingleResolverSoak < Minitest::Test
42
94
  run_many_asynch_queries_test_single_res(50)
43
95
  end
44
96
 
45
- def run_many_asynch_queries_test_single_res(num_resolvers)
97
+ def test_many_asynchronous_queries_one_single_resolver_tcp
98
+ run_many_asynch_queries_test_single_res(1, true)
99
+ end
100
+
101
+ def test_many_asynchronous_queries_many_single_resolvers_tcp
102
+ run_many_asynch_queries_test_single_res(50, true)
103
+ end
104
+
105
+ def test_many_asynchronous_queries_one_single_resolver_tcp_pipelining
106
+ run_many_asynch_queries_test_single_res(1, true, true)
107
+ end
108
+
109
+ def test_many_asynchronous_queries_many_single_resolvers_tcp_pipelining
110
+ run_many_asynch_queries_test_single_res(50, true, true)
111
+ end
112
+
113
+ def run_many_asynch_queries_test_single_res(num_resolvers, tcp = false, pipelining = false)
46
114
  q = Queue.new
47
- resolvers = []
48
- timed_out = 0
49
- query_count = 0
50
- num_resolvers.times do |n|
51
- resolvers.push(SingleResolver.new)
52
- resolvers[n].packet_timeout=4
115
+ timeout_count = 0
116
+ resolvers = Array.new(num_resolvers) do
117
+ SingleResolver.new(server: IP,
118
+ port: PORT,
119
+ do_caching: false,
120
+ do_validation: false,
121
+ tcp_pipelining: pipelining,
122
+ packet_timeout: 10,
123
+ tcp_pipelining_max_queries: 5,
124
+ use_tcp: tcp)
53
125
  end
54
- res_pos = 0
55
126
  start = Time.now
127
+
56
128
  # @todo@ On windows, MAX_FILES is 256. This means that we have to limit
57
129
  # this test while we're not using single sockets.
58
130
  # We run four queries per iteration, so we're limited to 64 runs.
59
- 63.times do |i|
60
- rr_count = 0
61
- TestSoakBase::Rrs.each do |data|
62
- rr_count+=1
63
- res = resolvers[res_pos]
64
- res_pos=+1
65
- if (res_pos >= num_resolvers)
66
- res_pos = 0
131
+ messages = TestSoakBase::Rrs.map do |data|
132
+ message = Message.new(data[:name], data[:type])
133
+ message.do_validation = false
134
+ message.do_caching = false
135
+ message
136
+ end
137
+
138
+ query_count = SINGLE_RESOLVER_QUERY_TIMES * messages.count
139
+
140
+ receive_thread = Thread.new do
141
+ query_count.times do
142
+ _id, ret, error = q.pop
143
+ if error.is_a?(ResolvTimeout)
144
+ timeout_count+=1
145
+ elsif ret.class != Message
146
+ p "ERROR RETURNED : #{error}"
67
147
  end
68
- res.send_async(Message.new(data[:name], data[:type]), q, [i,rr_count])
69
- # p "Sent #{i}, #{rr_count}, Queue #{q}"
70
- query_count+=1
71
148
  end
72
149
  end
73
- query_count.times do |i|
74
- id,ret, error = q.pop
75
- if (error.class == ResolvTimeout)
76
- timed_out+=1
77
- elsif (ret.class != Message)
78
- p "ERROR RETURNED : #{error}"
79
- end
80
150
 
151
+ resolver_cycler = resolvers.cycle
152
+
153
+ SINGLE_RESOLVER_QUERY_TIMES.times do |i|
154
+ rr_count = 0
155
+ messages.each do | message |
156
+ rr_count += 1
157
+ resolver_cycler.next.send_async(message, q, rr_count + i * messages.count)
158
+ # p "Sent #{i}, #{rr_count}, Queue #{q}"
159
+ end
81
160
  end
82
- stop=Time.now
83
- time_taken=stop-start
84
- p "Query count : #{query_count}, #{timed_out} timed out. #{time_taken} time taken"
85
- assert(timed_out < query_count * 0.1, "#{timed_out} of #{query_count} timed out!")
86
- end
87
161
 
162
+ receive_thread.join
163
+
164
+ time_taken = Time.now - start
165
+ puts "Query count : #{query_count}, #{timeout_count} timed out. #{time_taken} time taken"
166
+ assert(timeout_count < query_count * 0.1, "#{timeout_count} of #{query_count} timed out!")
167
+ end
88
168
 
89
169
  def test_many_threads_on_one_single_resolver_synchronous
90
170
  # Test multi-threaded behaviour
91
171
  # Check the header IDs to make sure they're all different
92
172
  threads = Array.new
93
- res = SingleResolver.new
173
+
174
+ res = create_default_single_resolver
94
175
  ids = []
95
176
  mutex = Mutex.new
96
- timed_out = 0
177
+ timeout_count = 0
97
178
  query_count = 0
98
179
  res.packet_timeout=4
99
180
  start=Time.now
@@ -107,16 +188,12 @@ class TestSingleResolverSoak < Minitest::Test
107
188
  threads[i] = Thread.new{
108
189
  40.times do |j|
109
190
  TestSoakBase::Rrs.each do |data|
110
- mutex.synchronize do
111
- query_count+=1
112
- end
191
+ mutex.synchronize { query_count += 1 }
113
192
  packet=nil
114
193
  begin
115
194
  packet = res.query(data[:name], data[:type])
116
195
  rescue ResolvTimeout
117
- mutex.synchronize {
118
- timed_out+=1
119
- }
196
+ mutex.synchronize { timeout_count += 1 }
120
197
  next
121
198
  end
122
199
  assert(packet)
@@ -131,9 +208,9 @@ class TestSingleResolverSoak < Minitest::Test
131
208
  end
132
209
  stop=Time.now
133
210
  time_taken=stop-start
134
- p "Query count : #{query_count}, #{timed_out} timed out. #{time_taken} time taken"
211
+ puts "Query count : #{query_count}, #{timeout_count} timed out. #{time_taken} time taken"
135
212
  # check_ids(ids) # only do this if we expect all different IDs - e.g. if we stream over a single socket
136
- assert(timed_out < query_count * 0.1, "#{timed_out} of #{query_count} timed out!")
213
+ assert(timeout_count < query_count * 0.1, "#{timeout_count} of #{query_count} timed out!")
137
214
  end
138
215
 
139
216
  def check_ids(ids)
@@ -152,7 +229,7 @@ class TestSingleResolverSoak < Minitest::Test
152
229
  # @todo@ Check the header IDs to make sure they're all different
153
230
  threads = Array.new
154
231
  mutex = Mutex.new
155
- timed_out = 0
232
+ timeout_count = 0
156
233
  query_count = 0
157
234
  start=Time.now
158
235
  num_times=250
@@ -162,23 +239,28 @@ class TestSingleResolverSoak < Minitest::Test
162
239
  end
163
240
  num_times.times do |i|
164
241
  threads[i] = Thread.new{
165
- res = SingleResolver.new
166
- res.packet_timeout=4
242
+ res = create_default_single_resolver
167
243
  40.times do |j|
168
244
  TestSoakBase::Rrs.each do |data|
169
245
  mutex.synchronize do
170
246
  query_count+=1
171
247
  end
172
248
  q = Queue.new
173
- res.send_async(Message.new(data[:name], data[:type]), q, [i,j])
249
+
250
+ message = Message.new(data[:name], data[:type])
251
+ message.do_validation = false
252
+ message.do_caching = false
253
+
254
+ res.send_async(message, q, [i,j])
255
+
174
256
  id, packet, error = q.pop
175
257
  if (error.class == ResolvTimeout)
176
258
  mutex.synchronize {
177
- timed_out+=1
259
+ timeout_count+=1
178
260
  }
179
261
  next
180
262
  elsif (packet.class!=Message)
181
- p "ERROR! #{error}"
263
+ puts "ERROR! #{error}"
182
264
  end
183
265
 
184
266
  assert(packet)
@@ -187,14 +269,21 @@ class TestSingleResolverSoak < Minitest::Test
187
269
  end
188
270
  }
189
271
  end
190
- threads.each do |thread|
191
- thread.join
192
- end
193
- stop=Time.now
194
- time_taken=stop-start
195
- p "Query count : #{query_count}, #{timed_out} timed out. #{time_taken} time taken"
196
- assert(timed_out < query_count * 0.1, "#{timed_out} of #{query_count} timed out!")
272
+ # NOTE: For methods on the objects taking no params, we can use this shorthand.
273
+ threads.each(&:join)
274
+
275
+ time_taken = Time.now - start
276
+ puts "Query count : #{query_count}, #{timeout_count} timed out. #{time_taken} time taken"
277
+ assert(timeout_count < query_count * 0.1, "#{timeout_count} of #{query_count} timed out!")
197
278
  end
198
279
 
199
280
 
200
- end
281
+ def create_default_single_resolver
282
+ SingleResolver.new(server: IP,
283
+ port: PORT,
284
+ do_caching: false,
285
+ do_validation: false,
286
+ packet_timeout: 10)
287
+
288
+ end
289
+ end