dnsruby 1.74.0 → 2.0.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/.github/workflows/ci.yml +16 -4
- data/DNSSEC +3 -2
- data/EXAMPLES +1 -1
- data/README.md +370 -64
- data/RELEASE_NOTES.md +302 -1
- data/Rakefile +0 -3
- data/demo/axfr.rb +24 -6
- data/demo/rubydig.rb +0 -2
- data/dnsruby.gemspec +5 -4
- data/lib/dnsruby/cache.rb +279 -47
- data/lib/dnsruby/code_mapper.rb +35 -8
- data/lib/dnsruby/code_mappers.rb +63 -49
- data/lib/dnsruby/config.rb +75 -4
- data/lib/dnsruby/deadline_queue.rb +75 -0
- data/lib/dnsruby/dnssec.rb +148 -45
- data/lib/dnsruby/ipv6.rb +45 -5
- data/lib/dnsruby/key_cache.rb +202 -35
- data/lib/dnsruby/message/decoder.rb +85 -19
- data/lib/dnsruby/message/encoder.rb +27 -16
- data/lib/dnsruby/message/header.rb +8 -12
- data/lib/dnsruby/message/message.rb +53 -22
- data/lib/dnsruby/message/section.rb +12 -34
- data/lib/dnsruby/name.rb +5 -2
- data/lib/dnsruby/packet_sender.rb +645 -203
- data/lib/dnsruby/presentation.rb +147 -0
- data/lib/dnsruby/query_control.rb +132 -0
- data/lib/dnsruby/recursor.rb +795 -339
- data/lib/dnsruby/resolver.rb +468 -186
- data/lib/dnsruby/resource/CAA.rb +67 -16
- data/lib/dnsruby/resource/DLV.rb +6 -0
- data/lib/dnsruby/resource/DNSKEY.rb +108 -16
- data/lib/dnsruby/resource/DS.rb +27 -28
- data/lib/dnsruby/resource/HTTPS.rb +16 -0
- data/lib/dnsruby/resource/IN.rb +19 -0
- data/lib/dnsruby/resource/MX.rb +2 -2
- data/lib/dnsruby/resource/NSEC.rb +6 -10
- data/lib/dnsruby/resource/NSEC3.rb +55 -19
- data/lib/dnsruby/resource/NSEC3PARAM.rb +4 -6
- data/lib/dnsruby/resource/RR.rb +108 -65
- data/lib/dnsruby/resource/RRSIG.rb +5 -5
- data/lib/dnsruby/resource/RRSet.rb +38 -22
- data/lib/dnsruby/resource/SRV.rb +2 -2
- data/lib/dnsruby/resource/SVCB.rb +775 -0
- data/lib/dnsruby/resource/TSIG.rb +266 -231
- data/lib/dnsruby/resource/TXT.rb +34 -42
- data/lib/dnsruby/resource/domain_name.rb +1 -1
- data/lib/dnsruby/resource/generic.rb +31 -0
- data/lib/dnsruby/resource/generic_cache.rb +384 -0
- data/lib/dnsruby/resource/generic_constants.rb +79 -0
- data/lib/dnsruby/select_thread.rb +979 -291
- data/lib/dnsruby/serial_time.rb +25 -0
- data/lib/dnsruby/single_resolver.rb +25 -6
- data/lib/dnsruby/single_verifier.rb +1620 -530
- data/lib/dnsruby/transport_clock.rb +9 -0
- data/lib/dnsruby/update.rb +8 -6
- data/lib/dnsruby/validation_budget.rb +143 -0
- data/lib/dnsruby/validator_thread.rb +139 -58
- data/lib/dnsruby/version.rb +1 -1
- data/lib/dnsruby/zone_reader.rb +196 -129
- data/lib/dnsruby/zone_transfer.rb +157 -25
- data/lib/dnsruby.rb +7 -2
- data/test/ci_watchdog.rb +33 -0
- data/test/fixtures/org-ds-2026-11-05.zone +5 -0
- data/test/fixtures/root-dnskey-2026-11-05.zone +7 -0
- data/test/fork_test_helper.rb +37 -0
- data/test/local_dns_server.rb +57 -0
- data/test/soak_server.rb +152 -0
- data/test/socket_helpers.rb +26 -0
- data/test/spec_helper.rb +8 -13
- data/test/tc_attempt_configuration.rb +137 -0
- data/test/tc_caa.rb +2 -2
- data/test/tc_cache_keys.rb +36 -0
- data/test/tc_cancelled_send_capacity.rb +158 -0
- data/test/tc_dnssec_alias_validation.rb +289 -0
- data/test/tc_dnssec_budget.rb +489 -0
- data/test/tc_dnssec_cache_bounds.rb +197 -0
- data/test/tc_dnssec_cache_lifetime.rb +615 -0
- data/test/tc_dnssec_denial_proofs.rb +285 -0
- data/test/tc_dnssec_recursion_limits.rb +186 -0
- data/test/tc_dnssec_recursor_review_fixes.rb +351 -0
- data/test/tc_dnssec_trust_anchors.rb +286 -0
- data/test/tc_dnssec_trust_races.rb +147 -0
- data/test/tc_dnssec_wildcard_proofs.rb +249 -0
- data/test/tc_ds.rb +5 -5
- data/test/tc_edns_retry_regressions.rb +255 -0
- data/test/tc_escapedchars.rb +8 -0
- data/test/tc_final_review_fixes.rb +167 -0
- data/test/tc_final_transport_contracts.rb +104 -0
- data/test/tc_generic_cache.rb +852 -0
- data/test/tc_header.rb +26 -0
- data/test/tc_ipv6.rb +120 -0
- data/test/tc_key_cache_identity.rb +382 -0
- data/test/tc_malformed_tcp.rb +712 -0
- data/test/tc_name.rb +99 -5
- data/test/tc_nameserver_bootstrap.rb +322 -0
- data/test/tc_nsec.rb +4 -45
- data/test/tc_nsec3.rb +17 -1
- data/test/tc_pipeline_retirement.rb +358 -0
- data/test/tc_pipeline_retry.rb +462 -0
- data/test/tc_pipeline_send.rb +663 -0
- data/test/tc_protocol_review_fixes.rb +95 -0
- data/test/tc_query_control.rb +90 -0
- data/test/tc_record_codec_contracts.rb +107 -0
- data/test/tc_record_regressions.rb +225 -0
- data/test/tc_recur.rb +1 -0
- data/test/tc_recursor_address_fallback.rb +169 -0
- data/test/tc_recursor_limits.rb +139 -0
- data/test/tc_release_auxiliary_fixes.rb +288 -0
- data/test/tc_release_configuration_fixes.rb +612 -0
- data/test/tc_release_dnssec_completion.rb +405 -0
- data/test/tc_release_dnssec_fixes.rb +305 -0
- data/test/tc_release_negative_cache.rb +327 -0
- data/test/tc_release_recursor_cache.rb +212 -0
- data/test/tc_release_review_transport.rb +372 -0
- data/test/tc_release_review_transport_native.rb +56 -0
- data/test/tc_release_transport.rb +906 -0
- data/test/tc_release_transport_fixes.rb +196 -0
- data/test/tc_release_zone_defaults.rb +157 -0
- data/test/tc_release_zone_ttls.rb +39 -0
- data/test/tc_res_config.rb +42 -20
- data/test/tc_res_opt.rb +21 -1
- data/test/tc_resolver.rb +21 -19
- data/test/tc_resolver_concurrency.rb +383 -0
- data/test/tc_resolver_configuration_contracts.rb +31 -0
- data/test/tc_resolver_validation.rb +211 -0
- data/test/tc_review_cache.rb +163 -0
- data/test/tc_review_code_mapper.rb +279 -0
- data/test/tc_review_control_deadlines.rb +169 -0
- data/test/tc_review_followup_transport.rb +583 -0
- data/test/tc_review_packet_contracts.rb +369 -0
- data/test/tc_review_presentation.rb +213 -0
- data/test/tc_review_record_compatibility.rb +101 -0
- data/test/tc_review_transport_fixes.rb +195 -0
- data/test/tc_review_transport_native.rb +415 -0
- data/test/tc_review_validation_priming.rb +178 -0
- data/test/tc_rr-opt.rb +5 -2
- data/test/tc_rr-unknown.rb +19 -0
- data/test/tc_rr.rb +14 -0
- data/test/tc_rrset_grouping.rb +50 -0
- data/test/tc_rrset_review_fixes.rb +58 -0
- data/test/tc_rrsig.rb +21 -1
- data/test/tc_scoped_ipv6.rb +153 -0
- data/test/tc_scoped_ipv6_select.rb +157 -0
- data/test/tc_scoped_ipv6_transport.rb +329 -0
- data/test/tc_selector_wakeup.rb +42 -0
- data/test/tc_soak.rb +118 -196
- data/test/tc_socket_helpers.rb +90 -0
- data/test/tc_supporting_lookup_limits.rb +252 -0
- data/test/tc_svcb.rb +1043 -0
- data/test/tc_svcb_signed_target.rb +145 -0
- data/test/tc_svcb_transport.rb +120 -0
- data/test/tc_tcp.rb +69 -43
- data/test/tc_tcp_pipelining.rb +95 -204
- data/test/tc_transport_fork.rb +179 -0
- data/test/tc_tsig.rb +2 -2
- data/test/tc_tsig_api.rb +251 -0
- data/test/tc_tsig_rfc8945.rb +357 -0
- data/test/tc_v2_release_dnssec.rb +442 -0
- data/test/tc_v2_release_transport.rb +304 -0
- data/test/tc_v2_zone_transfer_native.rb +58 -0
- data/test/tc_zone_reader.rb +217 -1
- data/test/tcp_pipelining_server.rb +150 -0
- data/test/ts_offline.rb +77 -6
- data/test/ts_online.rb +2 -1
- metadata +105 -10
- data/.coveralls.yml +0 -2
data/test/tc_soak.rb
CHANGED
|
@@ -1,57 +1,31 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Copyright 2007 Nominet UK
|
|
3
|
-
#
|
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
# you may not use this file except in compliance with the License.
|
|
6
|
-
# You may obtain a copy of the License at
|
|
7
|
-
#
|
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
#
|
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
# See the License for the specific language governing permissions and
|
|
14
|
-
# limitations under the License.
|
|
15
|
-
# ++
|
|
1
|
+
# frozen_string_literal: true
|
|
16
2
|
|
|
17
3
|
require_relative 'spec_helper'
|
|
4
|
+
require_relative 'soak_server'
|
|
18
5
|
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
require_relative 'test_dnsserver'
|
|
22
|
-
require_relative 'localdns'
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# This class tries to soak test the Dnsruby library.
|
|
26
|
-
# It can't do this very well, owing to the small number of sockets allowed to be open simultaneously.
|
|
27
|
-
# @TODO@ Future versions of dnsruby will allow random streaming over a fixed number of (cycling) random sockets,
|
|
28
|
-
# so this test can be beefed up considerably at that point.
|
|
29
|
-
|
|
6
|
+
# Defaults preserve the historical workload: 81,512 queries in eight cases.
|
|
7
|
+
# Increase the counts for a longer run. SOAK_TIMEOUT bounds each complete case.
|
|
30
8
|
class TestSingleResolverSoak < Minitest::Test
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def initialize(arg)
|
|
36
|
-
super(arg)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def teardown
|
|
40
|
-
Celluloid.shutdown
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
SINGLE_RESOLVER_QUERY_TIMES = 63
|
|
9
|
+
SINGLE_RESOLVER_QUERY_TIMES = Integer(ENV.fetch('SOAK_ASYNC_ROUNDS', '63'))
|
|
10
|
+
THREAD_COUNT = Integer(ENV.fetch('SOAK_THREADS', RUBY_PLATFORM.include?('java') ? '50' : '250'))
|
|
11
|
+
ROUNDS = Integer(ENV.fetch('SOAK_ROUNDS', '40'))
|
|
12
|
+
CASE_TIMEOUT = Float(ENV.fetch('SOAK_TIMEOUT', '120'))
|
|
44
13
|
|
|
45
14
|
def setup
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@
|
|
49
|
-
|
|
15
|
+
@resolvers = []
|
|
16
|
+
@workers = []
|
|
17
|
+
@queues = []
|
|
18
|
+
raise ArgumentError, 'Soak counts and timeout must be positive' unless
|
|
19
|
+
[SINGLE_RESOLVER_QUERY_TIMES, THREAD_COUNT, ROUNDS, CASE_TIMEOUT].all?(&:positive?)
|
|
20
|
+
@server = SoakServer.new
|
|
50
21
|
end
|
|
51
22
|
|
|
52
23
|
def teardown
|
|
53
|
-
|
|
54
|
-
|
|
24
|
+
@workers.each { |worker| worker.kill if worker.alive? }
|
|
25
|
+
@workers.each(&:join)
|
|
26
|
+
@resolvers.each(&:close)
|
|
27
|
+
@server&.stop
|
|
28
|
+
@queues.each { |queue| assert(queue.empty?, 'Unexpected extra/duplicate completion') }
|
|
55
29
|
end
|
|
56
30
|
|
|
57
31
|
def test_many_asynchronous_queries_one_single_resolver
|
|
@@ -79,178 +53,126 @@ class TestSingleResolverSoak < Minitest::Test
|
|
|
79
53
|
end
|
|
80
54
|
|
|
81
55
|
def run_many_asynch_queries_test_single_res(num_resolvers, tcp = false, pipelining = false)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
do_validation: false,
|
|
89
|
-
tcp_pipelining: pipelining,
|
|
90
|
-
packet_timeout: 10,
|
|
91
|
-
tcp_pipelining_max_queries: 5,
|
|
92
|
-
use_tcp: tcp)
|
|
93
|
-
end
|
|
94
|
-
start = Time.now
|
|
95
|
-
|
|
96
|
-
# @todo@ On windows, MAX_FILES is 256. This means that we have to limit
|
|
97
|
-
# this test while we're not using single sockets.
|
|
98
|
-
# We run four queries per iteration, so we're limited to 64 runs.
|
|
99
|
-
messages = TestSoakBase::Rrs.map do |data|
|
|
100
|
-
message = Dnsruby::Message.new(data[:name], data[:type])
|
|
101
|
-
message.do_validation = false
|
|
102
|
-
message.do_caching = false
|
|
103
|
-
message
|
|
56
|
+
queue = tracked_queue
|
|
57
|
+
resolvers = Array.new(num_resolvers) { create_resolver(tcp, pipelining) }
|
|
58
|
+
count = SINGLE_RESOLVER_QUERY_TIMES * SoakServer::TYPES.length
|
|
59
|
+
timed_case(count) do
|
|
60
|
+
count.times { |id| resolvers[id % num_resolvers].send_async(request_for(id), queue, id) }
|
|
61
|
+
receive_all(queue, count)
|
|
104
62
|
end
|
|
63
|
+
end
|
|
105
64
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
_id, ret, error = q.pop
|
|
111
|
-
if error.is_a?(Dnsruby::ResolvTimeout)
|
|
112
|
-
timeout_count+=1
|
|
113
|
-
elsif ret.class != Dnsruby::Message
|
|
114
|
-
p "ERROR RETURNED : #{error}"
|
|
115
|
-
end
|
|
116
|
-
end
|
|
65
|
+
def test_many_threads_on_one_single_resolver_synchronous
|
|
66
|
+
resolver = create_resolver
|
|
67
|
+
run_threaded_queries do |id, _queue|
|
|
68
|
+
[id, resolver.query(query_name(id), query_type(id)), nil]
|
|
117
69
|
end
|
|
70
|
+
end
|
|
118
71
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
end
|
|
72
|
+
def test_many_threads_on_many_single_resolvers
|
|
73
|
+
resolvers = Array.new(THREAD_COUNT) { create_resolver }
|
|
74
|
+
run_threaded_queries do |id, queue|
|
|
75
|
+
resolver = resolvers[id / (ROUNDS * SoakServer::TYPES.length)]
|
|
76
|
+
resolver.send_async(request_for(id), queue, id)
|
|
77
|
+
completion = queue.pop
|
|
78
|
+
raise "Wrong completion ID: expected #{id}, got #{completion[0].inspect}" unless completion[0] == id
|
|
79
|
+
completion
|
|
128
80
|
end
|
|
81
|
+
end
|
|
129
82
|
|
|
130
|
-
|
|
83
|
+
private
|
|
131
84
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
85
|
+
def tracked_queue
|
|
86
|
+
queue = Queue.new
|
|
87
|
+
@queues << queue
|
|
88
|
+
queue
|
|
135
89
|
end
|
|
136
90
|
|
|
137
|
-
def
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
timeout_count = 0
|
|
146
|
-
query_count = 0
|
|
147
|
-
res.packet_timeout=4
|
|
148
|
-
start=Time.now
|
|
149
|
-
# Windows limits us to 256 sockets
|
|
150
|
-
num_times=250
|
|
151
|
-
if (/java/ =~ RUBY_PLATFORM)
|
|
152
|
-
# JRuby threads are native threads, so let's not go too mad!
|
|
153
|
-
num_times=50
|
|
154
|
-
end
|
|
155
|
-
num_times.times do |i|
|
|
156
|
-
threads[i] = Thread.new{
|
|
157
|
-
40.times do |j|
|
|
158
|
-
TestSoakBase::Rrs.each do |data|
|
|
159
|
-
mutex.synchronize { query_count += 1 }
|
|
160
|
-
packet=nil
|
|
161
|
-
begin
|
|
162
|
-
packet = res.query(data[:name], data[:type])
|
|
163
|
-
rescue Dnsruby::ResolvTimeout
|
|
164
|
-
mutex.synchronize { timeout_count += 1 }
|
|
165
|
-
next
|
|
166
|
-
end
|
|
167
|
-
assert(packet)
|
|
168
|
-
ids.push(packet.header.id)
|
|
169
|
-
assert_equal(packet.question[0].qclass, 'IN', 'Class correct' )
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
}
|
|
173
|
-
end
|
|
174
|
-
threads.each do |thread|
|
|
175
|
-
thread.join
|
|
176
|
-
end
|
|
177
|
-
stop=Time.now
|
|
178
|
-
time_taken=stop-start
|
|
179
|
-
puts "Query count : #{query_count}, #{timeout_count} timed out. #{time_taken} time taken"
|
|
180
|
-
# check_ids(ids) # only do this if we expect all different IDs - e.g. if we stream over a single socket
|
|
181
|
-
assert(timeout_count < query_count * 0.1, "#{timeout_count} of #{query_count} timed out!")
|
|
91
|
+
def create_resolver(tcp = false, pipelining = false)
|
|
92
|
+
resolver = Dnsruby::SingleResolver.new(server: SoakServer::IP,
|
|
93
|
+
port: @server.port, do_caching: false, do_validation: false,
|
|
94
|
+
dnssec: false, use_tcp: tcp, packet_timeout: 10,
|
|
95
|
+
tcp_pipelining: pipelining, tcp_pipelining_max_queries: 5)
|
|
96
|
+
assert_equal(pipelining, resolver.single_resolvers.first.tcp_pipelining)
|
|
97
|
+
@resolvers << resolver
|
|
98
|
+
resolver
|
|
182
99
|
end
|
|
183
100
|
|
|
184
|
-
def
|
|
185
|
-
|
|
186
|
-
count = 0
|
|
187
|
-
ids.each do |id|
|
|
188
|
-
count+=1
|
|
189
|
-
if (count < ids.length-1)
|
|
190
|
-
assert(ids[count+1] != id, "Two identical header ids used!")
|
|
191
|
-
end
|
|
192
|
-
end
|
|
101
|
+
def query_name(id)
|
|
102
|
+
"q#{id}.soak.test."
|
|
193
103
|
end
|
|
194
104
|
|
|
195
|
-
def
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
threads = Array.new
|
|
199
|
-
mutex = Mutex.new
|
|
200
|
-
timeout_count = 0
|
|
201
|
-
query_count = 0
|
|
202
|
-
start=Time.now
|
|
203
|
-
num_times=250
|
|
204
|
-
if (/java/ =~ RUBY_PLATFORM)
|
|
205
|
-
# JRuby threads are native threads, so let's not go too mad!
|
|
206
|
-
num_times=50
|
|
207
|
-
end
|
|
208
|
-
num_times.times do |i|
|
|
209
|
-
threads[i] = Thread.new{
|
|
210
|
-
res = create_default_single_resolver
|
|
211
|
-
40.times do |j|
|
|
212
|
-
TestSoakBase::Rrs.each do |data|
|
|
213
|
-
mutex.synchronize do
|
|
214
|
-
query_count+=1
|
|
215
|
-
end
|
|
216
|
-
q = Queue.new
|
|
105
|
+
def query_type(id)
|
|
106
|
+
SoakServer::TYPES[id % SoakServer::TYPES.length]
|
|
107
|
+
end
|
|
217
108
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
109
|
+
def request_for(id)
|
|
110
|
+
request = Dnsruby::Message.new(query_name(id), query_type(id))
|
|
111
|
+
request.do_caching = false
|
|
112
|
+
request.do_validation = false
|
|
113
|
+
request
|
|
114
|
+
end
|
|
221
115
|
|
|
222
|
-
|
|
116
|
+
def timed_case(count)
|
|
117
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
118
|
+
Timeout.timeout(CASE_TIMEOUT) { yield }
|
|
119
|
+
@server.check!
|
|
120
|
+
puts "Verified #{count} responses in #{(Process.clock_gettime(Process::CLOCK_MONOTONIC) - started).round(3)}s"
|
|
121
|
+
rescue Timeout::Error
|
|
122
|
+
@server.check!
|
|
123
|
+
flunk "Soak case exceeded #{CASE_TIMEOUT}s; a query or worker did not complete"
|
|
124
|
+
end
|
|
223
125
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
126
|
+
def run_threaded_queries
|
|
127
|
+
per_thread = ROUNDS * SoakServer::TYPES.length
|
|
128
|
+
completions = tracked_queue
|
|
129
|
+
queues = Array.new(THREAD_COUNT) { tracked_queue }
|
|
130
|
+
timed_case(THREAD_COUNT * per_thread) do
|
|
131
|
+
THREAD_COUNT.times do |worker_id|
|
|
132
|
+
@workers << Thread.new do
|
|
133
|
+
Thread.current.abort_on_exception = false
|
|
134
|
+
Thread.current.report_on_exception = false
|
|
135
|
+
begin
|
|
136
|
+
per_thread.times do |iteration|
|
|
137
|
+
id = worker_id * per_thread + iteration
|
|
138
|
+
completions << yield(id, queues[worker_id])
|
|
232
139
|
end
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
assert_equal(packet.question[0].qclass, 'IN', 'Class correct' )
|
|
140
|
+
rescue StandardError => error
|
|
141
|
+
completions << [nil, nil, error]
|
|
236
142
|
end
|
|
237
143
|
end
|
|
238
|
-
|
|
144
|
+
end
|
|
145
|
+
receive_all(completions, THREAD_COUNT * per_thread)
|
|
146
|
+
@workers.each(&:join)
|
|
239
147
|
end
|
|
240
|
-
# NOTE: For methods on the objects taking no params, we can use this shorthand.
|
|
241
|
-
threads.each(&:join)
|
|
242
|
-
|
|
243
|
-
time_taken = Time.now - start
|
|
244
|
-
puts "Query count : #{query_count}, #{timeout_count} timed out. #{time_taken} time taken"
|
|
245
|
-
assert(timeout_count < query_count * 0.1, "#{timeout_count} of #{query_count} timed out!")
|
|
246
148
|
end
|
|
247
149
|
|
|
248
|
-
def
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
150
|
+
def receive_all(queue, count)
|
|
151
|
+
seen = {}
|
|
152
|
+
count.times do
|
|
153
|
+
id, response, error = queue.pop
|
|
154
|
+
assert_nil(error, "Query #{id.inspect}: #{error.class}: #{error}")
|
|
155
|
+
assert(id.is_a?(Integer) && id >= 0 && id < count, "Unexpected query ID #{id.inspect}")
|
|
156
|
+
refute(seen.key?(id), "Duplicate completion #{id}")
|
|
157
|
+
seen[id] = true
|
|
158
|
+
assert_instance_of(Dnsruby::Message, response)
|
|
159
|
+
assert_equal('NOERROR', response.rcode.to_s)
|
|
160
|
+
assert_equal(1, response.question.length)
|
|
161
|
+
question = response.question.first
|
|
162
|
+
assert_equal([query_name(id), query_type(id), 'IN'],
|
|
163
|
+
[question.qname.to_s(true), question.qtype.to_s, question.qclass.to_s])
|
|
164
|
+
assert_equal(1, response.answer.length)
|
|
165
|
+
answer = response.answer.first
|
|
166
|
+
assert_equal([query_name(id), query_type(id), 'IN'],
|
|
167
|
+
[answer.name.to_s(true), answer.type.to_s, answer.klass.to_s])
|
|
168
|
+
case query_type(id)
|
|
169
|
+
when 'A' then assert_equal(SoakServer::ADDRESS, answer.address.to_s)
|
|
170
|
+
when 'MX' then assert_equal([10, SoakServer::EXCHANGE], [answer.preference, answer.exchange.to_s(true)])
|
|
171
|
+
when 'CNAME' then assert_equal(SoakServer::ALIAS, answer.domainname.to_s(true))
|
|
172
|
+
when 'TXT' then assert_equal([SoakServer::TEXT], answer.strings)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
assert_equal(count, seen.length)
|
|
176
|
+
assert(queue.empty?, 'Extra/duplicate completion after the expected count')
|
|
255
177
|
end
|
|
256
178
|
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require_relative 'spec_helper'
|
|
2
|
+
require_relative 'socket_helpers'
|
|
3
|
+
require 'minitest/mock'
|
|
4
|
+
|
|
5
|
+
class TestSocketHelpers < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
@tcp_sockets = []
|
|
8
|
+
@udp_sockets = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def teardown
|
|
12
|
+
(@tcp_sockets + @udp_sockets).each { |socket| socket.close unless socket.closed? }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_collision_retries_without_releasing_the_successful_pair
|
|
16
|
+
blocker, first_tcp = TestSockets.udp_tcp_pair
|
|
17
|
+
blocked_port = blocker.addr[1]
|
|
18
|
+
tcp_new = TCPServer.method(:new)
|
|
19
|
+
udp_new = UDPSocket.method(:new)
|
|
20
|
+
tcp_factory = lambda do |*args|
|
|
21
|
+
socket = @tcp_sockets.empty? ? first_tcp : tcp_new.call(*args)
|
|
22
|
+
@tcp_sockets << socket
|
|
23
|
+
socket
|
|
24
|
+
end
|
|
25
|
+
udp_factory = ->(*args) { udp_new.call(*args).tap { |socket| @udp_sockets << socket } }
|
|
26
|
+
TCPServer.stub(:new, tcp_factory) do
|
|
27
|
+
UDPSocket.stub(:new, udp_factory) do
|
|
28
|
+
udp, tcp = TestSockets.udp_tcp_pair
|
|
29
|
+
assert_operator(@tcp_sockets.length, :>=, 2)
|
|
30
|
+
assert(first_tcp.closed?)
|
|
31
|
+
assert(@udp_sockets.first.closed?)
|
|
32
|
+
assert_equal(udp.addr[1], tcp.addr[1])
|
|
33
|
+
refute_equal(blocked_port, tcp.addr[1])
|
|
34
|
+
refute(udp.closed?)
|
|
35
|
+
refute(tcp.closed?)
|
|
36
|
+
refute(blocker.closed?)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
ensure
|
|
40
|
+
blocker&.close unless blocker&.closed?
|
|
41
|
+
first_tcp&.close unless first_tcp&.closed?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_repeated_collisions_are_bounded_and_close_every_attempt
|
|
45
|
+
with_bind_error(Errno::EADDRINUSE) do
|
|
46
|
+
assert_raises(Errno::EADDRINUSE) { TestSockets.udp_tcp_pair }
|
|
47
|
+
end
|
|
48
|
+
assert_equal(10, @tcp_sockets.length)
|
|
49
|
+
assert_equal(10, @udp_sockets.length)
|
|
50
|
+
assert((@tcp_sockets + @udp_sockets).all?(&:closed?))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_other_bind_errors_are_not_retried
|
|
54
|
+
with_bind_error(Errno::EACCES) do
|
|
55
|
+
assert_raises(Errno::EACCES) { TestSockets.udp_tcp_pair }
|
|
56
|
+
end
|
|
57
|
+
assert_equal(1, @tcp_sockets.length)
|
|
58
|
+
assert_equal(1, @udp_sockets.length)
|
|
59
|
+
assert((@tcp_sockets + @udp_sockets).all?(&:closed?))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_udp_creation_failure_releases_the_tcp_listener
|
|
63
|
+
tcp_new = TCPServer.method(:new)
|
|
64
|
+
factory = ->(*args) { tcp_new.call(*args).tap { |socket| @tcp_sockets << socket } }
|
|
65
|
+
TCPServer.stub(:new, factory) do
|
|
66
|
+
UDPSocket.stub(:new, ->(*) { raise Errno::EMFILE }) do
|
|
67
|
+
assert_raises(Errno::EMFILE) { TestSockets.udp_tcp_pair }
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
assert_equal(1, @tcp_sockets.length)
|
|
71
|
+
assert(@tcp_sockets.first.closed?)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def with_bind_error(error)
|
|
77
|
+
tcp_new = TCPServer.method(:new)
|
|
78
|
+
udp_new = UDPSocket.method(:new)
|
|
79
|
+
tcp_factory = ->(*args) { tcp_new.call(*args).tap { |socket| @tcp_sockets << socket } }
|
|
80
|
+
udp_factory = lambda do |*args|
|
|
81
|
+
udp_new.call(*args).tap do |socket|
|
|
82
|
+
@udp_sockets << socket
|
|
83
|
+
socket.define_singleton_method(:bind) { |*| raise error }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
TCPServer.stub(:new, tcp_factory) do
|
|
87
|
+
UDPSocket.stub(:new, udp_factory) { yield }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|