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.
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 +302 -1
  7. data/Rakefile +0 -3
  8. data/demo/axfr.rb +24 -6
  9. data/demo/rubydig.rb +0 -2
  10. data/dnsruby.gemspec +5 -4
  11. data/lib/dnsruby/cache.rb +279 -47
  12. data/lib/dnsruby/code_mapper.rb +35 -8
  13. data/lib/dnsruby/code_mappers.rb +63 -49
  14. data/lib/dnsruby/config.rb +75 -4
  15. data/lib/dnsruby/deadline_queue.rb +75 -0
  16. data/lib/dnsruby/dnssec.rb +148 -45
  17. data/lib/dnsruby/ipv6.rb +45 -5
  18. data/lib/dnsruby/key_cache.rb +202 -35
  19. data/lib/dnsruby/message/decoder.rb +85 -19
  20. data/lib/dnsruby/message/encoder.rb +27 -16
  21. data/lib/dnsruby/message/header.rb +8 -12
  22. data/lib/dnsruby/message/message.rb +53 -22
  23. data/lib/dnsruby/message/section.rb +12 -34
  24. data/lib/dnsruby/name.rb +5 -2
  25. data/lib/dnsruby/packet_sender.rb +645 -203
  26. data/lib/dnsruby/presentation.rb +147 -0
  27. data/lib/dnsruby/query_control.rb +132 -0
  28. data/lib/dnsruby/recursor.rb +795 -339
  29. data/lib/dnsruby/resolver.rb +468 -186
  30. data/lib/dnsruby/resource/CAA.rb +67 -16
  31. data/lib/dnsruby/resource/DLV.rb +6 -0
  32. data/lib/dnsruby/resource/DNSKEY.rb +108 -16
  33. data/lib/dnsruby/resource/DS.rb +27 -28
  34. data/lib/dnsruby/resource/HTTPS.rb +16 -0
  35. data/lib/dnsruby/resource/IN.rb +19 -0
  36. data/lib/dnsruby/resource/MX.rb +2 -2
  37. data/lib/dnsruby/resource/NSEC.rb +6 -10
  38. data/lib/dnsruby/resource/NSEC3.rb +55 -19
  39. data/lib/dnsruby/resource/NSEC3PARAM.rb +4 -6
  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 +979 -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 +42 -20
  122. data/test/tc_res_opt.rb +21 -1
  123. data/test/tc_resolver.rb +21 -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 +105 -10
  167. data/.coveralls.yml +0 -2
data/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,307 @@
1
1
  # Release Notes
2
2
 
3
+ ## v2.0.0
4
+
5
+ Dnsruby 2.0 adds SVCB and HTTPS records and support for Ed25519 and Ed448 DNSSEC
6
+ signatures. It makes queries, TCP connections, zone transfers and DNSSEC
7
+ validation more reliable. It also bounds caches and validation work, updates
8
+ the root trust anchors, and improves performance when many queries are in
9
+ progress.
10
+
11
+ Most existing query code can stay as it is. Before upgrading, check the Ruby
12
+ requirement, TSIG settings and record persistence described below, followed by
13
+ any DNSSEC or timeout settings your application uses.
14
+
15
+ ### Compatibility with 1.74.0
16
+
17
+ * **Ruby 3.0 or later is required.** Upgrade older Ruby installations before
18
+ installing Dnsruby 2.0.
19
+ * **TSIG now defaults to HMAC-SHA256.** If you previously omitted the algorithm
20
+ and your server uses HMAC-MD5, update the key on both sides or select MD5
21
+ explicitly: `resolver.tsig = key_name, secret, 'hmac-md5'`. Explicit MD5
22
+ settings still work, but MD5 is prohibited by RFC 8945 section 6. Selecting it
23
+ logs one warning per process at WARN level; the default log level is FATAL.
24
+ SHA-1 remains supported, and SHA-256 is recommended. There is no automatic
25
+ fallback to another algorithm. See [updating TSIG keys](README.md#updating-tsig-keys)
26
+ for SHA-256 settings that also work with 1.74.0.
27
+ * **Automatically generated record classes no longer support Marshal/YAML
28
+ persistence.** Unknown type/class pairs now use a cache of at most 256 classes,
29
+ and those classes are no longer permanent Ruby constants. Ordinary named
30
+ classes such as `Dnsruby::RR::IN::A` are unaffected. Records and classes your
31
+ application holds remain usable even after their automatic `ClassHash` entry
32
+ is evicted. To customise a generated class, keep the result of
33
+ `Dnsruby::RR.get_class(type, klass)` and use `class_eval`. For a fixed set of
34
+ application-defined types that needs permanent names and normal Marshal
35
+ behavior, call `Dnsruby::RR::Generic.create(type, klass)` before creating records. These
36
+ explicit registrations stay in memory: do not use them for arbitrary types
37
+ received from the network. The 256-class limit excludes explicit registrations
38
+ and objects retained by your application or the response cache.
39
+ * **DNSSEC uses current algorithm policy.** Resolver validation treats SHA-1
40
+ signing algorithms 5 and 7 as unsupported under RFC 9905. Answers relying
41
+ solely on them become `INSECURE`, meaning they are not protected by an
42
+ accepted DNSSEC chain. Explicit cryptographic verification and historical
43
+ record decoding remain available. DSA algorithms 3 and 6 are also
44
+ unsupported for validation, as required by RFC 8624. An authenticated
45
+ delegation with only unsupported DS algorithms or digests is insecure; a
46
+ mixed set still requires validation through a supported DS. Within an
47
+ authenticated DS set, SHA-256 takes precedence over SHA-1. `DS.from_key` now
48
+ defaults to SHA-256 and rejects new DS records using SHA-1 digests or signing
49
+ algorithms 5 or 7. RSA keys with noncanonical encodings or sizes outside the
50
+ protocol limits are rejected. See [DNSSEC algorithm policy](README.md#dnssec-algorithm-policy).
51
+ Ed25519 and Ed448 validation require version 3.0 or later of the `openssl` gem and
52
+ an OpenSSL provider that supports it. Otherwise it follows the same rules
53
+ as other unsupported algorithms.
54
+ * **DNSSEC validation has work and time limits.** The defaults are 64 signature
55
+ attempts, 100,000 work units and five seconds per answer, including queries
56
+ needed to follow the chain of trust. A limit raises `ValidationLimitError`,
57
+ a subclass of `VerifyError`; it never turns a failed validation into an
58
+ insecure answer. Up to four workers validate responses at once, with another
59
+ 64 responses waiting. Applications with unusual validation workloads can
60
+ [adjust these limits](README.md#dnssec-validation-limits).
61
+ * **Recursive lookups have shared discovery limits.** A complete lookup now
62
+ defaults to 30 seconds, 128 transmission attempts, 256 discovery work units
63
+ and eight address-discovery worker threads. Supporting lookups share these
64
+ limits, and repeated dependencies on the same path are skipped. A shorter
65
+ Resolver timeout still takes precedence. Applications can configure
66
+ `Recursor#recursion_limits`; work or transmission exhaustion raises
67
+ `RecursionLimitError`. See [recursive lookup limits](README.md#recursive-lookup-limits).
68
+ * **Retries, failures and transfers have bounded lifetimes.** For each question,
69
+ RFC 9520 limits attempts to three per server address and transport, including
70
+ protocol retries; `retry_times` values above three are capped. Completed
71
+ failures are cached for one second, so an immediate repeat may return the
72
+ same error without a new exchange. The total `query_timeout` now includes
73
+ local DNSSEC validation. Reusable TCP connections close after ten idle
74
+ seconds by default; set `tcp_idle_timeout` to a positive number of seconds
75
+ to change this. Connections with outstanding queries stay open. ZoneTransfer
76
+ allows 30 seconds by default to read or write each complete frame; increase
77
+ `io_timeout` for slower transfers. Its optional `transfer_timeout` limits a
78
+ complete attempt with one server. See [response caching and retries](README.md#response-caching-and-retries)
79
+ and [zone transfer deadlines](README.md#zone-transfer-deadlines).
80
+ * **The default EDNS UDP payload size is 1232 bytes, down from 4096.** This
81
+ reduces fragmentation risk but can cause more TCP retries. Explicit sizes
82
+ such as 4096 still work. DNSSEC raises smaller limits to 1220 bytes, the
83
+ minimum in RFC 4035 section 4.1. The 1232-byte default is permitted by
84
+ RFC 10001 section 3.2.
85
+ * **SVCB and HTTPS are now parsed records.** Types 64 and 65 use their registered
86
+ names in text output and expose `priority`, `target` and `params`. Their
87
+ `data` and `rdata` readers return binary RDATA snapshots reflecting those
88
+ fields, and `rdlength` reports the length. To replace the data, use `data=`,
89
+ `rdata=` or `RR.create` with `rdata:`. The original decoded `@rdata` remains
90
+ available for existing callers such as JSONDNS, but this private field does
91
+ not track later edits. Malformed escapes are rejected. ALPN and DoC path
92
+ lists permit only escaped commas and backslashes at their second escape
93
+ layer; the `mandatory`, `port`, `ipv4hint`, `ech` and `ipv6hint` mnemonics
94
+ do not permit presentation escapes. Numeric `keyNNNNN` forms still accept
95
+ escaped wire octets. [PR #222](https://github.com/alexdalitz/dnsruby/pull/222).
96
+ * **Some invalid names and numeric values are now rejected.** DNS names longer
97
+ than 255 wire octets raise `ResolvError`, including some 256- and 257-octet
98
+ names previously accepted. Numeric type/class constructors and RRSIG
99
+ type-covered setters require values in 0–65535.
100
+ [PR #223](https://github.com/alexdalitz/dnsruby/pull/223).
101
+ * **Numeric and IPv6 text remain predictable.** Numeric spellings retain their
102
+ presentation on each object but no longer register global aliases or change
103
+ a code's preferred name. Explicit `Types.add_pair` and `Classes.add_pair`
104
+ aliases still work. Existing `IPv6#to_s`, `#inspect` and record presentations
105
+ keep their 1.74.0 output; use `IPv6#to_s_canonical` for RFC 5952 formatting.
106
+ * **The simpleidn range is now `>= 0.2.1`, `< 0.4.0`.** Updating from 0.2.x can
107
+ change Unicode-to-DNS name conversion. Check the results if your application
108
+ depends on exact converted names.
109
+ [PR #224](https://github.com/alexdalitz/dnsruby/pull/224).
110
+
111
+ Zone files now resolve relative `$ORIGIN` directives and SOA names against the
112
+ current origin, including names with an escaped final dot. TXT records preserve
113
+ UTF-8 and escaped bytes through wire encoding, and their 255-octet string limit
114
+ is checked before writing. ZoneReader treats nonnumeric escapes literally under
115
+ DNS zone-file rules, including `\n` as `n`; direct `RR::TXT.parse` and
116
+ `RR.create` retain their legacy C-style escapes for compatibility. Escaped
117
+ whitespace also works in `$ORIGIN`, MX and NSEC names.
118
+ DS, CDS and DLV records preserve private algorithm
119
+ numbers and unknown digest numbers. Name compression also keeps literal dots
120
+ inside labels distinct from the dots that separate labels.
121
+
122
+ ### More reliable queries and transfers
123
+
124
+ * TCP pipelining handles concurrent sends, immediate replies, partial writes
125
+ and disconnected servers. Connection setup, writes and waiting for a pipeline
126
+ slot all respect the controlling query's deadline and cancellation. Malformed
127
+ replies no longer interrupt other queries or cause endless TCP retries.
128
+ UDP reads cannot block after a readiness notification, and changes to TCP
129
+ pipelining take effect consistently on subsequent attempts. Updating a source
130
+ port pool no longer briefly exposes an empty pool to concurrent sends.
131
+ Invalid replacements leave the previous configuration intact, and an occupied
132
+ port no longer prevents a send from trying the other configured ports.
133
+ Each attempt retains one transport configuration snapshot through connection
134
+ setup, reply matching and caching. Adding an explicit source port replaces
135
+ the default `[0]` pool; zero cannot be mixed with explicit ports.
136
+ A zero retry delay keeps every scheduled nameserver attempt.
137
+ [#155](https://github.com/alexdalitz/dnsruby/issues/155).
138
+ * Cancellation and timeouts wake waiting queries promptly, including while
139
+ validation or nameserver discovery is running. Late replies or callbacks
140
+ cannot complete an expired query or affect a later query that reuses its ID.
141
+ Once validation starts, another nameserver's reply cannot replace the accepted
142
+ answer. Closing a client's completion Queue no longer interrupts other
143
+ resolvers. After `fork`, the child uses fresh connections and query state;
144
+ the parent's pending work remains with the parent.
145
+ * Ordinary queries reject replies with the wrong ID, opcode or query/response
146
+ flag; `send_raw` retains its existing behavior. UDP-to-TCP fallback still
147
+ respects `no_tcp` and `ignore_truncation`. Retrying without optional EDNS keeps
148
+ the current transport, and TCP can handle a FORMERR reply without a question;
149
+ UDP still requires a matching question. Queries requiring local DNSSEC
150
+ validation keep EDNS enabled. Setting DO on an existing OPT preserves its
151
+ payload size, options and other flags.
152
+ * Hostname nameservers no longer cause recursive configuration hangs. Existing
153
+ queries continue while replacement nameservers are resolved, and concurrent
154
+ configuration changes are preserved. IPv6 link-local nameservers accept
155
+ interface zones such as `fe80::1%eth0`, including in `resolv.conf`. Recursive
156
+ discovery tries other nameservers and address families after ordinary lookup
157
+ failures, within the same cancellation and validation limits. A Recursor
158
+ honors the transport and source settings of its supplied Resolver and applies
159
+ its query timeout to the complete lookup. Changes to DNSSEC root hints take
160
+ effect on subsequent lookups. Root hints are also refreshed when all their
161
+ cached addresses expire, including during a lookup. Root NS TTLs separately
162
+ bound bootstrap reuse, partial priming replies trigger discovery of missing
163
+ root addresses, and zero-TTL priming addresses remain usable by their current
164
+ lookup. Ancestor lookup preserves escaped label boundaries and relative names.
165
+ [#179](https://github.com/alexdalitz/dnsruby/issues/179),
166
+ [#184](https://github.com/alexdalitz/dnsruby/issues/184),
167
+ [PR #221](https://github.com/alexdalitz/dnsruby/pull/221).
168
+ * A SingleResolver keeps only its selected server after initialization and
169
+ later `server=` changes. A Resolver with `retry_times = 0` completes with
170
+ a timeout immediately instead of leaving an empty retry plan waiting.
171
+ * Zone transfers complete partial writes, check response envelopes and require
172
+ matching opening and closing SOAs. TSIG transfers authenticate the whole
173
+ sequence, permit up to 99 unsigned messages between signatures and reject
174
+ decreasing signed timestamps. Reusing a ZoneTransfer gives each request a
175
+ fresh signing time unless one was set explicitly.
176
+
177
+ ### Authentication, DNSSEC and caching
178
+
179
+ * Positive answers are kept separate for different upstream servers, ports,
180
+ source settings and query options. Truncated replies are not cached. A
181
+ recursor follows one delegation that encloses the question and still accepts
182
+ valid sibling glue from the parent zone.
183
+
184
+ * TSIG queries always require a fresh authenticated exchange and bypass the
185
+ shared response cache. Unsigned replies and invalid MACs are ignored while
186
+ waiting for a valid reply; if none arrives, the timeout reports the
187
+ authentication error. Authenticated BADTIME and BADTRUNC replies report the
188
+ server's error. Queries are prepared before signing, including when the key
189
+ is set on a Message; already signed messages retain their headers and OPT
190
+ record. Verification checks the MAC before timestamps and truncation policy,
191
+ and uses the Original ID as required by RFC 8945. Valid truncated MACs are
192
+ supported; `mac_length` sets the signing length and minimum accepted length
193
+ in octets. Malformed TSIG records are rejected, and key secrets are no longer
194
+ written to the log.
195
+ * Root trust anchors include KSK-2024 (38696) and remove revoked KSK-2010
196
+ (19036). `Dnssec.reset` restores the current anchors.
197
+ `Dnssec.clear_trusted_keys` clears learned keys and DS/DLV anchors while
198
+ retaining configured DNSKEY anchors. Removing or expiring a configured anchor
199
+ also removes keys learned from it. Keys sharing a tag can coexist, and an
200
+ unusable matching key does not prevent another from validating the answer.
201
+ DS matching accepts the same owner name with or without a trailing dot,
202
+ including when the DS is a configured trust anchor.
203
+ Learned DNSKEY and DS records have size limits, and validation looks up only
204
+ matching candidates. DS anchors now honor their requested expiration times,
205
+ just as DNSKEY anchors do. Direct RRset verification can try another trusted
206
+ verifier when the first has no suitable key.
207
+ [#225](https://github.com/alexdalitz/dnsruby/issues/225).
208
+ * Signed aliases can lead to authenticated unsigned zones and correctly return
209
+ `INSECURE`. CNAME and DNAME chains validate through to the terminal answer,
210
+ including negative answers, wildcard NODATA and NSEC3 Opt-Out delegations.
211
+ A synthesized CNAME must match its signed DNAME; valid responses that omit
212
+ the synthesized CNAME also work. Cross-zone aliases discover the target's
213
+ chain within the same validation budget. An unsigned delegation requires
214
+ authenticated proof: lookup failures cannot become insecure results.
215
+ A provisional insecure classification never suppresses a required record's
216
+ verification error; unsigned alias responses check every required owner and
217
+ the terminal name. Signed records discover their signer zones without extra
218
+ DS queries for each record owner.
219
+ Signer zones, DNSKEY Zone Key flags and DS owner/class bindings are checked.
220
+ A key authenticating a DNSKEY RRset must also be present in that set.
221
+ Missing NSEC or RRSIG bitmap bits do not prove those types absent.
222
+ NSEC3 records with unsupported flags are ignored as denial proofs while
223
+ supported records remain usable.
224
+ * Signed Additional records are verified when their keys are available.
225
+ Unverifiable signed extras are removed without rejecting an independently
226
+ authenticated answer. Unsigned glue remains available without acquiring
227
+ authentication metadata from records in another section; unverified extras
228
+ do not enter the trusted-key cache. Validation and cancellation limits still
229
+ apply to optional data.
230
+ * Validated answers and cached copies expire according to their original
231
+ receipt time, signatures and chain of trust. Revalidation does not renew
232
+ those lifetimes, and failed or BOGUS validation is never cached as successful.
233
+ Trying another verifier preserves a successful insecure result's authenticated
234
+ lifetime, so negative answers can still be cached under the default policy.
235
+ Bounded negative caches use the SOA lifetime required by RFC 2308; failure
236
+ caches follow RFC 9520. Entries respect the question class, configuration and
237
+ trust changes. Cancellation, raw messages, UPDATE and TSIG bypass these new
238
+ negative and failure caches. Recursive delegation caches are also bounded,
239
+ and delegation and glue-address lifetimes are tracked separately. Cached
240
+ child routes cannot outlive the parent route through which they were learned;
241
+ parent-cache eviction alone does not discard an already admitted child.
242
+ * Changing a DNSKEY's key or algorithm clears its cached public key.
243
+ `verify_rrset` accepts a single DS/DLV anchor as well as an RRSet. NSEC3 and
244
+ NSEC3PARAM correctly handle unsigned length octets and retain salt lengths.
245
+
246
+ ### Records, parsing and performance
247
+
248
+ * SVCB and HTTPS work in DNS messages and zone files, including relative targets,
249
+ numeric `keyNNNNN` parameters and escaped text. IPv6 hints use RFC 5952 text.
250
+ * CAA accepts extensible alphanumeric tags and empty or binary values, including
251
+ an empty `issue` value. Wire decoding preserves the value independently of
252
+ presentation parsing. Private RRSIG algorithm numbers round-trip unchanged.
253
+ Decoded CAA records retain the historical String form of `rdata`, including
254
+ comparisons with text-created records and existing JSONDNS serialization.
255
+ Received ordinary TTLs with their high bit set become zero under RFC 2181;
256
+ OPT fields and TSIG's zero-TTL rule retain their special handling. Mandatory
257
+ names require their wire encoding, while valid empty UPDATE markers still
258
+ round-trip. DS, CDS and DLV equality and hashing agree for digest case variants.
259
+ * Zone-file TTLs and SOA intervals accept combined time suffixes such as
260
+ `1h30m`. Both class-before-TTL and TTL-before-class formats work. Quoted,
261
+ escaped and multiline values preserve their punctuation, Unicode and binary
262
+ character strings, including single-quoted TXT and SPF values. An explicit
263
+ record TTL leaves the `$TTL` default in place for later records. Relative
264
+ names in KX, NSEC, NXT, RRSIG, IPSECKEY and HIP data now use the zone origin.
265
+ Zone files also accept RFC 3597 hexadecimal data for known record types
266
+ without changing its bytes.
267
+ * Query scheduling, retries and cancellation avoid repeatedly scanning every
268
+ pending query. TCP reply matching is faster when many queries share a
269
+ connection. DNSSEC reuses canonical data and verifies reusable DS, DNSKEY
270
+ and wildcard proof sets once per message. Cache hits spend less time holding
271
+ the cache lock, and record parsing and numeric lookups allocate fewer
272
+ temporary objects. Unknown-record class handling and replacement no longer
273
+ accumulate memory or slow garbage collection on Ruby 3.0–3.2.
274
+ Name compression shares immutable label copies between suffixes, reducing
275
+ allocations while preserving case and escaped label boundaries.
276
+ RRset grouping uses an index and avoids cloning unrelated records. It
277
+ preserves section order and TTL normalization, and charges validation work
278
+ while grouping records and checking duplicates.
279
+ Canonical RRset sorting also avoids repeated duplicate scans, and binary
280
+ message decoding avoids temporary one-byte strings while retaining bounds
281
+ checks, compression safeguards and legacy nonbinary input behavior.
282
+ * Recursive answers now complete CNAME and DNAME chains within the original
283
+ lookup's deadline and work limits. Zero-TTL nameserver addresses are usable
284
+ during the lookup that obtained them without entering shared caches. DNSSEC
285
+ work and time limits are shared across the lookup's network hops, parallel
286
+ address discovery and final answer validation.
287
+ * DNSSEC validation evaluates applicable trust anchors before accepting a
288
+ provisional insecure result. RRSIG validity and learned-key expiration use
289
+ the serial-number arithmetic required for timestamps that cross the
290
+ 32-bit boundary.
291
+ * SRV targets, DNAME targets, RRSIG signer names and NSEC successor names are
292
+ encoded without name compression. Valid empty UPDATE prerequisites and
293
+ deletions work for all applicable record types. Zone-file owner names retain
294
+ escaped spaces and quotes. Decoding reuses validated compression suffixes
295
+ within each packet while keeping returned names independently mutable.
296
+ * `RRSet#add` accepts another RRSet, and deleting signatures keeps the signature
297
+ count correct. Bulk duplicate checks avoid repeatedly encoding every record
298
+ already in a set while preserving mutable record behavior.
299
+ * Replacing a SingleResolver's server preserves concurrent configuration
300
+ changes. TCP replies report their byte size. Zone transfers retain verified
301
+ BADTIME and BADTRUNC diagnostics and no longer allocate unused IXFR state
302
+ for each received record.
303
+
304
+
3
305
  ## v1.74.0
4
306
 
5
307
  * Resolve all configured nameservers in parallel
@@ -293,4 +595,3 @@
293
595
  * Minor bug fixes
294
596
  * IPSECKEY RR added
295
597
  * Clients can now manipulate Name::Labels
296
-
data/Rakefile CHANGED
@@ -12,9 +12,6 @@ if ENV['RUN_EXTRA_TASK'] == 'TRUE'
12
12
  rd.main = "Dnsruby"
13
13
  # rd.options << "--ri"
14
14
  end
15
-
16
- require 'coveralls/rake/task'
17
- Coveralls::RakeTask.new
18
15
  end
19
16
 
20
17
  def create_task(task_name, test_suite_filespec)
data/demo/axfr.rb CHANGED
@@ -31,6 +31,11 @@
31
31
  # output, and stores the zone to a file. If the zone has already been
32
32
  # stored in a file, axfr will read the file instead of performing a
33
33
  # zone transfer.
34
+ #
35
+ # Records with dynamically generated classes cannot be stored with Marshal.
36
+ # Such transfers are printed without updating the cache; use -f to transfer
37
+ # again if an older cache exists. Unreadable legacy dynamic-record caches are
38
+ # ignored and the zone is transferred again.
34
39
  #
35
40
  # Zones will be stored in a directory hierarchy. For example, the
36
41
  # zone transfer for foo.bar.com will be stored in the file
@@ -68,7 +73,7 @@ unless (1..2).include?(ARGV.length)
68
73
  end
69
74
 
70
75
 
71
- require 'getoptLong'
76
+ require 'getoptlong'
72
77
  require 'dnsruby'
73
78
 
74
79
 
@@ -134,16 +139,22 @@ end
134
139
  # ------------------------------------------------------------------------------
135
140
 
136
141
  if FileTest.exist?(zonefile) && !opt_f
137
- zoneref = Marshal.load(File.open(zonefile))
138
- if zoneref.nil?
139
- raise RuntimeError, "couldn't retrieve zone from #{zonefile}: #{$!}\n"
142
+ begin
143
+ zoneref = File.open(zonefile, 'rb') { |file| Marshal.load(file) }
144
+ if zoneref.nil?
145
+ raise RuntimeError, "couldn't retrieve zone from #{zonefile}: #{$!}\n"
146
+ end
147
+ rescue ArgumentError => error
148
+ raise unless error.message.match?(/\Aundefined class\/module Dnsruby::RR::Generic::Type\d+_Class\d+\z/)
149
+ warn "Cannot read dynamic records from #{zonefile}; transferring the zone again."
150
+ opt_f = true
140
151
  end
141
152
 
142
153
  # ----------------------------------------------------------------------
143
154
  # Check the SOA serial number if desired.
144
155
  # ----------------------------------------------------------------------
145
156
 
146
- if opt_s
157
+ if opt_s && !opt_f
147
158
  serial_file = serial_zone = nil
148
159
 
149
160
  zoneref.each do |rr|
@@ -185,7 +196,14 @@ if opt_f
185
196
  if zoneref.nil?
186
197
  raise RuntimeError, "couldn't transfer zone\n"
187
198
  end
188
- Marshal.dump(zoneref, File.open(zonefile, File::CREAT | File::RDWR))
199
+ begin
200
+ cache = Marshal.dump(zoneref)
201
+ rescue TypeError => error
202
+ raise unless error.message.start_with?("can't dump anonymous class")
203
+ warn "Zone transferred but dynamic records cannot be cached; #{zonefile} was not changed. Use -f to transfer again."
204
+ else
205
+ File.binwrite(zonefile, cache)
206
+ end
189
207
  end
190
208
 
191
209
  # ------------------------------------------------------------------------------
data/demo/rubydig.rb CHANGED
@@ -60,8 +60,6 @@ if ARGV[0] =~ /^@/
60
60
  else
61
61
  puts "Setting nameserver : #{nameserver}"
62
62
  resolver.nameserver = (nameserver.sub(/^@/, ''))
63
- resolver.dnssec = true
64
- resolver.do_validation = true
65
63
  puts "nameservers = #{resolver.config.nameserver}"
66
64
  zone_transfer.server = (nameserver.sub(/^@/, ''))
67
65
  end
data/dnsruby.gemspec CHANGED
@@ -14,10 +14,10 @@ SPEC = Gem::Specification.new do |s|
14
14
  'Dnsruby is a pure Ruby DNS client library which implements a
15
15
  stub resolver. It aims to comply with all DNS RFCs, including
16
16
  DNSSEC NSEC3 support.'
17
- s.license = "Apache License, Version 2.0"
17
+ s.license = "Apache-2.0"
18
18
 
19
19
  # Add explicit Ruby version requirement
20
- s.required_ruby_version = '>= 2.8.0'
20
+ s.required_ruby_version = '>= 3.0.0'
21
21
 
22
22
  s.files = `git ls-files -z`.split("\x0")
23
23
 
@@ -40,14 +40,15 @@ DNSSEC NSEC3 support.'
40
40
 
41
41
  s.add_development_dependency 'rake', '>= 13.0.6'
42
42
  s.add_development_dependency 'minitest', '~> 5.25'
43
+ # Retained for the legacy localdns/test_dnsserver helpers; rake soak uses stdlib.
43
44
  s.add_development_dependency 'rubydns', '>= 2.0.2'
44
45
  s.add_development_dependency 'nio4r', '>= 2.5.8'
45
46
  s.add_development_dependency 'minitest-display', '>= 0.3.1'
46
47
  s.add_development_dependency 'rdoc', '>= 6.5', '< 8.0'
47
48
  s.add_development_dependency 'yard', '>= 0.9'
48
- s.add_development_dependency 'coveralls', '~> 0.8.23'
49
+ s.add_development_dependency 'simplecov', '~> 0.22.0'
49
50
 
50
51
  s.add_runtime_dependency 'base64', '>= 0.2'
51
52
  s.add_runtime_dependency 'logger', '~> 1.6'
52
- s.add_runtime_dependency 'simpleidn', '~> 0.2.1'
53
+ s.add_runtime_dependency 'simpleidn', '>= 0.2.1', '< 0.4.0'
53
54
  end