[ruby/resolv] Implement SVCB and HTTPS RRs
(https://github.com/ruby/resolv/pull/32) * Add MessageDecoder#get_list This method repeats yielding until all the data upto the current limit is consumed, and then returns an Array containig the block results. * Implement SVCB and HTTPS RRs [RFC 9460] > This patch implements SVCB and HTTPS resource record types defined in > [RFC 9460]. > > The RR types are now supported by many server implementations including > BIND, unbound, PowerDNS, and Knot DNS. Major browsers such as Chrome, > Edge, and Safari have started to query HTTPS records, with the records > gradually adopted by websites. Also, SVCB is actually deployed in the > public DNS resolvers such as Cloudflare DNS and Google Public DNS for > [DDR]. > > With such wide adoption, we have plenty of real-world use cases, and > it is unlikely the wire format will change further in an incompatible > way. It is time to implement them in the client libraries! > > # Rationale for proposed API > > ## `Resolv::DNS::Resource::IN::ServiceBinding` > > This is an abstract class for SVCB-compatible RR types. > SVCB-compatible RR types, as defined in the Draft, shares the wire > format and the semantics of their RDATA fields with SVCB to allow > implementations to share the processing of these RR types. So we do > so. > > The interface of this class is straightforward: It has three > attributes `priority`, `target`, and `params`, which correspond the > RDATA fields SvcPriority, TargetName, and SvcParams, resp. > > SVCB RR type is defined specifically within IN class. Thus, this > class is placed in the `Resolv::DNS::Resource::IN` namespace. > > ## `Resolv::DNS::Resource::IN::SVCB`, `Resolv::DNS::Resource::IN::HTTPS` > > Just inherits ServiceBinding class. > > ## `Resolv::DNS::SvcParam` > > This class represents a pair of a SvcParamKey and a SvcParamValue. > Aligned with the design of `Resolv::DNS::Resource`, each SvcParamKey > has its own subclass of `Resolv::DNS::SvcParam`. > > ## `Resolv::DNS::SvcParam::Generic` > > This is an abstract class representing a SvcParamKey that is unknown > to this library. `Generic.create(key)` dynamically defines its > subclass for specific `key`. E.g., `Generic.create(667)` will define > `Generic::Key667`. > > This class holds SvcParamValue in its wire format. > > SvcParam with an unknown SvcParamKey will be decoded as a subclass of > this class. Also, users of this library can generate a non-supported > SvcParam if they know its wire format. > > ## `Resolv::DNS::SvcParams` > > This is conceptually a set of `SvcParam`s, whose elements have the > unique SvcParamKeys. It behaves like a set, and for convenience > provides indexing by SvcParamKey. > > - `#initialize(params)` takes an Enumerable of `SvcParam`s as the > initial content. If it contains `SvcParam`s with the duplicate key, > the one that appears last takes precedence. > - `#[](key)` fetches the `SvcParam` with the given key. The key can be > specified by its name (e.g., `:alpn`) or number (e.g., `1`). > - `#add(param)` adds a `SvcParam` to the set. If the set already has a > `SvcParam` with the same key, it will be replaced. > - `#delete(key)` deletes a `SvcParam` by its key and returns it. The key > can be specified by its name or number. * Update comments referring to draft-ietf-dnsop-svcb-https-12 Published as RFC 9460. https://datatracker.ietf.org/doc/rfc9460/ [draft-ietf-dnsop-svcb-https-12]: https://datatracker.ietf.org/doc/draft-ietf-dnsop-svcb-https/12/ [RFC 9460]: https://datatracker.ietf.org/doc/rfc9460/ [DDR]: https://datatracker.ietf.org/doc/draft-ietf-add-ddr/ https://github.com/ruby/resolv/commit/b3ced7f039
This commit is contained in:
parent
1ffaff884e
commit
608a518b42
429
lib/resolv.rb
429
lib/resolv.rb
@ -1618,6 +1618,14 @@ class Resolv
|
||||
strings
|
||||
end
|
||||
|
||||
def get_list
|
||||
[].tap do |values|
|
||||
while @index < @limit
|
||||
values << yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_name
|
||||
return Name.new(self.get_labels)
|
||||
end
|
||||
@ -1678,6 +1686,349 @@ class Resolv
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# SvcParams for service binding RRs. [RFC9460]
|
||||
|
||||
class SvcParams
|
||||
include Enumerable
|
||||
|
||||
##
|
||||
# Create a list of SvcParams with the given initial content.
|
||||
#
|
||||
# +params+ has to be an enumerable of +SvcParam+s.
|
||||
# If its content has +SvcParam+s with the duplicate key,
|
||||
# the one appears last takes precedence.
|
||||
|
||||
def initialize(params = [])
|
||||
@params = {}
|
||||
|
||||
params.each do |param|
|
||||
add param
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Get SvcParam for the given +key+ in this list.
|
||||
|
||||
def [](key)
|
||||
@params[canonical_key(key)]
|
||||
end
|
||||
|
||||
##
|
||||
# Get the number of SvcParams in this list.
|
||||
|
||||
def count
|
||||
@params.count
|
||||
end
|
||||
|
||||
##
|
||||
# Get whether this list is empty.
|
||||
|
||||
def empty?
|
||||
@params.empty?
|
||||
end
|
||||
|
||||
##
|
||||
# Add the SvcParam +param+ to this list, overwriting the existing one with the same key.
|
||||
|
||||
def add(param)
|
||||
@params[param.class.key_number] = param
|
||||
end
|
||||
|
||||
##
|
||||
# Remove the +SvcParam+ with the given +key+ and return it.
|
||||
|
||||
def delete(key)
|
||||
@params.delete(canonical_key(key))
|
||||
end
|
||||
|
||||
##
|
||||
# Enumerate the +SvcParam+s in this list.
|
||||
|
||||
def each(&block)
|
||||
return enum_for(:each) unless block
|
||||
@params.each_value(&block)
|
||||
end
|
||||
|
||||
def encode(msg) # :nodoc:
|
||||
@params.keys.sort.each do |key|
|
||||
msg.put_pack('n', key)
|
||||
msg.put_length16 do
|
||||
@params.fetch(key).encode(msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.decode(msg) # :nodoc:
|
||||
params = msg.get_list do
|
||||
key, = msg.get_unpack('n')
|
||||
msg.get_length16 do
|
||||
SvcParam::ClassHash[key].decode(msg)
|
||||
end
|
||||
end
|
||||
|
||||
return self.new(params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def canonical_key(key) # :nodoc:
|
||||
case key
|
||||
when Integer
|
||||
key
|
||||
when /\Akey(\d+)\z/
|
||||
Integer($1)
|
||||
when Symbol
|
||||
SvcParam::ClassHash[key].key_number
|
||||
else
|
||||
raise TypeError, 'key must be either String or Symbol'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
##
|
||||
# Base class for SvcParam. [RFC9460]
|
||||
|
||||
class SvcParam
|
||||
|
||||
##
|
||||
# Get the presentation name of the SvcParamKey.
|
||||
|
||||
def self.key_name
|
||||
const_get(:KeyName)
|
||||
end
|
||||
|
||||
##
|
||||
# Get the registered number of the SvcParamKey.
|
||||
|
||||
def self.key_number
|
||||
const_get(:KeyNumber)
|
||||
end
|
||||
|
||||
ClassHash = Hash.new do |h, key| # :nodoc:
|
||||
case key
|
||||
when Integer
|
||||
Generic.create(key)
|
||||
when /\Akey(?<key>\d+)\z/
|
||||
Generic.create(key.to_int)
|
||||
when Symbol
|
||||
raise KeyError, "unknown key #{key}"
|
||||
else
|
||||
raise TypeError, 'key must be either String or Symbol'
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Generic SvcParam abstract class.
|
||||
|
||||
class Generic < SvcParam
|
||||
|
||||
##
|
||||
# SvcParamValue in wire-format byte string.
|
||||
|
||||
attr_reader :value
|
||||
|
||||
##
|
||||
# Create generic SvcParam
|
||||
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def encode(msg) # :nodoc:
|
||||
msg.put_bytes(@value)
|
||||
end
|
||||
|
||||
def self.decode(msg) # :nodoc:
|
||||
return self.new(msg.get_bytes)
|
||||
end
|
||||
|
||||
def self.create(key_number)
|
||||
c = Class.new(Generic)
|
||||
key_name = :"key#{key_number}"
|
||||
c.const_set(:KeyName, key_name)
|
||||
c.const_set(:KeyNumber, key_number)
|
||||
self.const_set(:"Key#{key_number}", c)
|
||||
ClassHash[key_name] = ClassHash[key_number] = c
|
||||
return c
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# "mandatory" SvcParam -- Mandatory keys in service binding RR
|
||||
|
||||
class Mandatory < SvcParam
|
||||
KeyName = :mandatory
|
||||
KeyNumber = 0
|
||||
ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
|
||||
|
||||
##
|
||||
# Mandatory keys.
|
||||
|
||||
attr_reader :keys
|
||||
|
||||
##
|
||||
# Initialize "mandatory" ScvParam.
|
||||
|
||||
def initialize(keys)
|
||||
@keys = keys.map(&:to_int)
|
||||
end
|
||||
|
||||
def encode(msg) # :nodoc:
|
||||
@keys.sort.each do |key|
|
||||
msg.put_pack('n', key)
|
||||
end
|
||||
end
|
||||
|
||||
def self.decode(msg) # :nodoc:
|
||||
keys = msg.get_list { msg.get_unpack('n')[0] }
|
||||
return self.new(keys)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# "alpn" SvcParam -- Additional supported protocols
|
||||
|
||||
class ALPN < SvcParam
|
||||
KeyName = :alpn
|
||||
KeyNumber = 1
|
||||
ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
|
||||
|
||||
##
|
||||
# Supported protocol IDs.
|
||||
|
||||
attr_reader :protocol_ids
|
||||
|
||||
##
|
||||
# Initialize "alpn" ScvParam.
|
||||
|
||||
def initialize(protocol_ids)
|
||||
@protocol_ids = protocol_ids.map(&:to_str)
|
||||
end
|
||||
|
||||
def encode(msg) # :nodoc:
|
||||
msg.put_string_list(@protocol_ids)
|
||||
end
|
||||
|
||||
def self.decode(msg) # :nodoc:
|
||||
return self.new(msg.get_string_list)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# "no-default-alpn" SvcParam -- No support for default protocol
|
||||
|
||||
class NoDefaultALPN < SvcParam
|
||||
KeyName = :'no-default-alpn'
|
||||
KeyNumber = 2
|
||||
ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
|
||||
|
||||
def encode(msg) # :nodoc:
|
||||
# no payload
|
||||
end
|
||||
|
||||
def self.decode(msg) # :nodoc:
|
||||
return self.new
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# "port" SvcParam -- Port for alternative endpoint
|
||||
|
||||
class Port < SvcParam
|
||||
KeyName = :port
|
||||
KeyNumber = 3
|
||||
ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
|
||||
|
||||
##
|
||||
# Port number.
|
||||
|
||||
attr_reader :port
|
||||
|
||||
##
|
||||
# Initialize "port" ScvParam.
|
||||
|
||||
def initialize(port)
|
||||
@port = port.to_int
|
||||
end
|
||||
|
||||
def encode(msg) # :nodoc:
|
||||
msg.put_pack('n', @port)
|
||||
end
|
||||
|
||||
def self.decode(msg) # :nodoc:
|
||||
port, = msg.get_unpack('n')
|
||||
return self.new(port)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# "ipv4hint" SvcParam -- IPv4 address hints
|
||||
|
||||
class IPv4Hint < SvcParam
|
||||
KeyName = :ipv4hint
|
||||
KeyNumber = 4
|
||||
ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
|
||||
|
||||
##
|
||||
# Set of IPv4 addresses.
|
||||
|
||||
attr_reader :addresses
|
||||
|
||||
##
|
||||
# Initialize "ipv4hint" ScvParam.
|
||||
|
||||
def initialize(addresses)
|
||||
@addresses = addresses.map {|address| IPv4.create(address) }
|
||||
end
|
||||
|
||||
def encode(msg) # :nodoc:
|
||||
@addresses.each do |address|
|
||||
msg.put_bytes(address.address)
|
||||
end
|
||||
end
|
||||
|
||||
def self.decode(msg) # :nodoc:
|
||||
addresses = msg.get_list { IPv4.new(msg.get_bytes(4)) }
|
||||
return self.new(addresses)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# "ipv6hint" SvcParam -- IPv6 address hints
|
||||
|
||||
class IPv6Hint < SvcParam
|
||||
KeyName = :ipv6hint
|
||||
KeyNumber = 6
|
||||
ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
|
||||
|
||||
##
|
||||
# Set of IPv6 addresses.
|
||||
|
||||
attr_reader :addresses
|
||||
|
||||
##
|
||||
# Initialize "ipv6hint" ScvParam.
|
||||
|
||||
def initialize(addresses)
|
||||
@addresses = addresses.map {|address| IPv6.create(address) }
|
||||
end
|
||||
|
||||
def encode(msg) # :nodoc:
|
||||
@addresses.each do |address|
|
||||
msg.put_bytes(address.address)
|
||||
end
|
||||
end
|
||||
|
||||
def self.decode(msg) # :nodoc:
|
||||
addresses = msg.get_list { IPv6.new(msg.get_bytes(16)) }
|
||||
return self.new(addresses)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
# A DNS query abstract class.
|
||||
|
||||
@ -2341,6 +2692,84 @@ class Resolv
|
||||
return self.new(priority, weight, port, target)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Common implementation for SVCB-compatible resource records.
|
||||
|
||||
class ServiceBinding
|
||||
|
||||
##
|
||||
# Create a service binding resource record.
|
||||
|
||||
def initialize(priority, target, params = [])
|
||||
@priority = priority.to_int
|
||||
@target = Name.create(target)
|
||||
@params = SvcParams.new(params)
|
||||
end
|
||||
|
||||
##
|
||||
# The priority of this target host.
|
||||
#
|
||||
# The range is 0-65535.
|
||||
# If set to 0, this RR is in AliasMode. Otherwise, it is in ServiceMode.
|
||||
|
||||
attr_reader :priority
|
||||
|
||||
##
|
||||
# The domain name of the target host.
|
||||
|
||||
attr_reader :target
|
||||
|
||||
##
|
||||
# The service paramters for the target host.
|
||||
|
||||
attr_reader :params
|
||||
|
||||
##
|
||||
# Whether this RR is in AliasMode.
|
||||
|
||||
def alias_mode?
|
||||
self.priority == 0
|
||||
end
|
||||
|
||||
##
|
||||
# Whether this RR is in ServiceMode.
|
||||
|
||||
def service_mode?
|
||||
!alias_mode?
|
||||
end
|
||||
|
||||
def encode_rdata(msg) # :nodoc:
|
||||
msg.put_pack("n", @priority)
|
||||
msg.put_name(@target, compress: false)
|
||||
@params.encode(msg)
|
||||
end
|
||||
|
||||
def self.decode_rdata(msg) # :nodoc:
|
||||
priority, = msg.get_unpack("n")
|
||||
target = msg.get_name
|
||||
params = SvcParams.decode(msg)
|
||||
return self.new(priority, target, params)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# SVCB resource record [RFC9460]
|
||||
|
||||
class SVCB < ServiceBinding
|
||||
TypeValue = 64
|
||||
ClassValue = IN::ClassValue
|
||||
ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
|
||||
end
|
||||
|
||||
##
|
||||
# HTTPS resource record [RFC9460]
|
||||
|
||||
class HTTPS < ServiceBinding
|
||||
TypeValue = 65
|
||||
ClassValue = IN::ClassValue
|
||||
ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
204
test/resolv/test_svcb_https.rb
Normal file
204
test/resolv/test_svcb_https.rb
Normal file
@ -0,0 +1,204 @@
|
||||
# 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
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user