dnsruby 1.74.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +16 -4
- data/DNSSEC +3 -2
- data/EXAMPLES +1 -1
- data/README.md +370 -64
- data/RELEASE_NOTES.md +302 -1
- data/Rakefile +0 -3
- data/demo/axfr.rb +24 -6
- data/demo/rubydig.rb +0 -2
- data/dnsruby.gemspec +5 -4
- data/lib/dnsruby/cache.rb +279 -47
- data/lib/dnsruby/code_mapper.rb +35 -8
- data/lib/dnsruby/code_mappers.rb +63 -49
- data/lib/dnsruby/config.rb +75 -4
- data/lib/dnsruby/deadline_queue.rb +75 -0
- data/lib/dnsruby/dnssec.rb +148 -45
- data/lib/dnsruby/ipv6.rb +45 -5
- data/lib/dnsruby/key_cache.rb +202 -35
- data/lib/dnsruby/message/decoder.rb +85 -19
- data/lib/dnsruby/message/encoder.rb +27 -16
- data/lib/dnsruby/message/header.rb +8 -12
- data/lib/dnsruby/message/message.rb +53 -22
- data/lib/dnsruby/message/section.rb +12 -34
- data/lib/dnsruby/name.rb +5 -2
- data/lib/dnsruby/packet_sender.rb +645 -203
- data/lib/dnsruby/presentation.rb +147 -0
- data/lib/dnsruby/query_control.rb +132 -0
- data/lib/dnsruby/recursor.rb +795 -339
- data/lib/dnsruby/resolver.rb +468 -186
- data/lib/dnsruby/resource/CAA.rb +67 -16
- data/lib/dnsruby/resource/DLV.rb +6 -0
- data/lib/dnsruby/resource/DNSKEY.rb +108 -16
- data/lib/dnsruby/resource/DS.rb +27 -28
- data/lib/dnsruby/resource/HTTPS.rb +16 -0
- data/lib/dnsruby/resource/IN.rb +19 -0
- data/lib/dnsruby/resource/MX.rb +2 -2
- data/lib/dnsruby/resource/NSEC.rb +6 -10
- data/lib/dnsruby/resource/NSEC3.rb +55 -19
- data/lib/dnsruby/resource/NSEC3PARAM.rb +4 -6
- data/lib/dnsruby/resource/RR.rb +108 -65
- data/lib/dnsruby/resource/RRSIG.rb +5 -5
- data/lib/dnsruby/resource/RRSet.rb +38 -22
- data/lib/dnsruby/resource/SRV.rb +2 -2
- data/lib/dnsruby/resource/SVCB.rb +775 -0
- data/lib/dnsruby/resource/TSIG.rb +266 -231
- data/lib/dnsruby/resource/TXT.rb +34 -42
- data/lib/dnsruby/resource/domain_name.rb +1 -1
- data/lib/dnsruby/resource/generic.rb +31 -0
- data/lib/dnsruby/resource/generic_cache.rb +384 -0
- data/lib/dnsruby/resource/generic_constants.rb +79 -0
- data/lib/dnsruby/select_thread.rb +979 -291
- data/lib/dnsruby/serial_time.rb +25 -0
- data/lib/dnsruby/single_resolver.rb +25 -6
- data/lib/dnsruby/single_verifier.rb +1620 -530
- data/lib/dnsruby/transport_clock.rb +9 -0
- data/lib/dnsruby/update.rb +8 -6
- data/lib/dnsruby/validation_budget.rb +143 -0
- data/lib/dnsruby/validator_thread.rb +139 -58
- data/lib/dnsruby/version.rb +1 -1
- data/lib/dnsruby/zone_reader.rb +196 -129
- data/lib/dnsruby/zone_transfer.rb +157 -25
- data/lib/dnsruby.rb +7 -2
- data/test/ci_watchdog.rb +33 -0
- data/test/fixtures/org-ds-2026-11-05.zone +5 -0
- data/test/fixtures/root-dnskey-2026-11-05.zone +7 -0
- data/test/fork_test_helper.rb +37 -0
- data/test/local_dns_server.rb +57 -0
- data/test/soak_server.rb +152 -0
- data/test/socket_helpers.rb +26 -0
- data/test/spec_helper.rb +8 -13
- data/test/tc_attempt_configuration.rb +137 -0
- data/test/tc_caa.rb +2 -2
- data/test/tc_cache_keys.rb +36 -0
- data/test/tc_cancelled_send_capacity.rb +158 -0
- data/test/tc_dnssec_alias_validation.rb +289 -0
- data/test/tc_dnssec_budget.rb +489 -0
- data/test/tc_dnssec_cache_bounds.rb +197 -0
- data/test/tc_dnssec_cache_lifetime.rb +615 -0
- data/test/tc_dnssec_denial_proofs.rb +285 -0
- data/test/tc_dnssec_recursion_limits.rb +186 -0
- data/test/tc_dnssec_recursor_review_fixes.rb +351 -0
- data/test/tc_dnssec_trust_anchors.rb +286 -0
- data/test/tc_dnssec_trust_races.rb +147 -0
- data/test/tc_dnssec_wildcard_proofs.rb +249 -0
- data/test/tc_ds.rb +5 -5
- data/test/tc_edns_retry_regressions.rb +255 -0
- data/test/tc_escapedchars.rb +8 -0
- data/test/tc_final_review_fixes.rb +167 -0
- data/test/tc_final_transport_contracts.rb +104 -0
- data/test/tc_generic_cache.rb +852 -0
- data/test/tc_header.rb +26 -0
- data/test/tc_ipv6.rb +120 -0
- data/test/tc_key_cache_identity.rb +382 -0
- data/test/tc_malformed_tcp.rb +712 -0
- data/test/tc_name.rb +99 -5
- data/test/tc_nameserver_bootstrap.rb +322 -0
- data/test/tc_nsec.rb +4 -45
- data/test/tc_nsec3.rb +17 -1
- data/test/tc_pipeline_retirement.rb +358 -0
- data/test/tc_pipeline_retry.rb +462 -0
- data/test/tc_pipeline_send.rb +663 -0
- data/test/tc_protocol_review_fixes.rb +95 -0
- data/test/tc_query_control.rb +90 -0
- data/test/tc_record_codec_contracts.rb +107 -0
- data/test/tc_record_regressions.rb +225 -0
- data/test/tc_recur.rb +1 -0
- data/test/tc_recursor_address_fallback.rb +169 -0
- data/test/tc_recursor_limits.rb +139 -0
- data/test/tc_release_auxiliary_fixes.rb +288 -0
- data/test/tc_release_configuration_fixes.rb +612 -0
- data/test/tc_release_dnssec_completion.rb +405 -0
- data/test/tc_release_dnssec_fixes.rb +305 -0
- data/test/tc_release_negative_cache.rb +327 -0
- data/test/tc_release_recursor_cache.rb +212 -0
- data/test/tc_release_review_transport.rb +372 -0
- data/test/tc_release_review_transport_native.rb +56 -0
- data/test/tc_release_transport.rb +906 -0
- data/test/tc_release_transport_fixes.rb +196 -0
- data/test/tc_release_zone_defaults.rb +157 -0
- data/test/tc_release_zone_ttls.rb +39 -0
- data/test/tc_res_config.rb +42 -20
- data/test/tc_res_opt.rb +21 -1
- data/test/tc_resolver.rb +21 -19
- data/test/tc_resolver_concurrency.rb +383 -0
- data/test/tc_resolver_configuration_contracts.rb +31 -0
- data/test/tc_resolver_validation.rb +211 -0
- data/test/tc_review_cache.rb +163 -0
- data/test/tc_review_code_mapper.rb +279 -0
- data/test/tc_review_control_deadlines.rb +169 -0
- data/test/tc_review_followup_transport.rb +583 -0
- data/test/tc_review_packet_contracts.rb +369 -0
- data/test/tc_review_presentation.rb +213 -0
- data/test/tc_review_record_compatibility.rb +101 -0
- data/test/tc_review_transport_fixes.rb +195 -0
- data/test/tc_review_transport_native.rb +415 -0
- data/test/tc_review_validation_priming.rb +178 -0
- data/test/tc_rr-opt.rb +5 -2
- data/test/tc_rr-unknown.rb +19 -0
- data/test/tc_rr.rb +14 -0
- data/test/tc_rrset_grouping.rb +50 -0
- data/test/tc_rrset_review_fixes.rb +58 -0
- data/test/tc_rrsig.rb +21 -1
- data/test/tc_scoped_ipv6.rb +153 -0
- data/test/tc_scoped_ipv6_select.rb +157 -0
- data/test/tc_scoped_ipv6_transport.rb +329 -0
- data/test/tc_selector_wakeup.rb +42 -0
- data/test/tc_soak.rb +118 -196
- data/test/tc_socket_helpers.rb +90 -0
- data/test/tc_supporting_lookup_limits.rb +252 -0
- data/test/tc_svcb.rb +1043 -0
- data/test/tc_svcb_signed_target.rb +145 -0
- data/test/tc_svcb_transport.rb +120 -0
- data/test/tc_tcp.rb +69 -43
- data/test/tc_tcp_pipelining.rb +95 -204
- data/test/tc_transport_fork.rb +179 -0
- data/test/tc_tsig.rb +2 -2
- data/test/tc_tsig_api.rb +251 -0
- data/test/tc_tsig_rfc8945.rb +357 -0
- data/test/tc_v2_release_dnssec.rb +442 -0
- data/test/tc_v2_release_transport.rb +304 -0
- data/test/tc_v2_zone_transfer_native.rb +58 -0
- data/test/tc_zone_reader.rb +217 -1
- data/test/tcp_pipelining_server.rb +150 -0
- data/test/ts_offline.rb +77 -6
- data/test/ts_online.rb +2 -1
- metadata +105 -10
- data/.coveralls.yml +0 -2
|
@@ -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,7 +218,9 @@ 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
|
|
|
207
225
|
ignore_config_resolv_errors = false;
|
|
208
226
|
if arg.nil?
|
|
@@ -223,6 +241,7 @@ module Dnsruby
|
|
|
223
241
|
end
|
|
224
242
|
end
|
|
225
243
|
rescue Exception => e
|
|
244
|
+
raise if attr.to_s == 'tsig' || ValidationBudget.abort?(e)
|
|
226
245
|
Dnsruby.log.error { "PacketSender : Argument #{attr}, #{arg[attr]} not valid : #{e}\n" }
|
|
227
246
|
end
|
|
228
247
|
end
|
|
@@ -251,7 +270,7 @@ module Dnsruby
|
|
|
251
270
|
@ipv6=false
|
|
252
271
|
rescue Exception
|
|
253
272
|
begin
|
|
254
|
-
|
|
273
|
+
Config.ipv6_server_address(@server)
|
|
255
274
|
# @src_address6 = '::'
|
|
256
275
|
@ipv6=true
|
|
257
276
|
rescue Exception
|
|
@@ -260,9 +279,61 @@ module Dnsruby
|
|
|
260
279
|
end
|
|
261
280
|
end
|
|
262
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
|
+
|
|
263
306
|
def close
|
|
264
|
-
|
|
265
|
-
#
|
|
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
|
|
266
337
|
end
|
|
267
338
|
|
|
268
339
|
# Asynchronously send a Message to the server. The send can be done using just
|
|
@@ -306,6 +377,7 @@ module Dnsruby
|
|
|
306
377
|
# validation for this particular packet (assuming SingleResolver#dnssec == true)
|
|
307
378
|
# Note that these options should not normally be used!
|
|
308
379
|
def send_async(*args) # msg, client_queue, client_query_id, use_tcp=@use_tcp)
|
|
380
|
+
ensure_current_process
|
|
309
381
|
# @TODO@ Need to select a good Header ID here - see forgery-resilience RFC draft for details
|
|
310
382
|
msg = args[0]
|
|
311
383
|
client_query_id = nil
|
|
@@ -333,31 +405,36 @@ module Dnsruby
|
|
|
333
405
|
end
|
|
334
406
|
end
|
|
335
407
|
end
|
|
336
|
-
#
|
|
337
|
-
# #Are we using EventMachine or native Dnsruby?
|
|
338
|
-
# if (Resolver.eventmachine?)
|
|
339
|
-
# return send_eventmachine(query_packet, msg, client_query_id, client_queue, use_tcp)
|
|
340
|
-
# else
|
|
408
|
+
# Keep the request MAC for response verification (RFC 8945 section 4.3.1).
|
|
341
409
|
if (!client_query_id)
|
|
342
410
|
client_query_id = Time.now + rand(10000) # is this safe?!
|
|
343
411
|
end
|
|
412
|
+
response_settings = response_query_settings(client_query_id)
|
|
413
|
+
disable_edns = args[4] || (response_settings && response_settings.disable_edns)
|
|
344
414
|
|
|
345
415
|
begin
|
|
346
|
-
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)
|
|
347
418
|
rescue EncodeError => err
|
|
348
419
|
Dnsruby.log.error { "#{err}" }
|
|
349
420
|
st = SelectThread.instance
|
|
350
|
-
|
|
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
|
|
351
426
|
return
|
|
352
427
|
end
|
|
353
428
|
|
|
354
|
-
if (msg.do_caching && (msg.class != Update))
|
|
429
|
+
if (msg.do_caching && !msg.send_raw && (msg.class != Update) && !@tsig && !msg.tsig && !msg.signed?)
|
|
355
430
|
# Check the cache!!
|
|
356
431
|
cachedanswer = nil
|
|
357
432
|
if (msg.header.rd)
|
|
358
|
-
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))
|
|
359
435
|
else
|
|
360
|
-
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))
|
|
361
438
|
end
|
|
362
439
|
if (cachedanswer)
|
|
363
440
|
TheLog.debug("Sending cached answer to client\n")
|
|
@@ -366,7 +443,11 @@ module Dnsruby
|
|
|
366
443
|
# If we can find the answer, send it to the client straight away
|
|
367
444
|
# Post the result to the client using SelectThread
|
|
368
445
|
st = SelectThread.instance
|
|
369
|
-
|
|
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
|
|
370
451
|
return client_query_id
|
|
371
452
|
end
|
|
372
453
|
end
|
|
@@ -376,13 +457,18 @@ module Dnsruby
|
|
|
376
457
|
# Can't send the message - abort!
|
|
377
458
|
err=IOError.new("Can't send message - too big for UDP and no_tcp=true")
|
|
378
459
|
Dnsruby.log.error { "#{err}" }
|
|
379
|
-
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
|
|
380
466
|
return
|
|
381
467
|
end
|
|
382
468
|
Dnsruby.log.debug { "Query packet length exceeds max UDP packet size - using TCP" }
|
|
383
469
|
use_tcp = true
|
|
384
470
|
end
|
|
385
|
-
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)
|
|
386
472
|
return client_query_id
|
|
387
473
|
# end
|
|
388
474
|
end
|
|
@@ -396,15 +482,26 @@ module Dnsruby
|
|
|
396
482
|
# In general, every subsequent call the function will either return the current tcp
|
|
397
483
|
# pipeline socket or a new connected socket if the current one was closed by the server
|
|
398
484
|
def tcp_pipeline_socket(src_port)
|
|
485
|
+
ensure_current_process
|
|
399
486
|
Dnsruby.log.debug("Using tcp_pipeline_socket")
|
|
400
|
-
|
|
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])
|
|
401
493
|
|
|
402
494
|
reuse_pipeline_socket = -> do
|
|
403
495
|
begin
|
|
404
|
-
max = @
|
|
496
|
+
max = @pipeline_socket_max_queries
|
|
405
497
|
use_count = @use_counts[@pipeline_socket]
|
|
406
|
-
if
|
|
407
|
-
|
|
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)
|
|
408
505
|
@use_counts.delete(@pipeline_socket)
|
|
409
506
|
@pipeline_socket = nil
|
|
410
507
|
Dnsruby.log.debug("Max queries per connection attained - creating new socket")
|
|
@@ -414,6 +511,7 @@ module Dnsruby
|
|
|
414
511
|
rescue Errno::EISCONN
|
|
415
512
|
#already connected, do nothing and reuse!
|
|
416
513
|
rescue IOError, Errno::ECONNRESET #close by remote host, reconnect
|
|
514
|
+
@use_counts.delete(@pipeline_socket)
|
|
417
515
|
@pipeline_socket = nil
|
|
418
516
|
Dnsruby.log.debug("Connection closed - recreating socket")
|
|
419
517
|
end
|
|
@@ -421,14 +519,20 @@ module Dnsruby
|
|
|
421
519
|
|
|
422
520
|
create_pipeline_socket = -> do
|
|
423
521
|
@tcp_pipeline_local_port = src_port
|
|
424
|
-
src_address =
|
|
522
|
+
src_address = configuration[2]
|
|
425
523
|
begin
|
|
426
|
-
@
|
|
427
|
-
@
|
|
428
|
-
@
|
|
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)
|
|
429
530
|
Dnsruby.log.debug("Creating socket #{src_address}:#{src_port}")
|
|
430
531
|
@use_counts[@pipeline_socket] = 0
|
|
431
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
|
|
432
536
|
@pipeline_socket = nil
|
|
433
537
|
raise e
|
|
434
538
|
end
|
|
@@ -446,35 +550,254 @@ module Dnsruby
|
|
|
446
550
|
end
|
|
447
551
|
|
|
448
552
|
# This method sends the packet using the built-in pure Ruby event loop, with no dependencies.
|
|
449
|
-
def send_dnsruby(query_bytes, query, client_query_id, client_queue, use_tcp) #:nodoc: all
|
|
450
|
-
|
|
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)
|
|
451
735
|
# First send the query (synchronously)
|
|
452
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
|
|
453
748
|
socket = nil
|
|
454
749
|
runnextportloop = true
|
|
455
750
|
numtries = 0
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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]
|
|
460
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?
|
|
461
761
|
begin
|
|
762
|
+
if TransportClock.now >= endtime
|
|
763
|
+
report_error.call(ResolvTimeout.new("Query timed out before connect"))
|
|
764
|
+
return
|
|
765
|
+
end
|
|
462
766
|
numtries += 1
|
|
463
|
-
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
|
|
464
773
|
if (use_tcp)
|
|
465
774
|
begin
|
|
466
|
-
if
|
|
467
|
-
|
|
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
|
|
468
790
|
src_port = @tcp_pipeline_local_port
|
|
469
791
|
else
|
|
470
|
-
socket =
|
|
792
|
+
socket = connect_tcp(server, port, src_address, src_port,
|
|
793
|
+
endtime, previous_settings.send_token)
|
|
471
794
|
new_socket = true
|
|
472
795
|
end
|
|
473
796
|
rescue Errno::EBADF, Errno::ENETUNREACH => e
|
|
474
797
|
# Can't create a connection
|
|
475
|
-
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}")
|
|
476
799
|
Dnsruby.log.error { "#{err}" }
|
|
477
|
-
|
|
800
|
+
report_error.call(err)
|
|
478
801
|
return
|
|
479
802
|
end
|
|
480
803
|
else
|
|
@@ -484,81 +807,150 @@ module Dnsruby
|
|
|
484
807
|
socket = UDPSocket.new()
|
|
485
808
|
else
|
|
486
809
|
# ipv6 = @src_address =~ /:/
|
|
487
|
-
socket = UDPSocket.new(
|
|
810
|
+
socket = UDPSocket.new(configuration[:ipv6] ? Socket::AF_INET6 : Socket::AF_INET)
|
|
488
811
|
end
|
|
812
|
+
st.track_socket(socket)
|
|
489
813
|
new_socket = true
|
|
490
814
|
socket.bind(src_address, src_port)
|
|
491
|
-
socket.connect(
|
|
815
|
+
socket.connect(server, port)
|
|
492
816
|
end
|
|
493
|
-
|
|
817
|
+
rescue SendCancelled
|
|
818
|
+
return
|
|
494
819
|
rescue Exception => e
|
|
495
820
|
if (socket!=nil)
|
|
496
821
|
begin
|
|
497
822
|
#let the select thread close the socket if tcp_pipeli
|
|
498
|
-
socket.close unless
|
|
823
|
+
socket.close unless pipeline && !new_socket
|
|
499
824
|
rescue Exception
|
|
500
825
|
end
|
|
501
826
|
end
|
|
502
|
-
#
|
|
503
|
-
#
|
|
504
|
-
if
|
|
505
|
-
|
|
506
|
-
|
|
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}"
|
|
507
835
|
err=IOError.new(err_msg)
|
|
508
836
|
Dnsruby.log.error( "#{err}")
|
|
509
837
|
Dnsruby.log.error(e.backtrace)
|
|
510
|
-
if
|
|
511
|
-
|
|
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))
|
|
512
842
|
else
|
|
513
|
-
|
|
843
|
+
report_error.call(err)
|
|
514
844
|
end
|
|
515
845
|
return
|
|
516
846
|
end
|
|
847
|
+
next # Retry a random source-port collision before registration.
|
|
517
848
|
end
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
return
|
|
524
|
-
end
|
|
525
|
-
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}" }
|
|
526
|
-
# print "#{Time.now} : Sending packet to #{@server} : #{query.question()[0].qname}, #{query.question()[0].qtype}\n"
|
|
527
|
-
# Listen for the response before we send the packet (to avoid any race conditions)
|
|
528
|
-
query_settings = SelectThread::QuerySettings.new(query_bytes, query, @ignore_truncation, client_queue, client_query_id, socket, @server, @port, endtime, udp_packet_size, self)
|
|
529
|
-
query_settings.is_persistent_socket = @tcp_pipelining if use_tcp
|
|
530
|
-
query_settings.tcp_pipelining_max_queries = @tcp_pipelining_max_queries if @tcp_pipelining
|
|
531
|
-
begin
|
|
532
|
-
if (use_tcp)
|
|
533
|
-
lenmsg = [query_bytes.length].pack('n')
|
|
534
|
-
socket.send(lenmsg, 0)
|
|
535
|
-
end
|
|
536
|
-
socket.send(query_bytes, 0)
|
|
537
|
-
|
|
538
|
-
# The select thread will now wait for the response and send that or a
|
|
539
|
-
# timeout back to the client_queue.
|
|
540
|
-
st.add_to_select(query_settings)
|
|
541
|
-
rescue Exception => e
|
|
542
|
-
err_msg = "Send failed to #{@server}:#{@port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp}, exception : #{e}"
|
|
543
|
-
err=IOError.new(err_msg)
|
|
544
|
-
Dnsruby.log.error { "#{err}" }
|
|
545
|
-
Dnsruby.log.error(e.backtrace)
|
|
546
|
-
if @tcp_pipelining
|
|
547
|
-
st.push_exception_to_select(client_query_id, client_queue, SocketEofResolvError.new(err_msg), nil) if new_socket
|
|
548
|
-
else
|
|
549
|
-
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
|
|
550
854
|
end
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
|
556
890
|
end
|
|
557
|
-
|
|
891
|
+
runnextportloop = false
|
|
558
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
|
|
559
943
|
|
|
560
|
-
|
|
561
|
-
|
|
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?
|
|
562
954
|
end
|
|
563
955
|
|
|
564
956
|
# The source port to send queries from
|
|
@@ -567,10 +959,8 @@ module Dnsruby
|
|
|
567
959
|
#
|
|
568
960
|
# Defaults to 0 - random port
|
|
569
961
|
def src_port
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
end
|
|
573
|
-
return @src_port
|
|
962
|
+
ports = @src_port
|
|
963
|
+
ports.length == 1 ? ports[0] : ports
|
|
574
964
|
end
|
|
575
965
|
|
|
576
966
|
# Can be a single Integer or a Range or an Array
|
|
@@ -582,8 +972,9 @@ module Dnsruby
|
|
|
582
972
|
# res.src_port=60015..60115
|
|
583
973
|
#
|
|
584
974
|
def src_port=(p)
|
|
585
|
-
|
|
586
|
-
|
|
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)
|
|
587
978
|
end
|
|
588
979
|
|
|
589
980
|
# Can be a single Integer or a Range or an Array
|
|
@@ -599,57 +990,81 @@ module Dnsruby
|
|
|
599
990
|
# res.add_src_port(60015..60115)
|
|
600
991
|
#
|
|
601
992
|
def add_src_port(p)
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
@src_port.push(x)
|
|
609
|
-
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
|
|
610
999
|
end
|
|
611
1000
|
end
|
|
612
1001
|
|
|
613
1002
|
|
|
614
1003
|
def get_next_src_port
|
|
615
|
-
#
|
|
616
|
-
#
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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)]
|
|
1011
|
+
end
|
|
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
|
|
633
1035
|
end
|
|
1036
|
+
private :transport_configuration
|
|
634
1037
|
|
|
635
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
|
|
636
1044
|
# @TODO@ Should send_raw avoid this?
|
|
637
1045
|
if (!query.send_raw)
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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
|
|
642
1051
|
end
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
|
647
1062
|
end
|
|
648
|
-
#
|
|
649
|
-
#
|
|
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).
|
|
650
1065
|
if (query.class == Update)
|
|
651
|
-
#
|
|
652
|
-
|
|
1066
|
+
# TODO: Validate the zone section for UPDATE replies.
|
|
1067
|
+
elsif !(tcp && response.question.empty? && response.header.get_header_rcode != RCode.NOERROR)
|
|
653
1068
|
if ((response.question.size == 0) ||
|
|
654
1069
|
(response.question[0].qname.labels != query.question[0].qname.labels) ||
|
|
655
1070
|
(response.question[0].qtype != query.question[0].qtype) ||
|
|
@@ -660,19 +1075,29 @@ module Dnsruby
|
|
|
660
1075
|
return false
|
|
661
1076
|
end
|
|
662
1077
|
end
|
|
663
|
-
|
|
664
|
-
#
|
|
665
|
-
#
|
|
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.
|
|
666
1081
|
if ((response.header.get_header_rcode == RCode.FORMERR) &&
|
|
667
|
-
|
|
668
|
-
#
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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)
|
|
672
1097
|
return false
|
|
673
1098
|
end
|
|
674
|
-
if (response.header.tc && !tcp &&
|
|
675
|
-
if (
|
|
1099
|
+
if (response.header.tc && !tcp && !ignore_truncation)
|
|
1100
|
+
if (no_tcp)
|
|
676
1101
|
Dnsruby.log.debug { "Truncated response - not resending over TCP as no_tcp==true" }
|
|
677
1102
|
else
|
|
678
1103
|
# Try to resend over tcp
|
|
@@ -684,21 +1109,32 @@ module Dnsruby
|
|
|
684
1109
|
end
|
|
685
1110
|
end
|
|
686
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
|
|
687
1120
|
return true
|
|
688
1121
|
end
|
|
689
1122
|
|
|
690
1123
|
def check_tsig(query, response, response_bytes)
|
|
691
1124
|
if (query.tsig)
|
|
692
|
-
if (response.tsig)
|
|
1125
|
+
if (response_signature = response.tsig)
|
|
693
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
|
|
694
1129
|
# Discard packet and wait for correctly signed response
|
|
695
1130
|
Dnsruby.log.error { "TSIG authentication failed!" }
|
|
696
1131
|
return TsigError.new
|
|
697
1132
|
end
|
|
698
1133
|
else
|
|
699
|
-
#
|
|
700
|
-
#
|
|
701
|
-
|
|
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
|
|
702
1138
|
Dnsruby.log.error { "Expecting TSIG signed response, but got unsigned response - discarding" }
|
|
703
1139
|
return TsigNotSignedResponseError.new
|
|
704
1140
|
end
|
|
@@ -721,52 +1157,52 @@ module Dnsruby
|
|
|
721
1157
|
end
|
|
722
1158
|
|
|
723
1159
|
# Prepare the packet for sending
|
|
724
|
-
def make_query_packet(packet, use_tcp = @use_tcp) #:nodoc: all
|
|
725
|
-
|
|
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
|
|
726
1166
|
if (packet.header.opcode == OpCode.QUERY || @recurse)
|
|
727
1167
|
packet.header.rd=@recurse
|
|
728
1168
|
end
|
|
729
1169
|
|
|
730
|
-
|
|
731
|
-
if (@dnssec)
|
|
1170
|
+
if @dnssec && !disable_edns
|
|
732
1171
|
prepare_for_dnssec(packet)
|
|
733
|
-
elsif (
|
|
734
|
-
# if ((udp_packet_size > Resolver::DefaultUDPSize) && !use_tcp)
|
|
735
|
-
# @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
|
|
736
1173
|
add_opt_rr(packet)
|
|
737
1174
|
end
|
|
738
1175
|
end
|
|
739
1176
|
|
|
740
|
-
|
|
741
|
-
|
|
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
|
|
742
1183
|
end
|
|
743
|
-
|
|
1184
|
+
encoded
|
|
744
1185
|
end
|
|
745
1186
|
|
|
746
1187
|
def add_opt_rr(packet)
|
|
747
1188
|
Dnsruby.log.debug { ";; Adding EDNS extension with UDP packetsize #{udp_packet_size}.\n" }
|
|
748
|
-
#
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
if (packet.additional.rrset(packet.question()[0].qname, Types::OPT).rrs.length == 0)
|
|
753
|
-
packet.add_additional(optrr)
|
|
754
|
-
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
|
|
755
1193
|
end
|
|
756
1194
|
|
|
757
1195
|
def prepare_for_dnssec(packet)
|
|
758
|
-
#
|
|
1196
|
+
# Request DNSSEC records with DO (RFC 4035 section 4.1).
|
|
759
1197
|
Dnsruby.log.debug { ";; Adding EDNS extension with UDP packetsize #{udp_packet_size} and DNS OK bit set\n" }
|
|
760
|
-
optrr =
|
|
1198
|
+
optrr = add_opt_rr(packet)
|
|
761
1199
|
optrr.dnssec_ok=true
|
|
762
1200
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
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
|
|
768
1204
|
|
|
769
|
-
#
|
|
1205
|
+
# Local validation needs the original data, even if the server rejects it.
|
|
770
1206
|
if (packet.do_validation)
|
|
771
1207
|
packet.header.cd = true
|
|
772
1208
|
end
|
|
@@ -778,10 +1214,16 @@ module Dnsruby
|
|
|
778
1214
|
|
|
779
1215
|
# Return the packet size to use for UDP
|
|
780
1216
|
def udp_packet_size
|
|
781
|
-
#
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
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)
|
|
785
1226
|
end
|
|
1227
|
+
private :udp_receive_size
|
|
786
1228
|
end
|
|
787
1229
|
end
|