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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 31fc09cb1cf4b1c2174c67a16e96601eb25c6509e6525c3cef9159da5fd656c2
4
- data.tar.gz: e96495f35da0ff50636052f3f27623b4fc620572c0c4109f6c47a5a81eb77801
3
+ metadata.gz: 4ce202a48aff5609526c43e70ca92eebfeff66a78aa041f78e4915c1fd5a73e7
4
+ data.tar.gz: a0daa3de8611482fe3352c36aa6966504ab66dea196d4587604bc75af615cceb
5
5
  SHA512:
6
- metadata.gz: 84c58dc7dbf8d3c33d11ee7ea10d076d806cf1700c327b5e84fbebac08484fba8179734dbe9631c85daec38518b29ca3d8e3a29629d71a69a955f615bdf60346
7
- data.tar.gz: 06b7c82e2b1e979f1c18aaa9567596f6724a7fb5eae8da688921958506ab9cc9ed1657f168699ec6a0112c3c25afcba419d333b7f3b4f618537fa6e6f1ee91c7
6
+ metadata.gz: ca80e16585009122a9026e86932b57a21431259ebbb59addca7bb256b23eaef8bc050f2ae21575bac49e3d872e986adb1a11442c176de65d2586c5e68ee94b01
7
+ data.tar.gz: 199c5067d450e346e1d3ffb4e16cd2160d5c0cb1e6d53e93b9e0f58ae97beb733f85e34c35be8b7c398c8cf7389fc68b0f1466b1024d7d9ac085f52f9255418e
@@ -5,22 +5,34 @@ on: [push, pull_request]
5
5
  jobs:
6
6
  build:
7
7
  runs-on: ubuntu-latest
8
+ timeout-minutes: 15
8
9
  strategy:
9
10
  fail-fast: false
10
11
  matrix:
11
- ruby: [ '3.2.10', '3.3.11', '3.4.10', '4.0.5' ] # , 'ruby-head' ]
12
+ ruby: [ '3.0', '3.1', '3.2', '3.3', '3.4', '4.0' ] # , 'ruby-head' ]
12
13
  rubyopt: ['']
13
14
  include:
14
15
  - ruby: '3.4'
15
16
  rubyopt: "--enable-frozen-string-literal --debug-frozen-string-literal"
16
17
 
17
- name: Ruby ${{ matrix.ruby }} tests
18
+ name: Ruby ${{ matrix.ruby }} tests${{ matrix.rubyopt != '' && ' (frozen strings)' || '' }}
18
19
  steps:
19
- - uses: actions/checkout@v2
20
+ - uses: actions/checkout@v7
20
21
  - name: Setup Ruby
21
22
  uses: ruby/setup-ruby@v1
22
23
  with:
23
24
  ruby-version: ${{ matrix.ruby }}
24
25
  bundler-cache: true
25
26
  - name: Run tests ${{ matrix.rubyopt }}
26
- run: bundle exec rake test RUBYOPT="${{ matrix.rubyopt }}"
27
+ timeout-minutes: 10
28
+ env:
29
+ DNSRUBY_TEST_TIMEOUT: '120'
30
+ run: timeout --kill-after=10s 8m bundle exec rake test RUBYOPT="${{ matrix.rubyopt }}"
31
+ - name: Upload coverage
32
+ if: matrix.ruby == '3.4' && matrix.rubyopt == ''
33
+ uses: coverallsapp/github-action@v2
34
+ with:
35
+ file: coverage/.resultset.json
36
+ format: simplecov
37
+ # Reporting outages must not change the test result.
38
+ fail-on-error: false
data/DNSSEC CHANGED
@@ -9,11 +9,12 @@ The dnssec security status of a message is stored in Message#security_level (def
9
9
 
10
10
  It is possible to tell Dnsruby to use a Recursor or a defined (or system default) Resolver to perform the validation. The default is to use a Recursor, as many systems are behind dodgy servers which mangle the DNS records. Using a Recursor means that only authoritative nameservers are queried for the DNSSEC records.
11
11
 
12
- In the absence of a signed root, Dnsruby has no trust anchor to validate messages against. It is possible to manually configure dnsruby with individual trust ancors. It is also possible to import a trust anchor repository (such as the one maintained by IANA), and configure the ISC DLV registry. Dnsruby contains basic methods to do this, although they are not currently secured. Clients are recommended to develop their own means of obtaining the initial trust anchors.
12
+ Dnsruby includes the IANA root DS trust anchors for KSK-2017 (key tag 20326) and KSK-2024 (key tag 38696). KSK-2024 began signing the root DNSKEY RRSet on 11 October 2026. The revoked KSK-2010 (key tag 19036) is no longer trusted. Applications may also configure local DNSKEY or DS trust anchors using Dnsruby::Dnssec.add_trust_anchor. Obtain trust anchors through a trusted channel; IANA publishes the root anchors and their validity periods at https://www.iana.org/dnssec/files. Dnsruby does not automatically update these configured anchors using RFC 5011, so they must be kept current through software or application updates.
13
13
 
14
14
  It is possible to turn off dnssec validation on a per-message basis. Simply set Message#do_validation to false.
15
15
 
16
- DNSSEC is on by default - if desired, you can turn it off with the dnssec flag in Dnsruby::(Single)Resolver if desired. EDNS0 support is also enabled by default - if desired, you can turn this off by setting the Dnsruby::(Single)Resolver#udp_packet_size property to be 512. There should generally be no need to do this.
16
+ Resolver#do_validation defaults to false; set it to true to enable local DNSSEC validation. The dnssec flag controls requesting DNSSEC records and is distinct from local validation. Recursor#query attempts validation unless its no_validation argument is true. Missing root anchors affect queries that need to establish trust from the root; an application using local trust anchors or previously authenticated cached keys may behave differently.
17
17
 
18
18
  Dnsruby maintains a cache of responses, and a cache of trusted keys. Once the initial keys have been downloaded, and a set of trusted keys built up, very little overhead is required to enjoy the benefits of DNSSEC. There is, however, some initial cost (to build up the caches).
19
19
 
20
+ Dnsruby::Dnssec.clear_trusted_keys clears learned DNSKEYs and all DS/DLV anchors, including the built-in root DS anchors; configured DNSKEY anchors are retained. To remove local anchors and built-in root trust, call clear_trust_anchors followed by clear_trusted_keys. Use remove_trust_anchor or clear_trust_anchors to remove local anchors explicitly. Dnsruby::Dnssec.reset restores the built-in root anchors, clears local anchors and cached keys, and resets the validation policy and resolver settings to their defaults.
data/EXAMPLES CHANGED
@@ -48,7 +48,7 @@ tsig = Dnsruby::RR.create({
48
48
  :type => "TSIG",
49
49
  :ttl => 0,
50
50
  :klass => "ANY",
51
- :algorithm => "hmac-md5",
51
+ :algorithm => "hmac-sha256", # must match the server's key configuration
52
52
  :fudge => 300,
53
53
  :key => "8n6gugn4aJ7MazyNlMccGKH1WxD2B3UvN/O/RA6iBupO2/03u9CTa3Ewz3gBWTSBCH3crY4Kk+tigNdeJBAvrw==",
54
54
  :error => 0
data/README.md CHANGED
@@ -1,96 +1,402 @@
1
- [![Build Status](https://github.com/alexdalitz/dnsruby/actions/workflows/ci.yml/badge.svg)]
1
+ [![Build Status](https://github.com/alexdalitz/dnsruby/actions/workflows/ci.yml/badge.svg)](https://github.com/alexdalitz/dnsruby/actions/workflows/ci.yml)
2
2
  [![Coverage Status](https://img.shields.io/coveralls/alexdalitz/dnsruby.svg)](https://coveralls.io/r/alexdalitz/dnsruby?branch=master)
3
3
 
4
4
  <img src="http://caerkettontech.com/dnsruby/DNSRuby-colour-mid.png" width="200" height="200" />
5
5
 
6
- Dnsruby
7
- =======
6
+ # Dnsruby
8
7
 
9
- Dnsruby is a pure Ruby DNS client library which implements a
10
- stub resolver. It aims to comply with all DNS RFCs.
8
+ Dnsruby is a pure Ruby DNS client library. Its stub resolver sends queries to
9
+ configured DNS servers, with an API based on Ruby's `Resolv` library and extended
10
+ to support DNSSEC, dynamic updates and zone transfers. It aims to comply with
11
+ the DNS RFCs.
11
12
 
12
- Dnsruby presents an enhanced API for DNS. It is based on Ruby's core
13
- resolv.rb Resolv API, but has been much extended to provide a
14
- complete DNS implementation.
13
+ **Upgrading from 1.74.0:** [Read the v2 upgrade guide](#upgrading-to-v2) for changed
14
+ defaults and compatibility notes.
15
15
 
16
- Dnsruby runs a single I/O thread to handle all concurrent
17
- queries. It is therefore suitable for high volume DNS applications.
16
+ ## Getting started
18
17
 
19
- The following is a (non-exhaustive) list of features :
18
+ Install the gem:
20
19
 
21
- - Implemented RRs : A, AAAA, AFSDB, ANY, CAA, CERT, CNAME, DNAME,
22
- GPOS, HINFO, ISDN, LOC, MB, MG, MINFO, MR, MX, NAPTR, NS, NSAP,
23
- NXT, OPT, PTR, PX, RP, RT, SOA, SPF, SRV, TKEY, TSIG, TXT,
24
- WKS, X25, DNSKEY, RRSIG, NSEC, NSEC3, NSEC3PARAM, DS, DLV
20
+ ```sh
21
+ gem install dnsruby
22
+ ```
23
+
24
+ Or add it to your Gemfile and run `bundle install`:
25
+
26
+ ```ruby
27
+ gem 'dnsruby'
28
+ ```
29
+
30
+ To query the nameservers configured on your system:
31
+
32
+ ```ruby
33
+ require 'dnsruby'
34
+
35
+ resolver = Dnsruby::Resolver.new
36
+ begin
37
+ response = resolver.query('example.com', 'A')
38
+ response.answer.each { |record| puts record }
39
+ ensure
40
+ resolver.close
41
+ end
42
+ ```
43
+
44
+ The second argument selects the record type: for example, `'AAAA'`, `'MX'` or
45
+ `'TXT'`. If you leave it out, the query asks for an A record. Reuse a resolver
46
+ for multiple queries, then close it when you have finished.
47
+
48
+ See the [API documentation](https://www.rubydoc.info/gems/dnsruby/),
49
+ [usage articles in the wiki](https://github.com/alexdalitz/dnsruby/wiki), and
50
+ [demo programs](demo) for more examples.
51
+
52
+ ### Dependencies
53
+
54
+ Dnsruby requires **Ruby 3.0 or later**. RubyGems installs the `base64`, `logger`
55
+ and `simpleidn` dependencies. TSIG signing and DNSSEC validation also require
56
+ Ruby's OpenSSL library.
57
+
58
+ ### Demo code
59
+
60
+ The [demo folder](demo) includes `rubydig`, a basic dig-style command-line tool,
61
+ and examples of resolving many names concurrently.
62
+
63
+ ## Usage help
64
+
65
+ ### IPv6 nameservers and address formatting
66
+
67
+ An IPv6 link-local nameserver needs an interface name or index after `%`, so
68
+ the operating system knows which network interface to use:
69
+
70
+ ```ruby
71
+ resolver = Dnsruby::Resolver.new(nameserver: 'fe80::1%en0')
72
+ begin
73
+ response = resolver.query('example.com')
74
+ ensure
75
+ resolver.close
76
+ end
77
+ ```
78
+
79
+ Replace the address and `en0` with your DNS server's address and local interface.
80
+ The same format works in `resolv.conf`, for example `nameserver fe80::1%en0`.
81
+ The interface suffix is only for local routing; do not put it in an AAAA record.
25
82
 
26
- - Generic RR types supported (RFC3597)
83
+ IPv6 text output is unchanged from 1.74.0. To request the standard RFC 5952
84
+ format explicitly, use:
27
85
 
28
- - (Signed) Zone transfer (AXFR and IXFR) supported
86
+ ```ruby
87
+ Dnsruby::IPv6.create('2001:DB8::1').to_s_canonical # => '2001:db8::1'
88
+ ```
89
+
90
+ ## Features
91
+
92
+ - DNSSEC validation.
93
+ - Zone transfers (AXFR and IXFR), including TSIG-signed transfers.
94
+ - Dynamic updates, including TSIG-signed updates.
95
+ - Unknown record types through the generic record API (RFC 3597).
96
+ - Record types including A, AAAA, AFSDB, ANY, CAA, CERT, CNAME, DNAME, GPOS,
97
+ HINFO, ISDN, LOC, MB, MG, MINFO, MR, MX, NAPTR, NS, NSAP, NXT, OPT, PTR, PX,
98
+ RP, RT, SOA, SPF, SRV, SVCB, HTTPS, TKEY, TSIG, TXT, WKS, X25, DNSKEY, RRSIG,
99
+ NSEC, NSEC3, NSEC3PARAM, DS and DLV.
29
100
 
30
- - (Signed) Dynamic updates supported
101
+ ## Upgrading to v2
31
102
 
32
- - DNSSEC validation supported
103
+ Version 2 adds SVCB and HTTPS records and improves concurrent queries, TCP
104
+ connections, DNSSEC validation, caching and zone-file parsing. Start with the
105
+ checks below; the [release notes](RELEASE_NOTES.md#compatibility-with-1740)
106
+ describe the compatibility changes in full.
33
107
 
34
- Dependencies
35
- ------------
108
+ ### Check these before upgrading
36
109
 
37
- Dnsruby can run with no dependencies. However, if you wish to
38
- use TSIG or DNSSEC then the OpenSSL library must be available.
39
- This is a part of the Ruby standard library, but appears not to
40
- be present on all Ruby platforms. If it is not available, then
41
- the test code will not run the tests which require it. Code which
42
- attempts to use the library (if it is not present) will raise an
43
- exception.
110
+ 1. **Use Ruby 3.0 or later.** Older Ruby versions are no longer supported.
111
+ 2. **Check TSIG keys if you sign updates or transfers.** An omitted algorithm
112
+ now means HMAC-SHA256, rather than HMAC-MD5. The application and server must
113
+ agree. See [updating TSIG keys](#updating-tsig-keys).
114
+ 3. **Check saved records of unknown types.** Automatically generated record
115
+ classes no longer have permanent Ruby names, so saving them with Marshal or
116
+ YAML is unsupported. Ordinary named classes such as `Dnsruby::RR::IN::A`
117
+ are unaffected. For a small, fixed set of application-defined types,
118
+ `Dnsruby::RR::Generic.create(type, klass)` can register permanent classes
119
+ before records are created. See the [persistence notes](RELEASE_NOTES.md#compatibility-with-1740)
120
+ for details.
121
+ 4. **Review DNSSEC results and DS creation if you use them.** Legacy SHA-1
122
+ signing algorithms are no longer accepted by resolver validation, and new
123
+ DS records use SHA-256 by default. See [DNSSEC algorithm policy](#dnssec-algorithm-policy).
124
+ 5. **Review any custom retry or transfer settings.** Retries are capped at
125
+ three per question, server address and transport. A zone transfer now has a
126
+ 30-second limit for reading or writing each complete DNS message. See
127
+ [retries](#response-caching-and-retries) and [transfer deadlines](#zone-transfer-deadlines).
44
128
 
45
- Demo Code
46
- ---------
129
+ The default UDP payload size is also smaller: **1232 bytes, down from 4096**.
130
+ This reduces fragmentation but can cause more large replies to retry over TCP.
131
+ An explicit `udp_size` setting still works. Existing IPv6 text output is preserved.
47
132
 
48
- The demo folder contains some example programs using Dnsruby.
49
- These examples include a basic dig tool (rubydig) and a tool to
50
- concurrently resolve many names, amongst others.
133
+ ### Updating TSIG keys
51
134
 
52
- Unit Tests
53
- ----------
135
+ The key name, secret and algorithm must match the DNS server's configuration.
136
+ SHA-256 is the default in v2. If the server currently uses MD5, migrate both
137
+ sides to SHA-256 or explicitly name the old algorithm during the transition.
54
138
 
55
- Tests require a current version of minitest (see the .gemspec file
56
- for which version is required). In order for the tests to run
57
- successfully you may need to have the bundler gem installed and
58
- run `bundle` or `bundle install` from the project root to install
59
- a suitable version of minitest.
139
+ For BIND, generate a SHA-256 key with
140
+ [tsig-keygen](https://bind9.readthedocs.io/en/stable/manpages.html#tsig-keygen-tsig-key-generation-tool):
60
141
 
61
- There are "online" and "offline" tests. You can use rake to
62
- conveniently run the tests. From the project root you can run:
142
+ ```sh
143
+ tsig-keygen -a hmac-sha256 dnsruby-key.example.
63
144
  ```
64
- rake test # run all tests
65
- rake test_offline # run only offline tests
66
- rake test_online # run only online tests
145
+
146
+ Install the key on the server, grant the required update or transfer permissions,
147
+ and reload its configuration. Update any scripts that generate keys as well.
148
+ Then configure the application with the same name and base64-encoded secret:
149
+
150
+ ```ruby
151
+ resolver.tsig = ['dnsruby-key.example.', secret, 'hmac-sha256']
67
152
  ```
68
- If you get the following error when running rake test tasks,
69
- then you may need to preface the command with bundle exec to
70
- ensure that the gem versions specified in Gemfile.lock are used
71
- at runtime:
72
153
 
154
+ Or set the key on an individual message:
155
+
156
+ ```ruby
157
+ message.set_tsig(name: 'dnsruby-key.example.', key: secret,
158
+ algorithm: 'hmac-sha256')
73
159
  ```
74
- bundle exec rake test
160
+
161
+ These explicit SHA-256 settings also work in 1.74.0, so you can migrate the keys
162
+ before upgrading Dnsruby. Keep the generated secret private.
163
+
164
+ If you must keep an existing MD5 key temporarily, add the algorithm explicitly:
165
+
166
+ ```ruby
167
+ resolver.tsig = [key_name, secret, 'hmac-md5']
75
168
  ```
76
169
 
77
- Usage Help
78
- ----------
170
+ In particular, replace existing two-element settings such as
171
+ `resolver.tsig = [key_name, secret]`: they now select SHA-256. For a message,
172
+ use `algorithm: 'hmac-md5'` in the `set_tsig` hash.
173
+
174
+ Explicit MD5 remains available for compatibility, although
175
+ [RFC 8945 section 6](https://www.rfc-editor.org/rfc/rfc8945.html#section-6)
176
+ prohibits its use. Dnsruby does not fall back to MD5 automatically. Selecting it
177
+ logs one warning per process; set `Dnsruby.log.level = Logger::WARN` before
178
+ configuring TSIG to see it. The default log level is FATAL.
179
+
180
+ ### DNSSEC algorithm policy
181
+
182
+ Dnsruby validates signatures made with RSA/SHA-256 (8), RSA/SHA-512 (10),
183
+ ECDSA P-256 (13), ECDSA P-384 (14), Ed25519 (15), and Ed448 (16). Ed25519 and Ed448 require version
184
+ 3.0 or later of the `openssl` gem and an OpenSSL provider that supports it
185
+ (normally OpenSSL 1.1.1 or later). On older Ruby releases, including Ruby 3.0,
186
+ you may need to install a newer compatible `openssl` gem. An unavailable
187
+ algorithm is treated as unsupported.
188
+
189
+ Resolver validation treats RSA/SHA-1 signing algorithms 5 and 7 as unsupported,
190
+ as required by [RFC 9905](https://www.rfc-editor.org/rfc/rfc9905.html#section-2).
191
+ An answer whose authenticated chain relies only on these algorithms is
192
+ `INSECURE`: it is not protected by an accepted DNSSEC chain. A signature using
193
+ a supported algorithm must still validate where one is required; a failed
194
+ check does not become an insecure result.
195
+
196
+ Explicit `Dnsruby::Dnssec.verify` calls retain the ability to verify historical
197
+ RSA/SHA-1 signatures. Within an authenticated set of DS records, SHA-256 digests
198
+ take preference over SHA-1.
199
+
200
+ `Dnsruby::RR::DS.from_key(key)` now creates a SHA-256 DS record. New DS creation
201
+ rejects SHA-1 digests and signing algorithms 5/7. Existing records remain
202
+ readable, and `digest_key` and `check_key` still support historical verification.
203
+
204
+ ### DNSSEC validation limits
205
+
206
+ Dnsruby limits the time and work spent validating an answer, including extra
207
+ lookups needed to establish trust. This prevents an unusually expensive reply
208
+ from occupying a validator indefinitely.
209
+
210
+ The defaults for one validation are:
211
+
212
+ - **Five seconds** of elapsed time, including supporting lookups.
213
+ - **64 signature verification attempts.**
214
+ - **100,000 work units**, counting operations such as key/signature comparisons
215
+ and NSEC3 hashes used to check that a name or record is absent.
216
+
217
+ Reaching a limit raises `Dnsruby::ValidationLimitError`, a subclass of
218
+ `Dnsruby::VerifyError`. Validation fails; the answer is not accepted as insecure.
219
+ If you need to validate unusually large amounts of trusted data, set the limits
220
+ before starting queries:
79
221
 
80
- There are a couple of blog articles that might be helpful
81
- in understanding how to use Dnsruby. These used to be hosted by
82
- Nominet UK, however the original content has been copied to the
83
- dnsruby github wiki at :
222
+ ```ruby
223
+ Dnsruby::Dnssec.validation_limits = {
224
+ max_signature_verifications: 128,
225
+ max_work: 200_000,
226
+ timeout: 10.0
227
+ }
228
+ ```
229
+
230
+ Omitted settings use their defaults, and all values must be positive. Validation
231
+ also stops when the query times out or the resolver closes. Cancellation is
232
+ checked between operations and while waiting on the network; an OpenSSL
233
+ operation already in progress is allowed to finish.
234
+
235
+ At most four workers validate asynchronous responses at once, with 64 more
236
+ responses waiting. Further responses fail with `ValidationLimitError`.
237
+
238
+ ### Response caching and retries
239
+
240
+ Dnsruby now caches negative answers: **NXDOMAIN** means the name does not exist,
241
+ and **NODATA** means there is no record of the requested type. It uses the shorter
242
+ of the authority's SOA TTL and SOA MINIMUM as the cache lifetime, shortened
243
+ further when record or DNSSEC lifetimes require it. Completed lookup failures
244
+ are cached for one second, so repeated requests do not immediately repeat the
245
+ same failed lookup.
246
+
247
+ These caches have size limits and keep results separate for different resolver
248
+ settings and DNSSEC trust policies. Cancelled queries, raw messages, dynamic
249
+ updates and TSIG-signed transactions do not populate them. To disable them, set
250
+ `resolver.do_caching = false`; a Recursor uses the setting on its supplied
251
+ Resolver. `PacketSender.clear_caches` and `Recursor.clear_caches` also invalidate
252
+ these entries.
253
+
254
+ Positive answers are kept separate for each upstream server, port, source
255
+ configuration and query options, including EDNS. Truncated replies are never
256
+ reused from the cache, so a later query can still retry over TCP when needed.
257
+ Each send attempt uses one snapshot of its transport configuration; subsequent
258
+ attempts can use newer settings. Adding an explicit source port replaces the
259
+ default `[0]` pool (random source ports). A pool cannot mix zero and explicit ports.
260
+
261
+ For each question, Dnsruby makes at most **three attempts per server address per
262
+ transport**, including retries without optional EDNS support. UDP and TCP have
263
+ separate limits. Setting `retry_times` above three is still accepted, but does
264
+ not increase this limit.
265
+
266
+ Recursor caches also have size limits. Cached delegations expire when their NS
267
+ records expire, even if the associated nameserver addresses have longer TTLs.
268
+ Their reusable lifetimes are also bounded by the parent route through which
269
+ they were learned, so a child cannot keep that route alive indefinitely. An
270
+ unrelated parent-cache eviction does not discard an already admitted child.
271
+ Root NS TTLs govern bootstrap reuse, and missing addresses are resolved even
272
+ when a priming reply includes addresses for only some root nameservers.
273
+ Fresh nameserver addresses with a zero TTL remain usable for the current
274
+ lookup, but are not retained in shared caches.
275
+ When you supply a Resolver to `Recursor.new`, its transport and source-address
276
+ settings apply to the recursive queries. Its `query_timeout` limits the whole
277
+ lookup, including nameserver discovery and validation. TSIG keys remain tied
278
+ to the configured peer; they are not passed to arbitrary authoritative servers.
279
+
280
+ Learned DNSKEY and DS records also have size limits. Configured trust anchors
281
+ are kept until you remove them or their requested expiration time arrives;
282
+ this applies to both DNSKEY and DS anchors.
283
+
284
+ ### Recursive lookup limits
285
+
286
+ Each `Recursor` lookup has a 30-second deadline, including root bootstrap,
287
+ nameserver address discovery and DNSSEC lookups. A shorter Resolver
288
+ `query_timeout` or inherited deadline takes precedence. The recursive deadline
289
+ also applies when the Resolver's `query_timeout` is zero.
290
+
291
+ For ordinary questions, `Recursor` follows CNAME and DNAME aliases and returns
292
+ the alias chain together with the target answer under the original question.
293
+ Alias lookups share the same deadline and work limits. Questions explicitly
294
+ requesting CNAME, DNAME or ANY return the received records directly. When
295
+ validation is enabled, the DNSSEC limits above are shared across the entire
296
+ recursive lookup, including parallel address discovery and the final answer.
297
+
298
+ The lookup shares limits of 128 transmission attempts and 256 discovery work
299
+ units across its supporting queries. Work units count recursive steps and
300
+ nameserver candidates. At most eight extra threads discover nameserver
301
+ addresses; further discovery runs within an existing thread. Root address
302
+ queries run in batches of at most eight. Repeated address dependencies on the
303
+ same discovery path are skipped.
304
+
305
+ Set these limits before starting queries when your workload needs more time
306
+ or discovery work:
307
+
308
+ ```ruby
309
+ recursor.recursion_limits = {
310
+ timeout: 60.0,
311
+ max_queries: 256,
312
+ max_steps: 512,
313
+ max_workers: 8
314
+ }
315
+ ```
316
+
317
+ Omitted settings use their defaults. The timeout must be positive and finite;
318
+ other limits must be positive integers. Exhausted work or transmission limits
319
+ raise `Dnsruby::RecursionLimitError`, a subclass of `Dnsruby::ResolvError`, and
320
+ are not cached as failures of the individual question. An expired recursive
321
+ deadline raises `Dnsruby::ResolvTimeout`.
322
+
323
+ ### Zone transfer deadlines
324
+
325
+ Zone transfers now have a deadline for each complete DNS message read or
326
+ written. This is a total time limit for that message, not a timer that restarts
327
+ each time more bytes arrive.
328
+
329
+ | Setting | Default | What it limits |
330
+ | --- | --- | --- |
331
+ | `connect_timeout` | 5 seconds | Opening a connection to a server. |
332
+ | `io_timeout` | 30 seconds | Reading or writing one complete DNS message. |
333
+ | `transfer_timeout` | `nil` (no overall limit) | One complete attempt against a server, including the connection and an IXFR-to-AXFR fallback. |
334
+
335
+ Increase `io_timeout` for a slow connection, or set `transfer_timeout` to bound
336
+ the complete attempt. Both accept positive durations in seconds; use `nil` for
337
+ `transfer_timeout` to remove the overall limit. If an attempt fails, the next
338
+ configured server may be tried.
339
+
340
+ Use an authoritative server that allows transfers from your application. If it
341
+ requires TSIG, configure `transfer.tsig` with the matching key before transferring.
342
+
343
+ ```ruby
344
+ transfer = Dnsruby::ZoneTransfer.new
345
+ transfer.server = '192.0.2.53' # Replace with your zone's authoritative server
346
+ transfer.io_timeout = 60
347
+ transfer.transfer_timeout = 300
348
+ records = transfer.transfer('example.org')
349
+ ```
350
+
351
+ Transfers handle partial reads and writes, check that replies match the request,
352
+ and require matching opening and closing SOA records. Multi-message TSIG
353
+ verification remains supported.
354
+
355
+ ## Unit tests
356
+
357
+ From the project root, install the development dependencies and run the tests:
358
+
359
+ ```sh
360
+ bundle install
361
+ bundle exec rake test_offline # Tests that do not need public DNS access
362
+ bundle exec rake test_online # Tests that need network access
363
+ bundle exec rake test # Both suites
364
+ ```
365
+
366
+ Using `bundle exec` selects the dependency versions required by the project,
367
+ including minitest. Tests that need OpenSSL are skipped if it is unavailable.
368
+
369
+ ### Longer concurrency tests
370
+
371
+ The separate `soak` task exercises concurrent UDP and TCP queries against its
372
+ own local test server. It needs no public DNS access, privileged port or
373
+ separately running server, and is not included in the standard suites.
374
+
375
+ Start with a small workload:
376
+
377
+ ```sh
378
+ SOAK_THREADS=4 SOAK_ROUNDS=3 SOAK_ASYNC_ROUNDS=3 SOAK_TIMEOUT=30 bundle exec rake soak
379
+ ```
380
+
381
+ For the default workload of 81,512 queries across eight cases, allow enough open
382
+ files and a longer per-case deadline on a busy machine:
383
+
384
+ ```sh
385
+ ulimit -n 4096
386
+ SOAK_TIMEOUT=300 bundle exec rake soak
387
+ ```
84
388
 
85
- https://github.com/alexdalitz/dnsruby/wiki
389
+ Increase `SOAK_ROUNDS` and `SOAK_ASYNC_ROUNDS` for longer runs. The tests check
390
+ errors, wrong responses and duplicate completions. They do not measure memory
391
+ or file-descriptor growth over a multi-day run. The older RubyDNS/NIO helpers
392
+ remain available but are not used by this task.
86
393
 
87
- Contact/Links
88
- -------
394
+ ## Contact/Links
89
395
 
90
- | Link Type | Link/Text |
91
- |-----|-----
92
- | Author Email | alex@caerkettontech.com |
93
- | Github | https://github.com/alexdalitz/dnsruby |
396
+ | Link | Address |
397
+ | --- | --- |
398
+ | Author | alex@caerkettontech.com |
399
+ | GitHub | https://github.com/alexdalitz/dnsruby |
94
400
  | Google Group | https://groups.google.com/forum/#!forum/dnsruby |
95
- | Rubygems | http://rubygems.org/gems/dnsruby/ |
401
+ | RubyGems | https://rubygems.org/gems/dnsruby/ |
96
402
  | Documentation | https://www.rubydoc.info/gems/dnsruby/ |