summaryrefslogtreecommitdiff
path: root/test/resolv
diff options
context:
space:
mode:
Diffstat (limited to 'test/resolv')
-rw-r--r--test/resolv/test_addr.rb4
-rw-r--r--test/resolv/test_dns.rb251
-rw-r--r--test/resolv/test_resource.rb76
-rw-r--r--test/resolv/test_svcb_https.rb231
4 files changed, 557 insertions, 5 deletions
diff --git a/test/resolv/test_addr.rb b/test/resolv/test_addr.rb
index 62092676ba..f701c31d3e 100644
--- a/test/resolv/test_addr.rb
+++ b/test/resolv/test_addr.rb
@@ -28,6 +28,10 @@ class TestResolvAddr < Test::Unit::TestCase
assert_match(Resolv::IPv6::Regex, "FE80:2:3:4:5:6:7:8%EM1", bug17112)
assert_match(Resolv::IPv6::Regex, "FE80::20D:3AFF:FE7D:9760%ETH0", bug17112)
assert_match(Resolv::IPv6::Regex, "FE80::1%EM1", bug17112)
+
+ bug17524 = "[ruby-core:101992]"
+ assert_match(Resolv::IPv6::Regex, "FE80::20D:3AFF:FE7D:9760%ruby_3.0.0-1", bug17524)
+ assert_match(Resolv::IPv6::Regex, "fe80::1%ruby_3.0.0-1", bug17524)
end
def test_valid_socket_ip_address_list
diff --git a/test/resolv/test_dns.rb b/test/resolv/test_dns.rb
index 5171604a82..40c5406db8 100644
--- a/test/resolv/test_dns.rb
+++ b/test/resolv/test_dns.rb
@@ -44,6 +44,16 @@ class TestResolvDNS < Test::Unit::TestCase
BasicSocket.do_not_reverse_lookup = @save_do_not_reverse_lookup
end
+ def with_tcp(host, port)
+ t = TCPServer.new(host, port)
+ begin
+ t.listen(1)
+ yield t
+ ensure
+ t.close
+ end
+ end
+
def with_udp(host, port)
u = UDPSocket.new
begin
@@ -54,6 +64,50 @@ class TestResolvDNS < Test::Unit::TestCase
end
end
+ def with_udp_and_tcp(host, port)
+ if port == 0
+ # Automatic port; we might need to retry until we find a port which is free on both UDP _and_ TCP.
+ retries_remaining = 5
+ t = nil
+ u = nil
+ begin
+ begin
+ u = UDPSocket.new
+ u.bind(host, 0)
+ _, udp_port, _, _ = u.addr
+ t = TCPServer.new(host, udp_port)
+ t.listen(1)
+ rescue Errno::EADDRINUSE, Errno::EACCES
+ # ADDRINUSE is what should get thrown if we try and bind a port which is already bound on UNIXen,
+ # but windows can sometimes throw EACCESS.
+ # See: https://stackoverflow.com/questions/48478869/cannot-bind-to-some-ports-due-to-permission-denied
+ retries_remaining -= 1
+ if retries_remaining > 0
+ t&.close
+ t = nil
+ u&.close
+ u = nil
+ retry
+ end
+ raise
+ end
+
+ # If we get to this point, we have a valid t & u socket
+ yield u, t
+ ensure
+ t&.close
+ u&.close
+ end
+ else
+ # Explicitly specified port, don't retry the bind.
+ with_udp(host, port) do |u|
+ with_tcp(host, port) do |t|
+ yield u, t
+ end
+ end
+ end
+ end
+
# [ruby-core:65836]
def test_resolve_with_2_ndots
conf = Resolv::DNS::Config.new :nameserver => ['127.0.0.1'], :ndots => 2
@@ -72,7 +126,7 @@ class TestResolvDNS < Test::Unit::TestCase
begin
OpenSSL
rescue LoadError
- skip 'autoload problem. see [ruby-dev:45021][Bug #5786]'
+ omit 'autoload problem. see [ruby-dev:45021][Bug #5786]'
end if defined?(OpenSSL)
with_udp('127.0.0.1', 0) {|u|
@@ -157,13 +211,173 @@ class TestResolvDNS < Test::Unit::TestCase
}
end
- def test_query_ipv4_duplicate_responses
+ def test_query_ipv4_address_truncated_tcp_fallback
begin
OpenSSL
rescue LoadError
skip 'autoload problem. see [ruby-dev:45021][Bug #5786]'
end if defined?(OpenSSL)
+ num_records = 50
+
+ with_udp_and_tcp('127.0.0.1', 0) {|u, t|
+ _, server_port, _, server_address = u.addr
+ client_thread = Thread.new {
+ Resolv::DNS.open(:nameserver_port => [[server_address, server_port]]) {|dns|
+ dns.getresources("foo.example.org", Resolv::DNS::Resource::IN::A)
+ }
+ }
+ udp_server_thread = Thread.new {
+ msg, (_, client_port, _, client_address) = Timeout.timeout(5) {u.recvfrom(4096)}
+ id, word2, qdcount, ancount, nscount, arcount = msg.unpack("nnnnnn")
+ qr = (word2 & 0x8000) >> 15
+ opcode = (word2 & 0x7800) >> 11
+ aa = (word2 & 0x0400) >> 10
+ tc = (word2 & 0x0200) >> 9
+ rd = (word2 & 0x0100) >> 8
+ ra = (word2 & 0x0080) >> 7
+ z = (word2 & 0x0070) >> 4
+ rcode = word2 & 0x000f
+ rest = msg[12..-1]
+ assert_equal(0, qr) # 0:query 1:response
+ assert_equal(0, opcode) # 0:QUERY 1:IQUERY 2:STATUS
+ assert_equal(0, aa) # Authoritative Answer
+ assert_equal(0, tc) # TrunCation
+ assert_equal(1, rd) # Recursion Desired
+ assert_equal(0, ra) # Recursion Available
+ assert_equal(0, z) # Reserved for future use
+ assert_equal(0, rcode) # 0:No-error 1:Format-error 2:Server-failure 3:Name-Error 4:Not-Implemented 5:Refused
+ assert_equal(1, qdcount) # number of entries in the question section.
+ assert_equal(0, ancount) # number of entries in the answer section.
+ assert_equal(0, nscount) # number of entries in the authority records section.
+ assert_equal(0, arcount) # number of entries in the additional records section.
+ name = [3, "foo", 7, "example", 3, "org", 0].pack("Ca*Ca*Ca*C")
+ assert_operator(rest, :start_with?, name)
+ rest = rest[name.length..-1]
+ assert_equal(4, rest.length)
+ qtype, _ = rest.unpack("nn")
+ assert_equal(1, qtype) # A
+ assert_equal(1, qtype) # IN
+ id = id
+ qr = 1
+ opcode = opcode
+ aa = 0
+ tc = 1
+ rd = rd
+ ra = 1
+ z = 0
+ rcode = 0
+ qdcount = 0
+ ancount = num_records
+ nscount = 0
+ arcount = 0
+ word2 = (qr << 15) |
+ (opcode << 11) |
+ (aa << 10) |
+ (tc << 9) |
+ (rd << 8) |
+ (ra << 7) |
+ (z << 4) |
+ rcode
+ msg = [id, word2, qdcount, ancount, nscount, arcount].pack("nnnnnn")
+ type = 1
+ klass = 1
+ ttl = 3600
+ rdlength = 4
+ num_records.times do |i|
+ rdata = [192,0,2,i].pack("CCCC") # 192.0.2.x (TEST-NET address) RFC 3330
+ rr = [name, type, klass, ttl, rdlength, rdata].pack("a*nnNna*")
+ msg << rr
+ end
+ u.send(msg[0...512], 0, client_address, client_port)
+ }
+ tcp_server_thread = Thread.new {
+ ct = t.accept
+ msg = ct.recv(512)
+ msg.slice!(0..1) # Size (only for TCP)
+ id, word2, qdcount, ancount, nscount, arcount = msg.unpack("nnnnnn")
+ qr = (word2 & 0x8000) >> 15
+ opcode = (word2 & 0x7800) >> 11
+ aa = (word2 & 0x0400) >> 10
+ tc = (word2 & 0x0200) >> 9
+ rd = (word2 & 0x0100) >> 8
+ ra = (word2 & 0x0080) >> 7
+ z = (word2 & 0x0070) >> 4
+ rcode = word2 & 0x000f
+ rest = msg[12..-1]
+ assert_equal(0, qr) # 0:query 1:response
+ assert_equal(0, opcode) # 0:QUERY 1:IQUERY 2:STATUS
+ assert_equal(0, aa) # Authoritative Answer
+ assert_equal(0, tc) # TrunCation
+ assert_equal(1, rd) # Recursion Desired
+ assert_equal(0, ra) # Recursion Available
+ assert_equal(0, z) # Reserved for future use
+ assert_equal(0, rcode) # 0:No-error 1:Format-error 2:Server-failure 3:Name-Error 4:Not-Implemented 5:Refused
+ assert_equal(1, qdcount) # number of entries in the question section.
+ assert_equal(0, ancount) # number of entries in the answer section.
+ assert_equal(0, nscount) # number of entries in the authority records section.
+ assert_equal(0, arcount) # number of entries in the additional records section.
+ name = [3, "foo", 7, "example", 3, "org", 0].pack("Ca*Ca*Ca*C")
+ assert_operator(rest, :start_with?, name)
+ rest = rest[name.length..-1]
+ assert_equal(4, rest.length)
+ qtype, _ = rest.unpack("nn")
+ assert_equal(1, qtype) # A
+ assert_equal(1, qtype) # IN
+ id = id
+ qr = 1
+ opcode = opcode
+ aa = 0
+ tc = 0
+ rd = rd
+ ra = 1
+ z = 0
+ rcode = 0
+ qdcount = 0
+ ancount = num_records
+ nscount = 0
+ arcount = 0
+ word2 = (qr << 15) |
+ (opcode << 11) |
+ (aa << 10) |
+ (tc << 9) |
+ (rd << 8) |
+ (ra << 7) |
+ (z << 4) |
+ rcode
+ msg = [id, word2, qdcount, ancount, nscount, arcount].pack("nnnnnn")
+ type = 1
+ klass = 1
+ ttl = 3600
+ rdlength = 4
+ num_records.times do |i|
+ rdata = [192,0,2,i].pack("CCCC") # 192.0.2.x (TEST-NET address) RFC 3330
+ rr = [name, type, klass, ttl, rdlength, rdata].pack("a*nnNna*")
+ msg << rr
+ end
+ msg = "#{[msg.bytesize].pack("n")}#{msg}" # Prefix with size
+ ct.send(msg, 0)
+ ct.close
+ }
+ result, _ = assert_join_threads([client_thread, udp_server_thread, tcp_server_thread])
+ assert_instance_of(Array, result)
+ assert_equal(50, result.length)
+ result.each_with_index do |rr, i|
+ assert_instance_of(Resolv::DNS::Resource::IN::A, rr)
+ assert_instance_of(Resolv::IPv4, rr.address)
+ assert_equal("192.0.2.#{i}", rr.address.to_s)
+ assert_equal(3600, rr.ttl)
+ end
+ }
+ end
+
+ def test_query_ipv4_duplicate_responses
+ begin
+ OpenSSL
+ rescue LoadError
+ omit 'autoload problem. see [ruby-dev:45021][Bug #5786]'
+ end if defined?(OpenSSL)
+
with_udp('127.0.0.1', 0) {|u|
_, server_port, _, server_address = u.addr
begin
@@ -297,7 +511,7 @@ class TestResolvDNS < Test::Unit::TestCase
end
def test_no_server
- skip if /mswin/ =~ RUBY_PLATFORM && ENV.key?('GITHUB_ACTIONS') # not working from the beginning
+ omit if /mswin/ =~ RUBY_PLATFORM && ENV.key?('GITHUB_ACTIONS') # not working from the beginning
u = UDPSocket.new
u.bind("127.0.0.1", 0)
_, port, _, host = u.addr
@@ -314,7 +528,7 @@ class TestResolvDNS < Test::Unit::TestCase
rescue Timeout::Error
if RUBY_PLATFORM.match?(/mingw/)
# cannot repo locally
- skip 'Timeout Error on MinGW CI'
+ omit 'Timeout Error on MinGW CI'
else
raise Timeout::Error
end
@@ -374,7 +588,8 @@ class TestResolvDNS < Test::Unit::TestCase
["2001:db8::1", "2001:db8::0:1"],
["::", "0:0:0:0:0:0:0:0"],
["2001::", "2001::0"],
- ["2001:db8::1:1:1:1:1", "2001:db8:0:1:1:1:1:1"],
+ ["2001:db8:0:1:1:1:1:1", "2001:db8:0:1:1:1:1:1"], # RFC 5952 Section 4.2.2.
+ ["2001:db8::1:1:1:1", "2001:db8:0:0:1:1:1:1"],
["1::1:0:0:0:1", "1:0:0:1:0:0:0:1"],
["1::1:0:0:1", "1:0:0:0:1:0:0:1"],
]
@@ -457,4 +672,30 @@ class TestResolvDNS < Test::Unit::TestCase
end
assert_raise(Resolv::ResolvError) { dns.each_name('example.com') }
end
+
+ def test_unreachable_server
+ unreachable_ip = '127.0.0.1'
+ sock = UDPSocket.new
+ sock.connect(unreachable_ip, 53)
+ begin
+ sock.send('1', 0)
+ rescue Errno::ENETUNREACH, Errno::EHOSTUNREACH
+ else
+ omit('cannot test unreachable server, as IP used is reachable')
+ end
+
+ config = {
+ :nameserver => [unreachable_ip],
+ :search => ['lan'],
+ :ndots => 1
+ }
+ r = Resolv.new([Resolv::DNS.new(config)])
+ assert_equal([], r.getaddresses('www.google.com'))
+
+ config[:raise_timeout_errors] = true
+ r = Resolv.new([Resolv::DNS.new(config)])
+ assert_raise(Resolv::ResolvError) { r.getaddresses('www.google.com') }
+ ensure
+ sock&.close
+ end
end
diff --git a/test/resolv/test_resource.rb b/test/resolv/test_resource.rb
index b75cf67f31..434380236e 100644
--- a/test/resolv/test_resource.rb
+++ b/test/resolv/test_resource.rb
@@ -23,4 +23,80 @@ class TestResolvResource < Test::Unit::TestCase
def test_coord
Resolv::LOC::Coord.create('1 2 1.1 N')
end
+
+ def test_srv_no_compress
+ # Domain name in SRV RDATA should not be compressed
+ issue29 = 'https://github.com/ruby/resolv/issues/29'
+ m = Resolv::DNS::Message.new(0)
+ m.add_answer('example.com', 0, Resolv::DNS::Resource::IN::SRV.new(0, 0, 0, 'www.example.com'))
+ assert_equal "\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x07example\x03com\x00\x00\x21\x00\x01\x00\x00\x00\x00\x00\x17\x00\x00\x00\x00\x00\x00\x03www\x07example\x03com\x00", m.encode, issue29
+ end
+end
+
+class TestResolvResourceCAA < Test::Unit::TestCase
+ def test_caa_roundtrip
+ raw_msg = "\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x03new\x07example\x03com\x00\x01\x01\x00\x01\x00\x00\x00\x00\x00\x16\x00\x05issueca1.example.net\xC0\x0C\x01\x01\x00\x01\x00\x00\x00\x00\x00\x0C\x80\x03tbsUnknown".b
+
+ m = Resolv::DNS::Message.new(0)
+ m.add_answer('new.example.com', 0, Resolv::DNS::Resource::IN::CAA.new(0, 'issue', 'ca1.example.net'))
+ m.add_answer('new.example.com', 0, Resolv::DNS::Resource::IN::CAA.new(128, 'tbs', 'Unknown'))
+ assert_equal raw_msg, m.encode
+
+ m = Resolv::DNS::Message.decode(raw_msg)
+ assert_equal 2, m.answer.size
+ _, _, caa0 = m.answer[0]
+ assert_equal 0, caa0.flags
+ assert_equal false, caa0.critical?
+ assert_equal 'issue', caa0.tag
+ assert_equal 'ca1.example.net', caa0.value
+ _, _, caa1 = m.answer[1]
+ assert_equal true, caa1.critical?
+ assert_equal 128, caa1.flags
+ assert_equal 'tbs', caa1.tag
+ assert_equal 'Unknown', caa1.value
+ end
+
+ def test_caa_stackoverflow
+ # gathered in the wild
+ raw_msg = "\x8D\x32\x81\x80\x00\x01\x00\x0B\x00\x00\x00\x00\x0Dstackoverflow\x03com\x00\x01\x01\x00\x01\xC0\x0C\x01\x01\x00\x01\x00\x00\x01\x2C\x00\x13\x00\x05issuecomodoca.com\xC0\x0C\x01\x01\x00\x01\x00\x00\x01\x2C\x00\x2D\x00\x05issuedigicert.com; cansignhttpexchanges=yes\xC0\x0C\x01\x01\x00\x01\x00\x00\x01\x2C\x00\x16\x00\x05issueletsencrypt.org\xC0\x0C\x01\x01\x00\x01\x00\x00\x01\x2C\x00\x29\x00\x05issuepki.goog; cansignhttpexchanges=yes\xC0\x0C\x01\x01\x00\x01\x00\x00\x01\x2C\x00\x12\x00\x05issuesectigo.com\xC0\x0C\x01\x01\x00\x01\x00\x00\x01\x2C\x00\x17\x00\x09issuewildcomodoca.com\xC0\x0C\x01\x01\x00\x01\x00\x00\x01\x2C\x00\x31\x00\x09issuewilddigicert.com; cansignhttpexchanges=yes\xC0\x0C\x01\x01\x00\x01\x00\x00\x01\x2C\x00\x1A\x00\x09issuewildletsencrypt.org\xC0\x0C\x01\x01\x00\x01\x00\x00\x01\x2C\x00\x2D\x00\x09issuewildpki.goog; cansignhttpexchanges=yes\xC0\x0C\x01\x01\x00\x01\x00\x00\x01\x2C\x00\x16\x00\x09issuewildsectigo.com\xC0\x0C\x01\x01\x00\x01\x00\x00\x01\x2C\x00\x2D\x80\x05iodefmailto:sysadmin-team@stackoverflow.com".b
+
+ m = Resolv::DNS::Message.decode(raw_msg)
+ assert_equal 11, m.answer.size
+ _, _, caa3 = m.answer[3]
+ assert_equal 0, caa3.flags
+ assert_equal 'issue', caa3.tag
+ assert_equal 'pki.goog; cansignhttpexchanges=yes', caa3.value
+ _, _, caa8 = m.answer[8]
+ assert_equal 0, caa8.flags
+ assert_equal 'issuewild', caa8.tag
+ assert_equal 'pki.goog; cansignhttpexchanges=yes', caa8.value
+ _, _, caa10 = m.answer[10]
+ assert_equal 128, caa10.flags
+ assert_equal 'iodef', caa10.tag
+ assert_equal 'mailto:sysadmin-team@stackoverflow.com', caa10.value
+ end
+
+ def test_caa_flags
+ assert_equal 255,
+ Resolv::DNS::Resource::IN::CAA.new(255, 'issue', 'ca1.example.net').flags
+ assert_raise(ArgumentError) do
+ Resolv::DNS::Resource::IN::CAA.new(256, 'issue', 'ca1.example.net')
+ end
+
+ assert_raise(ArgumentError) do
+ Resolv::DNS::Resource::IN::CAA.new(-1, 'issue', 'ca1.example.net')
+ end
+ end
+
+ def test_caa_tag
+ assert_raise(ArgumentError, 'Empty tag should be rejected') do
+ Resolv::DNS::Resource::IN::CAA.new(0, '', 'ca1.example.net')
+ end
+
+ assert_equal '123456789012345',
+ Resolv::DNS::Resource::IN::CAA.new(0, '123456789012345', 'ca1.example.net').tag
+ assert_raise(ArgumentError, 'Tag longer than 15 bytes should be rejected') do
+ Resolv::DNS::Resource::IN::CAA.new(0, '1234567890123456', 'ca1.example.net')
+ end
+ end
end
diff --git a/test/resolv/test_svcb_https.rb b/test/resolv/test_svcb_https.rb
new file mode 100644
index 0000000000..5dc3163d9e
--- /dev/null
+++ b/test/resolv/test_svcb_https.rb
@@ -0,0 +1,231 @@
+# frozen_string_literal: false
+require 'test/unit'
+require 'resolv'
+
+class TestResolvSvcbHttps < Test::Unit::TestCase
+ # Wraps a RR in answer section
+ def wrap_rdata(rrtype, rrclass, rdata)
+ [
+ "\x00\x00\x00\x00", # ID/FLAGS
+ [0, 1, 0, 0].pack('nnnn'), # QDCOUNT/ANCOUNT/NSCOUNT/ARCOUNT
+ "\x07example\x03com\x00", # NAME
+ [rrtype, rrclass, 0, rdata.bytesize].pack('nnNn'), # TYPE/CLASS/TTL/RDLENGTH
+ rdata,
+ ].join.b
+ end
+
+ def test_svcparams
+ params = Resolv::DNS::SvcParams.new([Resolv::DNS::SvcParam::Mandatory.new([1])])
+
+ assert_equal 1, params.count
+
+ params.add Resolv::DNS::SvcParam::NoDefaultALPN.new
+ params.add Resolv::DNS::SvcParam::ALPN.new(%w[h2 h3])
+
+ assert_equal 3, params.count
+
+ assert_equal [1], params[:mandatory].keys
+ assert_equal [1], params[0].keys
+
+ assert_equal %w[h2 h3], params[:alpn].protocol_ids
+ assert_equal %w[h2 h3], params[1].protocol_ids
+
+ params.delete :mandatory
+ params.delete :alpn
+
+ assert_equal 1, params.count
+
+ assert_nil params[:mandatory]
+ assert_nil params[1]
+
+ ary = params.each.to_a
+
+ assert_instance_of Resolv::DNS::SvcParam::NoDefaultALPN, ary.first
+ end
+
+ def test_svcb
+ rr = Resolv::DNS::Resource::IN::SVCB.new(0, 'example.com.')
+
+ assert_equal 0, rr.priority
+ assert rr.alias_mode?
+ assert !rr.service_mode?
+ assert_equal Resolv::DNS::Name.create('example.com.'), rr.target
+ assert rr.params.empty?
+
+ rr = Resolv::DNS::Resource::IN::SVCB.new(16, 'example.com.', [
+ Resolv::DNS::SvcParam::ALPN.new(%w[h2 h3]),
+ ])
+
+ assert_equal 16, rr.priority
+ assert !rr.alias_mode?
+ assert rr.service_mode?
+
+ assert_equal 1, rr.params.count
+ assert_instance_of Resolv::DNS::SvcParam::ALPN, rr.params[:alpn]
+ end
+
+ def test_svcb_encode_order
+ msg = Resolv::DNS::Message.new(0)
+ msg.add_answer(
+ 'example.com.', 0,
+ Resolv::DNS::Resource::IN::SVCB.new(16, 'foo.example.org.', [
+ Resolv::DNS::SvcParam::ALPN.new(%w[h2 h3-19]),
+ Resolv::DNS::SvcParam::Mandatory.new([4, 1]),
+ Resolv::DNS::SvcParam::IPv4Hint.new(['192.0.2.1']),
+ ])
+ )
+
+ expected = wrap_rdata 64, 1, "\x00\x10\x03foo\x07example\x03org\x00" +
+ "\x00\x00\x00\x04\x00\x01\x00\x04" +
+ "\x00\x01\x00\x09\x02h2\x05h3-19" +
+ "\x00\x04\x00\x04\xc0\x00\x02\x01"
+
+ assert_equal expected, msg.encode
+ end
+
+ ## Test vectors from [RFC9460]
+
+ def test_alias_mode
+ wire = wrap_rdata 65, 1, "\x00\x00\x03foo\x07example\x03com\x00"
+ msg = Resolv::DNS::Message.decode(wire)
+ _, _, rr = msg.answer.first
+
+ assert_equal 0, rr.priority
+ assert_equal Resolv::DNS::Name.create('foo.example.com.'), rr.target
+ assert_equal 0, rr.params.count
+
+ assert_equal wire, msg.encode
+ end
+
+ def test_target_name_is_root
+ wire = wrap_rdata 64, 1, "\x00\x01\x00"
+ msg = Resolv::DNS::Message.decode(wire)
+ _, _, rr = msg.answer.first
+
+ assert_equal 1, rr.priority
+ assert_equal Resolv::DNS::Name.create('.'), rr.target
+ assert_equal 0, rr.params.count
+
+ assert_equal wire, msg.encode
+ end
+
+ def test_specifies_port
+ wire = wrap_rdata 64, 1, "\x00\x10\x03foo\x07example\x03com\x00" +
+ "\x00\x03\x00\x02\x00\x35"
+ msg = Resolv::DNS::Message.decode(wire)
+ _, _, rr = msg.answer.first
+
+ assert_equal 16, rr.priority
+ assert_equal Resolv::DNS::Name.create('foo.example.com.'), rr.target
+ assert_equal 1, rr.params.count
+ assert_equal 53, rr.params[:port].port
+
+ assert_equal wire, msg.encode
+ end
+
+ def test_generic_key
+ wire = wrap_rdata 64, 1, "\x00\x01\x03foo\x07example\x03com\x00" +
+ "\x02\x9b\x00\x05hello"
+ msg = Resolv::DNS::Message.decode(wire)
+ _, _, rr = msg.answer.first
+
+ assert_equal 1, rr.priority
+ assert_equal Resolv::DNS::Name.create('foo.example.com.'), rr.target
+ assert_equal 1, rr.params.count
+ assert_equal 'hello', rr.params[:key667].value
+
+ assert_equal wire, msg.encode
+ end
+
+ def test_two_ipv6hints
+ wire = wrap_rdata 64, 1, "\x00\x01\x03foo\x07example\x03com\x00" +
+ "\x00\x06\x00\x20" +
+ ("\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" +
+ "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x53\x00\x01")
+ msg = Resolv::DNS::Message.decode(wire)
+ _, _, rr = msg.answer.first
+
+ assert_equal 1, rr.priority
+ assert_equal Resolv::DNS::Name.create('foo.example.com.'), rr.target
+ assert_equal 1, rr.params.count
+ assert_equal [Resolv::IPv6.create('2001:db8::1'), Resolv::IPv6.create('2001:db8::53:1')],
+ rr.params[:ipv6hint].addresses
+
+ assert_equal wire, msg.encode
+ end
+
+ def test_ipv6hint_embedded_ipv4
+ wire = wrap_rdata 64, 1, "\x00\x01\x07example\x03com\x00" +
+ "\x00\x06\x00\x10\x20\x01\x0d\xb8\x01\x22\x03\x44\x00\x00\x00\x00\xc0\x00\x02\x21"
+ msg = Resolv::DNS::Message.decode(wire)
+ _, _, rr = msg.answer.first
+
+ assert_equal 1, rr.priority
+ assert_equal Resolv::DNS::Name.create('example.com.'), rr.target
+ assert_equal 1, rr.params.count
+ assert_equal [Resolv::IPv6.create('2001:db8:122:344::192.0.2.33')],
+ rr.params[:ipv6hint].addresses
+
+ assert_equal wire, msg.encode
+ end
+
+ def test_mandatory_alpn_ipv4hint
+ wire = wrap_rdata 64, 1, "\x00\x10\x03foo\x07example\x03org\x00" +
+ "\x00\x00\x00\x04\x00\x01\x00\x04" +
+ "\x00\x01\x00\x09\x02h2\x05h3-19" +
+ "\x00\x04\x00\x04\xc0\x00\x02\x01"
+ msg = Resolv::DNS::Message.decode(wire)
+ _, _, rr = msg.answer.first
+
+ assert_equal 16, rr.priority
+ assert_equal Resolv::DNS::Name.create('foo.example.org.'), rr.target
+ assert_equal 3, rr.params.count
+ assert_equal [1, 4], rr.params[:mandatory].keys
+ assert_equal ['h2', 'h3-19'], rr.params[:alpn].protocol_ids
+ assert_equal [Resolv::IPv4.create('192.0.2.1')], rr.params[:ipv4hint].addresses
+
+ assert_equal wire, msg.encode
+ end
+
+ def test_alpn_comma_backslash
+ wire = wrap_rdata 64, 1, "\x00\x10\x03foo\x07example\x03org\x00" +
+ "\x00\x01\x00\x0c\x08f\\oo,bar\x02h2"
+ msg = Resolv::DNS::Message.decode(wire)
+ _, _, rr = msg.answer.first
+
+ assert_equal 16, rr.priority
+ assert_equal Resolv::DNS::Name.create('foo.example.org.'), rr.target
+ assert_equal 1, rr.params.count
+ assert_equal ['f\oo,bar', 'h2'], rr.params[:alpn].protocol_ids
+
+ assert_equal wire, msg.encode
+ end
+
+ ## For [RFC9461]
+
+ def test_dohpath
+ wire = wrap_rdata 64, 1, "\x00\x01\x03one\x03one\x03one\x03one\x00" +
+ "\x00\x01\x00\x03\x02h2" +
+ "\x00\x03\x00\x02\x01\xbb" +
+ "\x00\x04\x00\x08\x01\x01\x01\x01\x01\x00\x00\x01" +
+ "\x00\x06\x00\x20" +
+ ("\x26\x06\x47\x00\x47\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11" +
+ "\x26\x06\x47\x00\x47\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x01") +
+ "\x00\x07\x00\x10/dns-query{?dns}"
+ msg = Resolv::DNS::Message.decode(wire)
+ _, _, rr = msg.answer.first
+
+ assert_equal 1, rr.priority
+ assert_equal Resolv::DNS::Name.create('one.one.one.one.'), rr.target
+ assert_equal 5, rr.params.count
+ assert_equal ['h2'], rr.params[:alpn].protocol_ids
+ assert_equal 443, rr.params[:port].port
+ assert_equal [Resolv::IPv4.create('1.1.1.1'), Resolv::IPv4.create('1.0.0.1')],
+ rr.params[:ipv4hint].addresses
+ assert_equal [Resolv::IPv6.create('2606:4700:4700::1111'), Resolv::IPv6.create('2606:4700:4700::1001')],
+ rr.params[:ipv6hint].addresses
+ assert_equal '/dns-query{?dns}', rr.params[:dohpath].template
+
+ assert_equal wire, msg.encode
+ end
+end