File: tc_resolver.rb

package info (click to toggle)
dnsruby 1.61.5-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 1,520 kB
  • sloc: ruby: 17,811; makefile: 3
file content (404 lines) | stat: -rw-r--r-- 13,227 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# --
# Copyright 2007 Nominet UK
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either tmexpress or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ++
require_relative 'spec_helper'

require 'socket'

# @TODO@ We also need a test server so we can control behaviour of server to test
# different aspects of retry strategy.
# Of course, with Ruby's limit of 256 open sockets per process, we'd need to run
# the server in a different Ruby process.

class TestResolver < Minitest::Test

  include Dnsruby

  Thread::abort_on_exception = true

  GOOD_DOMAIN_NAME = 'example.com'
  BAD_DOMAIN_NAME  = 'dnsruby-test-of-bad-domain-name.blah'

  PORT = 42138
  @@port = PORT

  def setup
    Dnsruby::Config.reset
  end

  def assert_valid_response(response)
    assert(response.kind_of?(Message), "Expected response to be a message but was a #{response.class}")
  end

  def assert_nil_response(response)
    assert(response.nil?, "Expected no response but got a #{response.class}:\n#{response}")
  end

  def assert_error_is_exception(error, error_class = Exception)
    assert(error.is_a?(error_class), "Expected error to be an #{error_class}, but was a #{error.class}:\n#{error}")
  end

  def assert_nil_error(error)
    assert(error.nil?, "Expected no error but got a #{error.class}:\n#{error}")
  end

  def test_send_message
    response = Resolver.new.send_message(Message.new("example.com", Types.A))
    assert_valid_response(response)
  end

  def test_send_message_bang_noerror
    response, error = Resolver.new.send_message!(Message.new(GOOD_DOMAIN_NAME, Types.A))
    assert_nil_error(error)
    assert_valid_response(response)
  end

  def test_send_message_bang_error
    message = Message.new(BAD_DOMAIN_NAME, Types.A)
    response, error = Resolver.new.send_message!(message)
    assert_nil_response(response)
    assert_error_is_exception(error)
  end

  def test_send_plain_message
    resolver = Resolver.new
    response, error = resolver.send_plain_message(Message.new("cnn.com"))
    assert_nil_error(error)
    assert_valid_response(response)

    m = Message.new(BAD_DOMAIN_NAME)
    m.header.rd = true
    response, error = resolver.send_plain_message(m)
    assert_valid_response(response)
    assert_error_is_exception(error, NXDomain)
  end

  def test_query
    response = Resolver.new.query("example.com")
    assert_valid_response(response)
  end

  def test_query_bang_noerror
    response, error = Resolver.new.query!(GOOD_DOMAIN_NAME)
    assert_nil_error(error)
    assert_valid_response(response)
  end

  def test_query_bang_error
    response, error = Resolver.new.query!(BAD_DOMAIN_NAME)
    assert_nil_response(response)
    assert_error_is_exception(error)
  end

  def test_query_async
    q = Queue.new
    Resolver.new.send_async(Message.new("example.com", Types.A),q,q)
    id, response, error = q.pop
    assert_equal(id, q, "Id wrong!")
    assert_valid_response(response)
    assert_nil_error(error)
  end

  def test_query_one_duff_server_one_good
    res = Resolver.new({:nameserver => ["8.8.8.8", "8.8.8.7"]})
    res.retry_delay=1
    q = Queue.new
    res.send_async(Message.new("example.com", Types.A),q,q)
    id, response, error = q.pop
    assert_equal(id, q, "Id wrong!")
    assert_valid_response(response)
    assert_nil_error(error)
  end

  #  @TODO@ Implement!!  But then, why would anyone want to do this?
  #   def test_many_threaded_clients
  #     assert(false, "IMPLEMENT!")
  #   end

  def test_reverse_lookup
    m = Message.new("8.8.8.8", Types.PTR)
    r = Resolver.new
    q=Queue.new
    r.send_async(m,q,q)
    id,ret, error=q.pop
    assert(ret.kind_of?(Message))
    no_pointer=true
    ret.each_answer do |answer|
      if (answer.type==Types.PTR)
        no_pointer=false
        assert(answer.domainname.to_s=~/google/)
      end
    end
    assert(!no_pointer)
  end

#  def test_bad_host
#    res = Resolver.new({:nameserver => "localhost"})
#    res.retry_times=1
#    res.retry_delay=0
#    res.query_timeout = 1
#    q = Queue.new
#    res.send_async(Message.new("example.com", Types.A), q, q)
#    id, m, err = q.pop
#    assert(id==q)
#    assert(m == nil)
#    assert(err.kind_of?(OtherResolvError) || err.kind_of?(IOError), "OtherResolvError or IOError expected : got #{err.class}")
#  end
#
  def test_nxdomain
    resolver = Resolver.new
    q = Queue.new
    resolver .send_async(Message.new(BAD_DOMAIN_NAME, Types.A), q, 1)
    id, m, error = q.pop
    assert(id==1, "Id should have been 1 but was #{id}")
    assert(m.rcode == RCode.NXDOMAIN, "Expected NXDOMAIN but got #{m.rcode} instead.")
    assert_error_is_exception(error, NXDomain)
  end

  def test_timeouts
    # test timeout behaviour for different retry, retrans, total timeout etc.
    # Problem here is that many sockets will be created for queries which time out.
    #  Run a query which will not respond, and check that the timeout works
    if (RUBY_PLATFORM !~ /darwin/)
      start=stop=0
      retry_times = 3
      retry_delay=1
      packet_timeout=2
      #  Work out what time should be, then time it to check
      expected = ((2**(retry_times-1))*retry_delay) + packet_timeout
      begin
        res = Dnsruby::Resolver.new({:nameserver => "10.0.1.128"})
        #       res = Resolver.new({:nameserver => "213.248.199.17"})
        res.packet_timeout=packet_timeout
        res.retry_times=retry_times
        res.retry_delay=retry_delay
        start=Time.now
        m = res.send_message(Message.new("a.t.dnsruby.validation-test-servers.nominet.org.uk", Types.A))
        fail
      rescue ResolvTimeout
        stop=Time.now
        time = stop-start
        assert(time <= expected * 1.3 && time >= expected * 0.9, "Wrong time take, expected #{expected}, took #{time}")
      end
  end
  end

  def test_packet_timeout
        res = Dnsruby::Resolver.new({:nameserver => []})
#      res = Resolver.new({:nameserver => "10.0.1.128"})
      start=stop=0
      retry_times = retry_delay = packet_timeout= 10
      query_timeout=2
      begin
        res.packet_timeout=packet_timeout
        res.retry_times=retry_times
        res.retry_delay=retry_delay
        res.query_timeout=query_timeout
        #  Work out what time should be, then time it to check
        expected = query_timeout
        start=Time.now
        m = res.send_message(Message.new("a.t.dnsruby.validation-test-servers.nominet.org.uk", Types.A))
        fail
      rescue Dnsruby::ResolvTimeout
        stop=Time.now
        time = stop-start
        assert(time <= expected * 1.3 && time >= expected * 0.9, "Wrong time take, expected #{expected}, took #{time}")
      end    #
  end

  def test_queue_packet_timeout
#    if (!RUBY_PLATFORM=~/darwin/)
      res = Dnsruby::Resolver.new({:nameserver => "10.0.1.128"})
#      bad = SingleResolver.new("localhost")
      res.add_server("localhost")
      expected = 2
      res.query_timeout=expected
      q = Queue.new
      start = Time.now
      m = res.send_async(Message.new("a.t.dnsruby.validation-test-servers.nominet.org.uk", Types.A), q, q)
      id,ret,err = q.pop
      stop = Time.now
      assert(id=q)
      assert(ret==nil)
      assert(err.class == ResolvTimeout, "#{err.class}, #{err}")
      time = stop-start
      assert(time <= expected * 1.3 && time >= expected * 0.9, "Wrong time take, expected #{expected}, took #{time}")
#    end
  end

  def test_illegal_src_port
    #  Also test all singleresolver ports ok
    #  Try to set src_port to an illegal value - make sure error raised, and port OK
    res = Dnsruby::Resolver.new
    res.port = 56789
    tests = [53, 387, 1265, 3210, 48619]
    tests.each do |bad_port|
      begin
        res.src_port = bad_port
        fail("bad port #{bad_port}")
      rescue
      end
    end
    assert(res.single_resolvers[0].src_port = 56789)
  end

  def test_add_src_port
    #  Try setting and adding port ranges, and invalid ports, and 0.
    #  Also test all singleresolver ports ok
    res = Resolver.new
    res.src_port = [56789,56790, 56793]
    assert(res.src_port == [56789,56790, 56793])
    res.src_port = 56889..56891
    assert(res.src_port == [56889,56890,56891])
    res.add_src_port(60000..60002)
    assert(res.src_port == [56889,56890,56891,60000,60001,60002])
    res.add_src_port([60004,60005])
    assert(res.src_port == [56889,56890,56891,60000,60001,60002,60004,60005])
    res.add_src_port(60006)
    assert(res.src_port == [56889,56890,56891,60000,60001,60002,60004,60005,60006])
    #  Now test invalid src_ports
    tests = [0, 53, [60007, 53], [60008, 0], 55..100]
    tests.each do |x|
      begin
        res.add_src_port(x)
        fail()
      rescue
      end
    end
    assert(res.src_port == [56889,56890,56891,60000,60001,60002,60004,60005,60006])
    assert(res.single_resolvers[0].src_port == [56889,56890,56891,60000,60001,60002,60004,60005,60006])
  end

  def test_eventtype_api
    #  @TODO@ TEST THE Resolver::EventType interface!
  end
end


# Tests to see that query_raw handles send_plain_message's return values correctly.
class TestRawQuery < Minitest::Test

  KEY_NAME = 'key-name'
  KEY  = '0123456789'
  ALGO = 'hmac-md5'

  class CustomError < RuntimeError; end

  # Returns a new resolver whose send_plain_message method always returns
  # nil for the response, and a RuntimeError for the error.
  def resolver_returning_error
    resolver = Dnsruby::Resolver.new
    def resolver.send_plain_message(_message)
      [nil, CustomError.new]
    end
    resolver
  end

  # Returns a new resolver whose send_plain_message is overridden to return
  # :response_from_send_plain_message instead of a real Dnsruby::Message,
  # for easy comparison in the tests.
  def resolver_returning_response
    resolver = Dnsruby::Resolver.new
    def resolver.send_plain_message(_message)
      [:response_from_send_plain_message, nil]
    end
    resolver
  end

  # Test that when a strategy other than :raise or :return is passed,
  # an ArgumentError is raised.
  def test_bad_strategy
    assert_raises(ArgumentError) do
      resolver_returning_error.query_raw(Dnsruby::Message.new, :invalid_strategy)
    end
  end

  # Test that when send_plain_message returns an error,
  # and the error strategy is :raise, query_raw raises an error.
  def test_raise_error
    assert_raises(CustomError) do
      resolver_returning_error.query_raw(Dnsruby::Message.new, :raise)
    end
  end

  # Tests that if you don't specify an error strategy, an error will be
  # returned rather than raised (i.e. strategy defaults to :return).
  def test_return_error_is_default
    _response, error = resolver_returning_error.query_raw(Dnsruby::Message.new)
    assert error.is_a?(CustomError)
  end

  # Tests that when no error is returned, no error is raised.
  def test_raise_no_error
    response, _error = resolver_returning_response.query_raw(Dnsruby::Message.new, :raise)
    assert_equal :response_from_send_plain_message, response
  end

  # Test that when send_plain_message returns an error, and the error strategy
  # is set to :return, then an error is returned.
  def test_return_error
    _response, error = resolver_returning_error.query_raw(Dnsruby::Message.new, :return)
    assert error.is_a?(CustomError)
  end

  # Test that when send_plain_message returns a valid and response
  # and nil error, the same are returned by query_raw.
  def test_return_no_error
    response, error = resolver_returning_response.query_raw(Dnsruby::Message.new, :return)
    assert_nil error
    assert_equal :response_from_send_plain_message, response
  end

  def test_2_args_init
    options = Dnsruby::Resolver.create_tsig_options(KEY_NAME, KEY)
    assert_equal KEY_NAME, options[:name]
    assert_equal KEY, options[:key]
    assert_nil options[:algorithm]
  end

  def test_3_args_init
    options = Dnsruby::Resolver.create_tsig_options(KEY_NAME,KEY,ALGO)
    assert_equal KEY_NAME, options[:name]
    assert_equal KEY, options[:key]
    assert_equal ALGO, options[:algorithm]
  end
  
  def test_threads
    resolver = Dnsruby::Resolver.new(nameserver: ["8.8.8.8", "8.8.4.4"])
        resolver.query("google.com", "MX")
         resolver.query("google.com", "MX")
          resolver.query("google.com", "MX")
          begin
            resolver.query("googlöe.com", "MX") 
          rescue Dnsruby::ResolvError => e
            # fine
          end
          resolver.query("google.com", "MX")
          resolver.query("google.com", "MX")
          begin
            resolver.query("googlöe.com", "MX")
          rescue Dnsruby::ResolvError => e
            # fine
          end
          begin
            resolver.query("googlöe.com", "MX") 
          rescue Dnsruby::ResolvError => e
            # fine
          end
#          Dnsruby::Cache.delete("googlöe.com", "MX")
          
  end
end