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
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
# ++
|
|
16
16
|
require 'dnsruby/select_thread'
|
|
17
17
|
require 'ipaddr'
|
|
18
|
+
require 'securerandom'
|
|
18
19
|
# require 'dnsruby/iana_ports'
|
|
19
20
|
module Dnsruby
|
|
20
21
|
|
|
@@ -24,40 +25,41 @@ module Dnsruby
|
|
|
24
25
|
|
|
25
26
|
@@authoritative_cache = Cache.new
|
|
26
27
|
@@recursive_cache = Cache.new
|
|
28
|
+
PIPELINE_MUTEX_CREATION = Mutex.new
|
|
29
|
+
private_constant :PIPELINE_MUTEX_CREATION
|
|
27
30
|
|
|
28
31
|
|
|
29
|
-
def PacketSender.cache(query, response)
|
|
30
|
-
return if response.cached
|
|
31
|
-
# ONLY cache the response if it is not an update response
|
|
32
|
+
def PacketSender.cache(query, response, scope: response.instance_variable_get(:@dnsruby_cache_scope))
|
|
33
|
+
return if response.cached || query.send_raw || scope == false || !query.do_caching || query.class == Update
|
|
32
34
|
question = query.question()[0]
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
end
|
|
35
|
+
return unless question && question.qtype != Types::AXFR && question.qtype != Types::IXFR &&
|
|
36
|
+
response.rcode == RCode::NOERROR && response.header.ancount > 0
|
|
37
|
+
# TSIG authentication belongs to this transaction. Verification removes
|
|
38
|
+
# the response's TSIG record, so its absence does not make it cacheable.
|
|
39
|
+
return if query.tsig || query.signed? || response.signed? || response.tsig
|
|
40
|
+
# Alias-bearing NODATA belongs to the scoped negative cache, even
|
|
41
|
+
# though its CNAME records make the answer section nonempty.
|
|
42
|
+
return if Cache.negative_soa(response)
|
|
43
|
+
# Don't cache packets with "*" in the query name (RFC 1034 section 4.3.3).
|
|
44
|
+
return if question.qname.to_s.include?('*')
|
|
45
|
+
if query.header.rd
|
|
46
|
+
PacketSender.cache_recursive(response, scope: scope)
|
|
47
|
+
else
|
|
48
|
+
PacketSender.cache_authoritative(response, scope: scope)
|
|
48
49
|
end
|
|
49
50
|
end
|
|
50
51
|
|
|
51
|
-
def PacketSender.cache_authoritative(answer)
|
|
52
|
+
def PacketSender.cache_authoritative(answer, scope: nil)
|
|
52
53
|
return if !answer.header.aa
|
|
53
|
-
@@authoritative_cache.add(answer)
|
|
54
|
+
@@authoritative_cache.add(answer, scope: scope)
|
|
54
55
|
end
|
|
55
56
|
|
|
56
|
-
def PacketSender.cache_recursive(answer)
|
|
57
|
-
@@recursive_cache.add(answer)
|
|
57
|
+
def PacketSender.cache_recursive(answer, scope: nil)
|
|
58
|
+
@@recursive_cache.add(answer, scope: scope)
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
def PacketSender.clear_caches
|
|
62
|
+
Cache.invalidate_resolutions
|
|
61
63
|
@@recursive_cache.clear
|
|
62
64
|
@@authoritative_cache.clear
|
|
63
65
|
end
|
|
@@ -86,6 +88,18 @@ module Dnsruby
|
|
|
86
88
|
# Limit the number of queries per pipeline
|
|
87
89
|
attr_accessor :tcp_pipelining_max_queries
|
|
88
90
|
|
|
91
|
+
# Maximum idle time for a reusable TCP connection, in seconds.
|
|
92
|
+
def tcp_idle_timeout
|
|
93
|
+
@tcp_idle_timeout || Resolver::DefaultTcpIdleTimeout
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def tcp_idle_timeout=(seconds)
|
|
97
|
+
unless seconds.is_a?(Numeric) && seconds.finite? && seconds > 0
|
|
98
|
+
raise ArgumentError, 'TCP idle timeout must be a positive finite number'
|
|
99
|
+
end
|
|
100
|
+
@tcp_idle_timeout = seconds
|
|
101
|
+
end
|
|
102
|
+
|
|
89
103
|
# Use UDP only - don't use TCP
|
|
90
104
|
# For test/debug purposes only
|
|
91
105
|
# Defaults to false
|
|
@@ -109,16 +123,15 @@ module Dnsruby
|
|
|
109
123
|
# Defaults to true
|
|
110
124
|
attr_accessor :recurse
|
|
111
125
|
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
# Defaults to 512
|
|
126
|
+
# Maximum UDP DNS payload size. Defaults to 1232 bytes; the minimum is 512.
|
|
127
|
+
# A caller-supplied OPT record sets the receive size for that query.
|
|
115
128
|
attr_reader :udp_size
|
|
116
129
|
|
|
117
130
|
# The address of the resolver to send queries to
|
|
118
131
|
attr_reader :server
|
|
119
132
|
|
|
120
133
|
# Use DNSSEC for this PacketSender
|
|
121
|
-
# dnssec defaults to
|
|
134
|
+
# dnssec defaults to false
|
|
122
135
|
attr_reader :dnssec
|
|
123
136
|
|
|
124
137
|
# Set the source address. If the arg is nil, do nothing
|
|
@@ -138,6 +151,8 @@ module Dnsruby
|
|
|
138
151
|
# Sets the TSIG to sign outgoing messages with.
|
|
139
152
|
# Pass in either a Dnsruby::RR::TSIG, or a key_name and key (or just a key)
|
|
140
153
|
# Pass in nil to stop tsig signing.
|
|
154
|
+
# Defaults to hmac-sha256; the algorithm must match the server's key.
|
|
155
|
+
# You can select hmac-md5 for legacy servers, which logs a warning.
|
|
141
156
|
# It is possible for client code to sign packets prior to sending - see
|
|
142
157
|
# Dnsruby::RR::TSIG#apply and Dnsruby::Message#sign
|
|
143
158
|
# Note that pre-signed packets will not be signed by PacketSender.
|
|
@@ -152,8 +167,8 @@ module Dnsruby
|
|
|
152
167
|
@dnssec=on
|
|
153
168
|
if (on)
|
|
154
169
|
# Set the UDP size (RFC 4035 section 4.1)
|
|
155
|
-
if (udp_packet_size < Resolver::
|
|
156
|
-
self.udp_size = Resolver::
|
|
170
|
+
if (udp_packet_size < Resolver::AbsoluteMinDnssecUdpSize)
|
|
171
|
+
self.udp_size = Resolver::AbsoluteMinDnssecUdpSize
|
|
157
172
|
end
|
|
158
173
|
end
|
|
159
174
|
end
|
|
@@ -176,6 +191,7 @@ module Dnsruby
|
|
|
176
191
|
# * :use_tcp
|
|
177
192
|
# * :tcp_pipelining
|
|
178
193
|
# * :tcp_pipelining_max_queries
|
|
194
|
+
# * :tcp_idle_timeout
|
|
179
195
|
# * :no_tcp
|
|
180
196
|
# * :ignore_truncation
|
|
181
197
|
# * :src_address
|
|
@@ -202,8 +218,11 @@ module Dnsruby
|
|
|
202
218
|
@recurse = true
|
|
203
219
|
@tcp_pipelining = false
|
|
204
220
|
@tcp_pipelining_max_queries = :infinite
|
|
221
|
+
@tcp_idle_timeout = Resolver::DefaultTcpIdleTimeout
|
|
205
222
|
@use_counts = {}
|
|
223
|
+
@process_pid = Process.pid
|
|
206
224
|
|
|
225
|
+
ignore_config_resolv_errors = false;
|
|
207
226
|
if arg.nil?
|
|
208
227
|
elsif arg.kind_of? String
|
|
209
228
|
@server = arg
|
|
@@ -212,18 +231,33 @@ module Dnsruby
|
|
|
212
231
|
elsif arg.kind_of? Hash
|
|
213
232
|
arg.keys.each do |attr|
|
|
214
233
|
begin
|
|
215
|
-
if (
|
|
216
|
-
|
|
234
|
+
if (attr.to_s == "ignore_config_resolv_errors")
|
|
235
|
+
ignore_config_resolv_errors = arg[attr]
|
|
217
236
|
else
|
|
218
|
-
|
|
237
|
+
if ((attr.to_s == "src_address" || attr.to_s == "src_address6") &&
|
|
238
|
+
(arg[attr] == nil || arg[attr] == ""))
|
|
239
|
+
else
|
|
240
|
+
send(attr.to_s + "=", arg[attr])
|
|
241
|
+
end
|
|
219
242
|
end
|
|
220
243
|
rescue Exception => e
|
|
244
|
+
raise if attr.to_s == 'tsig' || ValidationBudget.abort?(e)
|
|
221
245
|
Dnsruby.log.error { "PacketSender : Argument #{attr}, #{arg[attr]} not valid : #{e}\n" }
|
|
222
246
|
end
|
|
223
247
|
end
|
|
224
248
|
end
|
|
225
249
|
# Check server is IP
|
|
226
|
-
|
|
250
|
+
# We should allow the call of PacketSender.new to flag just to log an error if this fails
|
|
251
|
+
# and then return null
|
|
252
|
+
if ignore_config_resolv_errors
|
|
253
|
+
begin
|
|
254
|
+
@server=Config.resolve_server(@server)
|
|
255
|
+
rescue ArgumentError
|
|
256
|
+
return
|
|
257
|
+
end
|
|
258
|
+
else
|
|
259
|
+
@server=Config.resolve_server(@server)
|
|
260
|
+
end
|
|
227
261
|
|
|
228
262
|
check_ipv6
|
|
229
263
|
# ResolverRegister::register_single_resolver(self)
|
|
@@ -236,7 +270,7 @@ module Dnsruby
|
|
|
236
270
|
@ipv6=false
|
|
237
271
|
rescue Exception
|
|
238
272
|
begin
|
|
239
|
-
|
|
273
|
+
Config.ipv6_server_address(@server)
|
|
240
274
|
# @src_address6 = '::'
|
|
241
275
|
@ipv6=true
|
|
242
276
|
rescue Exception
|
|
@@ -245,9 +279,61 @@ module Dnsruby
|
|
|
245
279
|
end
|
|
246
280
|
end
|
|
247
281
|
|
|
282
|
+
# A child must never write to a stream shared with its parent, including
|
|
283
|
+
# connections that have not yet been registered with SelectThread.
|
|
284
|
+
def ensure_current_process
|
|
285
|
+
return if @process_pid == Process.pid
|
|
286
|
+
selector = @selector
|
|
287
|
+
selector.send(:ensure_current_process) if selector
|
|
288
|
+
PIPELINE_MUTEX_CREATION.synchronize do
|
|
289
|
+
if @process_pid != Process.pid
|
|
290
|
+
((@use_counts || {}).keys + [@pipeline_socket]).compact.uniq.each do |socket|
|
|
291
|
+
begin
|
|
292
|
+
socket.close unless socket.closed?
|
|
293
|
+
rescue IOError, SystemCallError
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
@use_counts = {}
|
|
297
|
+
@pipeline_socket = @pipeline_socket_configuration = @pipeline_socket_max_queries = nil
|
|
298
|
+
@pipeline_mutex = @pipeline_connect_context = @tcp_pipeline_local_port = nil
|
|
299
|
+
@selector = nil
|
|
300
|
+
@process_pid = Process.pid
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
private :ensure_current_process
|
|
305
|
+
|
|
248
306
|
def close
|
|
249
|
-
|
|
250
|
-
#
|
|
307
|
+
ensure_current_process
|
|
308
|
+
# Idle streams close immediately; streams still serving another Resolver
|
|
309
|
+
# drain before closing. Do not wait for a writer blocked in connect.
|
|
310
|
+
@selector.retire_sender(self) if @selector
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Reserve ownership while Resolver still holds its query lock, then perform
|
|
314
|
+
# the synchronous public send after releasing that lock. close/timeout can
|
|
315
|
+
# cancel this reservation even before send_async has started.
|
|
316
|
+
def prepare_send_async(message, queue, id, retire_connection = false, control = QueryControl.current, transmission_budget = nil) #:nodoc:
|
|
317
|
+
ensure_current_process
|
|
318
|
+
selector = @selector = SelectThread.instance
|
|
319
|
+
pending = SelectThread::QuerySettings.new(nil, message, @ignore_truncation,
|
|
320
|
+
queue, id, nil, @server, @port, TransportClock.now + @packet_timeout, udp_packet_size, self)
|
|
321
|
+
pending.send_token = SelectThread::SendToken.new(control, transmission_budget)
|
|
322
|
+
pending.endtime = [pending.endtime, control&.deadline].compact.min
|
|
323
|
+
pending.retire_connection = retire_connection
|
|
324
|
+
return unless selector.reserve_send(pending)
|
|
325
|
+
-> do
|
|
326
|
+
next unless selector.query_settings_for_id(id).equal?(pending)
|
|
327
|
+
previous_context = Thread.current[:dnsruby_response_query]
|
|
328
|
+
begin
|
|
329
|
+
Thread.current[:dnsruby_response_query] = pending
|
|
330
|
+
send_async(message, queue, id)
|
|
331
|
+
rescue StandardError => error
|
|
332
|
+
selector.fail_query_if_current(id, queue, pending, error)
|
|
333
|
+
ensure
|
|
334
|
+
Thread.current[:dnsruby_response_query] = previous_context
|
|
335
|
+
end
|
|
336
|
+
end
|
|
251
337
|
end
|
|
252
338
|
|
|
253
339
|
# Asynchronously send a Message to the server. The send can be done using just
|
|
@@ -291,6 +377,7 @@ module Dnsruby
|
|
|
291
377
|
# validation for this particular packet (assuming SingleResolver#dnssec == true)
|
|
292
378
|
# Note that these options should not normally be used!
|
|
293
379
|
def send_async(*args) # msg, client_queue, client_query_id, use_tcp=@use_tcp)
|
|
380
|
+
ensure_current_process
|
|
294
381
|
# @TODO@ Need to select a good Header ID here - see forgery-resilience RFC draft for details
|
|
295
382
|
msg = args[0]
|
|
296
383
|
client_query_id = nil
|
|
@@ -318,31 +405,36 @@ module Dnsruby
|
|
|
318
405
|
end
|
|
319
406
|
end
|
|
320
407
|
end
|
|
321
|
-
#
|
|
322
|
-
# #Are we using EventMachine or native Dnsruby?
|
|
323
|
-
# if (Resolver.eventmachine?)
|
|
324
|
-
# return send_eventmachine(query_packet, msg, client_query_id, client_queue, use_tcp)
|
|
325
|
-
# else
|
|
408
|
+
# Keep the request MAC for response verification (RFC 8945 section 4.3.1).
|
|
326
409
|
if (!client_query_id)
|
|
327
410
|
client_query_id = Time.now + rand(10000) # is this safe?!
|
|
328
411
|
end
|
|
412
|
+
response_settings = response_query_settings(client_query_id)
|
|
413
|
+
disable_edns = args[4] || (response_settings && response_settings.disable_edns)
|
|
329
414
|
|
|
330
415
|
begin
|
|
331
|
-
query_packet = make_query_packet(msg, use_tcp)
|
|
416
|
+
query_packet = make_query_packet(msg, use_tcp, disable_edns)
|
|
417
|
+
check_message_size(query_packet)
|
|
332
418
|
rescue EncodeError => err
|
|
333
419
|
Dnsruby.log.error { "#{err}" }
|
|
334
420
|
st = SelectThread.instance
|
|
335
|
-
|
|
421
|
+
if response_settings
|
|
422
|
+
st.fail_query_if_current(client_query_id, client_queue, response_settings, err)
|
|
423
|
+
else
|
|
424
|
+
st.push_exception_to_select(client_query_id, client_queue, err, nil)
|
|
425
|
+
end
|
|
336
426
|
return
|
|
337
427
|
end
|
|
338
428
|
|
|
339
|
-
if (msg.do_caching && (msg.class != Update))
|
|
429
|
+
if (msg.do_caching && !msg.send_raw && (msg.class != Update) && !@tsig && !msg.tsig && !msg.signed?)
|
|
340
430
|
# Check the cache!!
|
|
341
431
|
cachedanswer = nil
|
|
342
432
|
if (msg.header.rd)
|
|
343
|
-
cachedanswer = @@recursive_cache.find(msg.question()[0].qname, msg.question()[0].type)
|
|
433
|
+
cachedanswer = @@recursive_cache.find(msg.question()[0].qname, msg.question()[0].type, msg.question()[0].qclass,
|
|
434
|
+
scope: positive_cache_scope(msg, query_packet))
|
|
344
435
|
else
|
|
345
|
-
cachedanswer = @@authoritative_cache.find(msg.question()[0].qname, msg.question()[0].type)
|
|
436
|
+
cachedanswer = @@authoritative_cache.find(msg.question()[0].qname, msg.question()[0].type, msg.question()[0].qclass,
|
|
437
|
+
scope: positive_cache_scope(msg, query_packet))
|
|
346
438
|
end
|
|
347
439
|
if (cachedanswer)
|
|
348
440
|
TheLog.debug("Sending cached answer to client\n")
|
|
@@ -351,7 +443,11 @@ module Dnsruby
|
|
|
351
443
|
# If we can find the answer, send it to the client straight away
|
|
352
444
|
# Post the result to the client using SelectThread
|
|
353
445
|
st = SelectThread.instance
|
|
354
|
-
|
|
446
|
+
if response_settings
|
|
447
|
+
st.push_response_for_query(response_settings, cachedanswer, msg, self)
|
|
448
|
+
else
|
|
449
|
+
st.push_response_to_select(client_query_id, client_queue, cachedanswer, msg, self)
|
|
450
|
+
end
|
|
355
451
|
return client_query_id
|
|
356
452
|
end
|
|
357
453
|
end
|
|
@@ -361,13 +457,18 @@ module Dnsruby
|
|
|
361
457
|
# Can't send the message - abort!
|
|
362
458
|
err=IOError.new("Can't send message - too big for UDP and no_tcp=true")
|
|
363
459
|
Dnsruby.log.error { "#{err}" }
|
|
364
|
-
st
|
|
460
|
+
st = SelectThread.instance
|
|
461
|
+
if response_settings
|
|
462
|
+
st.fail_query_if_current(client_query_id, client_queue, response_settings, err)
|
|
463
|
+
else
|
|
464
|
+
st.push_exception_to_select(client_query_id, client_queue, err, nil)
|
|
465
|
+
end
|
|
365
466
|
return
|
|
366
467
|
end
|
|
367
468
|
Dnsruby.log.debug { "Query packet length exceeds max UDP packet size - using TCP" }
|
|
368
469
|
use_tcp = true
|
|
369
470
|
end
|
|
370
|
-
send_dnsruby(query_packet, msg, client_query_id, client_queue, use_tcp)
|
|
471
|
+
send_dnsruby(query_packet, msg, client_query_id, client_queue, use_tcp, disable_edns)
|
|
371
472
|
return client_query_id
|
|
372
473
|
# end
|
|
373
474
|
end
|
|
@@ -381,15 +482,26 @@ module Dnsruby
|
|
|
381
482
|
# In general, every subsequent call the function will either return the current tcp
|
|
382
483
|
# pipeline socket or a new connected socket if the current one was closed by the server
|
|
383
484
|
def tcp_pipeline_socket(src_port)
|
|
485
|
+
ensure_current_process
|
|
384
486
|
Dnsruby.log.debug("Using tcp_pipeline_socket")
|
|
385
|
-
|
|
487
|
+
# A live connection retains its endpoint and source binding. Compare the
|
|
488
|
+
# configured source-port pool, not each randomly selected candidate port.
|
|
489
|
+
attempt = @pipeline_connect_context&.[](2) || transport_configuration
|
|
490
|
+
configuration = [attempt[:server], attempt[:port],
|
|
491
|
+
attempt[:ipv6] ? attempt[:src_address6] : attempt[:src_address], attempt[:src_port], attempt[:ipv6]]
|
|
492
|
+
sockaddr = Socket.sockaddr_in(configuration[1], configuration[0])
|
|
386
493
|
|
|
387
494
|
reuse_pipeline_socket = -> do
|
|
388
495
|
begin
|
|
389
|
-
max = @
|
|
496
|
+
max = @pipeline_socket_max_queries
|
|
390
497
|
use_count = @use_counts[@pipeline_socket]
|
|
391
|
-
if
|
|
392
|
-
|
|
498
|
+
if !SelectThread.instance.reusable_socket?(@pipeline_socket) ||
|
|
499
|
+
max != attempt[:tcp_pipelining_max_queries] ||
|
|
500
|
+
@pipeline_socket_configuration != configuration ||
|
|
501
|
+
(use_count && max != :infinite && use_count >= max)
|
|
502
|
+
# Capacity and endpoint belong to a connection. Outstanding peers
|
|
503
|
+
# keep their old stream and drain before it closes.
|
|
504
|
+
SelectThread.instance.retire_socket(@pipeline_socket)
|
|
393
505
|
@use_counts.delete(@pipeline_socket)
|
|
394
506
|
@pipeline_socket = nil
|
|
395
507
|
Dnsruby.log.debug("Max queries per connection attained - creating new socket")
|
|
@@ -399,6 +511,7 @@ module Dnsruby
|
|
|
399
511
|
rescue Errno::EISCONN
|
|
400
512
|
#already connected, do nothing and reuse!
|
|
401
513
|
rescue IOError, Errno::ECONNRESET #close by remote host, reconnect
|
|
514
|
+
@use_counts.delete(@pipeline_socket)
|
|
402
515
|
@pipeline_socket = nil
|
|
403
516
|
Dnsruby.log.debug("Connection closed - recreating socket")
|
|
404
517
|
end
|
|
@@ -406,14 +519,20 @@ module Dnsruby
|
|
|
406
519
|
|
|
407
520
|
create_pipeline_socket = -> do
|
|
408
521
|
@tcp_pipeline_local_port = src_port
|
|
409
|
-
src_address =
|
|
522
|
+
src_address = configuration[2]
|
|
410
523
|
begin
|
|
411
|
-
@
|
|
412
|
-
@
|
|
413
|
-
@
|
|
524
|
+
@pipeline_socket_max_queries = attempt[:tcp_pipelining_max_queries]
|
|
525
|
+
@pipeline_socket_configuration = configuration
|
|
526
|
+
deadline, token = @pipeline_connect_context ||
|
|
527
|
+
[TransportClock.now + attempt[:packet_timeout], SelectThread::SendToken.new]
|
|
528
|
+
@pipeline_socket = connect_tcp(configuration[0], configuration[1], src_address, src_port,
|
|
529
|
+
deadline, token)
|
|
414
530
|
Dnsruby.log.debug("Creating socket #{src_address}:#{src_port}")
|
|
415
531
|
@use_counts[@pipeline_socket] = 0
|
|
416
532
|
rescue Exception => e
|
|
533
|
+
# A failed bind/connect never reaches the select thread, which would
|
|
534
|
+
# otherwise own closing the socket (for example, an unusable zone).
|
|
535
|
+
@pipeline_socket.close rescue nil
|
|
417
536
|
@pipeline_socket = nil
|
|
418
537
|
raise e
|
|
419
538
|
end
|
|
@@ -431,35 +550,254 @@ module Dnsruby
|
|
|
431
550
|
end
|
|
432
551
|
|
|
433
552
|
# This method sends the packet using the built-in pure Ruby event loop, with no dependencies.
|
|
434
|
-
def send_dnsruby(query_bytes, query, client_query_id, client_queue, use_tcp) #:nodoc: all
|
|
435
|
-
|
|
553
|
+
def send_dnsruby(query_bytes, query, client_query_id, client_queue, use_tcp, disable_edns = false) #:nodoc: all
|
|
554
|
+
ensure_current_process
|
|
555
|
+
st = @selector = SelectThread.instance
|
|
556
|
+
response_settings = response_query_settings(client_query_id)
|
|
557
|
+
# A configuration change applies to the next attempt, never half of this one.
|
|
558
|
+
configuration = transport_configuration
|
|
559
|
+
pipeline_mode = use_tcp && configuration[:tcp_pipelining]
|
|
560
|
+
begin
|
|
561
|
+
check_message_size(query_bytes)
|
|
562
|
+
rescue EncodeError => error
|
|
563
|
+
if response_settings
|
|
564
|
+
st.fail_query_if_current(client_query_id, client_queue, response_settings, error)
|
|
565
|
+
else
|
|
566
|
+
st.push_exception_to_select(client_query_id, client_queue, error, nil)
|
|
567
|
+
end
|
|
568
|
+
return
|
|
569
|
+
end
|
|
570
|
+
# A Resolver reservation already started this attempt's packet deadline.
|
|
571
|
+
# A protocol retry (which has bytes) receives a fresh packet deadline.
|
|
572
|
+
endtime = response_settings && response_settings.query_bytes.nil? ?
|
|
573
|
+
response_settings.endtime : TransportClock.now + configuration[:packet_timeout]
|
|
574
|
+
previous_settings = response_settings ||
|
|
575
|
+
(st.query_settings_for_id(client_query_id) if pipeline_mode)
|
|
576
|
+
pending = SelectThread::QuerySettings.new(query_bytes, query,
|
|
577
|
+
configuration[:ignore_truncation], client_queue, client_query_id, nil,
|
|
578
|
+
configuration[:server], configuration[:port],
|
|
579
|
+
endtime, udp_receive_size(query, configuration), self)
|
|
580
|
+
pending.transport_configuration = configuration
|
|
581
|
+
pending.send_token = previous_settings.send_token if previous_settings
|
|
582
|
+
# Carry the controlling lookup through selector retries and workers, whose
|
|
583
|
+
# thread-local control may differ from the thread which admitted it.
|
|
584
|
+
endtime = pending.endtime = [endtime, pending.send_token.control&.deadline].compact.min
|
|
585
|
+
pending.disable_edns = disable_edns || (previous_settings && previous_settings.disable_edns)
|
|
586
|
+
pending.cache_scope = positive_cache_scope(query, query_bytes, configuration)
|
|
587
|
+
# A protocol retry starts its own packet deadline at dispatch, including
|
|
588
|
+
# time queued for a worker. Retire the old socket membership atomically.
|
|
589
|
+
return unless st.reserve_send(pending, previous_settings)
|
|
590
|
+
previous_settings = pending
|
|
591
|
+
operation = -> do
|
|
592
|
+
send_dnsruby_with_socket(query_bytes, query, client_query_id, client_queue,
|
|
593
|
+
use_tcp, endtime, previous_settings, pipeline_mode)
|
|
594
|
+
end
|
|
595
|
+
send_operation = operation
|
|
596
|
+
if pipeline_mode
|
|
597
|
+
# Configuration-only senders remain Marshal-compatible. Allocate the
|
|
598
|
+
# writer mutex only on first use, including senders loaded from storage.
|
|
599
|
+
pipeline_mutex = PIPELINE_MUTEX_CREATION.synchronize { @pipeline_mutex ||= Mutex.new }
|
|
600
|
+
send_operation = -> do
|
|
601
|
+
locked = false
|
|
602
|
+
begin
|
|
603
|
+
until (locked = pipeline_mutex.try_lock)
|
|
604
|
+
break if previous_settings.send_token.cancelled? || TransportClock.now >= endtime ||
|
|
605
|
+
!st.query_settings_for_id(client_query_id).equal?(previous_settings)
|
|
606
|
+
sleep 0.005
|
|
607
|
+
end
|
|
608
|
+
if locked && !previous_settings.send_token.cancelled? && TransportClock.now < endtime
|
|
609
|
+
operation.call
|
|
610
|
+
else
|
|
611
|
+
st.fail_query_if_current(client_query_id, client_queue, previous_settings,
|
|
612
|
+
ResolvTimeout.new("Query timed out waiting for TCP pipeline"))
|
|
613
|
+
end
|
|
614
|
+
ensure
|
|
615
|
+
pipeline_mutex.unlock if locked
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
guarded_operation = -> do
|
|
620
|
+
send_operation.call
|
|
621
|
+
rescue StandardError => error
|
|
622
|
+
# Deferred callers do not read SendTask#value. Failures before socket
|
|
623
|
+
# registration still belong to this exact reserved attempt.
|
|
624
|
+
st.fail_query_if_current(client_query_id, client_queue, pending, error)
|
|
625
|
+
end
|
|
626
|
+
if use_tcp && st.selector_thread?
|
|
627
|
+
begin
|
|
628
|
+
st.defer_send(pipeline_mode ? self : nil, &guarded_operation)
|
|
629
|
+
rescue ThreadError => error
|
|
630
|
+
st.fail_query_if_current(client_query_id, client_queue, previous_settings,
|
|
631
|
+
ResolvTimeout.new("Cannot queue TCP send: #{error.message}"))
|
|
632
|
+
end
|
|
633
|
+
else
|
|
634
|
+
# Only the selector delegates I/O. Public caller sends remain synchronous.
|
|
635
|
+
guarded_operation.call
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
def check_message_size(query_bytes)
|
|
640
|
+
if query_bytes.bytesize > 65_535
|
|
641
|
+
raise EncodeError, "DNS message exceeds the 65535-byte wire limit (#{query_bytes.bytesize} bytes)"
|
|
642
|
+
end
|
|
643
|
+
end
|
|
644
|
+
private :check_message_size
|
|
645
|
+
|
|
646
|
+
class SendCancelled < StandardError; end
|
|
647
|
+
private_constant :SendCancelled
|
|
648
|
+
|
|
649
|
+
# Config has already resolved the server address. Check cancellation while
|
|
650
|
+
# connecting so an unresponsive peer cannot hold up a sender worker.
|
|
651
|
+
def connect_tcp(server, port, source, source_port, deadline, token)
|
|
652
|
+
socket = nil
|
|
653
|
+
connected = false
|
|
654
|
+
begin
|
|
655
|
+
raise SendCancelled if token.cancelled?
|
|
656
|
+
raise ResolvTimeout, 'Query timed out before TCP connect' if TransportClock.now >= deadline
|
|
657
|
+
socket = SelectThread.instance.track_socket(
|
|
658
|
+
Socket.new(server.include?(':') ? Socket::AF_INET6 : Socket::AF_INET, Socket::SOCK_STREAM, 0))
|
|
659
|
+
socket.bind(Socket.sockaddr_in(source_port, source))
|
|
660
|
+
result = socket.connect_nonblock(Socket.sockaddr_in(port, server), exception: false)
|
|
661
|
+
while result == :wait_writable || result == :wait_readable
|
|
662
|
+
raise SendCancelled if token.cancelled?
|
|
663
|
+
remaining = deadline - TransportClock.now
|
|
664
|
+
raise ResolvTimeout, 'Query timed out connecting TCP' if remaining <= 0
|
|
665
|
+
readers, writers = result == :wait_readable ? [[socket], nil] : [nil, [socket]]
|
|
666
|
+
next unless IO.select(readers, writers, nil, [remaining, 0.05].min)
|
|
667
|
+
error = socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR).int
|
|
668
|
+
raise SystemCallError.new('TCP connect', error) unless error.zero?
|
|
669
|
+
result = 0
|
|
670
|
+
end
|
|
671
|
+
raise SendCancelled if token.cancelled?
|
|
672
|
+
raise ResolvTimeout, 'Query timed out connecting TCP' if TransportClock.now >= deadline
|
|
673
|
+
connected = true
|
|
674
|
+
socket
|
|
675
|
+
ensure
|
|
676
|
+
socket.close if socket && !connected && !socket.closed?
|
|
677
|
+
end
|
|
678
|
+
end
|
|
679
|
+
private :connect_tcp
|
|
680
|
+
|
|
681
|
+
# Socket#send may write only a prefix of a TCP frame. Nonblocking writes keep
|
|
682
|
+
# a stalled peer from holding the pipeline writer past the packet deadline.
|
|
683
|
+
def write_tcp_frame(socket, query_bytes, endtime, send_token)
|
|
684
|
+
check_message_size(query_bytes)
|
|
685
|
+
frame = [query_bytes.bytesize].pack('n') + query_bytes
|
|
686
|
+
offset = 0
|
|
687
|
+
while offset < frame.bytesize
|
|
688
|
+
remaining = endtime - TransportClock.now
|
|
689
|
+
begin
|
|
690
|
+
written = send_token.write do
|
|
691
|
+
raise ResolvTimeout, "Query timed out writing TCP frame" if remaining <= 0
|
|
692
|
+
socket.write_nonblock(frame.byteslice(offset, frame.bytesize - offset))
|
|
693
|
+
end
|
|
694
|
+
return offset if written == false # Cancelled between partial writes.
|
|
695
|
+
raise IOError, "TCP write made no progress" unless written && written > 0
|
|
696
|
+
offset += written
|
|
697
|
+
rescue IO::WaitWritable
|
|
698
|
+
# A canceled pipeline may still have peers and remain open. Bound the
|
|
699
|
+
# wait so cancellation releases the worker even if it never becomes
|
|
700
|
+
# writable; the next iteration retires any incomplete frame.
|
|
701
|
+
IO.select(nil, [socket], nil, [remaining, 0.05].min)
|
|
702
|
+
end
|
|
703
|
+
end
|
|
704
|
+
true
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
# A datagram is sent atomically. Order the actual write with cancellation,
|
|
708
|
+
# as for TCP, without holding the token while waiting for a writable socket.
|
|
709
|
+
def write_udp_packet(socket, query_bytes, endtime, send_token, wait_for_writable)
|
|
710
|
+
loop do
|
|
711
|
+
remaining = endtime - TransportClock.now
|
|
712
|
+
begin
|
|
713
|
+
written = send_token.write do
|
|
714
|
+
raise ResolvTimeout, "Query timed out writing UDP packet" if remaining <= 0
|
|
715
|
+
socket.write_nonblock(query_bytes)
|
|
716
|
+
end
|
|
717
|
+
return false if written == false
|
|
718
|
+
raise IOError, "Incomplete UDP datagram write" unless written == query_bytes.bytesize
|
|
719
|
+
return true
|
|
720
|
+
rescue IO::WaitWritable
|
|
721
|
+
raise unless wait_for_writable
|
|
722
|
+
IO.select(nil, [socket], nil, [remaining, 0.05].min)
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
def response_query_settings(client_query_id)
|
|
728
|
+
settings = Thread.current[:dnsruby_response_query]
|
|
729
|
+
settings if settings && settings.client_query_id == client_query_id &&
|
|
730
|
+
settings.single_resolver.equal?(self)
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
def send_dnsruby_with_socket(query_bytes, query, client_query_id, client_queue,
|
|
734
|
+
use_tcp, endtime, previous_settings, pipeline_mode = use_tcp && @tcp_pipelining)
|
|
436
735
|
# First send the query (synchronously)
|
|
437
736
|
st = SelectThread.instance
|
|
737
|
+
report_error = ->(error) do
|
|
738
|
+
st.fail_query_if_current(client_query_id, client_queue, previous_settings, error)
|
|
739
|
+
end
|
|
740
|
+
return unless st.query_settings_for_id(client_query_id).equal?(previous_settings)
|
|
741
|
+
return if previous_settings.send_token.cancelled?
|
|
742
|
+
configuration = previous_settings.transport_configuration || transport_configuration
|
|
743
|
+
server, port = configuration.values_at(:server, :port)
|
|
744
|
+
unless previous_settings.send_token.transmission_budget.admit(server, port, use_tcp, query.question.first)
|
|
745
|
+
report_error.call(ResolvTimeout.new("DNS retry limit reached for server and transport"))
|
|
746
|
+
return
|
|
747
|
+
end
|
|
438
748
|
socket = nil
|
|
439
749
|
runnextportloop = true
|
|
440
750
|
numtries = 0
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
751
|
+
source_port_configuration = configuration[:src_port]
|
|
752
|
+
source_ports = source_port_configuration.uniq
|
|
753
|
+
random_source_port = source_ports.first == 0
|
|
754
|
+
src_address = configuration[configuration[:ipv6] ? :src_address6 : :src_address]
|
|
445
755
|
while (runnextportloop) do
|
|
756
|
+
# A pre-close reservation gets a one-use socket. Recheck on every
|
|
757
|
+
# admission retry without changing ownership or extending its deadline.
|
|
758
|
+
pipeline = pipeline_mode && !previous_settings.retire_connection
|
|
759
|
+
return unless st.query_settings_for_id(client_query_id).equal?(previous_settings)
|
|
760
|
+
return if previous_settings.send_token.cancelled?
|
|
446
761
|
begin
|
|
762
|
+
if TransportClock.now >= endtime
|
|
763
|
+
report_error.call(ResolvTimeout.new("Query timed out before connect"))
|
|
764
|
+
return
|
|
765
|
+
end
|
|
447
766
|
numtries += 1
|
|
448
|
-
src_port =
|
|
767
|
+
src_port = if random_source_port
|
|
768
|
+
# Keep this attempt's mode even if a caller replaces the pool.
|
|
769
|
+
50_000 + SecureRandom.random_number(15_536)
|
|
770
|
+
else
|
|
771
|
+
source_ports[SecureRandom.random_number(source_ports.length)]
|
|
772
|
+
end
|
|
449
773
|
if (use_tcp)
|
|
450
774
|
begin
|
|
451
|
-
if
|
|
452
|
-
|
|
775
|
+
if pipeline
|
|
776
|
+
begin
|
|
777
|
+
# RFC 7766 requires unique outstanding IDs on a connection.
|
|
778
|
+
# Moving a collision to a fresh stream preserves caller IDs,
|
|
779
|
+
# including raw messages and bytes covered by TSIG.
|
|
780
|
+
if @pipeline_socket && st.query_id_in_use?(@pipeline_socket, query.header.id)
|
|
781
|
+
st.retire_socket(@pipeline_socket)
|
|
782
|
+
@use_counts.delete(@pipeline_socket)
|
|
783
|
+
@pipeline_socket = nil
|
|
784
|
+
end
|
|
785
|
+
@pipeline_connect_context = [endtime, previous_settings.send_token, configuration]
|
|
786
|
+
socket, new_socket = tcp_pipeline_socket(src_port)
|
|
787
|
+
ensure
|
|
788
|
+
remove_instance_variable(:@pipeline_connect_context)
|
|
789
|
+
end
|
|
453
790
|
src_port = @tcp_pipeline_local_port
|
|
454
791
|
else
|
|
455
|
-
socket =
|
|
792
|
+
socket = connect_tcp(server, port, src_address, src_port,
|
|
793
|
+
endtime, previous_settings.send_token)
|
|
456
794
|
new_socket = true
|
|
457
795
|
end
|
|
458
796
|
rescue Errno::EBADF, Errno::ENETUNREACH => e
|
|
459
797
|
# Can't create a connection
|
|
460
|
-
err=IOError.new("TCP connection error to #{
|
|
798
|
+
err=IOError.new("TCP connection error to #{server}:#{port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp}, exception = #{e.class}, #{e}")
|
|
461
799
|
Dnsruby.log.error { "#{err}" }
|
|
462
|
-
|
|
800
|
+
report_error.call(err)
|
|
463
801
|
return
|
|
464
802
|
end
|
|
465
803
|
else
|
|
@@ -469,81 +807,150 @@ module Dnsruby
|
|
|
469
807
|
socket = UDPSocket.new()
|
|
470
808
|
else
|
|
471
809
|
# ipv6 = @src_address =~ /:/
|
|
472
|
-
socket = UDPSocket.new(
|
|
810
|
+
socket = UDPSocket.new(configuration[:ipv6] ? Socket::AF_INET6 : Socket::AF_INET)
|
|
473
811
|
end
|
|
812
|
+
st.track_socket(socket)
|
|
474
813
|
new_socket = true
|
|
475
814
|
socket.bind(src_address, src_port)
|
|
476
|
-
socket.connect(
|
|
815
|
+
socket.connect(server, port)
|
|
477
816
|
end
|
|
478
|
-
|
|
817
|
+
rescue SendCancelled
|
|
818
|
+
return
|
|
479
819
|
rescue Exception => e
|
|
480
820
|
if (socket!=nil)
|
|
481
821
|
begin
|
|
482
822
|
#let the select thread close the socket if tcp_pipeli
|
|
483
|
-
socket.close unless
|
|
823
|
+
socket.close unless pipeline && !new_socket
|
|
484
824
|
rescue Exception
|
|
485
825
|
end
|
|
486
826
|
end
|
|
487
|
-
#
|
|
488
|
-
#
|
|
489
|
-
if
|
|
490
|
-
|
|
491
|
-
|
|
827
|
+
# Only an occupied bind removes a candidate. A pipeline admission
|
|
828
|
+
# retry has not found the port unusable and must retain the pool.
|
|
829
|
+
source_ports.delete(src_port) if e.is_a?(Errno::EADDRINUSE) && !random_source_port
|
|
830
|
+
# Random ports keep the bounded retry policy; configured pools do
|
|
831
|
+
# not revisit an occupied member during this attempt.
|
|
832
|
+
if !e.is_a?(Errno::EADDRINUSE) || (random_source_port && numtries >= 50) ||
|
|
833
|
+
(!random_source_port && source_ports.empty?)
|
|
834
|
+
err_msg = "dnsruby can't connect to #{server}:#{port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp}, exception = #{e.class}, #{e} #{e.backtrace}"
|
|
492
835
|
err=IOError.new(err_msg)
|
|
493
836
|
Dnsruby.log.error( "#{err}")
|
|
494
837
|
Dnsruby.log.error(e.backtrace)
|
|
495
|
-
if
|
|
496
|
-
|
|
838
|
+
if e.kind_of?(Errno::ETIMEDOUT) || e.kind_of?(ResolvTimeout)
|
|
839
|
+
report_error.call(ResolvTimeout.new(err_msg))
|
|
840
|
+
elsif pipeline
|
|
841
|
+
report_error.call(SocketEofResolvError.new(err_msg))
|
|
497
842
|
else
|
|
498
|
-
|
|
843
|
+
report_error.call(err)
|
|
499
844
|
end
|
|
500
845
|
return
|
|
501
846
|
end
|
|
847
|
+
next # Retry a random source-port collision before registration.
|
|
502
848
|
end
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
return
|
|
509
|
-
end
|
|
510
|
-
Dnsruby.log.debug { "Sending packet to #{@server}:#{@port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp} : #{query.question()[0].qname}, #{query.question()[0].qtype}" }
|
|
511
|
-
# print "#{Time.now} : Sending packet to #{@server} : #{query.question()[0].qname}, #{query.question()[0].qtype}\n"
|
|
512
|
-
# Listen for the response before we send the packet (to avoid any race conditions)
|
|
513
|
-
query_settings = SelectThread::QuerySettings.new(query_bytes, query, @ignore_truncation, client_queue, client_query_id, socket, @server, @port, endtime, udp_packet_size, self)
|
|
514
|
-
query_settings.is_persistent_socket = @tcp_pipelining if use_tcp
|
|
515
|
-
query_settings.tcp_pipelining_max_queries = @tcp_pipelining_max_queries if @tcp_pipelining
|
|
516
|
-
begin
|
|
517
|
-
if (use_tcp)
|
|
518
|
-
lenmsg = [query_bytes.length].pack('n')
|
|
519
|
-
socket.send(lenmsg, 0)
|
|
520
|
-
end
|
|
521
|
-
socket.send(query_bytes, 0)
|
|
522
|
-
|
|
523
|
-
# The select thread will now wait for the response and send that or a
|
|
524
|
-
# timeout back to the client_queue.
|
|
525
|
-
st.add_to_select(query_settings)
|
|
526
|
-
rescue Exception => e
|
|
527
|
-
err_msg = "Send failed to #{@server}:#{@port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp}, exception : #{e}"
|
|
528
|
-
err=IOError.new(err_msg)
|
|
529
|
-
Dnsruby.log.error { "#{err}" }
|
|
530
|
-
Dnsruby.log.error(e.backtrace)
|
|
531
|
-
if @tcp_pipelining
|
|
532
|
-
st.push_exception_to_select(client_query_id, client_queue, SocketEofResolvError.new(err_msg), nil) if new_socket
|
|
533
|
-
else
|
|
534
|
-
st.push_exception_to_select(client_query_id, client_queue, err, nil)
|
|
849
|
+
if (socket==nil)
|
|
850
|
+
err=IOError.new("dnsruby can't connect to #{server}:#{port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp}")
|
|
851
|
+
Dnsruby.log.error { "#{err}" }
|
|
852
|
+
report_error.call(err)
|
|
853
|
+
return
|
|
535
854
|
end
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
855
|
+
Dnsruby.log.debug { "Sending packet to #{server}:#{port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp} : #{query.question()[0].qname}, #{query.question()[0].qtype}" }
|
|
856
|
+
# A reused pipeline is already readable by the selector. Publish its new
|
|
857
|
+
# query before writing so an immediate answer always has an owner.
|
|
858
|
+
query_settings = SelectThread::QuerySettings.new(query_bytes, query, previous_settings.ignore_truncation,
|
|
859
|
+
client_queue, client_query_id, socket, server, port, endtime, previous_settings.udp_packet_size, self)
|
|
860
|
+
query_settings.transport_configuration = configuration
|
|
861
|
+
query_settings.send_token = previous_settings.send_token
|
|
862
|
+
query_settings.disable_edns = previous_settings.disable_edns
|
|
863
|
+
query_settings.cache_scope = previous_settings.cache_scope
|
|
864
|
+
query_settings.is_persistent_socket = pipeline
|
|
865
|
+
query_settings.tcp_pipelining_max_queries = (@pipeline_socket_max_queries || configuration[:tcp_pipelining_max_queries]) if pipeline
|
|
866
|
+
if use_tcp
|
|
867
|
+
registered = st.register_query_if_current(query_settings, previous_settings)
|
|
868
|
+
if registered == :retry_connection
|
|
869
|
+
# No bytes were written and the original reservation still owns the
|
|
870
|
+
# attempt. Peers on the retiring stream keep their registrations.
|
|
871
|
+
@use_counts.delete(socket)
|
|
872
|
+
@pipeline_socket = nil if @pipeline_socket == socket
|
|
873
|
+
next
|
|
874
|
+
end
|
|
875
|
+
unless registered
|
|
876
|
+
# Completion can win during connect. Release only this unused socket
|
|
877
|
+
# reservation; other queries may still own an existing pipeline.
|
|
878
|
+
@use_counts[socket] -= 1 if pipeline && @use_counts[socket]
|
|
879
|
+
socket.close if new_socket && !socket.closed?
|
|
880
|
+
if pipeline && socket.closed?
|
|
881
|
+
@use_counts.delete(socket)
|
|
882
|
+
@pipeline_socket = nil if @pipeline_socket == socket
|
|
883
|
+
end
|
|
884
|
+
return
|
|
885
|
+
end
|
|
886
|
+
previous_settings = query_settings
|
|
887
|
+
elsif !st.query_settings_for_id(client_query_id).equal?(previous_settings)
|
|
888
|
+
socket.close
|
|
889
|
+
return
|
|
541
890
|
end
|
|
542
|
-
|
|
891
|
+
runnextportloop = false
|
|
543
892
|
end
|
|
893
|
+
send_packet = -> do
|
|
894
|
+
begin
|
|
895
|
+
if use_tcp
|
|
896
|
+
completed = write_tcp_frame(socket, query_bytes, endtime, query_settings.send_token)
|
|
897
|
+
# Cancellation of a partially written frame retires its whole stream;
|
|
898
|
+
# otherwise another frame could be appended to a corrupt prefix.
|
|
899
|
+
unless completed == true
|
|
900
|
+
if pipeline && completed > 0
|
|
901
|
+
st.fail_tcp_send(query_settings, SocketEofResolvError.new("TCP send cancelled"))
|
|
902
|
+
@use_counts.delete(socket)
|
|
903
|
+
@pipeline_socket = nil if @pipeline_socket == socket
|
|
904
|
+
elsif !pipeline
|
|
905
|
+
socket.close rescue nil
|
|
906
|
+
end
|
|
907
|
+
return
|
|
908
|
+
end
|
|
909
|
+
else
|
|
910
|
+
unless write_udp_packet(socket, query_bytes, endtime, query_settings.send_token, !st.selector_thread?)
|
|
911
|
+
socket.close
|
|
912
|
+
return
|
|
913
|
+
end
|
|
914
|
+
socket.close unless st.register_query_if_current(query_settings, previous_settings)
|
|
915
|
+
end
|
|
916
|
+
rescue IO::WaitWritable
|
|
917
|
+
# Only a congested UDP send reaches this branch on the selector. Keep
|
|
918
|
+
# ordinary UDP traffic independent of the pool's blocked TCP writers.
|
|
919
|
+
return st.defer_send { send_packet.call }
|
|
920
|
+
rescue Exception => e
|
|
921
|
+
err_msg = "Send failed to #{server}:#{port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp}, exception : #{e}"
|
|
922
|
+
err=IOError.new(err_msg)
|
|
923
|
+
Dnsruby.log.error { "#{err}" }
|
|
924
|
+
Dnsruby.log.error(e.backtrace)
|
|
925
|
+
if pipeline
|
|
926
|
+
error = e.kind_of?(ResolvTimeout) ? e : SocketEofResolvError.new(err_msg)
|
|
927
|
+
st.fail_tcp_send(query_settings, error)
|
|
928
|
+
@use_counts.delete(socket)
|
|
929
|
+
@pipeline_socket = nil if @pipeline_socket == socket
|
|
930
|
+
elsif e.kind_of?(ResolvTimeout)
|
|
931
|
+
report_error.call(e)
|
|
932
|
+
else
|
|
933
|
+
report_error.call(err)
|
|
934
|
+
end
|
|
935
|
+
begin
|
|
936
|
+
#we let the select_thread close the socket when doing tcp
|
|
937
|
+
#pipelining
|
|
938
|
+
socket.close unless pipeline && !new_socket
|
|
939
|
+
rescue Exception
|
|
940
|
+
end
|
|
941
|
+
return
|
|
942
|
+
end
|
|
544
943
|
|
|
545
|
-
|
|
546
|
-
|
|
944
|
+
Dnsruby.log.debug { "Packet sent to #{server}:#{port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp} : #{query.question()[0].qname}, #{query.question()[0].qtype}" }
|
|
945
|
+
end
|
|
946
|
+
send_packet.call
|
|
947
|
+
rescue StandardError => error
|
|
948
|
+
# Errors outside the expected connect/write failures must not disappear in
|
|
949
|
+
# a worker's result queue. Do not consume a replacement protocol attempt.
|
|
950
|
+
st.fail_query_if_current(client_query_id, client_queue, previous_settings, error)
|
|
951
|
+
# A failure between connect and registration has no selector-owned socket
|
|
952
|
+
# yet. Release that new descriptor here; reused streams retain their peers.
|
|
953
|
+
socket.close if socket && new_socket && !socket.closed?
|
|
547
954
|
end
|
|
548
955
|
|
|
549
956
|
# The source port to send queries from
|
|
@@ -552,10 +959,8 @@ module Dnsruby
|
|
|
552
959
|
#
|
|
553
960
|
# Defaults to 0 - random port
|
|
554
961
|
def src_port
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
end
|
|
558
|
-
return @src_port
|
|
962
|
+
ports = @src_port
|
|
963
|
+
ports.length == 1 ? ports[0] : ports
|
|
559
964
|
end
|
|
560
965
|
|
|
561
966
|
# Can be a single Integer or a Range or an Array
|
|
@@ -567,8 +972,9 @@ module Dnsruby
|
|
|
567
972
|
# res.src_port=60015..60115
|
|
568
973
|
#
|
|
569
974
|
def src_port=(p)
|
|
570
|
-
|
|
571
|
-
|
|
975
|
+
ports = Resolver.get_ports_from(p)
|
|
976
|
+
raise ArgumentError, 'Source port pool must not be empty' if ports.empty?
|
|
977
|
+
@src_port = ports if Resolver.check_port(ports)
|
|
572
978
|
end
|
|
573
979
|
|
|
574
980
|
# Can be a single Integer or a Range or an Array
|
|
@@ -584,57 +990,81 @@ module Dnsruby
|
|
|
584
990
|
# res.add_src_port(60015..60115)
|
|
585
991
|
#
|
|
586
992
|
def add_src_port(p)
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
@src_port.push(x)
|
|
594
|
-
end
|
|
993
|
+
additions = Resolver.get_ports_from(p)
|
|
994
|
+
# Adding a concrete port starts a pool instead of retaining the default
|
|
995
|
+
# random-port sentinel, which would otherwise hide every added port.
|
|
996
|
+
ports = @src_port == [0] && !additions.empty? && !additions.include?(0) ? [] : @src_port
|
|
997
|
+
if Resolver.check_port(additions, ports)
|
|
998
|
+
@src_port = ports + additions
|
|
595
999
|
end
|
|
596
1000
|
end
|
|
597
1001
|
|
|
598
1002
|
|
|
599
1003
|
def get_next_src_port
|
|
600
|
-
#
|
|
601
|
-
#
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
# # better to construct an array of all the ports we *can* use, and then just pick one at random!
|
|
608
|
-
# candidate = Iana::UNRESERVED_PORTS[rand(Iana::UNRESERVED_PORTS.length())]
|
|
609
|
-
# # while (!(Resolver.port_in_range(candidate)))
|
|
610
|
-
# # candidate = (rand(65535-1024) + 1024)
|
|
611
|
-
# # end
|
|
612
|
-
# @TODO@ Should probably construct a bitmap of the IANA ports...
|
|
613
|
-
candidate = 50000 + (rand(15535)) # pick one over 50000
|
|
614
|
-
return candidate
|
|
615
|
-
end
|
|
616
|
-
pos = rand(@src_port.length)
|
|
617
|
-
return @src_port[pos]
|
|
1004
|
+
# Preserve the existing high-port range and configured pools, using
|
|
1005
|
+
# unpredictable choices for the source-port contribution to DNS IDs.
|
|
1006
|
+
ports = @src_port.dup
|
|
1007
|
+
if (ports[0] == 0)
|
|
1008
|
+
return 50_000 + SecureRandom.random_number(15_536)
|
|
1009
|
+
end
|
|
1010
|
+
ports[SecureRandom.random_number(ports.length)]
|
|
618
1011
|
end
|
|
619
1012
|
|
|
1013
|
+
# Retain the request actually sent, including EDNS options, but not its
|
|
1014
|
+
# transaction ID. Never share answers across upstream or source routes.
|
|
1015
|
+
def positive_cache_scope(query, query_bytes = query.encode, configuration = nil) #:nodoc:
|
|
1016
|
+
route = if configuration
|
|
1017
|
+
configuration.values_at(:server, :port, :src_address, :src_address6, :src_port)
|
|
1018
|
+
else
|
|
1019
|
+
[@server.to_s.dup.freeze, @port, @src_address.to_s.dup.freeze,
|
|
1020
|
+
@src_address6.to_s.dup.freeze, @src_port.dup.freeze]
|
|
1021
|
+
end
|
|
1022
|
+
(route + [query.send_raw, query_bytes.byteslice(2..-1).b.freeze]).freeze
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
# Keep the endpoint, binding and response policy together while connect or
|
|
1026
|
+
# pipeline admission releases the caller to change resolver settings.
|
|
1027
|
+
def transport_configuration
|
|
1028
|
+
server = @server.to_s.dup.freeze
|
|
1029
|
+
{server: server, port: @port, src_address: @src_address.to_s.dup.freeze,
|
|
1030
|
+
src_address6: @src_address6.to_s.dup.freeze, src_port: @src_port.dup.freeze,
|
|
1031
|
+
ipv6: server.include?(':'), packet_timeout: @packet_timeout, udp_size: udp_packet_size,
|
|
1032
|
+
tcp_pipelining: @tcp_pipelining, tcp_pipelining_max_queries: @tcp_pipelining_max_queries,
|
|
1033
|
+
tcp_idle_timeout: tcp_idle_timeout, ignore_truncation: @ignore_truncation,
|
|
1034
|
+
no_tcp: @no_tcp, dnssec: @dnssec}.freeze
|
|
1035
|
+
end
|
|
1036
|
+
private :transport_configuration
|
|
1037
|
+
|
|
620
1038
|
def check_response(response, response_bytes, query, client_queue, client_query_id, tcp)
|
|
1039
|
+
settings = response_query_settings(client_query_id)
|
|
1040
|
+
configuration = settings&.transport_configuration
|
|
1041
|
+
dnssec = configuration ? configuration[:dnssec] : @dnssec
|
|
1042
|
+
ignore_truncation = settings ? settings.ignore_truncation : @ignore_truncation
|
|
1043
|
+
no_tcp = settings ? settings.no_tcp : @no_tcp
|
|
621
1044
|
# @TODO@ Should send_raw avoid this?
|
|
622
1045
|
if (!query.send_raw)
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
1046
|
+
# Reject unrelated packet headers before TSIG verification mutates the
|
|
1047
|
+
# response or an error/truncation triggers another transport attempt.
|
|
1048
|
+
unless response.header.qr && response.header.opcode == query.header.opcode &&
|
|
1049
|
+
response.header.id == query.header.id
|
|
1050
|
+
return false
|
|
627
1051
|
end
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
1052
|
+
checking_signature = query.tsig && response.tsig
|
|
1053
|
+
sig_value = check_tsig(query, response, response_bytes)
|
|
1054
|
+
authenticated_tsig_error = checking_signature && sig_value.kind_of?(TsigError) && response.verified? &&
|
|
1055
|
+
response.header.get_header_rcode == RCode.NOTAUTH && [18, 22].include?(response.tsigerror)
|
|
1056
|
+
if (sig_value != :okay) && !authenticated_tsig_error
|
|
1057
|
+
# Keep waiting for a signed response without extending the deadline
|
|
1058
|
+
# (RFC 8945 section 5.4). Save the error in case the query times out.
|
|
1059
|
+
settings = response_query_settings(client_query_id)
|
|
1060
|
+
settings.authentication_error = sig_value if settings
|
|
1061
|
+
return false
|
|
632
1062
|
end
|
|
633
|
-
#
|
|
634
|
-
#
|
|
1063
|
+
# Match the question when one is present. TCP error replies may omit
|
|
1064
|
+
# it (RFC 7766 section 7); UDP replies must match (RFC 5452 section 9.1).
|
|
635
1065
|
if (query.class == Update)
|
|
636
|
-
#
|
|
637
|
-
|
|
1066
|
+
# TODO: Validate the zone section for UPDATE replies.
|
|
1067
|
+
elsif !(tcp && response.question.empty? && response.header.get_header_rcode != RCode.NOERROR)
|
|
638
1068
|
if ((response.question.size == 0) ||
|
|
639
1069
|
(response.question[0].qname.labels != query.question[0].qname.labels) ||
|
|
640
1070
|
(response.question[0].qtype != query.question[0].qtype) ||
|
|
@@ -645,19 +1075,29 @@ module Dnsruby
|
|
|
645
1075
|
return false
|
|
646
1076
|
end
|
|
647
1077
|
end
|
|
648
|
-
|
|
649
|
-
#
|
|
650
|
-
#
|
|
1078
|
+
return sig_value if authenticated_tsig_error
|
|
1079
|
+
# Retry a FORMERR without optional EDNS (RFC 6891 section 6.2.2).
|
|
1080
|
+
# Queries requiring DNSSEC validation must keep EDNS and DO.
|
|
651
1081
|
if ((response.header.get_header_rcode == RCode.FORMERR) &&
|
|
652
|
-
|
|
653
|
-
#
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
1082
|
+
query.get_opt && !(dnssec && query.do_validation))
|
|
1083
|
+
# EDNS negotiation belongs to this attempt, not to the caller's raw
|
|
1084
|
+
# mode or Message (which may also be in use by another nameserver).
|
|
1085
|
+
retry_query = query.dup
|
|
1086
|
+
retry_query.header = query.header.dup
|
|
1087
|
+
retry_query.remove_additional
|
|
1088
|
+
query.additional.each do |rr|
|
|
1089
|
+
retry_query.add_additional(rr) unless rr.type == Types.OPT || rr.type == Types.TSIG
|
|
1090
|
+
end
|
|
1091
|
+
if query.tsig
|
|
1092
|
+
return TsigError.new unless query.tsig.key
|
|
1093
|
+
retry_query.tsigstate = :Unsigned
|
|
1094
|
+
retry_query.set_tsig(query.tsig.dup)
|
|
1095
|
+
end
|
|
1096
|
+
send_async(retry_query, client_queue, client_query_id, tcp, true)
|
|
657
1097
|
return false
|
|
658
1098
|
end
|
|
659
|
-
if (response.header.tc && !tcp &&
|
|
660
|
-
if (
|
|
1099
|
+
if (response.header.tc && !tcp && !ignore_truncation)
|
|
1100
|
+
if (no_tcp)
|
|
661
1101
|
Dnsruby.log.debug { "Truncated response - not resending over TCP as no_tcp==true" }
|
|
662
1102
|
else
|
|
663
1103
|
# Try to resend over tcp
|
|
@@ -669,21 +1109,32 @@ module Dnsruby
|
|
|
669
1109
|
end
|
|
670
1110
|
end
|
|
671
1111
|
end
|
|
1112
|
+
settings = response_query_settings(client_query_id)
|
|
1113
|
+
if settings && settings.cache_scope && settings.cache_scope == positive_cache_scope(query, settings.query_bytes)
|
|
1114
|
+
response.instance_variable_set(:@dnsruby_cache_scope, settings.cache_scope)
|
|
1115
|
+
else
|
|
1116
|
+
# Configuration changed during this exchange, or this response has no
|
|
1117
|
+
# tracked transport. Do not seed a cache with an ambiguous route.
|
|
1118
|
+
response.instance_variable_set(:@dnsruby_cache_scope, false)
|
|
1119
|
+
end
|
|
672
1120
|
return true
|
|
673
1121
|
end
|
|
674
1122
|
|
|
675
1123
|
def check_tsig(query, response, response_bytes)
|
|
676
1124
|
if (query.tsig)
|
|
677
|
-
if (response.tsig)
|
|
1125
|
+
if (response_signature = response.tsig)
|
|
678
1126
|
if !query.tsig.verify(query, response, response_bytes)
|
|
1127
|
+
authenticated_error = RR::TSIG.authenticated_error(response, response_signature)
|
|
1128
|
+
return authenticated_error if authenticated_error
|
|
679
1129
|
# Discard packet and wait for correctly signed response
|
|
680
1130
|
Dnsruby.log.error { "TSIG authentication failed!" }
|
|
681
1131
|
return TsigError.new
|
|
682
1132
|
end
|
|
683
1133
|
else
|
|
684
|
-
#
|
|
685
|
-
#
|
|
686
|
-
|
|
1134
|
+
# A missing signature is a format error (RFC 8945 section 5.4).
|
|
1135
|
+
# Keep a distinct exception so callers can diagnose unsigned peers.
|
|
1136
|
+
response.tsigstate = :Failed
|
|
1137
|
+
response.tsigerror = RCode.FORMERR
|
|
687
1138
|
Dnsruby.log.error { "Expecting TSIG signed response, but got unsigned response - discarding" }
|
|
688
1139
|
return TsigNotSignedResponseError.new
|
|
689
1140
|
end
|
|
@@ -706,52 +1157,52 @@ module Dnsruby
|
|
|
706
1157
|
end
|
|
707
1158
|
|
|
708
1159
|
# Prepare the packet for sending
|
|
709
|
-
def make_query_packet(packet, use_tcp = @use_tcp) #:nodoc: all
|
|
710
|
-
|
|
1160
|
+
def make_query_packet(packet, use_tcp = @use_tcp, disable_edns = false) #:nodoc: all
|
|
1161
|
+
packet.tsig.warn_if_legacy_algorithm if packet.tsig
|
|
1162
|
+
# TSIG signs the header and every section, so leave signed messages intact.
|
|
1163
|
+
# Decoded messages may have a TSIG without a signing state.
|
|
1164
|
+
prepared = packet.signed? || packet.tsig
|
|
1165
|
+
if !packet.send_raw && !prepared
|
|
711
1166
|
if (packet.header.opcode == OpCode.QUERY || @recurse)
|
|
712
1167
|
packet.header.rd=@recurse
|
|
713
1168
|
end
|
|
714
1169
|
|
|
715
|
-
|
|
716
|
-
if (@dnssec)
|
|
1170
|
+
if @dnssec && !disable_edns
|
|
717
1171
|
prepare_for_dnssec(packet)
|
|
718
|
-
elsif (
|
|
719
|
-
# if ((udp_packet_size > Resolver::DefaultUDPSize) && !use_tcp)
|
|
720
|
-
# @TODO@ What if an existing OPT RR is not big enough? Should we replace it?
|
|
1172
|
+
elsif !disable_edns && (udp_packet_size > 512) && !use_tcp
|
|
721
1173
|
add_opt_rr(packet)
|
|
722
1174
|
end
|
|
723
1175
|
end
|
|
724
1176
|
|
|
725
|
-
|
|
726
|
-
|
|
1177
|
+
# Prepare the whole message before encoding, which may sign it.
|
|
1178
|
+
# Use the resolver's key if the Message has none.
|
|
1179
|
+
encoded = packet.encode
|
|
1180
|
+
if @tsig && !packet.signed? && !packet.tsig
|
|
1181
|
+
packet.set_tsig(@tsig)
|
|
1182
|
+
encoded = packet.encode
|
|
727
1183
|
end
|
|
728
|
-
|
|
1184
|
+
encoded
|
|
729
1185
|
end
|
|
730
1186
|
|
|
731
1187
|
def add_opt_rr(packet)
|
|
732
1188
|
Dnsruby.log.debug { ";; Adding EDNS extension with UDP packetsize #{udp_packet_size}.\n" }
|
|
733
|
-
#
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
if (packet.additional.rrset(packet.question()[0].qname, Types::OPT).rrs.length == 0)
|
|
738
|
-
packet.add_additional(optrr)
|
|
739
|
-
end
|
|
1189
|
+
# Find OPT by type: its class field stores the payload size. Reuse it and
|
|
1190
|
+
# its options, since only one OPT is allowed (RFC 6891 section 6.1.1).
|
|
1191
|
+
packet.add_additional(RR::OPT.new(udp_packet_size)) unless packet.get_opt
|
|
1192
|
+
packet.get_opt
|
|
740
1193
|
end
|
|
741
1194
|
|
|
742
1195
|
def prepare_for_dnssec(packet)
|
|
743
|
-
#
|
|
1196
|
+
# Request DNSSEC records with DO (RFC 4035 section 4.1).
|
|
744
1197
|
Dnsruby.log.debug { ";; Adding EDNS extension with UDP packetsize #{udp_packet_size} and DNS OK bit set\n" }
|
|
745
|
-
optrr =
|
|
1198
|
+
optrr = add_opt_rr(packet)
|
|
746
1199
|
optrr.dnssec_ok=true
|
|
747
1200
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
packet.header.ad = false # RFC 4035 section 4.6
|
|
1201
|
+
# We use DO to request DNSSEC records and leave AD clear
|
|
1202
|
+
# (RFC 6840 section 5.7).
|
|
1203
|
+
packet.header.ad = false
|
|
753
1204
|
|
|
754
|
-
#
|
|
1205
|
+
# Local validation needs the original data, even if the server rejects it.
|
|
755
1206
|
if (packet.do_validation)
|
|
756
1207
|
packet.header.cd = true
|
|
757
1208
|
end
|
|
@@ -763,10 +1214,16 @@ module Dnsruby
|
|
|
763
1214
|
|
|
764
1215
|
# Return the packet size to use for UDP
|
|
765
1216
|
def udp_packet_size
|
|
766
|
-
#
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
1217
|
+
# Values below 512 mean 512 in EDNS (RFC 6891 section 6.2.3).
|
|
1218
|
+
[@udp_size, 512].max
|
|
1219
|
+
end
|
|
1220
|
+
|
|
1221
|
+
# Match the receive buffer to this query's OPT so a valid reply isn't
|
|
1222
|
+
# cut short when the caller advertises more than the resolver default.
|
|
1223
|
+
def udp_receive_size(query, configuration = nil)
|
|
1224
|
+
opt = query.get_opt
|
|
1225
|
+
opt ? [opt.payloadsize, 512].max : (configuration ? configuration[:udp_size] : udp_packet_size)
|
|
770
1226
|
end
|
|
1227
|
+
private :udp_receive_size
|
|
771
1228
|
end
|
|
772
1229
|
end
|