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
@@ -20,6 +20,14 @@ class TestEscapedChars < Minitest::Test
20
20
 
21
21
  include Dnsruby
22
22
 
23
+ def setup
24
+ @original_label_limit = Name::Label.class_variable_get(:@@max_length)
25
+ end
26
+
27
+ def teardown
28
+ Name::Label.set_max_length(@original_label_limit)
29
+ end
30
+
23
31
  def test_one
24
32
  Name::Label.set_max_length(150)
25
33
  #
@@ -0,0 +1,167 @@
1
+ require_relative 'spec_helper'
2
+
3
+ class FinalReviewFixesTest < Minitest::Test
4
+ def test_source_port_replacement_is_published_after_enumeration
5
+ sender = Dnsruby::PacketSender.new(server: '127.0.0.1', src_port: 60_001)
6
+ ports = Object.new
7
+ seen = []
8
+ ports.define_singleton_method(:each) do |&block|
9
+ [60_002, 60_003].each do |port|
10
+ seen << sender.get_next_src_port
11
+ block.call(port)
12
+ end
13
+ end
14
+ sender.src_port = ports
15
+ assert_equal([60_001, 60_001], seen)
16
+ assert_equal([60_002, 60_003], sender.src_port)
17
+ assert_raises(ArgumentError) { sender.src_port = [] }
18
+ assert_equal([60_002, 60_003], sender.src_port)
19
+ assert_raises(ArgumentError) { sender.src_port = [60_004, 53] }
20
+ assert_equal([60_002, 60_003], sender.src_port)
21
+ sender.src_port << 60_004
22
+ assert_equal([60_002, 60_003, 60_004], sender.src_port)
23
+ end
24
+
25
+ def test_compression_preserves_label_boundaries_and_case
26
+ names = ['a\\.b.example.', 'a.b.example.', 'A.B.example.', 'other.example.']
27
+ message = Dnsruby::Message.new
28
+ names.each { |name| message.add_question(name, 'A') }
29
+ decoded = Dnsruby::Message.decode(message.encode)
30
+ assert_equal(names.map { |name| Dnsruby::Name.create(name).labels.map(&:string) },
31
+ decoded.question.map { |question| question.qname.labels.map(&:string) })
32
+ end
33
+
34
+ def test_txt_round_trip_preserves_utf8_and_decimal_escape_octets
35
+ ['café', 'é' * 127, '"café" "\\255"'].each do |text|
36
+ record = Dnsruby::RR.create(name: 'text.example.', type: 'TXT', strings: text)
37
+ unless text.start_with?('"')
38
+ assert_equal(text, record.data)
39
+ assert_equal(text.encoding, record.data.encoding)
40
+ end
41
+ message = Dnsruby::Message.new
42
+ message.add_answer(record)
43
+ decoded = Dnsruby::Message.decode(message.encode).answer.first
44
+ assert_equal(record.strings.map(&:b), decoded.strings)
45
+ reparsed = Dnsruby::RR.create(decoded.to_s)
46
+ assert_equal(decoded.strings, reparsed.strings)
47
+ end
48
+ commented = Dnsruby::RR.create(name: 'text.example.', type: 'TXT', strings: '"café" ; comment')
49
+ assert_equal('café', commented.data)
50
+ assert_equal(Encoding::UTF_8, commented.data.encoding)
51
+ record = Dnsruby::RR.create(name: 'text.example.', type: 'TXT', strings: 'é' * 128)
52
+ assert_equal('é' * 128, record.data)
53
+ message = Dnsruby::Message.new
54
+ message.add_answer(record)
55
+ assert_raises(Dnsruby::EncodeError) { message.encode }
56
+ end
57
+
58
+ def test_txt_can_split_a_utf8_sequence_at_a_wire_string_boundary
59
+ text = 'é' * 128
60
+ presentation = '"'.b + text.b.byteslice(0, 255) + '" "' + text.b.byteslice(255, 1) + '"'
61
+ record = Dnsruby::RR.create(name: 'text.example.', type: 'TXT', strings: presentation)
62
+ assert_equal(text.b, record.data)
63
+ assert_equal([255, 1], record.strings.map(&:bytesize))
64
+ message = Dnsruby::Message.new
65
+ message.add_answer(record)
66
+ decoded = Dnsruby::Message.decode(message.encode).answer.first
67
+ assert_equal(text.b, decoded.data)
68
+ assert_equal(record.strings, decoded.strings)
69
+ end
70
+
71
+ def test_ds_family_preserves_private_algorithm_and_unknown_digest_numbers
72
+ %w[DS CDS DLV].each do |type|
73
+ record = Dnsruby::RR.create("example. 60 IN #{type} 12345 253 253 01020304")
74
+ message = Dnsruby::Message.new
75
+ message.add_answer(record)
76
+ decoded = Dnsruby::Message.decode(message.encode).answer.first
77
+ assert_equal(253, decoded.algorithm.code)
78
+ assert_equal(253, decoded.digest_type.code)
79
+ assert_equal(record.digestbin, decoded.digestbin)
80
+ assert_raises(Dnsruby::DecodeError) { record.algorithm = 256 }
81
+ assert_raises(Dnsruby::DecodeError) { record.digest_type = -1 }
82
+ end
83
+ end
84
+
85
+ def test_zone_reader_qualifies_relative_origins_soa_and_literal_final_dots
86
+ reader = Dnsruby::ZoneReader.new('example.', 60)
87
+ reader.process_line('$ORIGIN sub')
88
+ soa = Dnsruby::RR.create(reader.process_line('@ IN SOA ns hostmaster 1 60 60 60 60'))
89
+ assert_equal('sub.example.', soa.name.to_s(true))
90
+ assert_equal('ns.sub.example.', soa.mname.to_s(true))
91
+ assert_equal('hostmaster.sub.example.', soa.rname.to_s(true))
92
+ record = Dnsruby::RR.create(reader.process_line('owner\\. IN CNAME target\\.'))
93
+ assert_equal(['owner.', 'sub', 'example'], record.name.labels.map(&:string))
94
+ assert_equal(['target.', 'sub', 'example'], record.cname.labels.map(&:string))
95
+ reader.process_line('$ORIGIN elsewhere.')
96
+ record = Dnsruby::RR.create(reader.process_line('@ IN NS ns'))
97
+ assert_equal('elsewhere.', record.name.to_s(true))
98
+ assert_equal('ns.elsewhere.', record.nsdname.to_s(true))
99
+ end
100
+
101
+ def test_zone_reader_preserves_backslashes_when_replacing_names
102
+ origin = Dnsruby::Name.new(['slash\\origin', 'example'].map { |label| Dnsruby::Name::Label.new(label) }, true)
103
+ reader = Dnsruby::ZoneReader.new(origin, 60)
104
+ target = Dnsruby::Name.new([Dnsruby::Name::Label.new('label\\&')], false)
105
+ record = Dnsruby::RR.create(reader.process_line("#{target.to_s} IN CNAME @"))
106
+ assert_equal(target.labels + origin.labels, record.name.labels)
107
+ assert_equal(origin, record.cname)
108
+ record = Dnsruby::RR.create(reader.process_line('@ IN NS ns'))
109
+ assert_equal(origin, record.name)
110
+ end
111
+
112
+ def test_positive_cache_scope_separates_upstream_and_request_context
113
+ sender = Dnsruby::PacketSender.new(server: '192.0.2.53', dnssec: false)
114
+ query = Dnsruby::Message.new('answer.example.', 'A')
115
+ sender.make_query_packet(query)
116
+ scope = sender.positive_cache_scope(query)
117
+ query.header.id = (query.header.id + 1) % 65_536
118
+ assert_equal(scope, sender.positive_cache_scope(query))
119
+ cache = Dnsruby::Cache.new
120
+ response = Dnsruby::Message.new('answer.example.', 'A')
121
+ response.add_answer(Dnsruby::RR.create('answer.example. 60 IN A 192.0.2.1'))
122
+ cache.add(response, scope: scope)
123
+ assert(cache.find('answer.example.', 'A', scope: sender.positive_cache_scope(query)).cached)
124
+ sender.port = 5353
125
+ assert_nil(cache.find('answer.example.', 'A', scope: sender.positive_cache_scope(query)))
126
+ sender.port = 53
127
+ sender.server = '192.0.2.54'
128
+ assert_nil(cache.find('answer.example.', 'A', scope: sender.positive_cache_scope(query)))
129
+ sender.server = '192.0.2.53'
130
+ query.get_opt.dnssec_ok = true
131
+ assert_nil(cache.find('answer.example.', 'A', scope: sender.positive_cache_scope(query)))
132
+ end
133
+
134
+ def test_truncated_answers_are_never_cached
135
+ cache = Dnsruby::Cache.new
136
+ response = Dnsruby::Message.new('answer.example.', 'A')
137
+ response.add_answer(Dnsruby::RR.create('answer.example. 60 IN A 192.0.2.1'))
138
+ response.header.tc = true
139
+ assert_nil(cache.add(response))
140
+ assert_nil(cache.find('answer.example.', 'A'))
141
+ response.header.tc = false
142
+ refute_nil(cache.add(response))
143
+ end
144
+
145
+ def test_referral_keeps_one_enclosing_delegation_and_sibling_glue
146
+ recursor = Dnsruby::Recursor.allocate
147
+ response = Dnsruby::Message.new('www.child.example.', 'A')
148
+ response.add_authority(Dnsruby::RR.create('child.example. 60 IN NS ns.sibling.example.'))
149
+ response.add_authority(Dnsruby::RR.create('other.example. 10 IN NS ns.other.example.'))
150
+ response.add_additional(Dnsruby::RR.create('ns.sibling.example. 120 IN A 192.0.2.53'))
151
+ response.add_additional(Dnsruby::RR.create('ns.other.example. 120 IN A 192.0.2.54'))
152
+ zone, authorities, ttl = recursor.referral_authorities(response, 'example.', 'www.child.example.', 'IN')
153
+ assert_equal('child.example.', zone)
154
+ assert_equal(['ns.sibling.example.'], authorities.keys)
155
+ assert_equal(['192.0.2.53'], authorities.values.first.values)
156
+ assert_equal(60, ttl)
157
+ relative = recursor.referral_authorities(response, 'example.', 'www.child.example', 'IN')
158
+ assert_equal(zone, relative.first)
159
+ at_delegation = recursor.referral_authorities(response, 'example.', 'child.example', 'IN')
160
+ assert_equal(zone, at_delegation.first)
161
+ assert_nil(recursor.referral_authorities(response, 'child.example.', 'www.child.example.', 'IN'))
162
+ assert_nil(recursor.referral_authorities(response, 'example.', 'www.unrelated.example.', 'IN'))
163
+ response.authority.clear
164
+ response.add_authority(Dnsruby::RR.create('child.example. 60 IN SOA ns.child.example. hostmaster.child.example. 1 60 60 60 60'))
165
+ assert_nil(recursor.referral_authorities(response, 'example.', 'www.child.example.', 'IN'))
166
+ end
167
+ end
@@ -0,0 +1,104 @@
1
+ require_relative 'spec_helper'
2
+ require 'minitest/mock'
3
+ require 'open3'
4
+ require 'timeout'
5
+
6
+ class FinalTransportContractsTest < Minitest::Test
7
+ def test_single_resolver_replacement_preserves_concurrent_settings
8
+ resolver = Dnsruby::SingleResolver.new(server: '192.0.2.1', packet_timeout: 5)
9
+ entered, release = Queue.new, Queue.new
10
+ replacement = nil
11
+ original = Dnsruby::PacketSender.method(:new)
12
+ construct = lambda do |*args|
13
+ sender = original.call(*args)
14
+ if sender.server.to_s == '192.0.2.2'
15
+ entered << true
16
+ release.pop
17
+ end
18
+ sender
19
+ end
20
+ Dnsruby::PacketSender.stub(:new, construct) do
21
+ begin
22
+ replacement = Thread.new { resolver.server = '192.0.2.2' }
23
+ Timeout.timeout(5) { entered.pop }
24
+ resolver.packet_timeout = 9
25
+ resolver.port = 5353
26
+ resolver.src_address = '192.0.2.10'
27
+ resolver.dnssec = false
28
+ ensure
29
+ release << true
30
+ Timeout.timeout(5) { replacement&.value }
31
+ end
32
+ end
33
+ sender = resolver.single_resolvers.first
34
+ assert_equal('192.0.2.2', resolver.server)
35
+ [:packet_timeout, :port, :src_address, :dnssec].each do |field|
36
+ assert_equal(resolver.public_send(field), sender.public_send(field), field)
37
+ end
38
+ ensure
39
+ replacement&.kill if replacement&.alive?
40
+ resolver&.close
41
+ end
42
+
43
+ def test_tcp_reply_reports_its_message_size
44
+ # The selector is process-global; isolate its in-memory socket fixture.
45
+ source = <<~'RUBY'
46
+ require 'dnsruby'
47
+ selector_class = Dnsruby::SelectThread
48
+ selector_class.class_variable_set(:@@mutex, Mutex.new)
49
+ selector = selector_class.send(:allocate)
50
+ query = Dnsruby::Message.new('metadata.example.')
51
+ query.do_validation = query.do_caching = false
52
+ response = query.clone
53
+ response.header.qr = true
54
+ response.add_answer(Dnsruby::RR.create('metadata.example. 60 IN A 192.0.2.1'))
55
+ wire = response.encode
56
+ socket = Object.new
57
+ socket.define_singleton_method(:getsockopt) { |*| Struct.new(:data).new([Socket::SOCK_STREAM].pack('i')) }
58
+ socket.define_singleton_method(:closed?) { false }
59
+ selector.define_singleton_method(:tcp_read) { |_| wire }
60
+ settings = selector_class::QuerySettings.new(query.encode, query, false, Queue.new, :metadata,
61
+ socket, '192.0.2.53', 53, Dnsruby::TransportClock.now + 1, 512, nil)
62
+ selector_class.class_variable_set(:@@query_hash, {metadata: settings})
63
+ selector_class.class_variable_set(:@@socket_hash, {socket => [:metadata]})
64
+ decoded, = selector.get_incoming_data(socket, 512)
65
+ raise 'Wrong TCP size' unless decoded.answersize == wire.bytesize
66
+ raise 'Wrong TCP peer' unless decoded.answerip == '192.0.2.53'
67
+ RUBY
68
+ out, err, status = Open3.capture3(RbConfig.ruby, '-Ilib', '-e', source,
69
+ chdir: File.expand_path('..', __dir__))
70
+ assert(status.success?, "#{out}\n#{err}")
71
+ end
72
+
73
+ def test_zone_transfer_preserves_authenticated_server_errors
74
+ [Dnsruby::RCode::BADTIME, 22].each do |error_code|
75
+ key = Dnsruby::RR.create(name: 'transfer-key.example.', type: 'TSIG', klass: 'ANY',
76
+ key: ['test-only transfer secret'].pack('m0'), algorithm: 'hmac-sha256')
77
+ transfer = Dnsruby::ZoneTransfer.new
78
+ transfer.tsig = key
79
+ request = Dnsruby::Message.new('example.', 'AXFR')
80
+ sink = Object.new
81
+ sink.define_singleton_method(:write_nonblock) { |data| data.bytesize }
82
+ transfer.send_message(sink, request)
83
+ response = Dnsruby::Message.new('example.', 'AXFR')
84
+ response.header.qr = true
85
+ response.header.rcode = Dnsruby::RCode::NOTAUTH
86
+ signer = key.dup
87
+ signer.error = error_code
88
+ server_time = Time.now.to_i + 3600
89
+ if error_code == Dnsruby::RCode::BADTIME
90
+ signer.other_data = [server_time >> 32, server_time & 0xffffffff].pack('nN')
91
+ signer.other_size = 6
92
+ end
93
+ signer.apply(response, request)
94
+ wire = response.encode
95
+ frame = [wire.bytesize].pack('n') + wire
96
+ socket = Object.new
97
+ socket.define_singleton_method(:read_nonblock) { |size| frame.slice!(0, size) }
98
+ error = assert_raises(Dnsruby::TsigError) { transfer.receive_message(socket) }
99
+ assert_equal(:Verified, transfer.last_tsigstate)
100
+ assert_includes(error.message, error_code == Dnsruby::RCode::BADTIME ? 'BADTIME' : 'BADTRUNC')
101
+ assert_includes(error.message, server_time.to_s) if error_code == Dnsruby::RCode::BADTIME
102
+ end
103
+ end
104
+ end