dnsruby 1.57.0 → 1.58.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -2
- data/.travis.yml +1 -1
- data/README.md +2 -2
- data/RELEASE_NOTES.md +16 -0
- data/Rakefile +2 -0
- data/dnsruby.gemspec +2 -0
- data/lib/dnsruby.rb +5 -0
- data/lib/dnsruby/bit_mapping.rb +138 -0
- data/lib/dnsruby/bitmap.rb +108 -0
- data/lib/dnsruby/message/decoder.rb +90 -80
- data/lib/dnsruby/message/message.rb +16 -3
- data/lib/dnsruby/message/section.rb +3 -3
- data/lib/dnsruby/name.rb +2 -2
- data/lib/dnsruby/packet_sender.rb +73 -1
- data/lib/dnsruby/resolv.rb +56 -42
- data/lib/dnsruby/resolver.rb +95 -73
- data/lib/dnsruby/resource/GPOS.rb +9 -3
- data/lib/dnsruby/resource/HIP.rb +1 -1
- data/lib/dnsruby/resource/IN.rb +3 -1
- data/lib/dnsruby/resource/NAPTR.rb +2 -2
- data/lib/dnsruby/resource/NSEC3.rb +2 -2
- data/lib/dnsruby/resource/NXT.rb +302 -0
- data/lib/dnsruby/resource/OPT.rb +2 -2
- data/lib/dnsruby/resource/TXT.rb +2 -2
- data/lib/dnsruby/resource/generic.rb +1 -0
- data/lib/dnsruby/resource/type_bitmap.rb +0 -0
- data/lib/dnsruby/select_thread.rb +174 -83
- data/lib/dnsruby/single_resolver.rb +2 -2
- data/lib/dnsruby/version.rb +1 -1
- data/lib/dnsruby/zone_reader.rb +19 -9
- data/lib/dnsruby/zone_transfer.rb +1 -1
- data/test/spec_helper.rb +9 -1
- data/test/tc_axfr.rb +17 -4
- data/test/tc_gpos.rb +3 -3
- data/test/tc_message.rb +7 -1
- data/test/tc_nxt.rb +192 -0
- data/test/tc_recur.rb +2 -1
- data/test/tc_resolv.rb +73 -0
- data/test/tc_resolver.rb +22 -4
- data/test/tc_rr-opt.rb +9 -8
- data/test/tc_rr.rb +22 -0
- data/test/tc_single_resolver.rb +15 -15
- data/test/tc_soak.rb +154 -65
- data/test/tc_soak_base.rb +15 -15
- data/test/tc_tcp.rb +1 -1
- data/test/tc_tcp_pipelining.rb +203 -0
- data/test/tc_zone_reader.rb +73 -0
- data/test/test_dnsserver.rb +208 -0
- data/test/test_utils.rb +49 -0
- data/test/ts_offline.rb +59 -41
- data/test/ts_online.rb +92 -63
- metadata +40 -3
- data/test/tc_dnsruby.rb +0 -51
data/test/tc_rr-opt.rb
CHANGED
@@ -113,14 +113,15 @@ class TestRrOpt < Minitest::Test
|
|
113
113
|
assert(p.additional()[0].klass.code == 4096)
|
114
114
|
end
|
115
115
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
data/test/tc_rr.rb
CHANGED
@@ -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
|
data/test/tc_single_resolver.rb
CHANGED
@@ -28,30 +28,30 @@ class TestSingleResolver < Minitest::Test
|
|
28
28
|
Rrs = [
|
29
29
|
{
|
30
30
|
:type => Types.A,
|
31
|
-
:name => 'a.t.
|
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.
|
37
|
-
:exchange => 'a.t.
|
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.
|
43
|
-
:domainname => 'a.t.
|
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.
|
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("
|
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.
|
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.
|
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.
|
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,
|
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.
|
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.
|
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.
|
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
|
data/test/tc_soak.rb
CHANGED
@@ -16,24 +16,76 @@
|
|
16
16
|
|
17
17
|
require_relative 'spec_helper'
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
173
|
+
|
174
|
+
res = create_default_single_resolver
|
94
175
|
ids = []
|
95
176
|
mutex = Mutex.new
|
96
|
-
|
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
|
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
|
-
|
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(
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
259
|
+
timeout_count+=1
|
178
260
|
}
|
179
261
|
next
|
180
262
|
elsif (packet.class!=Message)
|
181
|
-
|
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
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
time_taken
|
195
|
-
|
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
|
-
|
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
|