dnsruby 1.73.1 → 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 +314 -1
- data/Rakefile +0 -3
- data/demo/axfr.rb +24 -6
- data/dnsruby.gemspec +6 -4
- data/lib/dnsruby/cache.rb +279 -49
- data/lib/dnsruby/code_mapper.rb +35 -8
- data/lib/dnsruby/code_mappers.rb +63 -49
- data/lib/dnsruby/config.rb +77 -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 +664 -207
- data/lib/dnsruby/presentation.rb +147 -0
- data/lib/dnsruby/query_control.rb +132 -0
- data/lib/dnsruby/recursor.rb +798 -341
- data/lib/dnsruby/resolver.rb +472 -176
- 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 +21 -2
- 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/OPT.rb +2 -3
- 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 +986 -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 +53 -2
- data/test/tc_res_opt.rb +21 -1
- data/test/tc_resolver.rb +34 -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 +127 -14
- data/.coveralls.yml +0 -2
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
require_relative 'tc_pipeline_send'
|
|
2
|
+
|
|
3
|
+
class ReviewFollowupTransportTest < Minitest::Test
|
|
4
|
+
def test_cancelled_connects_release_all_workers_in_both_tcp_modes
|
|
5
|
+
[false, true].each do |pipeline|
|
|
6
|
+
[:close, :deadline].each do |cancellation|
|
|
7
|
+
isolated("pipeline = #{pipeline}; cancellation = #{cancellation.inspect}\n" + <<~'RUBY')
|
|
8
|
+
SELECTOR.singleton_class.remove_method(:notify_queue_observers)
|
|
9
|
+
K.class_variable_set(:@@select_thread, Thread.current)
|
|
10
|
+
waiting, wrote = Queue.new, Queue.new
|
|
11
|
+
sockets, resolvers, queues = [], [], []
|
|
12
|
+
socket_mutex = Mutex.new
|
|
13
|
+
Socket.define_singleton_method(:new) do |*|
|
|
14
|
+
socket = FrameSocket.new
|
|
15
|
+
blocked = socket_mutex.synchronize { sockets << socket; sockets.size <= 4 }
|
|
16
|
+
socket.define_singleton_method(:bind) { |*| 0 }
|
|
17
|
+
socket.define_singleton_method(:connect_nonblock) do |*, **|
|
|
18
|
+
waiting << true if blocked
|
|
19
|
+
blocked ? :wait_writable : 0
|
|
20
|
+
end
|
|
21
|
+
socket.on_write = ->(*) { wrote << socket }
|
|
22
|
+
socket
|
|
23
|
+
end
|
|
24
|
+
IO.define_singleton_method(:select) do |readers, writers, _, duration|
|
|
25
|
+
raise 'invalid connect wait' unless readers.nil? && writers.length == 1 && duration <= 0.05
|
|
26
|
+
sleep(duration)
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
4.times do |id|
|
|
30
|
+
resolver = Dnsruby::Resolver.new(nameserver: '127.0.0.1', dnssec: false,
|
|
31
|
+
use_tcp: true, tcp_pipelining: pipeline, packet_timeout: 60,
|
|
32
|
+
query_timeout: 60, retry_times: 1)
|
|
33
|
+
queue = Queue.new
|
|
34
|
+
resolvers << resolver
|
|
35
|
+
queues << queue
|
|
36
|
+
resolver.send_async(query("cancelled-#{id}.example."), queue, id)
|
|
37
|
+
end
|
|
38
|
+
4.times { waiting.pop }
|
|
39
|
+
resolvers.each_with_index do |resolver, id|
|
|
40
|
+
if cancellation == :close
|
|
41
|
+
resolver.close
|
|
42
|
+
else
|
|
43
|
+
engine = resolver.instance_variable_get(:@resolver_ruby)
|
|
44
|
+
engine.instance_variable_get(:@timeouts)[id][0] = Dnsruby::TransportClock.now - 1
|
|
45
|
+
engine.send(:update_tick_deadline, id)
|
|
46
|
+
engine.tick
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
healthy = Dnsruby::Resolver.new(nameserver: '127.0.0.1', dnssec: false,
|
|
50
|
+
use_tcp: true, tcp_pipelining: pipeline, packet_timeout: 1,
|
|
51
|
+
query_timeout: 2, retry_times: 1)
|
|
52
|
+
request, completion = query('healthy.example.'), Queue.new
|
|
53
|
+
healthy.send_async(request, completion, :healthy)
|
|
54
|
+
socket = Timeout.timeout(2) { wrote.pop }
|
|
55
|
+
Timeout.timeout(2) { Thread.pass until sockets.first(4).all?(&:closed?) }
|
|
56
|
+
raise 'canceled connection wrote bytes' unless sockets.first(4).all? { |s| s.written.empty? }
|
|
57
|
+
response = request.clone
|
|
58
|
+
response.header.qr = true
|
|
59
|
+
SELECTOR.process_message(response, response.encode, socket)
|
|
60
|
+
SELECTOR.send_queued_validation_responses
|
|
61
|
+
raise 'healthy query did not finish' unless completion.size == 1 && completion.pop.last.nil?
|
|
62
|
+
expected = cancellation == :close ? Dnsruby::OtherResolvError : Dnsruby::ResolvTimeout
|
|
63
|
+
queues.each do |queue|
|
|
64
|
+
raise 'cancellation did not complete exactly once' unless queue.size == 1 && queue.pop.last.is_a?(expected)
|
|
65
|
+
end
|
|
66
|
+
(resolvers + [healthy]).each(&:close)
|
|
67
|
+
drain_errors
|
|
68
|
+
raise 'canceled attempt resurrected' unless K.class_variable_get(:@@query_hash).empty?
|
|
69
|
+
raise 'late canceled completion' unless queues.all?(&:empty?)
|
|
70
|
+
sockets.each { |s| assert_clean(s) }
|
|
71
|
+
RUBY
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def test_nonblocking_connector_checks_error_deadline_binding_and_cancellation
|
|
77
|
+
isolated(<<~'RUBY')
|
|
78
|
+
writer = Dnsruby::PacketSender.new(server: '127.0.0.1')
|
|
79
|
+
[:refused, :deadline, :cancelled, :bound_error, :success].each do |mode|
|
|
80
|
+
socket, bound, target = FrameSocket.new, nil, nil
|
|
81
|
+
socket.define_singleton_method(:bind) do |address|
|
|
82
|
+
bound = Socket.unpack_sockaddr_in(address)
|
|
83
|
+
raise Errno::EADDRINUSE if mode == :bound_error
|
|
84
|
+
end
|
|
85
|
+
socket.define_singleton_method(:connect_nonblock) do |address, **options|
|
|
86
|
+
target = Socket.unpack_sockaddr_in(address)
|
|
87
|
+
raise 'blocking connect requested' unless options[:exception] == false
|
|
88
|
+
:wait_writable
|
|
89
|
+
end
|
|
90
|
+
socket.define_singleton_method(:getsockopt) do |level, option|
|
|
91
|
+
raise 'SO_ERROR not queried' unless level == Socket::SOL_SOCKET && option == Socket::SO_ERROR
|
|
92
|
+
Struct.new(:int).new(mode == :refused ? Errno::ECONNREFUSED::Errno : 0)
|
|
93
|
+
end
|
|
94
|
+
Socket.define_singleton_method(:new) { |family, type, _| raise 'wrong family/type' unless family == Socket::AF_INET && type == Socket::SOCK_STREAM; socket }
|
|
95
|
+
token = K::SendToken.new
|
|
96
|
+
IO.define_singleton_method(:select) do |_, writers, _, duration|
|
|
97
|
+
raise 'unbounded wait' unless duration > 0 && duration <= 0.05 && writers == [socket]
|
|
98
|
+
token.cancel if mode == :cancelled
|
|
99
|
+
if mode == :deadline || mode == :cancelled
|
|
100
|
+
sleep(duration)
|
|
101
|
+
nil
|
|
102
|
+
else
|
|
103
|
+
[[], writers, []]
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
started = Dnsruby::TransportClock.now
|
|
107
|
+
begin
|
|
108
|
+
result = writer.send(:connect_tcp, '127.0.0.2', 5300, '127.0.0.3', 55000,
|
|
109
|
+
started + (mode == :cancelled ? 60 : 0.15), token)
|
|
110
|
+
raise 'unexpected connection success' unless mode == :success && result.equal?(socket)
|
|
111
|
+
rescue StandardError => error
|
|
112
|
+
expected = {refused: Errno::ECONNREFUSED, deadline: Dnsruby::ResolvTimeout,
|
|
113
|
+
bound_error: Errno::EADDRINUSE, cancelled: Dnsruby::PacketSender.const_get(:SendCancelled)}[mode]
|
|
114
|
+
raise error unless expected && error.is_a?(expected)
|
|
115
|
+
raise 'failed connection retained descriptor' unless socket.closed?
|
|
116
|
+
end
|
|
117
|
+
raise 'source binding changed' unless bound == [55000, '127.0.0.3']
|
|
118
|
+
raise 'destination changed' unless mode == :bound_error || target == [5300, '127.0.0.2']
|
|
119
|
+
elapsed = Dnsruby::TransportClock.now - started
|
|
120
|
+
raise 'deadline expired early' if mode == :deadline && elapsed < 0.14
|
|
121
|
+
raise 'cancellation waited for deadline' if mode == :cancelled && elapsed >= 2
|
|
122
|
+
socket.close
|
|
123
|
+
end
|
|
124
|
+
RUBY
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def test_closed_completion_queue_does_not_interrupt_other_resolvers
|
|
128
|
+
isolated(<<~'RUBY')
|
|
129
|
+
SELECTOR.singleton_class.remove_method(:notify_queue_observers)
|
|
130
|
+
sockets = []
|
|
131
|
+
Dnsruby::PacketSender.define_method(:connect_tcp) { |*| FrameSocket.new.tap { |s| sockets << s } }
|
|
132
|
+
resolvers = 2.times.map do
|
|
133
|
+
Dnsruby::Resolver.new(nameserver: '127.0.0.1', dnssec: false,
|
|
134
|
+
use_tcp: true, do_validation: false, do_caching: false)
|
|
135
|
+
end
|
|
136
|
+
requests, queues = 2.times.map { |id| query("queue-#{id}.example.") }, [Queue.new, Queue.new]
|
|
137
|
+
resolvers.each_with_index { |resolver, id| resolver.send_async(requests[id], queues[id], id) }
|
|
138
|
+
queues.first.close
|
|
139
|
+
sockets.each_with_index do |socket, id|
|
|
140
|
+
response = requests[id].clone
|
|
141
|
+
response.header.qr = true
|
|
142
|
+
SELECTOR.process_message(response, response.encode, socket)
|
|
143
|
+
SELECTOR.send_queued_validation_responses
|
|
144
|
+
end
|
|
145
|
+
raise 'healthy completion lost' unless queues.last.size == 1 && queues.last.pop.last.nil?
|
|
146
|
+
raise 'closed owner retained' unless K.class_variable_get(:@@query_hash).empty? &&
|
|
147
|
+
K.class_variable_get(:@@observers).empty?
|
|
148
|
+
resolvers.each(&:close)
|
|
149
|
+
# Direct PacketSender recipients can also abandon an event queue.
|
|
150
|
+
queue = Queue.new
|
|
151
|
+
queue.close
|
|
152
|
+
SELECTOR.push_exception_to_select(:abandoned, queue, IOError.new('gone'), nil)
|
|
153
|
+
SELECTOR.send_queued_exceptions
|
|
154
|
+
SELECTOR.push_response_to_select(:abandoned, queue, query, query, nil)
|
|
155
|
+
SELECTOR.send_queued_responses
|
|
156
|
+
RUBY
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def test_tsig_discards_unsigned_and_bad_mac_until_valid_response_or_deadline
|
|
160
|
+
isolated(<<~'RUBY')
|
|
161
|
+
[:unsigned, :bad_mac, :timeout].each do |mode|
|
|
162
|
+
socket = FrameSocket.new
|
|
163
|
+
writer = sender(socket)
|
|
164
|
+
request = query
|
|
165
|
+
key = Dnsruby::RR.create(name: 'key.example.', type: 'TSIG', klass: 'ANY',
|
|
166
|
+
key: 'c2VjcmV0', algorithm: 'hmac-sha256')
|
|
167
|
+
key.apply(request)
|
|
168
|
+
live = settings(socket, mode, request, writer)
|
|
169
|
+
SELECTOR.add_to_select(live)
|
|
170
|
+
reply = Dnsruby::Message.new('pipeline.example.')
|
|
171
|
+
reply.header.id, reply.header.qr = request.header.id, true
|
|
172
|
+
if mode == :bad_mac
|
|
173
|
+
wrong = key.dup
|
|
174
|
+
wrong.key = 'd3Jvbmc='
|
|
175
|
+
wrong.apply(reply, request)
|
|
176
|
+
end
|
|
177
|
+
bytes = reply.encode
|
|
178
|
+
SELECTOR.process_message(Dnsruby::Message.decode(bytes), bytes, socket)
|
|
179
|
+
raise 'unauthenticated reply completed query' unless SELECTOR.query_settings_for_id(mode).equal?(live) && live.client_queue.empty?
|
|
180
|
+
if mode == :timeout
|
|
181
|
+
K.class_variable_get(:@@timeouts)[mode] = Dnsruby::TransportClock.now - 1
|
|
182
|
+
SELECTOR.instance_variable_get(:@packet_deadlines)[mode] = K.class_variable_get(:@@timeouts)[mode]
|
|
183
|
+
SELECTOR.process_timeouts
|
|
184
|
+
raise 'timeout lost TSIG diagnostic' unless live.client_queue.size == 1 &&
|
|
185
|
+
live.client_queue.pop.last.is_a?(Dnsruby::TsigNotSignedResponseError)
|
|
186
|
+
else
|
|
187
|
+
valid = Dnsruby::Message.new('pipeline.example.')
|
|
188
|
+
valid.header.id, valid.header.qr = request.header.id, true
|
|
189
|
+
key.apply(valid, request)
|
|
190
|
+
bytes = valid.encode
|
|
191
|
+
SELECTOR.process_message(Dnsruby::Message.decode(bytes), bytes, socket)
|
|
192
|
+
SELECTOR.send_queued_validation_responses
|
|
193
|
+
raise 'authentic response lost' unless live.client_queue.size == 2 &&
|
|
194
|
+
2.times.map { live.client_queue.pop }.all? { |event| event.last.nil? && event[2].verified? }
|
|
195
|
+
raise 'completed query retained' if SELECTOR.query_settings_for_id(mode)
|
|
196
|
+
writer.close
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
RUBY
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def test_authenticated_tsig_errors_complete_but_forged_errors_keep_waiting
|
|
203
|
+
isolated(<<~'RUBY')
|
|
204
|
+
def error_wire(key, request, error_code, variant)
|
|
205
|
+
response = Dnsruby::Message.new(variant == :wrong_question ? 'wrong.example.' : 'pipeline.example.')
|
|
206
|
+
response.header.id, response.header.qr = request.header.id, true
|
|
207
|
+
response.header.rcode = variant == :wrong_rcode ? Dnsruby::RCode::NOERROR : Dnsruby::RCode::NOTAUTH
|
|
208
|
+
signer = key.dup
|
|
209
|
+
signer.error = error_code
|
|
210
|
+
signer.time_signed = request.tsig.time_signed
|
|
211
|
+
if error_code == 18 && variant != :bad_other_data
|
|
212
|
+
signer.other_data = [0, Time.now.to_i + 3600].pack('nN')
|
|
213
|
+
signer.other_size = 6
|
|
214
|
+
end
|
|
215
|
+
signer.name = 'different-key.example.' if variant == :wrong_key
|
|
216
|
+
signer.algorithm = 'hmac-sha1' if variant == :wrong_algorithm
|
|
217
|
+
if variant == :bad_other_data
|
|
218
|
+
signature = signer.dup
|
|
219
|
+
signature.original_id = response.header.id
|
|
220
|
+
input = [request.tsig.mac_size].pack('n') + request.tsig.mac + response.encode +
|
|
221
|
+
signer.sig_data(signer, signer.time_signed)
|
|
222
|
+
signature.mac = signer.calculate_mac(signer.algorithm, input)
|
|
223
|
+
signature.mac_size = signature.mac.bytesize
|
|
224
|
+
else
|
|
225
|
+
signature = signer.generate(response, request)
|
|
226
|
+
end
|
|
227
|
+
if variant == :bad_mac
|
|
228
|
+
signature.mac = signature.mac.dup
|
|
229
|
+
signature.mac.setbyte(0, signature.mac.getbyte(0) ^ 1)
|
|
230
|
+
elsif variant == :unsigned
|
|
231
|
+
signature.mac, signature.mac_size = ''.b, 0
|
|
232
|
+
end
|
|
233
|
+
response.add_additional(signature)
|
|
234
|
+
response.encode
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
[18, 22].each do |error_code|
|
|
238
|
+
[:valid, :bad_mac, :unsigned, :wrong_key, :wrong_algorithm, :wrong_rcode, :wrong_question].each do |variant|
|
|
239
|
+
socket = FrameSocket.new
|
|
240
|
+
writer = sender(socket)
|
|
241
|
+
request = query
|
|
242
|
+
key = Dnsruby::RR.create(name: 'key.example.', type: 'TSIG', klass: 'ANY',
|
|
243
|
+
key: 'c2VjcmV0', algorithm: 'hmac-sha256')
|
|
244
|
+
key.apply(request)
|
|
245
|
+
live = settings(socket, [error_code, variant], request, writer)
|
|
246
|
+
SELECTOR.add_to_select(live)
|
|
247
|
+
deadline = live.endtime
|
|
248
|
+
wire = error_wire(key, request, error_code, variant)
|
|
249
|
+
if variant == :valid
|
|
250
|
+
standalone = Dnsruby::Message.decode(wire)
|
|
251
|
+
raise 'TSIG verify changed its error return contract' unless key.verify(request, standalone, wire.dup) == false
|
|
252
|
+
raise 'valid server error was not authenticated' unless standalone.verified? && standalone.tsigerror == error_code
|
|
253
|
+
end
|
|
254
|
+
SELECTOR.process_message(Dnsruby::Message.decode(wire), wire, socket)
|
|
255
|
+
if variant != :valid
|
|
256
|
+
raise 'forged TSIG error claimed query' unless SELECTOR.query_settings_for_id(live.client_query_id).equal?(live)
|
|
257
|
+
raise 'forged TSIG error published event' unless live.client_queue.empty?
|
|
258
|
+
raise 'discard extended the deadline' unless live.endtime == deadline
|
|
259
|
+
wire = error_wire(key, request, error_code, :valid)
|
|
260
|
+
SELECTOR.process_message(Dnsruby::Message.decode(wire), wire, socket)
|
|
261
|
+
end
|
|
262
|
+
raise "authenticated TSIG error #{error_code}/#{variant} completed #{live.client_queue.size} times" unless live.client_queue.size == 1
|
|
263
|
+
event = live.client_queue.pop
|
|
264
|
+
name = error_code == 18 ? 'BADTIME' : 'BADTRUNC'
|
|
265
|
+
raise 'TSIG error diagnostic lost' unless event.last.is_a?(Dnsruby::TsigError) && event.last.message.include?(name)
|
|
266
|
+
raise 'verified server error state lost' unless event[2].verified? && event[2].tsigerror == error_code
|
|
267
|
+
raise 'authenticated error retained attempt' if SELECTOR.query_settings_for_id(live.client_query_id)
|
|
268
|
+
SELECTOR.retire_socket(socket)
|
|
269
|
+
assert_clean(socket)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
# Even a valid MAC cannot make malformed BADTIME Other Data acceptable.
|
|
273
|
+
request = query
|
|
274
|
+
key = Dnsruby::RR.create(name: 'key.example.', type: 'TSIG', klass: 'ANY', key: 'c2VjcmV0')
|
|
275
|
+
key.apply(request)
|
|
276
|
+
wire = error_wire(key, request, 18, :bad_other_data)
|
|
277
|
+
decoded = Dnsruby::Message.decode(wire)
|
|
278
|
+
raise 'malformed signed BADTIME accepted' unless key.verify(request, decoded, wire) == false && !decoded.verified?
|
|
279
|
+
RUBY
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def test_edns_preserves_existing_opt_options_payload_and_flags
|
|
283
|
+
[1232, 4096].product([false, true]).each do |payload, dnssec|
|
|
284
|
+
writer = Dnsruby::PacketSender.new(server: '127.0.0.1', dnssec: dnssec, udp_size: 1232)
|
|
285
|
+
request = Dnsruby::Message.new('opt.example.')
|
|
286
|
+
option = Dnsruby::RR::OPT::Option.new(65000, 'opaque'.b)
|
|
287
|
+
opt = Dnsruby::RR::OPT.new(payload, 0x0040, [option])
|
|
288
|
+
request.add_additional(opt)
|
|
289
|
+
writer.make_query_packet(request)
|
|
290
|
+
assert_same(opt, request.get_opt)
|
|
291
|
+
decoded = Dnsruby::Message.decode(request.encode)
|
|
292
|
+
assert_equal(1, decoded.additional.count { |rr| rr.type == Dnsruby::Types::OPT })
|
|
293
|
+
assert_equal(payload, decoded.get_opt.payloadsize)
|
|
294
|
+
assert_equal(0x0040 | (dnssec ? 0x8000 : 0), decoded.get_opt.flags)
|
|
295
|
+
assert_equal([[65000, 'opaque']], decoded.get_opt.options(nil).map { |o| [o.code, o.data] })
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def test_authenticated_notauth_response_remains_a_server_error
|
|
300
|
+
request = Dnsruby::Message.new('authenticated-error.example.')
|
|
301
|
+
key = Dnsruby::RR.create(name: 'key.example.', type: 'TSIG', klass: 'ANY',
|
|
302
|
+
key: 'c2VjcmV0', algorithm: 'hmac-sha256')
|
|
303
|
+
key.apply(request)
|
|
304
|
+
reply = Dnsruby::Message.new('authenticated-error.example.')
|
|
305
|
+
reply.header.id, reply.header.qr, reply.header.rcode = request.header.id, true, Dnsruby::RCode::NOTAUTH
|
|
306
|
+
key.apply(reply, request)
|
|
307
|
+
bytes = reply.encode
|
|
308
|
+
reply = Dnsruby::Message.decode(bytes)
|
|
309
|
+
writer = Dnsruby::PacketSender.new(server: '127.0.0.1', dnssec: false)
|
|
310
|
+
assert_equal(true, writer.check_response(reply, bytes, request, Queue.new, :notauth, false))
|
|
311
|
+
assert(reply.verified?)
|
|
312
|
+
assert_instance_of(Dnsruby::NotAuth, reply.get_exception)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def test_tsig_generate_and_apply_preserve_the_error_fields_covered_by_the_mac
|
|
316
|
+
[18, 22].product([:generate, :apply]).each do |error_code, operation|
|
|
317
|
+
request = Dnsruby::Message.new('signed-error.example.')
|
|
318
|
+
key = Dnsruby::RR.create(name: 'key.example.', type: 'TSIG', klass: 'ANY',
|
|
319
|
+
key: 'c2VjcmV0', algorithm: 'hmac-sha256')
|
|
320
|
+
key.apply(request)
|
|
321
|
+
signer = key.dup
|
|
322
|
+
signer.error = error_code
|
|
323
|
+
signer.time_signed = request.tsig.time_signed
|
|
324
|
+
expected_data = error_code == 18 ? [0, Time.now.to_i + 3600].pack('nN') : ''.b
|
|
325
|
+
signer.other_data, signer.other_size = expected_data, expected_data.bytesize
|
|
326
|
+
reply = Dnsruby::Message.new('signed-error.example.')
|
|
327
|
+
reply.header.id, reply.header.qr, reply.header.rcode = request.header.id, true, Dnsruby::RCode::NOTAUTH
|
|
328
|
+
if operation == :generate
|
|
329
|
+
reply.add_additional(signer.generate(reply, request))
|
|
330
|
+
else
|
|
331
|
+
signer.apply(reply, request)
|
|
332
|
+
end
|
|
333
|
+
wire = reply.encode
|
|
334
|
+
decoded = Dnsruby::Message.decode(wire)
|
|
335
|
+
assert_equal(expected_data.bytesize, decoded.tsig.other_size)
|
|
336
|
+
assert_equal(expected_data, decoded.tsig.other_data)
|
|
337
|
+
assert_equal(false, key.verify(request, decoded, wire))
|
|
338
|
+
assert(decoded.verified?, "#{operation} produced an invalid signed error")
|
|
339
|
+
assert_equal(error_code, decoded.tsigerror)
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def test_rechecking_stripped_tsig_errors_does_not_reuse_the_previous_authentication
|
|
344
|
+
[18, 22].each do |error_code|
|
|
345
|
+
request = Dnsruby::Message.new('signed-error.example.')
|
|
346
|
+
key = Dnsruby::RR.create(name: 'key.example.', type: 'TSIG', klass: 'ANY',
|
|
347
|
+
key: 'c2VjcmV0', algorithm: 'hmac-sha256')
|
|
348
|
+
key.apply(request)
|
|
349
|
+
signer = key.dup
|
|
350
|
+
signer.error = error_code
|
|
351
|
+
signer.time_signed = request.tsig.time_signed
|
|
352
|
+
signer.other_data = error_code == 18 ? [0, Time.now.to_i + 3600].pack('nN') : ''.b
|
|
353
|
+
signer.other_size = signer.other_data.bytesize
|
|
354
|
+
reply = Dnsruby::Message.new('signed-error.example.')
|
|
355
|
+
reply.header.id, reply.header.qr, reply.header.rcode = request.header.id, true, Dnsruby::RCode::NOTAUTH
|
|
356
|
+
signer.apply(reply, request)
|
|
357
|
+
wire = reply.encode
|
|
358
|
+
decoded = Dnsruby::Message.decode(wire)
|
|
359
|
+
writer = Dnsruby::PacketSender.new(server: '127.0.0.1', dnssec: false)
|
|
360
|
+
queue = Queue.new
|
|
361
|
+
assert_instance_of(Dnsruby::TsigError, writer.check_response(decoded, wire, request, queue, :repeat, false))
|
|
362
|
+
assert(decoded.verified?)
|
|
363
|
+
assert_nil(decoded.tsig)
|
|
364
|
+
assert_equal(false, writer.check_response(decoded, wire, request, queue, :repeat, false))
|
|
365
|
+
refute(decoded.verified?)
|
|
366
|
+
assert_equal(Dnsruby::RCode::FORMERR, decoded.tsigerror)
|
|
367
|
+
assert(queue.empty?)
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def test_udp_receive_capacity_uses_the_opt_advertised_by_each_attempt
|
|
372
|
+
isolated(<<~'RUBY')
|
|
373
|
+
[[1232, 4096], [4096, 1232], [1232, 0]].each do |configured, advertised|
|
|
374
|
+
socket = FrameSocket.new
|
|
375
|
+
socket.define_singleton_method(:bind) { |*| }
|
|
376
|
+
socket.define_singleton_method(:connect) { |*| }
|
|
377
|
+
socket.define_singleton_method(:getsockopt) { |*| Struct.new(:data).new([Socket::SOCK_DGRAM].pack('i')) }
|
|
378
|
+
UDPSocket.define_singleton_method(:new) { |*| socket }
|
|
379
|
+
writer = Dnsruby::PacketSender.new(server: '127.0.0.1', dnssec: false, udp_size: configured)
|
|
380
|
+
request = query('large-opt.example.')
|
|
381
|
+
request.add_additional(Dnsruby::RR::OPT.new(advertised))
|
|
382
|
+
queue = Queue.new
|
|
383
|
+
writer.send_async(request, queue, :opt)
|
|
384
|
+
live = SELECTOR.query_settings_for_id(:opt)
|
|
385
|
+
raise 'receive capacity disagrees with OPT' unless live.udp_packet_size == [advertised, 512].max
|
|
386
|
+
response = request.clone
|
|
387
|
+
response.header.qr = true
|
|
388
|
+
count = advertised > configured ? 7 : 1
|
|
389
|
+
response.add_answer(Dnsruby::RR.create('large-opt.example. 0 IN TXT ' +
|
|
390
|
+
Array.new(count) { '"' + 'x' * 240 + '"' }.join(' ')))
|
|
391
|
+
wire = response.encode
|
|
392
|
+
raise 'fixture did not exceed configured capacity' if count == 7 && wire.bytesize <= configured
|
|
393
|
+
socket.define_singleton_method(:recvfrom_nonblock) do |capacity|
|
|
394
|
+
[wire.byteslice(0, capacity), ['AF_INET', 53, '127.0.0.1', '127.0.0.1']]
|
|
395
|
+
end
|
|
396
|
+
incoming = SELECTOR.get_incoming_data(socket, live.udp_packet_size)
|
|
397
|
+
SELECTOR.process_message(*incoming, socket) if incoming
|
|
398
|
+
SELECTOR.send_queued_validation_responses
|
|
399
|
+
raise 'valid advertised-size reply was truncated' unless queue.size == 2 &&
|
|
400
|
+
2.times.map { queue.pop }.all? { |event| event.last.nil? && event[2].answer.first.strings.size == count }
|
|
401
|
+
raise 'completed OPT query retained' if SELECTOR.query_settings_for_id(:opt)
|
|
402
|
+
assert_clean(socket)
|
|
403
|
+
end
|
|
404
|
+
RUBY
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def test_explicit_udp_size_is_honoured_with_dnssec_in_either_option_order
|
|
408
|
+
[Dnsruby::Resolver, Dnsruby::SingleResolver, Dnsruby::PacketSender].each do |klass|
|
|
409
|
+
[{udp_size: 1232, dnssec: true}, {dnssec: true, udp_size: 1232}].each do |settings|
|
|
410
|
+
options = klass == Dnsruby::Resolver ? {nameserver: '127.0.0.1'} : {server: '127.0.0.1'}
|
|
411
|
+
object = klass.new(options.merge(settings))
|
|
412
|
+
assert_equal(1232, object.udp_size)
|
|
413
|
+
writer = object.is_a?(Dnsruby::Resolver) ? object.single_resolvers.first : object
|
|
414
|
+
request = Dnsruby::Message.new('payload.example.')
|
|
415
|
+
assert_equal(1232, writer.udp_packet_size)
|
|
416
|
+
assert_equal(1232, Dnsruby::Message.decode(writer.make_query_packet(request)).get_opt.payloadsize)
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
writer = Dnsruby::PacketSender.new(server: '127.0.0.1', dnssec: false, udp_size: 1232)
|
|
420
|
+
assert_equal(1232, Dnsruby::Message.decode(writer.make_query_packet(Dnsruby::Message.new('payload.example.'))).get_opt.payloadsize)
|
|
421
|
+
writer.udp_size = 100
|
|
422
|
+
assert_equal(512, writer.udp_packet_size)
|
|
423
|
+
assert_nil(Dnsruby::Message.decode(writer.make_query_packet(Dnsruby::Message.new('payload.example.'))).get_opt)
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def test_default_and_explicit_udp_sizes_match_the_wire_and_receive_capacity
|
|
427
|
+
assert_equal(1232, Dnsruby::Resolver::DefaultUDPSize)
|
|
428
|
+
assert_equal(1220, Dnsruby::Resolver::AbsoluteMinDnssecUdpSize)
|
|
429
|
+
assert_equal(4096, Dnsruby::Resolver::MinDnssecUdpSize)
|
|
430
|
+
isolated(<<~'RUBY')
|
|
431
|
+
[Dnsruby::Resolver, Dnsruby::SingleResolver, Dnsruby::PacketSender].product(
|
|
432
|
+
[false, true], [nil, 512, 1220, 1232, 4096]).each do |klass, dnssec, configured|
|
|
433
|
+
socket = FrameSocket.new
|
|
434
|
+
socket.define_singleton_method(:bind) { |*| }
|
|
435
|
+
socket.define_singleton_method(:connect) { |*| }
|
|
436
|
+
socket.define_singleton_method(:getsockopt) { |*| Struct.new(:data).new([Socket::SOCK_DGRAM].pack('i')) }
|
|
437
|
+
UDPSocket.define_singleton_method(:new) { |*| socket }
|
|
438
|
+
options = klass == Dnsruby::Resolver ? {nameserver: '127.0.0.1'} : {server: '127.0.0.1'}
|
|
439
|
+
options[:udp_size] = configured if configured
|
|
440
|
+
options[:dnssec] = dnssec
|
|
441
|
+
object = klass.new(options)
|
|
442
|
+
writer = object.is_a?(Dnsruby::Resolver) ? object.single_resolvers.first : object
|
|
443
|
+
expected = configured || 1232
|
|
444
|
+
expected = [expected, 1220].max if dnssec
|
|
445
|
+
raise "wrong configured size for #{klass}" unless object.udp_size == expected && writer.udp_size == expected
|
|
446
|
+
request, queue = query('udp-limit.example.'), Queue.new
|
|
447
|
+
writer.send_async(request, queue, :limit)
|
|
448
|
+
live = SELECTOR.query_settings_for_id(:limit)
|
|
449
|
+
raise 'wrong receive capacity' unless live.udp_packet_size == expected
|
|
450
|
+
sent = Dnsruby::Message.decode(socket.written)
|
|
451
|
+
if expected > 512
|
|
452
|
+
raise 'wrong OPT count' unless sent.additional.count { |rr| rr.type == Dnsruby::Types::OPT } == 1
|
|
453
|
+
raise 'wrong advertised capacity' unless sent.get_opt.payloadsize == expected
|
|
454
|
+
raise 'wrong DNSSEC flag' unless sent.get_opt.dnssec_ok == dnssec
|
|
455
|
+
else
|
|
456
|
+
raise 'classic DNS unexpectedly advertised EDNS' if sent.get_opt
|
|
457
|
+
end
|
|
458
|
+
response = request.clone
|
|
459
|
+
response.header.qr = true
|
|
460
|
+
wire = response.encode
|
|
461
|
+
socket.define_singleton_method(:recvfrom_nonblock) do |capacity|
|
|
462
|
+
raise 'receive buffer differs from advertisement' unless capacity == expected
|
|
463
|
+
[wire, ['AF_INET', 53, '127.0.0.1', '127.0.0.1']]
|
|
464
|
+
end
|
|
465
|
+
incoming = SELECTOR.get_incoming_data(socket, live.udp_packet_size)
|
|
466
|
+
SELECTOR.process_message(*incoming, socket)
|
|
467
|
+
SELECTOR.send_queued_validation_responses
|
|
468
|
+
raise 'response did not complete' unless queue.size == 2 &&
|
|
469
|
+
2.times.map { queue.pop }.all? { |event| event.last.nil? }
|
|
470
|
+
object.close
|
|
471
|
+
assert_clean(socket)
|
|
472
|
+
end
|
|
473
|
+
RUBY
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def test_questionless_error_matching_is_tcp_only_and_edns_retry_retains_transport
|
|
477
|
+
[false, true].product([false, true]).each do |tcp, edns|
|
|
478
|
+
writer = Dnsruby::PacketSender.new(server: '127.0.0.1', dnssec: false)
|
|
479
|
+
retries = []
|
|
480
|
+
writer.define_singleton_method(:send_async) { |*args| retries << args }
|
|
481
|
+
request = Dnsruby::Message.new('error.example.')
|
|
482
|
+
request.add_additional(Dnsruby::RR::OPT.new(1232)) if edns
|
|
483
|
+
error = Dnsruby::Message.new
|
|
484
|
+
error.header.id, error.header.qr, error.header.rcode = request.header.id, true, Dnsruby::RCode::FORMERR
|
|
485
|
+
result = writer.check_response(error, error.encode, request, Queue.new, :error, tcp)
|
|
486
|
+
assert_equal(tcp && !edns, result)
|
|
487
|
+
assert_equal(tcp && edns ? 1 : 0, retries.size)
|
|
488
|
+
if tcp && edns
|
|
489
|
+
assert_equal(true, retries.first[3])
|
|
490
|
+
assert_equal(true, retries.first[4])
|
|
491
|
+
assert_nil(retries.first[0].get_opt)
|
|
492
|
+
end
|
|
493
|
+
error.header.rcode = Dnsruby::RCode::NOERROR
|
|
494
|
+
assert_equal(false, writer.check_response(error, error.encode, request, Queue.new, :error, tcp))
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
def test_public_senders_prepare_messages_before_signing_and_preserve_existing_signatures
|
|
499
|
+
isolated(<<~'RUBY')
|
|
500
|
+
captured = []
|
|
501
|
+
Dnsruby::PacketSender.define_method(:send_dnsruby) { |wire, *| captured << wire }
|
|
502
|
+
[Dnsruby::Resolver, Dnsruby::SingleResolver, Dnsruby::PacketSender].product(
|
|
503
|
+
[false, true], [false, true], [false, true], [false, true],
|
|
504
|
+
[:message, :resolver, :both, :signed, :decoded, :raw_message, :raw_unsigned]).each do |klass, dnssec, rd, recurse, tcp, mode|
|
|
505
|
+
message_key = Dnsruby::RR.create(name: 'message-key.example.', type: 'TSIG', klass: 'ANY',
|
|
506
|
+
key: 'c2VjcmV0', algorithm: 'hmac-sha256')
|
|
507
|
+
resolver_key = Dnsruby::RR.create(name: 'resolver-key.example.', type: 'TSIG', klass: 'ANY',
|
|
508
|
+
key: 'b3RoZXI=', algorithm: 'hmac-sha256')
|
|
509
|
+
options = klass == Dnsruby::Resolver ? {nameserver: '127.0.0.1'} : {server: '127.0.0.1'}
|
|
510
|
+
object = klass.new(options.merge(dnssec: dnssec, recurse: recurse, use_tcp: tcp))
|
|
511
|
+
object.tsig = resolver_key unless [:message, :raw_message, :raw_unsigned].include?(mode)
|
|
512
|
+
request = query('signed-query.example.')
|
|
513
|
+
request.header.rd, request.header.ad = rd, true
|
|
514
|
+
request.add_additional(Dnsruby::RR::OPT.new(4096, 0x0040,
|
|
515
|
+
[Dnsruby::RR::OPT::Option.new(65000, 'opaque'.b)]))
|
|
516
|
+
request.send_raw = [:raw_message, :raw_unsigned].include?(mode)
|
|
517
|
+
if [:signed, :decoded].include?(mode)
|
|
518
|
+
message_key.apply(request)
|
|
519
|
+
original_wire = request.encode
|
|
520
|
+
request = Dnsruby::Message.decode(original_wire) if mode == :decoded
|
|
521
|
+
elsif [:message, :both, :raw_message].include?(mode)
|
|
522
|
+
request.set_tsig(message_key)
|
|
523
|
+
end
|
|
524
|
+
request.do_caching = request.do_validation = false
|
|
525
|
+
object.send_async(request, Queue.new, :signed)
|
|
526
|
+
raise "missing send for #{klass}/#{mode}" unless captured.size == 1
|
|
527
|
+
wire = captured.shift
|
|
528
|
+
decoded = Dnsruby::Message.decode(wire)
|
|
529
|
+
if [:signed, :decoded].include?(mode)
|
|
530
|
+
raise 'pre-signed packet was changed' unless wire == original_wire
|
|
531
|
+
end
|
|
532
|
+
protected = [:signed, :decoded, :raw_message, :raw_unsigned].include?(mode)
|
|
533
|
+
raise 'wrong RD policy' unless decoded.header.rd == (protected ? rd : recurse)
|
|
534
|
+
raise 'wrong DO policy' unless decoded.get_opt.dnssec_ok == (!protected && dnssec)
|
|
535
|
+
raise 'caller OPT payload changed' unless decoded.get_opt.payloadsize == 4096
|
|
536
|
+
raise 'caller OPT options changed' unless decoded.get_opt.options(nil).map { |opt| [opt.code, opt.data] } == [[65000, 'opaque']]
|
|
537
|
+
raise 'caller OPT flag lost' unless decoded.get_opt.flags & 0x0040 == 0x0040
|
|
538
|
+
if mode == :raw_unsigned
|
|
539
|
+
raise 'raw unsigned packet was signed' if decoded.tsig
|
|
540
|
+
else
|
|
541
|
+
key = mode == :resolver ? resolver_key : message_key
|
|
542
|
+
signature = decoded.tsig
|
|
543
|
+
raise 'wrong signing key precedence' unless signature && signature.name == key.name
|
|
544
|
+
raise "invalid request MAC for #{klass}/#{mode}/#{dnssec}/#{rd}/#{recurse}/#{tcp}" unless
|
|
545
|
+
key.generate(decoded, nil, '', wire, signature).mac == signature.mac
|
|
546
|
+
raise 'duplicate TSIG' unless decoded.additional.count { |rr| rr.type == Dnsruby::Types::TSIG } == 1
|
|
547
|
+
end
|
|
548
|
+
object.close
|
|
549
|
+
end
|
|
550
|
+
RUBY
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def test_socket_index_retains_duplicate_helper_ids_and_cleans_up_replacements
|
|
554
|
+
isolated(<<~'RUBY')
|
|
555
|
+
first_socket, second_socket = FrameSocket.new, FrameSocket.new
|
|
556
|
+
first = settings(first_socket, :first, query('first.example.'))
|
|
557
|
+
second = settings(first_socket, :second, query('second.example.'))
|
|
558
|
+
first.query.header.id = second.query.header.id = 123
|
|
559
|
+
[first, second].each { |s| SELECTOR.add_to_select(s) }
|
|
560
|
+
raise 'missing collision' unless SELECTOR.query_id_in_use?(first_socket, 123)
|
|
561
|
+
replacement = settings(second_socket, :first, first.query)
|
|
562
|
+
SELECTOR.add_to_select(replacement)
|
|
563
|
+
raise 'peer collision removed' unless SELECTOR.query_id_in_use?(first_socket, 123)
|
|
564
|
+
raise 'replacement collision missing' unless SELECTOR.query_id_in_use?(second_socket, 123)
|
|
565
|
+
SELECTOR.remove_id(:second)
|
|
566
|
+
raise 'old collision retained' if SELECTOR.query_id_in_use?(first_socket, 123)
|
|
567
|
+
SELECTOR.remove_id(:first)
|
|
568
|
+
raise 'replacement collision retained' if SELECTOR.query_id_in_use?(second_socket, 123)
|
|
569
|
+
[first_socket, second_socket].each { |socket| assert_clean(socket) }
|
|
570
|
+
RUBY
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
private
|
|
574
|
+
|
|
575
|
+
def isolated(body)
|
|
576
|
+
options = ''.frozen? ? ['--enable-frozen-string-literal'] : []
|
|
577
|
+
source = "#{TestPipelineSend::FIXTURE}\nTimeout.timeout(12) do\n#{body}\nend"
|
|
578
|
+
stdout, stderr, status = Open3.capture3(RbConfig.ruby, *options, '-I',
|
|
579
|
+
File.expand_path('../lib', __dir__), '-e', source)
|
|
580
|
+
assert(status.success?, "#{stdout}\n#{stderr}")
|
|
581
|
+
assert_empty(stderr)
|
|
582
|
+
end
|
|
583
|
+
end
|