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.
Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +16 -4
  3. data/DNSSEC +3 -2
  4. data/EXAMPLES +1 -1
  5. data/README.md +370 -64
  6. data/RELEASE_NOTES.md +314 -1
  7. data/Rakefile +0 -3
  8. data/demo/axfr.rb +24 -6
  9. data/dnsruby.gemspec +6 -4
  10. data/lib/dnsruby/cache.rb +279 -49
  11. data/lib/dnsruby/code_mapper.rb +35 -8
  12. data/lib/dnsruby/code_mappers.rb +63 -49
  13. data/lib/dnsruby/config.rb +77 -4
  14. data/lib/dnsruby/deadline_queue.rb +75 -0
  15. data/lib/dnsruby/dnssec.rb +148 -45
  16. data/lib/dnsruby/ipv6.rb +45 -5
  17. data/lib/dnsruby/key_cache.rb +202 -35
  18. data/lib/dnsruby/message/decoder.rb +85 -19
  19. data/lib/dnsruby/message/encoder.rb +27 -16
  20. data/lib/dnsruby/message/header.rb +8 -12
  21. data/lib/dnsruby/message/message.rb +53 -22
  22. data/lib/dnsruby/message/section.rb +12 -34
  23. data/lib/dnsruby/name.rb +5 -2
  24. data/lib/dnsruby/packet_sender.rb +664 -207
  25. data/lib/dnsruby/presentation.rb +147 -0
  26. data/lib/dnsruby/query_control.rb +132 -0
  27. data/lib/dnsruby/recursor.rb +798 -341
  28. data/lib/dnsruby/resolver.rb +472 -176
  29. data/lib/dnsruby/resource/CAA.rb +67 -16
  30. data/lib/dnsruby/resource/DLV.rb +6 -0
  31. data/lib/dnsruby/resource/DNSKEY.rb +108 -16
  32. data/lib/dnsruby/resource/DS.rb +27 -28
  33. data/lib/dnsruby/resource/HTTPS.rb +16 -0
  34. data/lib/dnsruby/resource/IN.rb +21 -2
  35. data/lib/dnsruby/resource/MX.rb +2 -2
  36. data/lib/dnsruby/resource/NSEC.rb +6 -10
  37. data/lib/dnsruby/resource/NSEC3.rb +55 -19
  38. data/lib/dnsruby/resource/NSEC3PARAM.rb +4 -6
  39. data/lib/dnsruby/resource/OPT.rb +2 -3
  40. data/lib/dnsruby/resource/RR.rb +108 -65
  41. data/lib/dnsruby/resource/RRSIG.rb +5 -5
  42. data/lib/dnsruby/resource/RRSet.rb +38 -22
  43. data/lib/dnsruby/resource/SRV.rb +2 -2
  44. data/lib/dnsruby/resource/SVCB.rb +775 -0
  45. data/lib/dnsruby/resource/TSIG.rb +266 -231
  46. data/lib/dnsruby/resource/TXT.rb +34 -42
  47. data/lib/dnsruby/resource/domain_name.rb +1 -1
  48. data/lib/dnsruby/resource/generic.rb +31 -0
  49. data/lib/dnsruby/resource/generic_cache.rb +384 -0
  50. data/lib/dnsruby/resource/generic_constants.rb +79 -0
  51. data/lib/dnsruby/select_thread.rb +986 -291
  52. data/lib/dnsruby/serial_time.rb +25 -0
  53. data/lib/dnsruby/single_resolver.rb +25 -6
  54. data/lib/dnsruby/single_verifier.rb +1620 -530
  55. data/lib/dnsruby/transport_clock.rb +9 -0
  56. data/lib/dnsruby/update.rb +8 -6
  57. data/lib/dnsruby/validation_budget.rb +143 -0
  58. data/lib/dnsruby/validator_thread.rb +139 -58
  59. data/lib/dnsruby/version.rb +1 -1
  60. data/lib/dnsruby/zone_reader.rb +196 -129
  61. data/lib/dnsruby/zone_transfer.rb +157 -25
  62. data/lib/dnsruby.rb +7 -2
  63. data/test/ci_watchdog.rb +33 -0
  64. data/test/fixtures/org-ds-2026-11-05.zone +5 -0
  65. data/test/fixtures/root-dnskey-2026-11-05.zone +7 -0
  66. data/test/fork_test_helper.rb +37 -0
  67. data/test/local_dns_server.rb +57 -0
  68. data/test/soak_server.rb +152 -0
  69. data/test/socket_helpers.rb +26 -0
  70. data/test/spec_helper.rb +8 -13
  71. data/test/tc_attempt_configuration.rb +137 -0
  72. data/test/tc_caa.rb +2 -2
  73. data/test/tc_cache_keys.rb +36 -0
  74. data/test/tc_cancelled_send_capacity.rb +158 -0
  75. data/test/tc_dnssec_alias_validation.rb +289 -0
  76. data/test/tc_dnssec_budget.rb +489 -0
  77. data/test/tc_dnssec_cache_bounds.rb +197 -0
  78. data/test/tc_dnssec_cache_lifetime.rb +615 -0
  79. data/test/tc_dnssec_denial_proofs.rb +285 -0
  80. data/test/tc_dnssec_recursion_limits.rb +186 -0
  81. data/test/tc_dnssec_recursor_review_fixes.rb +351 -0
  82. data/test/tc_dnssec_trust_anchors.rb +286 -0
  83. data/test/tc_dnssec_trust_races.rb +147 -0
  84. data/test/tc_dnssec_wildcard_proofs.rb +249 -0
  85. data/test/tc_ds.rb +5 -5
  86. data/test/tc_edns_retry_regressions.rb +255 -0
  87. data/test/tc_escapedchars.rb +8 -0
  88. data/test/tc_final_review_fixes.rb +167 -0
  89. data/test/tc_final_transport_contracts.rb +104 -0
  90. data/test/tc_generic_cache.rb +852 -0
  91. data/test/tc_header.rb +26 -0
  92. data/test/tc_ipv6.rb +120 -0
  93. data/test/tc_key_cache_identity.rb +382 -0
  94. data/test/tc_malformed_tcp.rb +712 -0
  95. data/test/tc_name.rb +99 -5
  96. data/test/tc_nameserver_bootstrap.rb +322 -0
  97. data/test/tc_nsec.rb +4 -45
  98. data/test/tc_nsec3.rb +17 -1
  99. data/test/tc_pipeline_retirement.rb +358 -0
  100. data/test/tc_pipeline_retry.rb +462 -0
  101. data/test/tc_pipeline_send.rb +663 -0
  102. data/test/tc_protocol_review_fixes.rb +95 -0
  103. data/test/tc_query_control.rb +90 -0
  104. data/test/tc_record_codec_contracts.rb +107 -0
  105. data/test/tc_record_regressions.rb +225 -0
  106. data/test/tc_recur.rb +1 -0
  107. data/test/tc_recursor_address_fallback.rb +169 -0
  108. data/test/tc_recursor_limits.rb +139 -0
  109. data/test/tc_release_auxiliary_fixes.rb +288 -0
  110. data/test/tc_release_configuration_fixes.rb +612 -0
  111. data/test/tc_release_dnssec_completion.rb +405 -0
  112. data/test/tc_release_dnssec_fixes.rb +305 -0
  113. data/test/tc_release_negative_cache.rb +327 -0
  114. data/test/tc_release_recursor_cache.rb +212 -0
  115. data/test/tc_release_review_transport.rb +372 -0
  116. data/test/tc_release_review_transport_native.rb +56 -0
  117. data/test/tc_release_transport.rb +906 -0
  118. data/test/tc_release_transport_fixes.rb +196 -0
  119. data/test/tc_release_zone_defaults.rb +157 -0
  120. data/test/tc_release_zone_ttls.rb +39 -0
  121. data/test/tc_res_config.rb +53 -2
  122. data/test/tc_res_opt.rb +21 -1
  123. data/test/tc_resolver.rb +34 -19
  124. data/test/tc_resolver_concurrency.rb +383 -0
  125. data/test/tc_resolver_configuration_contracts.rb +31 -0
  126. data/test/tc_resolver_validation.rb +211 -0
  127. data/test/tc_review_cache.rb +163 -0
  128. data/test/tc_review_code_mapper.rb +279 -0
  129. data/test/tc_review_control_deadlines.rb +169 -0
  130. data/test/tc_review_followup_transport.rb +583 -0
  131. data/test/tc_review_packet_contracts.rb +369 -0
  132. data/test/tc_review_presentation.rb +213 -0
  133. data/test/tc_review_record_compatibility.rb +101 -0
  134. data/test/tc_review_transport_fixes.rb +195 -0
  135. data/test/tc_review_transport_native.rb +415 -0
  136. data/test/tc_review_validation_priming.rb +178 -0
  137. data/test/tc_rr-opt.rb +5 -2
  138. data/test/tc_rr-unknown.rb +19 -0
  139. data/test/tc_rr.rb +14 -0
  140. data/test/tc_rrset_grouping.rb +50 -0
  141. data/test/tc_rrset_review_fixes.rb +58 -0
  142. data/test/tc_rrsig.rb +21 -1
  143. data/test/tc_scoped_ipv6.rb +153 -0
  144. data/test/tc_scoped_ipv6_select.rb +157 -0
  145. data/test/tc_scoped_ipv6_transport.rb +329 -0
  146. data/test/tc_selector_wakeup.rb +42 -0
  147. data/test/tc_soak.rb +118 -196
  148. data/test/tc_socket_helpers.rb +90 -0
  149. data/test/tc_supporting_lookup_limits.rb +252 -0
  150. data/test/tc_svcb.rb +1043 -0
  151. data/test/tc_svcb_signed_target.rb +145 -0
  152. data/test/tc_svcb_transport.rb +120 -0
  153. data/test/tc_tcp.rb +69 -43
  154. data/test/tc_tcp_pipelining.rb +95 -204
  155. data/test/tc_transport_fork.rb +179 -0
  156. data/test/tc_tsig.rb +2 -2
  157. data/test/tc_tsig_api.rb +251 -0
  158. data/test/tc_tsig_rfc8945.rb +357 -0
  159. data/test/tc_v2_release_dnssec.rb +442 -0
  160. data/test/tc_v2_release_transport.rb +304 -0
  161. data/test/tc_v2_zone_transfer_native.rb +58 -0
  162. data/test/tc_zone_reader.rb +217 -1
  163. data/test/tcp_pipelining_server.rb +150 -0
  164. data/test/ts_offline.rb +77 -6
  165. data/test/ts_online.rb +2 -1
  166. metadata +127 -14
  167. data/.coveralls.yml +0 -2
@@ -0,0 +1,9 @@
1
+ module Dnsruby
2
+ # Packet, query, retry, and I/O deadlines share one clock unaffected by wall
3
+ # clock corrections. DNS TTLs and signatures continue to use civil time.
4
+ module TransportClock #:nodoc:
5
+ def self.now
6
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
7
+ end
8
+ end
9
+ end
@@ -149,7 +149,11 @@ module Dnsruby
149
149
  klass = Classes.new(zone()[0].zclass)
150
150
  rdata=args[2]
151
151
  end
152
- rec = RR.create("#{name} #{ttl} #{klass} #{type} #{rdata}")
152
+ rec = if args.length > 2
153
+ RR.create("#{name} #{ttl} #{klass} #{type} #{rdata}")
154
+ else
155
+ RR.empty_update_record(name, type, klass)
156
+ end
153
157
  add_pre(rec)
154
158
  return rec
155
159
  else
@@ -172,8 +176,6 @@ module Dnsruby
172
176
  # (in the zone and class denoted by the Zone Section) can exist.
173
177
  #
174
178
  def absent(*args)
175
- ttl = 0
176
- rdata = ""
177
179
  klass = Classes.NONE
178
180
  if (args.length>=1) # domain (RFC2136, Section 2.4.5)
179
181
  name = args[0]
@@ -181,7 +183,7 @@ module Dnsruby
181
183
  if (args.length==2) # RRSET (RFC2136, Section 2.4.3)
182
184
  type = args[1]
183
185
  end
184
- rec = RR.create("#{name} #{ttl} #{klass} #{type} #{rdata}")
186
+ rec = RR.empty_update_record(name, type, klass)
185
187
  add_pre(rec)
186
188
  return rec
187
189
  else
@@ -262,10 +264,10 @@ module Dnsruby
262
264
  resource = nil
263
265
  case args.length
264
266
  when 1 # name
265
- resource = RR.create("#{args[0]} #{ttl} #{klass} #{Types.ANY} #{rdata}")
267
+ resource = RR.empty_update_record(args[0], Types.ANY, klass)
266
268
  add_update(resource)
267
269
  when 2 # name, type
268
- resource = RR.create("#{args[0]} #{ttl} #{klass} #{args[1]} #{rdata}")
270
+ resource = RR.empty_update_record(args[0], args[1], klass)
269
271
  add_update(resource)
270
272
  when 3 # name, type, rdata
271
273
  name = args[0]
@@ -0,0 +1,143 @@
1
+ require 'dnsruby/query_control'
2
+
3
+ module Dnsruby
4
+ # Resource limits are verification failures, never evidence that a zone is
5
+ # insecure. Keep this distinction through trust-anchor and algorithm fallbacks.
6
+ class ValidationLimitError < VerifyError; end
7
+ class ValidationCancelled < ValidationLimitError; end
8
+
9
+ # One budget counts DNSSEC work across nested validation calls. Supporting
10
+ # lookups share its deadline and cancellation signal through QueryControl.
11
+ class ValidationBudget # :nodoc: all
12
+ # Ordinary verification and lookup failures can select another key or
13
+ # anchor. Exhaustion must cross those fallbacks unchanged, then become the
14
+ # public verification error when the outermost validation returns.
15
+ class Abort < StandardError
16
+ attr_reader :public_error
17
+
18
+ def initialize(public_error)
19
+ @public_error = public_error
20
+ super(public_error.message)
21
+ end
22
+ end
23
+ private_constant :Abort
24
+
25
+ DEFAULT_LIMITS = {
26
+ max_signature_verifications: 64,
27
+ max_work: 100_000,
28
+ timeout: 5.0
29
+ }.freeze
30
+
31
+ attr_reader :control, :transaction
32
+
33
+ def self.current
34
+ Thread.current[:dnsruby_validation_budget]
35
+ end
36
+
37
+ # Supporting lookup configuration has legacy catch-all fallbacks. None
38
+ # may convert an enclosing operation's exhaustion into an ordinary failure.
39
+ def self.abort?(error)
40
+ error.is_a?(Abort) || error.is_a?(ValidationLimitError) ||
41
+ error.is_a?(QueryControl::Stopped) || error.is_a?(RecursionLimitError)
42
+ end
43
+
44
+ def self.with(budget = nil)
45
+ previous = current
46
+ budget = previous || budget || new
47
+ Thread.current[:dnsruby_validation_budget] = budget
48
+ QueryControl.with(budget.control) do
49
+ budget.check!
50
+ result = yield budget
51
+ budget.check!
52
+ result
53
+ end
54
+ rescue Abort, QueryControl::Stopped => error
55
+ raise if previous
56
+ raise public_error(error), cause: error
57
+ ensure
58
+ Thread.current[:dnsruby_validation_budget] = previous
59
+ end
60
+
61
+ def initialize(deadline: nil, limits: Dnssec.validation_limits, parent: nil)
62
+ # A plain token can be retained with learned records without retaining
63
+ # the budget, resolver control, or the rest of a validation operation.
64
+ if parent
65
+ @transaction = parent.transaction
66
+ @limits = parent.limits
67
+ @state = parent.state
68
+ @control = QueryControl.new(deadline: deadline, parent: parent.control)
69
+ else
70
+ @transaction = Object.new.freeze
71
+ @limits = limits
72
+ deadline = [deadline, TransportClock.now + limits[:timeout]].compact.min
73
+ @control = QueryControl.new(deadline: deadline, parent: QueryControl.current)
74
+ @state = { mutex: Mutex.new, signature_verifications: 0, work: 0 }
75
+ end
76
+ end
77
+
78
+ # A selector cancels its completed hop. Keep that signal local while all
79
+ # hops and parallel address workers count against the lookup's one budget.
80
+ def child(deadline: nil)
81
+ self.class.new(deadline: deadline, parent: self)
82
+ end
83
+
84
+ def deadline
85
+ @control.deadline
86
+ end
87
+
88
+ def cancel
89
+ @control.cancel
90
+ end
91
+
92
+ def check!(work = 0)
93
+ @control.check!
94
+ @state[:mutex].synchronize do
95
+ @state[:work] += work
96
+ check_counts!
97
+ end
98
+ true
99
+ rescue QueryControl::Stopped => error
100
+ raise if self.class.current
101
+ raise self.class.public_error(error), cause: error
102
+ end
103
+
104
+ def self.public_error(error)
105
+ public_error = case error
106
+ when Abort then error.public_error
107
+ when QueryControl::Cancelled then ValidationCancelled.new('DNSSEC validation cancelled')
108
+ else ValidationLimitError.new('DNSSEC validation deadline exceeded')
109
+ end
110
+ public_error.set_backtrace(error.backtrace)
111
+ public_error
112
+ end
113
+
114
+ def fail_with(error_class, message)
115
+ error = error_class.new(message)
116
+ raise(self.class.current ? Abort.new(error) : error)
117
+ end
118
+ private :fail_with
119
+
120
+ def check_counts!
121
+ if @state[:signature_verifications] > @limits[:max_signature_verifications]
122
+ fail_with(ValidationLimitError, 'DNSSEC signature verification limit exceeded')
123
+ end
124
+ if @state[:work] > @limits[:max_work]
125
+ fail_with(ValidationLimitError, 'DNSSEC validation work limit exceeded')
126
+ end
127
+ end
128
+ private :check_counts!
129
+
130
+ def signature!
131
+ check!
132
+ @state[:mutex].synchronize do
133
+ @state[:signature_verifications] += 1
134
+ check_counts!
135
+ end
136
+ true
137
+ end
138
+
139
+ protected
140
+
141
+ attr_reader :limits, :state
142
+ end
143
+ end
@@ -15,67 +15,149 @@
15
15
  # ++
16
16
 
17
17
  module Dnsruby
18
- # Takes care of the validation for the SelectThread. If queries need to be
19
- # made in order to validate the response, then a separate thread is fired up
20
- # to do this.
18
+ # Runs local DNSSEC validation outside SelectThread, using a bounded pool.
19
+ # Nested trust discovery shares the original validation budget.
21
20
  class ValidatorThread # :nodoc: all
22
- # include Singleton
21
+ WORKER_COUNT = 4
22
+ QUEUE_CAPACITY = 64
23
+ @worker_mutex = Mutex.new
24
+
25
+ # Chain discovery performs synchronous queries, so validation must run away
26
+ # from the selector. Bound both threads and queued responses under load.
27
+ def self.enqueue(validator)
28
+ rejected = []
29
+ accepted = @worker_mutex.synchronize do
30
+ # Threads do not survive fork. Parent work belongs to the parent, while
31
+ # the child's first admission must start its own pool.
32
+ if @worker_pid != Process.pid
33
+ @worker_pid = Process.pid
34
+ @work_queue = SizedQueue.new(QUEUE_CAPACITY)
35
+ @workers = []
36
+ end
37
+ @workers.select!(&:alive?)
38
+ begin
39
+ (WORKER_COUNT - @workers.length).times do
40
+ queue = @work_queue
41
+ @workers << Thread.new do
42
+ loop do
43
+ work = queue.pop
44
+ begin
45
+ work.do_validate
46
+ rescue StandardError => error
47
+ Dnsruby.log.error { "DNSSEC validation worker: #{error}" }
48
+ end
49
+ end
50
+ end
51
+ end
52
+ rescue ThreadError
53
+ # A partial pool remains usable and is replenished on later admission.
54
+ # If no worker could start, fail queued work rather than strand it.
55
+ end
56
+ if @workers.empty?
57
+ begin
58
+ loop { rejected << @work_queue.pop(true) }
59
+ rescue ThreadError
60
+ end
61
+ false
62
+ else
63
+ begin
64
+ @work_queue.push(validator, true)
65
+ true
66
+ rescue ThreadError
67
+ false
68
+ end
69
+ end
70
+ end
71
+ rejected.each(&:reject_capacity)
72
+ accepted
73
+ end
74
+
23
75
  def initialize(*args)
24
- @client_id, @client_queue, @response, @error, @query, @st, @res = args
25
- # Create the validation thread, and a queue to receive validation requests
26
- # Actually, need to have a thread per validator, as they make recursive calls.
27
- # @@mutex = Mutex.new
28
- # @@validation_queue = Queue.new
29
- # @@validator_thread = Thread.new{
30
- # do_validate
31
- # }
76
+ @client_id, @client_queue, @response, @error, @query, @st, @res, @transmission_budget = args
77
+ @registered = false
78
+ prepare_budget if should_validate
32
79
  end
80
+
81
+ def prepare_budget
82
+ return @budget if @budget
83
+ deadline = @st.validation_deadline(@client_queue, @client_id) if @st.respond_to?(:validation_deadline)
84
+ if !@transmission_budget && @st.respond_to?(:validation_transmission_budget)
85
+ @transmission_budget = @st.validation_transmission_budget(@client_queue, @client_id)
86
+ end
87
+ @transmission_budget ||= SelectThread::TransmissionBudget.current || SelectThread::TransmissionBudget.new
88
+ lookup_budget = @transmission_budget.context(:validation) { nil }
89
+ @budget = lookup_budget ? lookup_budget.child(deadline: deadline) : ValidationBudget.new(deadline: deadline)
90
+ end
91
+ private :prepare_budget
92
+
93
+ def register
94
+ return true if @registered
95
+ prepare_budget
96
+ return false unless @st.register_validation(@client_queue, @res, @budget.control)
97
+ @registered = true
98
+ end
99
+ private :register
100
+
33
101
  def run
34
- # ONLY START THE NEW THREAD IF VALIDATION NEED OCCUR!!
35
- if (should_validate)
36
- Thread.new{
37
- do_validate
38
- }
102
+ if should_validate
103
+ return unless register
104
+ unless self.class.enqueue(self)
105
+ reject_capacity
106
+ end
39
107
  else
40
108
  do_validate
41
109
  end
42
110
  end
43
111
 
44
-
45
- # def add_to_queue(item)
46
- # print "ADding to validator queue\n"
47
- # # @@mutex.synchronize{
48
- # @@validation_queue.push(item)
49
- # # }
50
- # end
51
112
  def do_validate
52
- # while (true)
53
- # item = nil
54
- # print "Waiting to pop validation item\n"
55
- # # @@mutex.synchronize{
56
- # item = @@validation_queue.pop
57
- # # }
58
- # print "Popped validation request\n"
59
- # client_id, client_queue, response, err, query, st, res = item
60
- validated_ok = validate(@query, @response, @res)
61
-
62
- validated_ok = false if (@error && !(NXDomain === @error))
63
-
64
- cache_if_valid(@query, @response)
65
-
66
- # Now send the response back to the client...
67
- # print "#{Time.now} : Got result for #{@query.question()[0].qname}, #{@query.question()[0].qtype}\n"
68
- if (validated_ok)
69
- @st.push_validation_response_to_select(@client_id, @client_queue, @response, nil, @query, @res)
113
+ needs_validation = should_validate
114
+ return if needs_validation && !register
115
+ validated_ok = if needs_validation
116
+ SelectThread::TransmissionBudget.with(@transmission_budget) do
117
+ ValidationBudget.with(@budget) { validate(@query, @response, @res) }
118
+ end
70
119
  else
71
- @st.push_validation_response_to_select(@client_id, @client_queue, @response,
72
- @response.security_error, @query, @res)
120
+ validate(@query, @response, @res)
121
+ end
122
+ validated_ok = false if @error && !@error.is_a?(NXDomain)
123
+ if @response.security_level == Message::SecurityLevel.BOGUS
124
+ detail = @response.security_error.to_s
125
+ raise VerifyError, detail.empty? ? 'DNSSEC validation failed' : detail
73
126
  end
127
+ @budget.check! if needs_validation
128
+ cache_if_valid(@query, @response) if validated_ok
129
+ @st.push_validation_response_to_select(@client_id, @client_queue, @response,
130
+ validated_ok ? nil : @response.security_error, @query, @res)
131
+ rescue StandardError => error
132
+ # Resolver observers ignore late completions; direct PacketSender callers
133
+ # still need a terminal error after cancellation or their sender closing.
134
+ unless error.is_a?(VerifyError) || error.is_a?(RecursionLimitError)
135
+ error = VerifyError.new("DNSSEC validation failed: #{error.message}")
136
+ end
137
+ finish_error(error)
138
+ ensure
139
+ unregister
140
+ end
74
141
 
142
+ def finish_error(error)
143
+ @response.security_error = error
144
+ @response.security_level = Message::SecurityLevel.BOGUS
145
+ @st.push_validation_response_to_select(@client_id, @client_queue, @response,
146
+ error, @query, @res)
147
+ end
148
+ private :finish_error
75
149
 
76
- # end
150
+ def unregister
151
+ @st.unregister_validation(@client_queue, @res, @budget.control) if @registered
152
+ @registered = false
77
153
  end
154
+ private :unregister
78
155
 
156
+ def reject_capacity
157
+ finish_error(ValidationLimitError.new('DNSSEC validation capacity exceeded'))
158
+ ensure
159
+ unregister
160
+ end
79
161
 
80
162
  def should_validate
81
163
  return ValidatorThread.requires_validation?(@query, @response, @error, @res)
@@ -95,26 +177,25 @@ module Dnsruby
95
177
  end
96
178
 
97
179
  def validate(query, response, res)
98
- if (should_validate)
180
+ return true unless should_validate
181
+ ValidationBudget.with do
99
182
  begin
100
- # So, we really need to be able to take the response out of the select thread, along
101
- # with the responsibility for sending the answer to the client.
102
- # Should we have a validator thread? Or a thread per validation?
103
- # Then, select thread gets response. It performs basic checks here.
104
- # After basic checks, the select-thread punts the response (along with queues, etc.)
105
- # to the validator thread.
106
- # The validator validates it (or just releases it with no validation), and then
107
- # sends the request to the client via the client queue.
108
- Dnssec.validate_with_query(query,response)
109
- return true
183
+ validated = Dnssec.validate_with_query(query, response)
184
+ if !validated || response.security_level == Message::SecurityLevel.BOGUS
185
+ error = response.security_error
186
+ error = VerifyError.new(error.to_s.empty? ? 'DNSSEC validation failed' : error.to_s) unless error.is_a?(VerifyError)
187
+ response.security_error = error
188
+ response.security_level = Message::SecurityLevel.BOGUS
189
+ return false
190
+ end
191
+ true
110
192
  rescue VerifyError => e
111
193
  response.security_error = e
112
194
  response.security_level = Message::SecurityLevel.BOGUS
113
195
  # Response security_level should already be set
114
- return false
196
+ false
115
197
  end
116
198
  end
117
- return true
118
199
  end
119
200
 
120
201
  def cache_if_valid(query, response)
@@ -1,3 +1,3 @@
1
1
  module Dnsruby
2
- VERSION = '1.73.1'
2
+ VERSION = '2.0.0'
3
3
  end