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
@@ -20,7 +20,7 @@ require 'dnsruby/resource/resource'
20
20
  module Dnsruby
21
21
  # ===Defines a DNS packet.
22
22
  #
23
- # RFC 1035 Section 4.1, RFC 2136 Section 2, RFC 2845
23
+ # RFC 1035 Section 4.1, RFC 2136 Section 2, RFC 8945
24
24
  #
25
25
  # ===Sections
26
26
  # Message objects have five sections:
@@ -102,7 +102,23 @@ module Dnsruby
102
102
  # * Dnsruby::Message.new('example.com', Dnsruby::Types.PTR, 'HS')
103
103
  #
104
104
  def initialize(*args)
105
- @header = Header.new()
105
+ initialize_fields(Header.new)
106
+ type = Types::A
107
+ klass = Classes::IN
108
+ if (args.length > 0)
109
+ name = args[0]
110
+ if (args.length > 1)
111
+ type = Types.new(args[1])
112
+ if (args.length > 2)
113
+ klass = Classes.new(args[2])
114
+ end
115
+ end
116
+ add_question(name, type, klass)
117
+ end
118
+ end
119
+
120
+ private def initialize_fields(header)
121
+ @header = header
106
122
  # @question = Section.new(self)
107
123
  @question = []
108
124
  @answer = Section.new(self)
@@ -119,18 +135,6 @@ module Dnsruby
119
135
  @security_level = SecurityLevel.UNCHECKED
120
136
  @security_error = nil
121
137
  @cached = false
122
- type = Types::A
123
- klass = Classes::IN
124
- if (args.length > 0)
125
- name = args[0]
126
- if (args.length > 1)
127
- type = Types.new(args[1])
128
- if (args.length > 2)
129
- klass = Classes.new(args[2])
130
- end
131
- end
132
- add_question(name, type, klass)
133
- end
134
138
  end
135
139
 
136
140
  # The question section, an array of Dnsruby::Question objects.
@@ -365,6 +369,8 @@ module Dnsruby
365
369
  # Sets the TSIG to sign this message with. Can either be a Dnsruby::RR::TSIG
366
370
  # object, or it can be a (name, key) tuple, or it can be a hash which takes
367
371
  # Dnsruby::RR::TSIG attributes (e.g. name, key, fudge, etc.)
372
+ # Defaults to hmac-sha256; the algorithm must match the server's key.
373
+ # You can select hmac-md5 for legacy servers, which logs a warning.
368
374
  def set_tsig(*args)
369
375
  if args.length == 1
370
376
  if args[0].instance_of?(RR::TSIG)
@@ -541,8 +547,11 @@ module Dnsruby
541
547
  def encode(canonical=false)
542
548
  if @tsigkey && (@tsigstate == :Unsigned) && !@signing
543
549
  @signing = true
544
- sign!
545
- @signing = false
550
+ begin
551
+ sign!
552
+ ensure
553
+ @signing = false
554
+ end
546
555
  end
547
556
 
548
557
  return MessageEncoder.new { |msg|
@@ -562,28 +571,41 @@ module Dnsruby
562
571
 
563
572
  # Decode the encoded message
564
573
  def Message.decode(m)
565
- o = Message.new()
574
+ # Incoming packets already contain an ID; do not allocate and randomize
575
+ # a query header only to replace it with the decoded response header.
576
+ o = Message.allocate
577
+ o.send(:initialize_fields, nil)
578
+ received_at = Time.now.to_i
566
579
  begin
567
580
  MessageDecoder.new(m) {|msg|
568
581
  o.header = Header.new(msg)
582
+ update = o.header.opcode == OpCode.Update
569
583
  o.header.qdcount.times {
570
584
  question = msg.get_question
571
585
  o.question << question
572
586
  }
573
587
  o.header.ancount.times {
574
- rr = msg.get_rr
588
+ rr = msg.get_rr(update_section: update ? :prerequisite : false)
589
+ rr.instance_variable_set(:@dnsruby_received_at, received_at)
590
+ if rr.type == Types::TSIG
591
+ raise DecodeError.new('TSIG must be the last record in the additional section')
592
+ end
575
593
  o.answer << rr
576
594
  }
577
595
  o.header.nscount.times {
578
- rr = msg.get_rr
596
+ rr = msg.get_rr(update_section: update ? :update : false)
597
+ rr.instance_variable_set(:@dnsruby_received_at, received_at)
598
+ if rr.type == Types::TSIG
599
+ raise DecodeError.new('TSIG must be the last record in the additional section')
600
+ end
579
601
  o.authority << rr
580
602
  }
581
603
  o.header.arcount.times { |count|
582
604
  start = msg.index
583
- rr = msg.get_rr
605
+ rr = msg.get_rr(update_section: false)
606
+ rr.instance_variable_set(:@dnsruby_received_at, received_at)
584
607
  if rr.type == Types::TSIG
585
608
  if count != o.header.arcount-1
586
- Dnsruby.log.Error('Incoming message has TSIG record before last record')
587
609
  raise DecodeError.new('TSIG record present before last record')
588
610
  end
589
611
  o.tsigstart = start # needed for TSIG verification
@@ -595,6 +617,7 @@ module Dnsruby
595
617
  # So we got a decode error
596
618
  # However, we might have been able to fill in many parts of the message
597
619
  # So let's raise the DecodeError, but add the partially completed message
620
+ o.header ||= Header.new
598
621
  e.partial_message = o
599
622
  raise e
600
623
  end
@@ -602,7 +625,15 @@ module Dnsruby
602
625
  end
603
626
 
604
627
  def clone
605
- Message.decode(self.encode)
628
+ copy = Message.decode(self.encode)
629
+ [answer, authority, additional].zip([copy.answer, copy.authority, copy.additional]) do |original, cloned|
630
+ original.zip(cloned) do |record, clone|
631
+ [:@dnsruby_received_at, :@dnsruby_authenticated_expiration, :@dnsruby_learned_trust].each do |field|
632
+ clone.instance_variable_set(field, record.instance_variable_get(field)) if record.instance_variable_defined?(field)
633
+ end
634
+ end
635
+ end
636
+ copy
606
637
  end
607
638
 
608
639
  # In dynamic update packets, the question section is known as zone and
@@ -18,11 +18,7 @@ class Section < Array
18
18
  end
19
19
  type_ok && (rr.klass == klass) && (rr.name.to_s(true).downcase == name.to_s().downcase)
20
20
  }
21
- rrset = RRSet.new()
22
- rrs.each do |rr|
23
- rrset.add(rr)
24
- end
25
- rrset
21
+ RRSet.new(rrs)
26
22
  end
27
23
 
28
24
  # Return an array of all the rrsets in the section
@@ -30,38 +26,20 @@ class Section < Array
30
26
  if type && !(Types === type)
31
27
  type = Types.new(type)
32
28
  end
33
- ret = []
29
+ groups = {}
30
+ budget = ValidationBudget.current if defined?(ValidationBudget)
34
31
  each do |rr|
35
- next if (!include_opt && (rr.type == Types::OPT))
36
- # if (type)
37
- # next if ((rr.type == Types.RRSIG) && (type != Types.RRSIG) && (rr.type_covered != type))
38
- # next if (rr.type != type)
39
- # end
40
- if (type)
41
- # if this is an rrsig type, then :
42
- # only include it if the type_covered is the type requested,
43
- # OR if the type requested is an RRSIG
44
- if rr.type == Types::RRSIG
45
- if (rr.type_covered == type) || (type == Types::RRSIG)
46
- else
47
- next
48
- end
49
- # next if ((rr.type_covered != type) || (type != Types.RRSIG))
50
- elsif rr.type != type
51
- next
52
- end
53
- end
32
+ budget&.check!(1)
33
+ next if !include_opt && rr.type == Types::OPT
34
+ covered_type = rr.type == Types::RRSIG ? rr.type_covered : rr.type
35
+ next if type && rr.type != type && covered_type != type
54
36
 
55
- found_rrset = false
56
- ret.each do |rrset|
57
- found_rrset = rrset.add(rr)
58
- break if found_rrset
59
- end
60
- unless found_rrset
61
- ret.push(RRSet.new(rr))
62
- end
37
+ # Hash insertion order preserves the section's first occurrence of each
38
+ # set. A signature joins the set it covers even when it appears first.
39
+ key = [rr.name.canonical, rr.klass.code, covered_type.code]
40
+ (groups[key] ||= []) << rr
63
41
  end
64
- ret
42
+ groups.values.map { |records| RRSet.new(records) }
65
43
  end
66
44
 
67
45
  def ==(other)
data/lib/dnsruby/name.rb CHANGED
@@ -54,7 +54,7 @@ module Dnsruby
54
54
  return Name.new([],false)
55
55
  end
56
56
  arg = punycode(arg)
57
- return Name.new(split_escaped(arg), /\.\z/ =~ arg ? true : false)
57
+ return Name.new(split_escaped(arg), /(?<!\\)(?:\\\\)*\.\z/.match?(arg))
58
58
  # return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
59
59
  when Array
60
60
  return Name.new(arg, /\.\z/ =~ (arg.last ? ((arg.last.kind_of?String)?arg.last : arg.last.string) : arg.last) ? true : false)
@@ -93,7 +93,10 @@ module Dnsruby
93
93
  # This method should only be called internally.
94
94
  # Use Name::create to create a new Name
95
95
  def initialize(labels, absolute=true) #:nodoc: all
96
- total_length=labels.length-1
96
+ # The 255-octet limit (RFC 1035 section 2.3.4, RFC 2181 section 11) is
97
+ # on the wire form, so count each label's length octet, plus the root's
98
+ # zero octet.
99
+ total_length=labels.length+1
97
100
  labels.each do |l|
98
101
  if (!l.kind_of?Label)
99
102
  raise ArgumentError.new("Name::new called with non-labels. Use Name::create instead?")