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.
- 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
|