2015-11-10 11:48:14 +00:00
# frozen_string_literal: true
2015-10-05 13:27:48 +00:00
2001-05-30 09:10:30 +00:00
require 'socket'
require 'timeout'
2015-05-06 20:30:43 +00:00
require 'io/wait'
2024-08-22 21:49:26 +02:00
require 'securerandom'
2008-08-08 01:58:40 +00:00
2005-06-09 22:47:35 +00:00
# Resolv is a thread-aware DNS resolver library written in Ruby. Resolv can
2013-07-18 13:50:32 +00:00
# handle multiple DNS requests concurrently without blocking the entire Ruby
2005-06-09 22:47:35 +00:00
# interpreter.
#
2010-01-02 18:10:47 +00:00
# See also resolv-replace.rb to replace the libc resolver with Resolv.
2008-09-14 15:18:53 +00:00
#
2005-06-09 22:47:35 +00:00
# Resolv can look up various DNS resources using the DNS module directly.
2008-09-14 15:18:53 +00:00
#
2005-06-09 22:47:35 +00:00
# Examples:
2008-09-14 15:18:53 +00:00
#
2005-06-09 22:47:35 +00:00
# p Resolv.getaddress "www.ruby-lang.org"
# p Resolv.getname "210.251.121.214"
2008-09-14 15:18:53 +00:00
#
2005-06-09 22:47:35 +00:00
# Resolv::DNS.open do |dns|
# ress = dns.getresources "www.ruby-lang.org", Resolv::DNS::Resource::IN::A
2016-11-21 23:50:31 +00:00
# p ress.map(&:address)
2005-06-09 22:47:35 +00:00
# ress = dns.getresources "ruby-lang.org", Resolv::DNS::Resource::IN::MX
# p ress.map { |r| [r.exchange.to_s, r.preference] }
# end
2008-09-14 15:18:53 +00:00
#
#
2005-06-09 22:47:35 +00:00
# == Bugs
2008-09-14 15:18:53 +00:00
#
2005-06-09 22:47:35 +00:00
# * NIS is not supported.
# * /etc/nsswitch.conf is not supported.
2001-05-30 09:10:30 +00:00
class Resolv
2005-06-09 22:47:35 +00:00
2024-12-13 10:12:09 +09:00
VERSION = " 0.6.0 "
2023-03-30 13:29:05 +09:00
2005-06-09 22:47:35 +00:00
##
# Looks up the first IP address for +name+.
2008-09-14 15:18:53 +00:00
2001-05-30 09:10:30 +00:00
def self . getaddress ( name )
DefaultResolver . getaddress ( name )
end
2005-06-09 22:47:35 +00:00
##
# Looks up all IP address for +name+.
2008-09-14 15:18:53 +00:00
2001-11-01 05:11:24 +00:00
def self . getaddresses ( name )
DefaultResolver . getaddresses ( name )
end
2005-06-09 22:47:35 +00:00
##
# Iterates over all IP addresses for +name+.
2001-11-01 05:11:24 +00:00
def self . each_address ( name , & block )
DefaultResolver . each_address ( name , & block )
end
2005-06-09 22:47:35 +00:00
##
# Looks up the hostname of +address+.
2001-05-30 09:10:30 +00:00
def self . getname ( address )
DefaultResolver . getname ( address )
end
2005-06-09 22:47:35 +00:00
##
# Looks up all hostnames for +address+.
2001-11-01 05:11:24 +00:00
def self . getnames ( address )
DefaultResolver . getnames ( address )
end
2005-06-09 22:47:35 +00:00
##
# Iterates over all hostnames for +address+.
2001-11-01 05:11:24 +00:00
def self . each_name ( address , & proc )
DefaultResolver . each_name ( address , & proc )
end
2005-06-09 22:47:35 +00:00
##
# Creates a new Resolv using +resolvers+.
2023-11-24 18:25:12 -08:00
#
# If +resolvers+ is not given, a hash, or +nil+, uses a Hosts resolver and
# and a DNS resolver. If +resolvers+ is a hash, uses the hash as
# configuration for the DNS resolver.
def initialize ( resolvers = ( arg_not_set = true ; nil ) , use_ipv6 : ( keyword_not_set = true ; nil ) )
if ! keyword_not_set && ! arg_not_set
warn " Support for separate use_ipv6 keyword is deprecated, as it is ignored if an argument is provided. Do not provide a positional argument if using the use_ipv6 keyword argument. " , uplevel : 1
end
2005-06-09 22:47:35 +00:00
2023-11-24 18:25:12 -08:00
@resolvers = case resolvers
when Hash , nil
[ Hosts . new , DNS . new ( DNS :: Config . default_config_hash . merge ( resolvers || { } ) ) ]
else
resolvers
end
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
##
# Looks up the first IP address for +name+.
2008-09-14 15:18:53 +00:00
2001-05-30 09:10:30 +00:00
def getaddress ( name )
2001-11-01 05:11:24 +00:00
each_address ( name ) { | address | return address }
raise ResolvError . new ( " no address for #{ name } " )
end
2005-06-09 22:47:35 +00:00
##
# Looks up all IP address for +name+.
2008-09-14 15:18:53 +00:00
2001-11-01 05:11:24 +00:00
def getaddresses ( name )
ret = [ ]
each_address ( name ) { | address | ret << address }
return ret
end
2005-06-09 22:47:35 +00:00
##
# Iterates over all IP addresses for +name+.
2001-11-01 05:11:24 +00:00
def each_address ( name )
if AddressRegex =~ name
yield name
return
2001-05-30 09:10:30 +00:00
end
2001-11-01 05:11:24 +00:00
yielded = false
@resolvers . each { | r |
r . each_address ( name ) { | address |
yield address . to_s
2003-07-24 01:03:28 +00:00
yielded = true
2001-11-01 05:11:24 +00:00
}
return if yielded
}
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
##
# Looks up the hostname of +address+.
2001-05-30 09:10:30 +00:00
def getname ( address )
2001-11-01 05:11:24 +00:00
each_name ( address ) { | name | return name }
raise ResolvError . new ( " no name for #{ address } " )
end
2005-06-09 22:47:35 +00:00
##
# Looks up all hostnames for +address+.
2001-11-01 05:11:24 +00:00
def getnames ( address )
ret = [ ]
each_name ( address ) { | name | ret << name }
return ret
end
2005-06-09 22:47:35 +00:00
##
# Iterates over all hostnames for +address+.
2001-11-01 05:11:24 +00:00
def each_name ( address )
yielded = false
@resolvers . each { | r |
r . each_name ( address ) { | name |
yield name . to_s
2003-07-24 01:03:28 +00:00
yielded = true
2001-11-01 05:11:24 +00:00
}
return if yielded
}
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
##
# Indicates a failure to resolve a name or address.
2001-05-30 09:10:30 +00:00
2005-06-09 22:47:35 +00:00
class ResolvError < StandardError ; end
##
# Indicates a timeout resolving a name or address.
2014-10-07 20:00:09 +00:00
class ResolvTimeout < Timeout :: Error ; end
2005-06-09 22:47:35 +00:00
2024-12-10 15:48:18 -06:00
WINDOWS = / mswin|cygwin|mingw|bccwin / =~ RUBY_PLATFORM || :: RbConfig :: CONFIG [ 'host_os' ] =~ / mswin /
private_constant :WINDOWS
2005-06-09 22:47:35 +00:00
##
2009-01-31 13:42:18 +00:00
# Resolv::Hosts is a hostname resolver that uses the system hosts file.
2002-01-16 03:37:23 +00:00
2001-05-30 09:10:30 +00:00
class Hosts
2024-12-10 15:48:18 -06:00
if WINDOWS
2019-07-17 14:45:33 +09:00
begin
2025-02-04 13:41:18 +09:00
require 'win32/resolv' unless defined? ( Win32 :: Resolv )
2019-07-17 14:45:33 +09:00
DefaultFileName = Win32 :: Resolv . get_hosts_path || IO :: NULL
rescue LoadError
end
2003-07-24 01:03:28 +00:00
end
2019-07-17 14:45:33 +09:00
DefaultFileName || = '/etc/hosts'
2001-05-30 09:10:30 +00:00
2005-06-09 22:47:35 +00:00
##
2009-01-31 13:42:18 +00:00
# Creates a new Resolv::Hosts, using +filename+ for its data source.
2005-06-09 22:47:35 +00:00
2001-05-30 09:10:30 +00:00
def initialize ( filename = DefaultFileName )
@filename = filename
2016-08-30 06:22:30 +00:00
@mutex = Thread :: Mutex . new
2001-09-08 14:17:53 +00:00
@initialized = nil
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
def lazy_initialize # :nodoc:
2001-05-30 09:10:30 +00:00
@mutex . synchronize {
unless @initialized
@name2addr = { }
@addr2name = { }
2017-12-20 04:18:31 +00:00
File . open ( @filename , 'rb' ) { | f |
2001-05-30 09:10:30 +00:00
f . each { | line |
line . sub! ( / # .* / , '' )
2023-04-28 21:59:57 +09:00
addr , * hostnames = line . split ( / \ s+ / )
2001-05-30 09:10:30 +00:00
next unless addr
2024-02-25 13:06:04 +09:00
( @addr2name [ addr ] || = [ ] ) . concat ( hostnames )
hostnames . each { | hostname | ( @name2addr [ hostname ] || = [ ] ) << addr }
2001-05-30 09:10:30 +00:00
}
}
@name2addr . each { | name , arr | arr . reverse! }
@initialized = true
end
}
2005-01-28 08:18:59 +00:00
self
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
##
# Gets the IP address of +name+ from the hosts file.
2001-05-30 09:10:30 +00:00
def getaddress ( name )
2001-11-01 05:11:24 +00:00
each_address ( name ) { | address | return address }
raise ResolvError . new ( " #{ @filename } has no name: #{ name } " )
end
2005-06-09 22:47:35 +00:00
##
# Gets all IP addresses for +name+ from the hosts file.
2001-11-01 05:11:24 +00:00
def getaddresses ( name )
ret = [ ]
each_address ( name ) { | address | ret << address }
return ret
end
2005-06-09 22:47:35 +00:00
##
# Iterates over all IP addresses for +name+ retrieved from the hosts file.
2001-11-01 05:11:24 +00:00
def each_address ( name , & proc )
2001-05-30 09:10:30 +00:00
lazy_initialize
2018-01-18 02:44:50 +00:00
@name2addr [ name ] & . each ( & proc )
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
##
# Gets the hostname of +address+ from the hosts file.
2001-05-30 09:10:30 +00:00
def getname ( address )
2001-11-01 05:11:24 +00:00
each_name ( address ) { | name | return name }
raise ResolvError . new ( " #{ @filename } has no address: #{ address } " )
end
2005-06-09 22:47:35 +00:00
##
# Gets all hostnames for +address+ from the hosts file.
2001-11-01 05:11:24 +00:00
def getnames ( address )
ret = [ ]
each_name ( address ) { | name | ret << name }
return ret
end
2005-06-09 22:47:35 +00:00
##
2008-06-04 09:37:38 +00:00
# Iterates over all hostnames for +address+ retrieved from the hosts file.
2005-06-09 22:47:35 +00:00
2001-11-01 05:11:24 +00:00
def each_name ( address , & proc )
2001-05-30 09:10:30 +00:00
lazy_initialize
2016-11-23 23:57:30 +00:00
@addr2name [ address ] & . each ( & proc )
2001-05-30 09:10:30 +00:00
end
end
2005-06-09 22:47:35 +00:00
##
# Resolv::DNS is a DNS stub resolver.
#
# Information taken from the following places:
#
# * STD0013
# * RFC 1035
# * ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters
# * etc.
2001-05-30 09:10:30 +00:00
class DNS
2005-06-09 22:47:35 +00:00
##
# Default DNS Port
2001-05-30 09:10:30 +00:00
Port = 53
2005-06-09 22:47:35 +00:00
##
# Default DNS UDP packet size
2001-05-30 09:10:30 +00:00
UDPSize = 512
2005-06-09 22:47:35 +00:00
##
# Creates a new DNS resolver. See Resolv::DNS.new for argument details.
#
# Yields the created DNS resolver to the block, if given, otherwise
# returns it.
2002-07-12 16:34:02 +00:00
def self . open ( * args )
dns = new ( * args )
return dns unless block_given?
begin
2003-07-24 01:03:28 +00:00
yield dns
2002-07-12 16:34:02 +00:00
ensure
2003-07-24 01:03:28 +00:00
dns . close
2002-07-12 16:34:02 +00:00
end
end
2005-06-09 22:47:35 +00:00
##
# Creates a new DNS resolver.
#
# +config_info+ can be:
2008-09-14 15:18:53 +00:00
#
2005-06-09 22:47:35 +00:00
# nil:: Uses /etc/resolv.conf.
# String:: Path to a file using /etc/resolv.conf's format.
# Hash:: Must contain :nameserver, :search and :ndots keys.
2010-01-02 07:03:24 +00:00
# :nameserver_port can be used to specify port number of nameserver address.
2021-09-17 17:17:20 -07:00
# :raise_timeout_errors can be used to raise timeout errors
# as exceptions instead of treating the same as an NXDOMAIN response.
2010-01-02 07:03:24 +00:00
#
# The value of :nameserver should be an address string or
# an array of address strings.
# - :nameserver => '8.8.8.8'
# - :nameserver => ['8.8.8.8', '8.8.4.4']
#
# The value of :nameserver_port should be an array of
# pair of nameserver address and port number.
# - :nameserver_port => [['8.8.8.8', 53], ['8.8.4.4', 53]]
2005-06-09 22:47:35 +00:00
#
# Example:
#
# Resolv::DNS.new(:nameserver => ['210.251.121.21'],
# :search => ['ruby-lang.org'],
# :ndots => 1)
2004-05-12 08:44:11 +00:00
def initialize ( config_info = nil )
2016-08-30 06:22:30 +00:00
@mutex = Thread :: Mutex . new
2004-05-12 08:44:11 +00:00
@config = Config . new ( config_info )
2001-09-08 14:17:53 +00:00
@initialized = nil
2001-05-30 09:10:30 +00:00
end
2011-10-22 08:46:12 +00:00
# Sets the resolver timeouts. This may be a single positive number
# or an array of positive numbers representing timeouts in seconds.
# If an array is specified, a DNS request will retry and wait for
# each successive interval in the array until a successful response
# is received. Specifying +nil+ reverts to the default timeouts:
# [ 5, second = 5 * 2 / nameserver_count, 2 * second, 4 * second ]
#
# Example:
#
# dns.timeouts = 3
#
def timeouts = ( values )
@config . timeouts = values
end
2005-06-09 22:47:35 +00:00
def lazy_initialize # :nodoc:
2001-05-30 09:10:30 +00:00
@mutex . synchronize {
unless @initialized
@config . lazy_initialize
@initialized = true
end
}
2005-01-28 08:18:59 +00:00
self
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
##
# Closes the DNS resolver.
2002-07-12 16:34:02 +00:00
def close
@mutex . synchronize {
if @initialized
@initialized = false
end
}
end
2005-06-09 22:47:35 +00:00
##
# Gets the IP address of +name+ from the DNS resolver.
#
# +name+ can be a Resolv::DNS::Name or a String. Retrieved address will
# be a Resolv::IPv4 or Resolv::IPv6
2001-05-30 09:10:30 +00:00
def getaddress ( name )
2001-11-01 05:11:24 +00:00
each_address ( name ) { | address | return address }
raise ResolvError . new ( " DNS result has no information for #{ name } " )
end
2005-06-09 22:47:35 +00:00
##
# Gets all IP addresses for +name+ from the DNS resolver.
#
# +name+ can be a Resolv::DNS::Name or a String. Retrieved addresses will
# be a Resolv::IPv4 or Resolv::IPv6
2001-11-01 05:11:24 +00:00
def getaddresses ( name )
ret = [ ]
each_address ( name ) { | address | ret << address }
return ret
end
2005-06-09 22:47:35 +00:00
##
# Iterates over all IP addresses for +name+ retrieved from the DNS
# resolver.
#
# +name+ can be a Resolv::DNS::Name or a String. Retrieved addresses will
# be a Resolv::IPv4 or Resolv::IPv6
2001-11-01 05:11:24 +00:00
def each_address ( name )
2009-01-31 13:40:05 +00:00
if use_ipv6?
each_resource ( name , Resource :: IN :: AAAA ) { | resource | yield resource . address }
end
2018-11-23 11:38:43 -05:00
each_resource ( name , Resource :: IN :: A ) { | resource | yield resource . address }
2009-01-31 13:40:05 +00:00
end
2011-05-13 17:52:01 +00:00
def use_ipv6? # :nodoc:
2024-10-08 15:24:13 +09:00
@config . lazy_initialize unless @config . instance_variable_get ( :@initialized )
2019-08-26 20:30:59 -07:00
use_ipv6 = @config . use_ipv6?
unless use_ipv6 . nil?
return use_ipv6
end
2009-01-31 13:40:05 +00:00
begin
list = Socket . ip_address_list
rescue NotImplementedError
return true
end
list . any? { | a | a . ipv6? && ! a . ipv6_loopback? && ! a . ipv6_linklocal? }
2001-05-30 09:10:30 +00:00
end
2009-01-31 13:40:05 +00:00
private :use_ipv6?
2001-05-30 09:10:30 +00:00
2005-06-09 22:47:35 +00:00
##
# Gets the hostname for +address+ from the DNS resolver.
#
# +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved
# name will be a Resolv::DNS::Name.
2001-05-30 09:10:30 +00:00
def getname ( address )
2001-11-01 05:11:24 +00:00
each_name ( address ) { | name | return name }
raise ResolvError . new ( " DNS result has no information for #{ address } " )
end
2005-06-09 22:47:35 +00:00
##
# Gets all hostnames for +address+ from the DNS resolver.
#
# +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved
# names will be Resolv::DNS::Name instances.
2001-11-01 05:11:24 +00:00
def getnames ( address )
ret = [ ]
each_name ( address ) { | name | ret << name }
return ret
end
2005-06-09 22:47:35 +00:00
##
# Iterates over all hostnames for +address+ retrieved from the DNS
# resolver.
#
# +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved
# names will be Resolv::DNS::Name instances.
2001-11-01 05:11:24 +00:00
def each_name ( address )
2001-05-30 09:10:30 +00:00
case address
when Name
ptr = address
2019-06-08 20:27:49 -07:00
when IPv4 , IPv6
ptr = address . to_name
2001-05-30 09:10:30 +00:00
when IPv4 :: Regex
ptr = IPv4 . create ( address ) . to_name
when IPv6 :: Regex
ptr = IPv6 . create ( address ) . to_name
else
raise ResolvError . new ( " cannot interpret as address: #{ address } " )
end
2001-11-08 06:43:14 +00:00
each_resource ( ptr , Resource :: IN :: PTR ) { | resource | yield resource . name }
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
##
# Look up the +typeclass+ DNS resource of +name+.
#
# +name+ must be a Resolv::DNS::Name or a String.
#
# +typeclass+ should be one of the following:
#
# * Resolv::DNS::Resource::IN::A
# * Resolv::DNS::Resource::IN::AAAA
# * Resolv::DNS::Resource::IN::ANY
# * Resolv::DNS::Resource::IN::CNAME
# * Resolv::DNS::Resource::IN::HINFO
# * Resolv::DNS::Resource::IN::MINFO
# * Resolv::DNS::Resource::IN::MX
# * Resolv::DNS::Resource::IN::NS
# * Resolv::DNS::Resource::IN::PTR
# * Resolv::DNS::Resource::IN::SOA
# * Resolv::DNS::Resource::IN::TXT
# * Resolv::DNS::Resource::IN::WKS
#
# Returned resource is represented as a Resolv::DNS::Resource instance,
# i.e. Resolv::DNS::Resource::IN::A.
2001-05-30 09:10:30 +00:00
def getresource ( name , typeclass )
2001-11-08 06:43:14 +00:00
each_resource ( name , typeclass ) { | resource | return resource }
2001-11-01 05:11:24 +00:00
raise ResolvError . new ( " DNS result has no information for #{ name } " )
end
2005-06-09 22:47:35 +00:00
##
# Looks up all +typeclass+ DNS resources for +name+. See #getresource for
# argument details.
2008-09-14 15:18:53 +00:00
2001-11-01 05:11:24 +00:00
def getresources ( name , typeclass )
ret = [ ]
2001-11-08 06:43:14 +00:00
each_resource ( name , typeclass ) { | resource | ret << resource }
2001-11-01 05:11:24 +00:00
return ret
end
2005-06-09 22:47:35 +00:00
##
# Iterates over all +typeclass+ DNS resources for +name+. See
# #getresource for argument details.
2008-09-14 15:18:53 +00:00
2001-11-08 06:43:14 +00:00
def each_resource ( name , typeclass , & proc )
2013-04-06 13:16:36 +00:00
fetch_resource ( name , typeclass ) { | reply , reply_name |
extract_resources ( reply , reply_name , typeclass , & proc )
}
end
def fetch_resource ( name , typeclass )
2001-05-30 09:10:30 +00:00
lazy_initialize
2024-06-01 10:07:46 +00:00
truncated = { }
2024-04-23 21:04:52 -07:00
requesters = { }
2024-06-01 10:07:46 +00:00
udp_requester = begin
make_udp_requester
rescue Errno :: EACCES
# fall back to TCP
end
2001-05-30 09:10:30 +00:00
senders = { }
2024-06-01 10:07:46 +00:00
2001-05-30 09:10:30 +00:00
begin
2024-04-23 21:04:52 -07:00
@config . resolv ( name ) do | candidate , tout , nameserver , port |
2001-05-30 09:10:30 +00:00
msg = Message . new
msg . rd = 1
msg . add_question ( candidate , typeclass )
2024-04-23 21:04:52 -07:00
2024-06-01 10:07:46 +00:00
requester = requesters . fetch ( [ nameserver , port ] ) do
if ! truncated [ candidate ] && udp_requester
udp_requester
else
requesters [ [ nameserver , port ] ] = make_tcp_requester ( nameserver , port )
2024-04-23 21:04:52 -07:00
end
2024-06-01 10:07:46 +00:00
end
2024-04-23 21:04:52 -07:00
unless sender = senders [ [ candidate , requester , nameserver , port ] ]
2014-02-07 15:03:34 +00:00
sender = requester . sender ( msg , candidate , nameserver , port )
next if ! sender
2024-04-23 21:04:52 -07:00
senders [ [ candidate , requester , nameserver , port ] ] = sender
2001-05-30 09:10:30 +00:00
end
2008-08-08 01:58:40 +00:00
reply , reply_name = requester . request ( sender , tout )
2001-05-30 09:10:30 +00:00
case reply . rcode
when RCode :: NoError
2010-10-29 21:01:53 +00:00
if reply . tc == 1 and not Requester :: TCP === requester
# Retry via TCP:
2024-06-01 10:07:46 +00:00
truncated [ candidate ] = true
2010-10-29 21:01:53 +00:00
redo
else
2013-04-06 13:16:36 +00:00
yield ( reply , reply_name )
2010-10-29 21:01:53 +00:00
end
2003-07-24 01:03:28 +00:00
return
2001-05-30 09:10:30 +00:00
when RCode :: NXDomain
2002-03-22 05:15:44 +00:00
raise Config :: NXDomain . new ( reply_name . to_s )
2001-05-30 09:10:30 +00:00
else
2002-03-22 05:15:44 +00:00
raise Config :: OtherResolvError . new ( reply_name . to_s )
2001-05-30 09:10:30 +00:00
end
2024-04-23 21:04:52 -07:00
end
2001-05-30 09:10:30 +00:00
ensure
2024-06-01 10:07:46 +00:00
udp_requester & . close
2024-04-23 21:04:52 -07:00
requesters . each_value { | requester | requester & . close }
2008-08-08 01:58:40 +00:00
end
end
2010-10-29 21:01:53 +00:00
def make_udp_requester # :nodoc:
2010-02-11 01:29:38 +00:00
nameserver_port = @config . nameserver_port
if nameserver_port . length == 1
Requester :: ConnectedUDP . new ( * nameserver_port [ 0 ] )
2008-08-08 01:58:40 +00:00
else
2010-02-11 01:29:38 +00:00
Requester :: UnconnectedUDP . new ( * nameserver_port )
2001-05-30 09:10:30 +00:00
end
end
2010-11-27 09:03:26 +00:00
def make_tcp_requester ( host , port ) # :nodoc:
return Requester :: TCP . new ( host , port )
2024-04-23 21:04:52 -07:00
rescue Errno :: ECONNREFUSED
# Treat a refused TCP connection attempt to a nameserver like a timeout,
# as Resolv::DNS::Config#resolv considers ResolvTimeout exceptions as a
# hint to try the next nameserver:
raise ResolvTimeout
2010-10-29 21:01:53 +00:00
end
2005-06-09 22:47:35 +00:00
def extract_resources ( msg , name , typeclass ) # :nodoc:
2001-11-01 05:11:24 +00:00
if typeclass < Resource :: ANY
2003-07-24 01:03:28 +00:00
n0 = Name . create ( name )
2016-11-05 08:33:03 +00:00
msg . each_resource { | n , ttl , data |
2003-07-24 01:03:28 +00:00
yield data if n0 == n
}
2001-11-01 05:11:24 +00:00
end
yielded = false
2001-05-30 09:10:30 +00:00
n0 = Name . create ( name )
2016-11-05 08:33:03 +00:00
msg . each_resource { | n , ttl , data |
2001-05-30 09:10:30 +00:00
if n0 == n
case data
when typeclass
2003-07-24 01:03:28 +00:00
yield data
yielded = true
2001-05-30 09:10:30 +00:00
when Resource :: CNAME
n0 = data . name
end
end
}
2001-11-01 05:11:24 +00:00
return if yielded
2016-11-05 08:33:03 +00:00
msg . each_resource { | n , ttl , data |
2001-05-30 09:10:30 +00:00
if n0 == n
case data
when typeclass
2003-07-24 01:03:28 +00:00
yield data
2001-05-30 09:10:30 +00:00
end
end
}
end
2024-08-22 21:49:26 +02:00
def self . random ( arg ) # :nodoc:
begin
SecureRandom . random_number ( arg )
rescue NotImplementedError
2008-08-08 01:58:40 +00:00
rand ( arg )
end
end
2011-05-13 17:52:01 +00:00
RequestID = { } # :nodoc:
2016-08-30 06:22:30 +00:00
RequestIDMutex = Thread :: Mutex . new # :nodoc:
2008-08-08 01:58:40 +00:00
def self . allocate_request_id ( host , port ) # :nodoc:
id = nil
RequestIDMutex . synchronize {
h = ( RequestID [ [ host , port ] ] || = { } )
begin
2018-03-06 03:31:46 +00:00
id = random ( 0x0000 .. 0xffff )
2008-09-14 15:18:53 +00:00
end while h [ id ]
2008-08-08 01:58:40 +00:00
h [ id ] = true
}
id
end
def self . free_request_id ( host , port , id ) # :nodoc:
RequestIDMutex . synchronize {
key = [ host , port ]
if h = RequestID [ key ]
h . delete id
if h . empty?
RequestID . delete key
end
end
}
end
2025-01-21 16:44:46 +09:00
case RUBY_PLATFORM
when * [
# https://www.rfc-editor.org/rfc/rfc6056.txt
# Appendix A. Survey of the Algorithms in Use by Some Popular Implementations
/ freebsd / , / linux / , / netbsd / , / openbsd / , / solaris / ,
/ darwin / , # the same as FreeBSD
] then
def self . bind_random_port ( udpsock , bind_host = " 0.0.0.0 " ) # :nodoc:
udpsock . bind ( bind_host , 0 )
end
else
# Sequential port assignment
def self . bind_random_port ( udpsock , bind_host = " 0.0.0.0 " ) # :nodoc:
# Ephemeral port number range recommended by RFC 6056
2018-03-06 03:31:46 +00:00
port = random ( 1024 .. 65535 )
2010-02-11 01:29:38 +00:00
udpsock . bind ( bind_host , port )
2014-02-22 17:38:57 +00:00
rescue Errno :: EADDRINUSE , # POSIX
Errno :: EACCES , # SunOS: See PRIV_SYS_NFS in privileges(5)
Errno :: EPERM # FreeBSD: security.mac.portacl.port_high is configurable. See mac_portacl(4).
2008-08-08 01:58:40 +00:00
retry
end
end
2005-06-09 22:47:35 +00:00
class Requester # :nodoc:
2001-05-30 09:10:30 +00:00
def initialize
@senders = { }
2010-02-11 01:29:38 +00:00
@socks = nil
2001-05-30 09:10:30 +00:00
end
2008-08-08 01:58:40 +00:00
def request ( sender , tout )
2015-05-29 01:40:26 +00:00
start = Process . clock_gettime ( Process :: CLOCK_MONOTONIC )
2011-10-31 12:03:49 +00:00
timelimit = start + tout
2013-04-06 14:32:34 +00:00
begin
sender . send
2015-01-07 22:19:19 +00:00
rescue Errno :: EHOSTUNREACH , # multi-homed IPv6 may generate this
Errno :: ENETUNREACH
2013-04-06 14:32:34 +00:00
raise ResolvTimeout
end
2010-02-24 13:44:08 +00:00
while true
2015-05-29 01:40:26 +00:00
before_select = Process . clock_gettime ( Process :: CLOCK_MONOTONIC )
2011-10-31 12:03:49 +00:00
timeout = timelimit - before_select
2010-02-24 13:44:08 +00:00
if timeout < = 0
raise ResolvTimeout
end
2015-05-06 20:30:43 +00:00
if @socks . size == 1
select_result = @socks [ 0 ] . wait_readable ( timeout ) ? [ @socks ] : nil
else
select_result = IO . select ( @socks , nil , nil , timeout )
end
2010-02-11 01:29:38 +00:00
if ! select_result
2015-05-29 01:40:26 +00:00
after_select = Process . clock_gettime ( Process :: CLOCK_MONOTONIC )
2011-10-31 12:03:49 +00:00
next if after_select < timelimit
2008-08-08 01:58:40 +00:00
raise ResolvTimeout
end
2010-05-29 00:06:17 +00:00
begin
reply , from = recv_reply ( select_result [ 0 ] )
2010-05-31 12:51:13 +00:00
rescue Errno :: ECONNREFUSED , # GNU/Linux, FreeBSD
Errno :: ECONNRESET # Windows
2010-05-29 00:06:17 +00:00
# No name server running on the server?
# Don't wait anymore.
raise ResolvTimeout
end
2008-08-08 01:58:40 +00:00
begin
msg = Message . decode ( reply )
rescue DecodeError
next # broken DNS message ignored
end
2021-03-26 18:16:07 +09:00
if sender == sender_for ( from , msg )
2008-08-08 01:58:40 +00:00
break
else
# unexpected DNS message ignored
2002-07-12 16:34:02 +00:00
end
end
2021-03-26 18:16:07 +09:00
return msg , sender . data
2002-07-12 16:34:02 +00:00
end
2013-04-06 14:32:34 +00:00
def sender_for ( addr , msg )
2021-03-26 18:16:07 +09:00
@senders [ [ addr , msg . id ] ]
2013-04-06 14:32:34 +00:00
end
2008-08-08 01:58:40 +00:00
def close
2010-02-11 01:29:38 +00:00
socks = @socks
@socks = nil
2016-11-22 13:11:43 +00:00
socks & . each ( & :close )
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
class Sender # :nodoc:
2008-08-08 01:58:40 +00:00
def initialize ( msg , data , sock )
2002-07-12 16:34:02 +00:00
@msg = msg
2001-05-30 09:10:30 +00:00
@data = data
2002-07-12 16:34:02 +00:00
@sock = sock
2001-05-30 09:10:30 +00:00
end
end
2005-06-09 22:47:35 +00:00
class UnconnectedUDP < Requester # :nodoc:
2010-02-11 01:29:38 +00:00
def initialize ( * nameserver_port )
2001-05-30 09:10:30 +00:00
super ( )
2010-02-11 01:29:38 +00:00
@nameserver_port = nameserver_port
2018-03-06 02:48:17 +00:00
@initialized = false
@mutex = Thread :: Mutex . new
end
def lazy_initialize
@mutex . synchronize {
next if @initialized
@initialized = true
@socks_hash = { }
@socks = [ ]
@nameserver_port . each { | host , port |
if host . index ( ':' )
bind_host = " :: "
af = Socket :: AF_INET6
else
bind_host = " 0.0.0.0 "
af = Socket :: AF_INET
end
next if @socks_hash [ bind_host ]
begin
sock = UDPSocket . new ( af )
2023-11-24 15:06:20 +11:00
rescue Errno :: EAFNOSUPPORT , Errno :: EPROTONOSUPPORT
2018-03-06 02:48:17 +00:00
next # The kernel doesn't support the address family.
end
@socks << sock
@socks_hash [ bind_host ] = sock
sock . do_not_reverse_lookup = true
DNS . bind_random_port ( sock , bind_host )
}
2010-02-11 01:29:38 +00:00
}
2018-03-06 02:48:17 +00:00
self
2001-05-30 09:10:30 +00:00
end
2010-02-11 01:29:38 +00:00
def recv_reply ( readable_socks )
2018-03-06 02:48:17 +00:00
lazy_initialize
2010-02-11 01:29:38 +00:00
reply , from = readable_socks [ 0 ] . recvfrom ( UDPSize )
2008-08-08 01:58:40 +00:00
return reply , [ from [ 3 ] , from [ 1 ] ]
end
def sender ( msg , data , host , port = Port )
2020-05-29 14:13:30 -07:00
host = Addrinfo . ip ( host ) . ip_address
2018-03-06 02:48:17 +00:00
lazy_initialize
2014-02-07 15:03:34 +00:00
sock = @socks_hash [ host . index ( ':' ) ? " :: " : " 0.0.0.0 " ]
return nil if ! sock
2001-05-30 09:10:30 +00:00
service = [ host , port ]
2008-08-08 01:58:40 +00:00
id = DNS . allocate_request_id ( host , port )
2001-05-30 09:10:30 +00:00
request = msg . encode
request [ 0 , 2 ] = [ id ] . pack ( 'n' )
return @senders [ [ service , id ] ] =
2010-02-11 01:29:38 +00:00
Sender . new ( request , data , sock , host , port )
2008-08-08 01:58:40 +00:00
end
def close
2018-03-06 02:48:17 +00:00
@mutex . synchronize {
if @initialized
super
@senders . each_key { | service , id |
DNS . free_request_id ( service [ 0 ] , service [ 1 ] , id )
}
@initialized = false
end
2008-08-08 01:58:40 +00:00
}
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
class Sender < Requester :: Sender # :nodoc:
2008-08-08 01:58:40 +00:00
def initialize ( msg , data , sock , host , port )
super ( msg , data , sock )
2001-05-30 09:10:30 +00:00
@host = host
@port = port
end
2008-08-08 01:58:40 +00:00
attr_reader :data
2001-05-30 09:10:30 +00:00
def send
2014-02-07 15:03:34 +00:00
raise " @sock is nil. " if @sock . nil?
2001-05-30 09:10:30 +00:00
@sock . send ( @msg , 0 , @host , @port )
end
end
end
2005-06-09 22:47:35 +00:00
class ConnectedUDP < Requester # :nodoc:
2001-05-30 09:10:30 +00:00
def initialize ( host , port = Port )
super ( )
@host = host
@port = port
2018-03-06 02:48:17 +00:00
@mutex = Thread :: Mutex . new
@initialized = false
end
def lazy_initialize
@mutex . synchronize {
next if @initialized
@initialized = true
is_ipv6 = @host . index ( ':' )
sock = UDPSocket . new ( is_ipv6 ? Socket :: AF_INET6 : Socket :: AF_INET )
@socks = [ sock ]
sock . do_not_reverse_lookup = true
DNS . bind_random_port ( sock , is_ipv6 ? " :: " : " 0.0.0.0 " )
sock . connect ( @host , @port )
}
self
2001-05-30 09:10:30 +00:00
end
2010-02-11 01:29:38 +00:00
def recv_reply ( readable_socks )
2018-03-06 02:48:17 +00:00
lazy_initialize
2010-02-11 01:29:38 +00:00
reply = readable_socks [ 0 ] . recv ( UDPSize )
2008-08-08 01:58:40 +00:00
return reply , nil
end
def sender ( msg , data , host = @host , port = @port )
2018-03-06 02:48:17 +00:00
lazy_initialize
2001-05-30 09:10:30 +00:00
unless host == @host && port == @port
raise RequestError . new ( " host/port don't match: #{ host } : #{ port } " )
end
2008-08-08 01:58:40 +00:00
id = DNS . allocate_request_id ( @host , @port )
2001-05-30 09:10:30 +00:00
request = msg . encode
request [ 0 , 2 ] = [ id ] . pack ( 'n' )
2010-02-11 01:29:38 +00:00
return @senders [ [ nil , id ] ] = Sender . new ( request , data , @socks [ 0 ] )
2008-08-08 01:58:40 +00:00
end
def close
2018-03-06 02:48:17 +00:00
@mutex . synchronize do
if @initialized
super
@senders . each_key { | from , id |
DNS . free_request_id ( @host , @port , id )
}
@initialized = false
end
end
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
class Sender < Requester :: Sender # :nodoc:
2001-05-30 09:10:30 +00:00
def send
2014-02-07 15:03:34 +00:00
raise " @sock is nil. " if @sock . nil?
2001-05-30 09:10:30 +00:00
@sock . send ( @msg , 0 )
end
2008-08-08 01:58:40 +00:00
attr_reader :data
2001-05-30 09:10:30 +00:00
end
end
2013-04-06 14:32:34 +00:00
class MDNSOneShot < UnconnectedUDP # :nodoc:
def sender ( msg , data , host , port = Port )
2018-03-06 02:48:17 +00:00
lazy_initialize
2013-04-06 14:32:34 +00:00
id = DNS . allocate_request_id ( host , port )
request = msg . encode
request [ 0 , 2 ] = [ id ] . pack ( 'n' )
sock = @socks_hash [ host . index ( ':' ) ? " :: " : " 0.0.0.0 " ]
return @senders [ id ] =
UnconnectedUDP :: Sender . new ( request , data , sock , host , port )
end
def sender_for ( addr , msg )
2018-03-06 02:48:17 +00:00
lazy_initialize
2013-04-06 14:32:34 +00:00
@senders [ msg . id ]
end
end
2005-06-09 22:47:35 +00:00
class TCP < Requester # :nodoc:
2001-05-30 09:10:30 +00:00
def initialize ( host , port = Port )
super ( )
@host = host
@port = port
2010-02-11 01:29:38 +00:00
sock = TCPSocket . new ( @host , @port )
@socks = [ sock ]
2001-05-30 09:10:30 +00:00
@senders = { }
end
2010-02-11 01:29:38 +00:00
def recv_reply ( readable_socks )
len = readable_socks [ 0 ] . read ( 2 ) . unpack ( 'n' ) [ 0 ]
reply = @socks [ 0 ] . read ( len )
2008-08-08 01:58:40 +00:00
return reply , nil
end
def sender ( msg , data , host = @host , port = @port )
2001-05-30 09:10:30 +00:00
unless host == @host && port == @port
raise RequestError . new ( " host/port don't match: #{ host } : #{ port } " )
end
2008-08-08 01:58:40 +00:00
id = DNS . allocate_request_id ( @host , @port )
2001-05-30 09:10:30 +00:00
request = msg . encode
request [ 0 , 2 ] = [ request . length , id ] . pack ( 'nn' )
2010-02-11 01:29:38 +00:00
return @senders [ [ nil , id ] ] = Sender . new ( request , data , @socks [ 0 ] )
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
class Sender < Requester :: Sender # :nodoc:
2001-05-30 09:10:30 +00:00
def send
@sock . print ( @msg )
@sock . flush
end
2008-08-08 01:58:40 +00:00
attr_reader :data
end
def close
super
@senders . each_key { | from , id |
DNS . free_request_id ( @host , @port , id )
}
2001-05-30 09:10:30 +00:00
end
end
2005-06-09 22:47:35 +00:00
##
# Indicates a problem with the DNS request.
2001-05-30 09:10:30 +00:00
class RequestError < StandardError
end
end
2005-06-09 22:47:35 +00:00
class Config # :nodoc:
2004-05-12 08:44:11 +00:00
def initialize ( config_info = nil )
2016-08-30 06:22:30 +00:00
@mutex = Thread :: Mutex . new
2004-05-12 08:44:11 +00:00
@config_info = config_info
2003-07-24 01:03:28 +00:00
@initialized = nil
2011-10-22 08:46:12 +00:00
@timeouts = nil
end
def timeouts = ( values )
if values
values = Array ( values )
values . each do | t |
Numeric === t or raise ArgumentError , " #{ t . inspect } is not numeric "
2013-12-12 06:17:31 +00:00
t > 0 . 0 or raise ArgumentError , " timeout= #{ t } must be positive "
2011-10-22 08:46:12 +00:00
end
@timeouts = values
else
@timeouts = nil
end
2001-05-30 09:10:30 +00:00
end
2004-05-12 08:44:11 +00:00
def Config . parse_resolv_conf ( filename )
nameserver = [ ]
search = nil
ndots = 1
2017-12-20 04:25:01 +00:00
File . open ( filename , 'rb' ) { | f |
2004-05-12 08:44:11 +00:00
f . each { | line |
line . sub! ( / [ # ;].* / , '' )
keyword , * args = line . split ( / \ s+ / )
next unless keyword
case keyword
when 'nameserver'
2024-10-29 14:43:55 +00:00
nameserver . concat ( args . each ( & :freeze ) )
2004-05-12 08:44:11 +00:00
when 'domain'
2005-01-18 16:29:23 +00:00
next if args . empty?
2024-10-29 14:43:55 +00:00
search = [ args [ 0 ] . freeze ]
2004-05-12 08:44:11 +00:00
when 'search'
2005-01-18 16:29:23 +00:00
next if args . empty?
2024-10-29 14:43:55 +00:00
search = args . each ( & :freeze )
2005-01-28 08:18:59 +00:00
when 'options'
args . each { | arg |
case arg
when / \ Andots:( \ d+) \ z /
ndots = $1 . to_i
end
}
2004-05-12 08:44:11 +00:00
end
}
}
2024-10-29 14:43:55 +00:00
return { :nameserver = > nameserver . freeze , :search = > search . freeze , :ndots = > ndots . freeze } . freeze
2004-05-12 08:44:11 +00:00
end
def Config . default_config_hash ( filename = " /etc/resolv.conf " )
if File . exist? filename
2024-10-29 14:43:55 +00:00
Config . parse_resolv_conf ( filename )
elsif WINDOWS
require 'win32/resolv' unless defined? ( Win32 :: Resolv )
search , nameserver = Win32 :: Resolv . get_resolv_info
config_hash = { }
config_hash [ :nameserver ] = nameserver if nameserver
config_hash [ :search ] = [ search ] . flatten if search
config_hash
2004-05-12 08:44:11 +00:00
else
2024-10-29 14:43:55 +00:00
{ }
2004-05-12 08:44:11 +00:00
end
end
2001-05-30 09:10:30 +00:00
def lazy_initialize
@mutex . synchronize {
unless @initialized
2010-01-02 07:03:24 +00:00
@nameserver_port = [ ]
2019-08-26 20:30:59 -07:00
@use_ipv6 = nil
2001-05-30 09:10:30 +00:00
@search = nil
@ndots = 1
2004-05-12 08:44:11 +00:00
case @config_info
when nil
config_hash = Config . default_config_hash
when String
config_hash = Config . parse_resolv_conf ( @config_info )
when Hash
config_hash = @config_info . dup
if String === config_hash [ :nameserver ]
config_hash [ :nameserver ] = [ config_hash [ :nameserver ] ]
2003-07-24 01:03:28 +00:00
end
2004-05-12 08:44:11 +00:00
if String === config_hash [ :search ]
config_hash [ :search ] = [ config_hash [ :search ] ]
end
else
raise ArgumentError . new ( " invalid resolv configuration: #{ @config_info . inspect } " )
2001-05-30 09:10:30 +00:00
end
2010-01-02 07:03:24 +00:00
if config_hash . include? :nameserver
@nameserver_port = config_hash [ :nameserver ] . map { | ns | [ ns , Port ] }
end
if config_hash . include? :nameserver_port
@nameserver_port = config_hash [ :nameserver_port ] . map { | ns , port | [ ns , ( port || Port ) ] }
end
2019-08-26 20:30:59 -07:00
if config_hash . include? :use_ipv6
@use_ipv6 = config_hash [ :use_ipv6 ]
end
2004-05-12 08:44:11 +00:00
@search = config_hash [ :search ] if config_hash . include? :search
@ndots = config_hash [ :ndots ] if config_hash . include? :ndots
2021-09-17 17:17:20 -07:00
@raise_timeout_errors = config_hash [ :raise_timeout_errors ]
2001-05-30 09:10:30 +00:00
2010-01-02 07:03:24 +00:00
if @nameserver_port . empty?
2010-01-19 10:58:28 +00:00
@nameserver_port << [ '0.0.0.0' , Port ]
2010-01-02 07:03:24 +00:00
end
2004-05-12 08:44:11 +00:00
if @search
@search = @search . map { | arg | Label . split ( arg ) }
else
2001-05-30 09:10:30 +00:00
hostname = Socket . gethostname
if / \ . / =~ hostname
2002-03-12 08:12:32 +00:00
@search = [ Label . split ( $' ) ]
2001-05-30 09:10:30 +00:00
else
2002-03-12 08:12:32 +00:00
@search = [ [ ] ]
2001-05-30 09:10:30 +00:00
end
end
2004-05-12 08:44:11 +00:00
2010-01-02 07:03:24 +00:00
if ! @nameserver_port . kind_of? ( Array ) ||
@nameserver_port . any? { | ns_port |
! ( Array === ns_port ) ||
ns_port . length != 2
! ( String === ns_port [ 0 ] ) ||
! ( Integer === ns_port [ 1 ] )
}
raise ArgumentError . new ( " invalid nameserver config: #{ @nameserver_port . inspect } " )
2004-05-12 08:44:11 +00:00
end
if ! @search . kind_of? ( Array ) ||
! @search . all? { | ls | ls . all? { | l | Label :: Str === l } }
raise ArgumentError . new ( " invalid search config: #{ @search . inspect } " )
end
if ! @ndots . kind_of? ( Integer )
raise ArgumentError . new ( " invalid ndots config: #{ @ndots . inspect } " )
end
2001-05-30 09:10:30 +00:00
@initialized = true
end
}
2005-01-28 08:18:59 +00:00
self
2001-05-30 09:10:30 +00:00
end
def single?
lazy_initialize
2010-01-02 07:03:24 +00:00
if @nameserver_port . length == 1
return @nameserver_port [ 0 ]
2001-05-30 09:10:30 +00:00
else
return nil
end
end
2010-02-11 01:29:38 +00:00
def nameserver_port
@nameserver_port
end
2019-08-26 20:30:59 -07:00
def use_ipv6?
@use_ipv6
end
2001-05-30 09:10:30 +00:00
def generate_candidates ( name )
candidates = nil
2003-07-24 01:03:28 +00:00
name = Name . create ( name )
if name . absolute?
2001-05-30 09:10:30 +00:00
candidates = [ name ]
2003-07-24 01:03:28 +00:00
else
if @ndots < = name . length - 1
candidates = [ Name . new ( name . to_a ) ]
else
candidates = [ ]
end
candidates . concat ( @search . map { | domain | Name . new ( name . to_a + domain ) } )
2014-11-21 20:04:27 +00:00
fname = Name . create ( " #{ name } . " )
if ! candidates . include? ( fname )
candidates << fname
end
2003-07-24 01:03:28 +00:00
end
2001-05-30 09:10:30 +00:00
return candidates
end
InitialTimeout = 5
def generate_timeouts
ts = [ InitialTimeout ]
2010-01-02 07:03:24 +00:00
ts << ts [ - 1 ] * 2 / @nameserver_port . length
2001-05-30 09:10:30 +00:00
ts << ts [ - 1 ] * 2
ts << ts [ - 1 ] * 2
return ts
end
def resolv ( name )
candidates = generate_candidates ( name )
2011-10-22 08:46:12 +00:00
timeouts = @timeouts || generate_timeouts
2021-09-17 17:17:20 -07:00
timeout_error = false
2001-05-30 09:10:30 +00:00
begin
candidates . each { | candidate |
begin
timeouts . each { | tout |
2010-01-02 07:03:24 +00:00
@nameserver_port . each { | nameserver , port |
2001-05-30 09:10:30 +00:00
begin
2010-01-02 07:03:24 +00:00
yield candidate , tout , nameserver , port
2002-01-16 03:37:23 +00:00
rescue ResolvTimeout
2001-05-30 09:10:30 +00:00
end
}
}
2021-09-17 17:17:20 -07:00
timeout_error = true
2001-05-30 09:10:30 +00:00
raise ResolvError . new ( " DNS resolv timeout: #{ name } " )
rescue NXDomain
end
}
2005-01-21 11:11:44 +00:00
rescue ResolvError
2021-09-17 17:17:20 -07:00
raise if @raise_timeout_errors && timeout_error
2001-05-30 09:10:30 +00:00
end
end
2005-06-09 22:47:35 +00:00
##
# Indicates no such domain was found.
2001-05-30 09:10:30 +00:00
class NXDomain < ResolvError
end
2005-06-09 22:47:35 +00:00
##
# Indicates some other unhandled resolver error was encountered.
2001-05-30 09:10:30 +00:00
class OtherResolvError < ResolvError
end
end
2005-06-09 22:47:35 +00:00
module OpCode # :nodoc:
2001-05-30 09:10:30 +00:00
Query = 0
IQuery = 1
Status = 2
Notify = 4
Update = 5
end
2005-06-09 22:47:35 +00:00
module RCode # :nodoc:
2001-05-30 09:10:30 +00:00
NoError = 0
FormErr = 1
ServFail = 2
NXDomain = 3
NotImp = 4
Refused = 5
YXDomain = 6
YXRRSet = 7
NXRRSet = 8
NotAuth = 9
NotZone = 10
BADVERS = 16
BADSIG = 16
BADKEY = 17
BADTIME = 18
BADMODE = 19
BADNAME = 20
BADALG = 21
end
2005-06-09 22:47:35 +00:00
##
# Indicates that the DNS response was unable to be decoded.
2001-05-30 09:10:30 +00:00
class DecodeError < StandardError
end
2005-06-09 22:47:35 +00:00
##
# Indicates that the DNS request was unable to be encoded.
2001-05-30 09:10:30 +00:00
class EncodeError < StandardError
end
2005-06-09 22:47:35 +00:00
module Label # :nodoc:
2001-05-30 09:10:30 +00:00
def self . split ( arg )
labels = [ ]
arg . scan ( / [^ \ .]+ / ) { labels << Str . new ( $& ) }
return labels
end
2005-06-09 22:47:35 +00:00
class Str # :nodoc:
2001-05-30 09:10:30 +00:00
def initialize ( string )
@string = string
2014-12-31 08:50:10 +00:00
# case insensivity of DNS labels doesn't apply non-ASCII characters. [RFC 4343]
# This assumes @string is given in ASCII compatible encoding.
2014-12-31 14:59:19 +00:00
@downcase = string . b . downcase
2001-05-30 09:10:30 +00:00
end
attr_reader :string , :downcase
def to_s
return @string
end
2003-07-24 01:03:28 +00:00
def inspect
2014-06-24 08:48:46 +00:00
return " # < #{ self . class } #{ self } > "
2003-07-24 01:03:28 +00:00
end
2001-11-01 05:11:24 +00:00
2001-05-30 09:10:30 +00:00
def == ( other )
2015-01-02 00:10:04 +00:00
return self . class == other . class && @downcase == other . downcase
2001-05-30 09:10:30 +00:00
end
def eql? ( other )
return self == other
end
def hash
return @downcase . hash
end
end
end
2005-06-09 22:47:35 +00:00
##
# A representation of a DNS name.
2001-05-30 09:10:30 +00:00
class Name
2008-09-14 15:18:53 +00:00
2005-06-09 22:47:35 +00:00
##
# Creates a new DNS name from +arg+. +arg+ can be:
#
# Name:: returns +arg+.
# String:: Creates a new Name.
2001-05-30 09:10:30 +00:00
def self . create ( arg )
case arg
when Name
return arg
when String
2002-03-12 08:12:32 +00:00
return Name . new ( Label . split ( arg ) , / \ . \ z / =~ arg ? true : false )
2001-05-30 09:10:30 +00:00
else
2002-03-12 08:12:32 +00:00
raise ArgumentError . new ( " cannot interpret as DNS name: #{ arg . inspect } " )
2001-05-30 09:10:30 +00:00
end
end
2005-06-09 22:47:35 +00:00
def initialize ( labels , absolute = true ) # :nodoc:
2015-01-02 00:10:04 +00:00
labels = labels . map { | label |
case label
when String then Label :: Str . new ( label )
when Label :: Str then label
else
raise ArgumentError , " unexpected label: #{ label . inspect } "
end
}
2001-05-30 09:10:30 +00:00
@labels = labels
2003-07-24 01:03:28 +00:00
@absolute = absolute
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
def inspect # :nodoc:
2014-06-24 08:48:46 +00:00
" # < #{ self . class } : #{ self } #{ @absolute ? '.' : '' } > "
2005-02-07 15:24:09 +00:00
end
2005-06-09 22:47:35 +00:00
##
# True if this name is absolute.
2002-03-12 08:12:32 +00:00
def absolute?
return @absolute
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
def == ( other ) # :nodoc:
2005-02-07 15:24:09 +00:00
return false unless Name === other
2014-12-30 07:16:14 +00:00
return false unless @absolute == other . absolute?
2014-12-31 04:45:05 +00:00
return @labels == other . to_a
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
alias eql? == # :nodoc:
##
# Returns true if +other+ is a subdomain.
#
# Example:
2005-02-07 15:24:09 +00:00
#
# domain = Resolv::DNS::Name.create("y.z")
# p Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true
# p Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true
# p Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false
# p Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false
# p Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false
# p Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false
#
2005-06-09 22:47:35 +00:00
2005-02-07 15:24:09 +00:00
def subdomain_of? ( other )
raise ArgumentError , " not a domain name: #{ other . inspect } " unless Name === other
return false if @absolute != other . absolute?
other_len = other . length
return false if @labels . length < = other_len
return @labels [ - other_len , other_len ] == other . to_a
end
2005-06-09 22:47:35 +00:00
def hash # :nodoc:
2002-03-12 08:12:32 +00:00
return @labels . hash ^ @absolute . hash
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
def to_a # :nodoc:
2001-05-30 09:10:30 +00:00
return @labels
end
2005-06-09 22:47:35 +00:00
def length # :nodoc:
2001-05-30 09:10:30 +00:00
return @labels . length
end
2005-06-09 22:47:35 +00:00
def [] ( i ) # :nodoc:
2001-05-30 09:10:30 +00:00
return @labels [ i ]
end
2005-06-09 22:47:35 +00:00
##
2005-02-07 15:24:09 +00:00
# returns the domain name as a string.
#
# The domain name doesn't have a trailing dot even if the name object is
# absolute.
#
2005-06-09 22:47:35 +00:00
# Example:
#
2005-02-07 15:24:09 +00:00
# p Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z"
# p Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z"
2005-06-09 22:47:35 +00:00
2001-05-30 09:10:30 +00:00
def to_s
return @labels . join ( '.' )
end
end
2005-06-09 22:47:35 +00:00
class Message # :nodoc:
2001-05-30 09:10:30 +00:00
@@identifier = - 1
def initialize ( id = ( @@identifier += 1 ) & 0xffff )
@id = id
@qr = 0
@opcode = 0
@aa = 0
@tc = 0
@rd = 0 # recursion desired
@ra = 0 # recursion available
@rcode = 0
@question = [ ]
@answer = [ ]
@authority = [ ]
@additional = [ ]
end
attr_accessor :id , :qr , :opcode , :aa , :tc , :rd , :ra , :rcode
attr_reader :question , :answer , :authority , :additional
def == ( other )
return @id == other . id &&
@qr == other . qr &&
@opcode == other . opcode &&
@aa == other . aa &&
@tc == other . tc &&
@rd == other . rd &&
@ra == other . ra &&
@rcode == other . rcode &&
@question == other . question &&
@answer == other . answer &&
@authority == other . authority &&
@additional == other . additional
end
def add_question ( name , typeclass )
@question << [ Name . create ( name ) , typeclass ]
end
def each_question
@question . each { | name , typeclass |
yield name , typeclass
}
end
def add_answer ( name , ttl , data )
@answer << [ Name . create ( name ) , ttl , data ]
end
def each_answer
@answer . each { | name , ttl , data |
yield name , ttl , data
}
end
def add_authority ( name , ttl , data )
@authority << [ Name . create ( name ) , ttl , data ]
end
def each_authority
@authority . each { | name , ttl , data |
yield name , ttl , data
}
end
def add_additional ( name , ttl , data )
@additional << [ Name . create ( name ) , ttl , data ]
end
def each_additional
@additional . each { | name , ttl , data |
yield name , ttl , data
}
end
def each_resource
each_answer { | name , ttl , data | yield name , ttl , data }
each_authority { | name , ttl , data | yield name , ttl , data }
each_additional { | name , ttl , data | yield name , ttl , data }
end
def encode
return MessageEncoder . new { | msg |
msg . put_pack ( 'nnnnnn' ,
@id ,
( @qr & 1 ) << 15 |
( @opcode & 15 ) << 11 |
( @aa & 1 ) << 10 |
( @tc & 1 ) << 9 |
( @rd & 1 ) << 8 |
( @ra & 1 ) << 7 |
( @rcode & 15 ) ,
@question . length ,
@answer . length ,
@authority . length ,
@additional . length )
@question . each { | q |
name , typeclass = q
msg . put_name ( name )
msg . put_pack ( 'nn' , typeclass :: TypeValue , typeclass :: ClassValue )
}
[ @answer , @authority , @additional ] . each { | rr |
rr . each { | r |
name , ttl , data = r
msg . put_name ( name )
msg . put_pack ( 'nnN' , data . class :: TypeValue , data . class :: ClassValue , ttl )
msg . put_length16 { data . encode_rdata ( msg ) }
}
}
} . to_s
end
2005-06-09 22:47:35 +00:00
class MessageEncoder # :nodoc:
2001-05-30 09:10:30 +00:00
def initialize
2015-10-05 13:27:48 +00:00
@data = '' . dup
2001-05-30 09:10:30 +00:00
@names = { }
yield self
end
def to_s
return @data
end
def put_bytes ( d )
@data << d
end
def put_pack ( template , * d )
@data << d . pack ( template )
end
def put_length16
length_index = @data . length
@data << " \0 \0 "
data_start = @data . length
yield
data_end = @data . length
@data [ length_index , 2 ] = [ data_end - data_start ] . pack ( " n " )
end
def put_string ( d )
self . put_pack ( " C " , d . length )
@data << d
end
2005-02-05 18:31:20 +00:00
def put_string_list ( ds )
ds . each { | d |
self . put_string ( d )
}
end
2023-04-08 12:25:04 +00:00
def put_name ( d , compress : true )
put_labels ( d . to_a , compress : compress )
2001-05-30 09:10:30 +00:00
end
2023-04-08 12:25:04 +00:00
def put_labels ( d , compress : true )
2001-05-30 09:10:30 +00:00
d . each_index { | i |
domain = d [ i .. - 1 ]
2023-04-08 12:25:04 +00:00
if compress && idx = @names [ domain ]
2001-05-30 09:10:30 +00:00
self . put_pack ( " n " , 0xc000 | idx )
return
else
2015-11-09 15:37:04 +00:00
if @data . length < 0x4000
@names [ domain ] = @data . length
end
2001-05-30 09:10:30 +00:00
self . put_label ( d [ i ] )
end
}
@data << " \0 "
end
def put_label ( d )
2007-12-26 13:50:31 +00:00
self . put_string ( d . to_s )
2001-05-30 09:10:30 +00:00
end
end
def Message . decode ( m )
o = Message . new ( 0 )
MessageDecoder . new ( m ) { | msg |
id , flag , qdcount , ancount , nscount , arcount =
msg . get_unpack ( 'nnnnnn' )
o . id = id
2021-03-08 15:28:04 -08:00
o . tc = ( flag >> 9 ) & 1
o . rcode = flag & 15
return o unless o . tc . zero?
2001-05-30 09:10:30 +00:00
o . qr = ( flag >> 15 ) & 1
o . opcode = ( flag >> 11 ) & 15
o . aa = ( flag >> 10 ) & 1
o . rd = ( flag >> 8 ) & 1
o . ra = ( flag >> 7 ) & 1
( 1 .. qdcount ) . each {
name , typeclass = msg . get_question
o . add_question ( name , typeclass )
}
( 1 .. ancount ) . each {
name , ttl , data = msg . get_rr
o . add_answer ( name , ttl , data )
}
( 1 .. nscount ) . each {
name , ttl , data = msg . get_rr
o . add_authority ( name , ttl , data )
}
( 1 .. arcount ) . each {
name , ttl , data = msg . get_rr
o . add_additional ( name , ttl , data )
}
}
return o
end
2005-06-09 22:47:35 +00:00
class MessageDecoder # :nodoc:
2001-05-30 09:10:30 +00:00
def initialize ( data )
@data = data
@index = 0
2017-01-14 02:26:49 +00:00
@limit = data . bytesize
2001-05-30 09:10:30 +00:00
yield self
end
2010-01-02 16:31:00 +00:00
def inspect
2017-01-14 02:26:49 +00:00
" \# < #{ self . class } : #{ @data . byteslice ( 0 , @index ) . inspect } #{ @data . byteslice ( @index .. - 1 ) . inspect } > "
2010-01-02 16:31:00 +00:00
end
2001-05-30 09:10:30 +00:00
def get_length16
len , = self . get_unpack ( 'n' )
save_limit = @limit
@limit = @index + len
2003-10-16 17:47:19 +00:00
d = yield ( len )
2001-05-30 09:10:30 +00:00
if @index < @limit
2004-03-29 07:54:38 +00:00
raise DecodeError . new ( " junk exists " )
2001-05-30 09:10:30 +00:00
elsif @limit < @index
2004-03-29 07:54:38 +00:00
raise DecodeError . new ( " limit exceeded " )
2001-05-30 09:10:30 +00:00
end
@limit = save_limit
return d
end
def get_bytes ( len = @limit - @index )
2014-02-08 06:29:54 +00:00
raise DecodeError . new ( " limit exceeded " ) if @limit < @index + len
2017-01-14 02:26:49 +00:00
d = @data . byteslice ( @index , len )
2001-05-30 09:10:30 +00:00
@index += len
return d
end
def get_unpack ( template )
len = 0
template . each_byte { | byte |
2006-08-08 10:29:02 +00:00
byte = " %c " % byte
2001-05-30 09:10:30 +00:00
case byte
when ?c , ?C
len += 1
when ?n
len += 2
when ?N
len += 4
else
raise StandardError . new ( " unsupported template: ' #{ byte . chr } ' in ' #{ template } ' " )
end
}
2004-03-29 07:54:38 +00:00
raise DecodeError . new ( " limit exceeded " ) if @limit < @index + len
2001-05-30 09:10:30 +00:00
arr = @data . unpack ( " @ #{ @index } #{ template } " )
@index += len
2001-06-19 04:35:17 +00:00
return arr
2001-05-30 09:10:30 +00:00
end
def get_string
2014-02-08 06:29:54 +00:00
raise DecodeError . new ( " limit exceeded " ) if @limit < = @index
2017-01-14 02:26:49 +00:00
len = @data . getbyte ( @index )
2004-03-29 07:54:38 +00:00
raise DecodeError . new ( " limit exceeded " ) if @limit < @index + 1 + len
2017-01-14 02:26:49 +00:00
d = @data . byteslice ( @index + 1 , len )
2001-05-30 09:10:30 +00:00
@index += 1 + len
return d
end
2005-02-05 18:31:20 +00:00
def get_string_list
strings = [ ]
while @index < @limit
strings << self . get_string
end
strings
end
[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
2023-11-24 10:35:26 +09:00
def get_list
[ ] . tap do | values |
while @index < @limit
values << yield
end
end
end
2001-05-30 09:10:30 +00:00
def get_name
return Name . new ( self . get_labels )
end
2014-02-08 07:35:24 +00:00
def get_labels
prev_index = @index
save_index = nil
2001-05-30 09:10:30 +00:00
d = [ ]
while true
2014-02-08 06:29:54 +00:00
raise DecodeError . new ( " limit exceeded " ) if @limit < = @index
2017-01-14 02:26:49 +00:00
case @data . getbyte ( @index )
2001-05-30 09:10:30 +00:00
when 0
@index += 1
2014-02-08 07:35:24 +00:00
if save_index
@index = save_index
end
2001-05-30 09:10:30 +00:00
return d
when 192 .. 255
idx = self . get_unpack ( 'n' ) [ 0 ] & 0x3fff
2014-02-08 07:35:24 +00:00
if prev_index < = idx
2001-05-30 09:10:30 +00:00
raise DecodeError . new ( " non-backward name pointer " )
end
2014-02-08 07:35:24 +00:00
prev_index = idx
if ! save_index
save_index = @index
end
2001-05-30 09:10:30 +00:00
@index = idx
else
d << self . get_label
end
end
end
def get_label
return Label :: Str . new ( self . get_string )
end
def get_question
name = self . get_name
type , klass = self . get_unpack ( " nn " )
return name , Resource . get_class ( type , klass )
end
def get_rr
name = self . get_name
type , klass , ttl = self . get_unpack ( 'nnN' )
typeclass = Resource . get_class ( type , klass )
2017-01-14 02:26:48 +00:00
res = self . get_length16 do
begin
typeclass . decode_rdata self
rescue = > e
raise DecodeError , e . message , e . backtrace
end
end
2005-06-15 15:32:46 +00:00
res . instance_variable_set :@ttl , ttl
return name , ttl , res
2001-05-30 09:10:30 +00:00
end
end
end
[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
2023-11-24 10:35:26 +09:00
##
# 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
2023-11-24 10:42:02 +09:00
##
# "dohpath" SvcParam -- DNS over HTTPS path template [RFC9461]
class DoHPath < SvcParam
KeyName = :dohpath
KeyNumber = 7
ClassHash [ KeyName ] = ClassHash [ KeyNumber ] = self # :nodoc:
##
# URI template for DoH queries.
attr_reader :template
##
# Initialize "dohpath" ScvParam.
def initialize ( template )
@template = template . encode ( 'utf-8' )
end
def encode ( msg ) # :nodoc:
msg . put_bytes ( @template )
end
def self . decode ( msg ) # :nodoc:
template = msg . get_bytes . force_encoding ( 'utf-8' )
return self . new ( template )
end
end
[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
2023-11-24 10:35:26 +09:00
end
2005-06-09 22:47:35 +00:00
##
# A DNS query abstract class.
2001-05-30 09:10:30 +00:00
class Query
2005-06-09 22:47:35 +00:00
def encode_rdata ( msg ) # :nodoc:
2008-09-14 15:18:53 +00:00
raise EncodeError . new ( " #{ self . class } is query. " )
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
def self . decode_rdata ( msg ) # :nodoc:
2008-09-14 15:18:53 +00:00
raise DecodeError . new ( " #{ self . class } is query. " )
2001-05-30 09:10:30 +00:00
end
end
2005-06-09 22:47:35 +00:00
##
# A DNS resource abstract class.
2001-05-30 09:10:30 +00:00
class Resource < Query
2005-06-15 15:32:46 +00:00
##
# Remaining Time To Live for this Resource.
attr_reader :ttl
2024-10-29 17:58:27 +00:00
ClassHash = Module . new do
module_function
def []= ( type_class_value , klass )
type_value , class_value = type_class_value
Resource . const_set ( :" Type #{ type_value } _Class #{ class_value } " , klass )
end
end
2005-06-09 22:47:35 +00:00
def encode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
raise NotImplementedError . new
end
2005-06-09 22:47:35 +00:00
def self . decode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
raise NotImplementedError . new
end
2005-06-09 22:47:35 +00:00
def == ( other ) # :nodoc:
2005-06-15 15:32:46 +00:00
return false unless self . class == other . class
s_ivars = self . instance_variables
s_ivars . sort!
2015-02-17 02:47:19 +00:00
s_ivars . delete :@ttl
2005-06-15 15:32:46 +00:00
o_ivars = other . instance_variables
o_ivars . sort!
2015-02-17 02:47:19 +00:00
o_ivars . delete :@ttl
2005-06-15 15:32:46 +00:00
return s_ivars == o_ivars &&
s_ivars . collect { | name | self . instance_variable_get name } ==
o_ivars . collect { | name | other . instance_variable_get name }
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
def eql? ( other ) # :nodoc:
2001-05-30 09:10:30 +00:00
return self == other
end
2005-06-09 22:47:35 +00:00
def hash # :nodoc:
2001-05-30 09:10:30 +00:00
h = 0
2005-06-15 15:32:46 +00:00
vars = self . instance_variables
2015-02-17 02:47:19 +00:00
vars . delete :@ttl
2005-06-15 15:32:46 +00:00
vars . each { | name |
h ^= self . instance_variable_get ( name ) . hash
2001-05-30 09:10:30 +00:00
}
return h
end
2005-06-09 22:47:35 +00:00
def self . get_class ( type_value , class_value ) # :nodoc:
2024-10-29 17:58:27 +00:00
cache = :" Type #{ type_value } _Class #{ class_value } "
return ( const_defined? ( cache ) && const_get ( cache ) ) ||
2001-05-30 09:10:30 +00:00
Generic . create ( type_value , class_value )
end
2005-06-09 22:47:35 +00:00
##
# A generic resource abstract class.
2001-05-30 09:10:30 +00:00
class Generic < Resource
2005-06-09 22:47:35 +00:00
##
# Creates a new generic resource.
2001-05-30 09:10:30 +00:00
def initialize ( data )
@data = data
end
2005-06-09 22:47:35 +00:00
##
# Data for this generic resource.
2001-05-30 09:10:30 +00:00
attr_reader :data
2005-06-09 22:47:35 +00:00
def encode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
msg . put_bytes ( data )
end
2005-06-09 22:47:35 +00:00
def self . decode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
return self . new ( msg . get_bytes )
end
2005-06-09 22:47:35 +00:00
def self . create ( type_value , class_value ) # :nodoc:
2001-05-30 09:10:30 +00:00
c = Class . new ( Generic )
c . const_set ( :TypeValue , type_value )
c . const_set ( :ClassValue , class_value )
Generic . const_set ( " Type #{ type_value } _Class #{ class_value } " , c )
ClassHash [ [ type_value , class_value ] ] = c
return c
end
end
2005-06-09 22:47:35 +00:00
##
# Domain Name resource abstract class.
2001-05-30 09:10:30 +00:00
class DomainName < Resource
2005-06-09 22:47:35 +00:00
##
# Creates a new DomainName from +name+.
2001-05-30 09:10:30 +00:00
def initialize ( name )
@name = name
end
2005-06-09 22:47:35 +00:00
##
# The name of this DomainName.
2001-05-30 09:10:30 +00:00
attr_reader :name
2005-06-09 22:47:35 +00:00
def encode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
msg . put_name ( @name )
end
2005-06-09 22:47:35 +00:00
def self . decode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
return self . new ( msg . get_name )
end
end
# Standard (class generic) RRs
2005-06-09 22:47:35 +00:00
ClassValue = nil # :nodoc:
##
# An authoritative name server.
2001-05-30 09:10:30 +00:00
class NS < DomainName
2005-06-09 22:47:35 +00:00
TypeValue = 2 # :nodoc:
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
##
# The canonical name for an alias.
2001-05-30 09:10:30 +00:00
class CNAME < DomainName
2005-06-09 22:47:35 +00:00
TypeValue = 5 # :nodoc:
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
##
# Start Of Authority resource.
2001-05-30 09:10:30 +00:00
class SOA < Resource
2005-06-09 22:47:35 +00:00
TypeValue = 6 # :nodoc:
##
# Creates a new SOA record. See the attr documentation for the
# details of each argument.
2001-05-30 09:10:30 +00:00
def initialize ( mname , rname , serial , refresh , retry_ , expire , minimum )
@mname = mname
@rname = rname
@serial = serial
@refresh = refresh
@retry = retry_
@expire = expire
@minimum = minimum
end
2005-06-09 22:47:35 +00:00
##
# Name of the host where the master zone file for this zone resides.
attr_reader :mname
##
# The person responsible for this domain name.
attr_reader :rname
##
# The version number of the zone file.
attr_reader :serial
##
# How often, in seconds, a secondary name server is to check for
# updates from the primary name server.
attr_reader :refresh
##
# How often, in seconds, a secondary name server is to retry after a
# failure to check for a refresh.
attr_reader :retry
##
# Time in seconds that a secondary name server is to use the data
# before refreshing from the primary name server.
attr_reader :expire
##
# The minimum number of seconds to be used for TTL values in RRs.
attr_reader :minimum
def encode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
msg . put_name ( @mname )
msg . put_name ( @rname )
msg . put_pack ( 'NNNNN' , @serial , @refresh , @retry , @expire , @minimum )
end
2005-06-09 22:47:35 +00:00
def self . decode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
mname = msg . get_name
rname = msg . get_name
serial , refresh , retry_ , expire , minimum = msg . get_unpack ( 'NNNNN' )
return self . new (
mname , rname , serial , refresh , retry_ , expire , minimum )
end
end
2005-06-09 22:47:35 +00:00
##
# A Pointer to another DNS name.
2001-05-30 09:10:30 +00:00
class PTR < DomainName
2005-06-09 22:47:35 +00:00
TypeValue = 12 # :nodoc:
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
##
# Host Information resource.
2001-05-30 09:10:30 +00:00
class HINFO < Resource
2005-06-09 22:47:35 +00:00
TypeValue = 13 # :nodoc:
##
# Creates a new HINFO running +os+ on +cpu+.
2001-05-30 09:10:30 +00:00
def initialize ( cpu , os )
@cpu = cpu
@os = os
end
2005-06-09 22:47:35 +00:00
##
# CPU architecture for this resource.
attr_reader :cpu
##
# Operating system for this resource.
attr_reader :os
def encode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
msg . put_string ( @cpu )
msg . put_string ( @os )
end
2005-06-09 22:47:35 +00:00
def self . decode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
cpu = msg . get_string
os = msg . get_string
return self . new ( cpu , os )
end
end
2005-06-09 22:47:35 +00:00
##
# Mailing list or mailbox information.
2001-05-30 09:10:30 +00:00
class MINFO < Resource
2005-06-09 22:47:35 +00:00
TypeValue = 14 # :nodoc:
2001-05-30 09:10:30 +00:00
def initialize ( rmailbx , emailbx )
@rmailbx = rmailbx
@emailbx = emailbx
end
2005-06-09 22:47:35 +00:00
##
# Domain name responsible for this mail list or mailbox.
attr_reader :rmailbx
##
# Mailbox to use for error messages related to the mail list or mailbox.
attr_reader :emailbx
def encode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
msg . put_name ( @rmailbx )
msg . put_name ( @emailbx )
end
2005-06-09 22:47:35 +00:00
def self . decode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
rmailbx = msg . get_string
emailbx = msg . get_string
return self . new ( rmailbx , emailbx )
end
end
2005-06-09 22:47:35 +00:00
##
# Mail Exchanger resource.
2001-05-30 09:10:30 +00:00
class MX < Resource
2005-06-09 22:47:35 +00:00
TypeValue = 15 # :nodoc:
##
# Creates a new MX record with +preference+, accepting mail at
# +exchange+.
2001-05-30 09:10:30 +00:00
def initialize ( preference , exchange )
@preference = preference
@exchange = exchange
end
2005-06-09 22:47:35 +00:00
##
# The preference for this MX.
attr_reader :preference
##
# The host of this MX.
attr_reader :exchange
def encode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
msg . put_pack ( 'n' , @preference )
msg . put_name ( @exchange )
end
2005-06-09 22:47:35 +00:00
def self . decode_rdata ( msg ) # :nodoc:
2001-11-01 05:11:24 +00:00
preference , = msg . get_unpack ( 'n' )
2001-05-30 09:10:30 +00:00
exchange = msg . get_name
return self . new ( preference , exchange )
end
end
2005-06-09 22:47:35 +00:00
##
# Unstructured text resource.
2001-05-30 09:10:30 +00:00
class TXT < Resource
2005-06-09 22:47:35 +00:00
TypeValue = 16 # :nodoc:
2001-05-30 09:10:30 +00:00
2005-02-05 18:31:20 +00:00
def initialize ( first_string , * rest_strings )
@strings = [ first_string , * rest_strings ]
end
2005-06-09 22:47:35 +00:00
##
# Returns an Array of Strings for this TXT record.
2005-02-05 18:31:20 +00:00
attr_reader :strings
2005-06-09 22:47:35 +00:00
##
2014-01-18 14:01:43 +00:00
# Returns the concatenated string from +strings+.
2005-06-09 22:47:35 +00:00
2005-02-05 18:31:20 +00:00
def data
2014-01-18 14:01:43 +00:00
@strings . join ( " " )
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
def encode_rdata ( msg ) # :nodoc:
2005-02-05 18:31:20 +00:00
msg . put_string_list ( @strings )
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
def self . decode_rdata ( msg ) # :nodoc:
2005-02-05 18:31:20 +00:00
strings = msg . get_string_list
return self . new ( * strings )
2001-05-30 09:10:30 +00:00
end
end
2013-04-06 14:52:48 +00:00
##
# Location resource
class LOC < Resource
TypeValue = 29 # :nodoc:
def initialize ( version , ssize , hprecision , vprecision , latitude , longitude , altitude )
@version = version
@ssize = Resolv :: LOC :: Size . create ( ssize )
@hprecision = Resolv :: LOC :: Size . create ( hprecision )
@vprecision = Resolv :: LOC :: Size . create ( vprecision )
@latitude = Resolv :: LOC :: Coord . create ( latitude )
@longitude = Resolv :: LOC :: Coord . create ( longitude )
@altitude = Resolv :: LOC :: Alt . create ( altitude )
end
##
# Returns the version value for this LOC record which should always be 00
attr_reader :version
##
# The spherical size of this LOC
# in meters using scientific notation as 2 integers of XeY
attr_reader :ssize
##
# The horizontal precision using ssize type values
# in meters using scientific notation as 2 integers of XeY
# for precision use value/2 e.g. 2m = +/-1m
attr_reader :hprecision
##
# The vertical precision using ssize type values
# in meters using scientific notation as 2 integers of XeY
# for precision use value/2 e.g. 2m = +/-1m
attr_reader :vprecision
##
# The latitude for this LOC where 2**31 is the equator
# in thousandths of an arc second as an unsigned 32bit integer
attr_reader :latitude
##
# The longitude for this LOC where 2**31 is the prime meridian
# in thousandths of an arc second as an unsigned 32bit integer
attr_reader :longitude
##
# The altitude of the LOC above a reference sphere whose surface sits 100km below the WGS84 spheroid
# in centimeters as an unsigned 32bit integer
attr_reader :altitude
def encode_rdata ( msg ) # :nodoc:
msg . put_bytes ( @version )
msg . put_bytes ( @ssize . scalar )
msg . put_bytes ( @hprecision . scalar )
msg . put_bytes ( @vprecision . scalar )
msg . put_bytes ( @latitude . coordinates )
msg . put_bytes ( @longitude . coordinates )
msg . put_bytes ( @altitude . altitude )
end
def self . decode_rdata ( msg ) # :nodoc:
version = msg . get_bytes ( 1 )
ssize = msg . get_bytes ( 1 )
hprecision = msg . get_bytes ( 1 )
vprecision = msg . get_bytes ( 1 )
latitude = msg . get_bytes ( 4 )
longitude = msg . get_bytes ( 4 )
altitude = msg . get_bytes ( 4 )
return self . new (
version ,
Resolv :: LOC :: Size . new ( ssize ) ,
Resolv :: LOC :: Size . new ( hprecision ) ,
Resolv :: LOC :: Size . new ( vprecision ) ,
Resolv :: LOC :: Coord . new ( latitude , " lat " ) ,
Resolv :: LOC :: Coord . new ( longitude , " lon " ) ,
Resolv :: LOC :: Alt . new ( altitude )
)
end
end
2005-06-09 22:47:35 +00:00
##
# A Query type requesting any RR.
2001-05-30 09:10:30 +00:00
class ANY < Query
2005-06-09 22:47:35 +00:00
TypeValue = 255 # :nodoc:
2001-05-30 09:10:30 +00:00
end
2024-02-28 06:48:40 +00:00
##
# CAA resource record defined in RFC 8659
#
# These records identify certificate authority allowed to issue
# certificates for the given domain.
class CAA < Resource
TypeValue = 257
##
# Creates a new CAA for +flags+, +tag+ and +value+.
def initialize ( flags , tag , value )
unless ( 0 .. 255 ) === flags
raise ArgumentError . new ( 'flags must be an Integer between 0 and 255' )
end
unless ( 1 .. 15 ) === tag . bytesize
raise ArgumentError . new ( 'length of tag must be between 1 and 15' )
end
@flags = flags
@tag = tag
@value = value
end
##
2025-05-29 05:37:51 +10:00
# Flags for this property:
2024-02-28 06:48:40 +00:00
# - Bit 0 : 0 = not critical, 1 = critical
attr_reader :flags
##
# Property tag ("issue", "issuewild", "iodef"...).
attr_reader :tag
##
# Property value.
attr_reader :value
##
# Whether the critical flag is set on this property.
def critical?
flags & 0x80 != 0
end
def encode_rdata ( msg ) # :nodoc:
msg . put_pack ( 'C' , @flags )
msg . put_string ( @tag )
msg . put_bytes ( @value )
end
def self . decode_rdata ( msg ) # :nodoc:
flags , = msg . get_unpack ( 'C' )
tag = msg . get_string
value = msg . get_bytes
self . new flags , tag , value
end
end
2005-06-09 22:47:35 +00:00
ClassInsensitiveTypes = [ # :nodoc:
2024-02-28 06:48:40 +00:00
NS , CNAME , SOA , PTR , HINFO , MINFO , MX , TXT , LOC , ANY , CAA
2001-05-30 09:10:30 +00:00
]
2005-06-09 22:47:35 +00:00
##
# module IN contains ARPA Internet specific RRs.
2001-05-30 09:10:30 +00:00
module IN
2005-06-09 22:47:35 +00:00
ClassValue = 1 # :nodoc:
2001-05-30 09:10:30 +00:00
ClassInsensitiveTypes . each { | s |
c = Class . new ( s )
c . const_set ( :TypeValue , s :: TypeValue )
c . const_set ( :ClassValue , ClassValue )
ClassHash [ [ s :: TypeValue , ClassValue ] ] = c
self . const_set ( s . name . sub ( / .*:: / , '' ) , c )
}
2005-06-09 22:47:35 +00:00
##
# IPv4 Address resource
2001-05-30 09:10:30 +00:00
class A < Resource
2006-02-20 00:15:36 +00:00
TypeValue = 1
ClassValue = IN :: ClassValue
ClassHash [ [ TypeValue , ClassValue ] ] = self # :nodoc:
2005-06-09 22:47:35 +00:00
##
# Creates a new A for +address+.
2001-05-30 09:10:30 +00:00
def initialize ( address )
@address = IPv4 . create ( address )
end
2005-06-09 22:47:35 +00:00
##
# The Resolv::IPv4 address for this A.
2001-05-30 09:10:30 +00:00
attr_reader :address
2005-06-09 22:47:35 +00:00
def encode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
msg . put_bytes ( @address . address )
end
2005-06-09 22:47:35 +00:00
def self . decode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
return self . new ( IPv4 . new ( msg . get_bytes ( 4 ) ) )
end
end
2005-06-09 22:47:35 +00:00
##
# Well Known Service resource.
2001-05-30 09:10:30 +00:00
class WKS < Resource
2006-02-20 00:15:36 +00:00
TypeValue = 11
ClassValue = IN :: ClassValue
ClassHash [ [ TypeValue , ClassValue ] ] = self # :nodoc:
2001-05-30 09:10:30 +00:00
def initialize ( address , protocol , bitmap )
@address = IPv4 . create ( address )
@protocol = protocol
@bitmap = bitmap
end
2005-06-09 22:47:35 +00:00
##
# The host these services run on.
attr_reader :address
##
# IP protocol number for these services.
attr_reader :protocol
##
# A bit map of enabled services on this host.
#
# If protocol is 6 (TCP) then the 26th bit corresponds to the SMTP
# service (port 25). If this bit is set, then an SMTP server should
# be listening on TCP port 25; if zero, SMTP service is not
# supported.
attr_reader :bitmap
def encode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
msg . put_bytes ( @address . address )
msg . put_pack ( " n " , @protocol )
msg . put_bytes ( @bitmap )
end
2005-06-09 22:47:35 +00:00
def self . decode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
address = IPv4 . new ( msg . get_bytes ( 4 ) )
2001-11-01 05:11:24 +00:00
protocol , = msg . get_unpack ( " n " )
2001-05-30 09:10:30 +00:00
bitmap = msg . get_bytes
return self . new ( address , protocol , bitmap )
end
end
2005-06-09 22:47:35 +00:00
##
# An IPv6 address record.
2001-05-30 09:10:30 +00:00
class AAAA < Resource
2006-02-20 00:15:36 +00:00
TypeValue = 28
ClassValue = IN :: ClassValue
ClassHash [ [ TypeValue , ClassValue ] ] = self # :nodoc:
2005-06-09 22:47:35 +00:00
##
# Creates a new AAAA for +address+.
2001-05-30 09:10:30 +00:00
def initialize ( address )
@address = IPv6 . create ( address )
end
2008-09-14 15:18:53 +00:00
2005-06-09 22:47:35 +00:00
##
# The Resolv::IPv6 address for this AAAA.
2001-05-30 09:10:30 +00:00
attr_reader :address
2005-06-09 22:47:35 +00:00
def encode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
msg . put_bytes ( @address . address )
end
2005-06-09 22:47:35 +00:00
def self . decode_rdata ( msg ) # :nodoc:
2001-05-30 09:10:30 +00:00
return self . new ( IPv6 . new ( msg . get_bytes ( 16 ) ) )
end
end
2005-01-29 05:14:11 +00:00
2005-06-09 22:47:35 +00:00
##
2005-01-29 05:14:11 +00:00
# SRV resource record defined in RFC 2782
2008-09-14 15:18:53 +00:00
#
2005-01-29 05:14:11 +00:00
# These records identify the hostname and port that a service is
# available at.
2005-06-09 22:47:35 +00:00
2005-01-29 05:14:11 +00:00
class SRV < Resource
2006-02-20 00:15:36 +00:00
TypeValue = 33
ClassValue = IN :: ClassValue
ClassHash [ [ TypeValue , ClassValue ] ] = self # :nodoc:
2005-01-29 05:14:11 +00:00
# Create a SRV resource record.
2005-06-09 22:47:35 +00:00
#
# See the documentation for #priority, #weight, #port and #target
# for +priority+, +weight+, +port and +target+ respectively.
2005-01-29 05:14:11 +00:00
def initialize ( priority , weight , port , target )
@priority = priority . to_int
@weight = weight . to_int
@port = port . to_int
@target = Name . create ( target )
end
2005-06-09 22:47:35 +00:00
# The priority of this target host.
#
# A client MUST attempt to contact the target host with the
# lowest-numbered priority it can reach; target hosts with the same
# priority SHOULD be tried in an order defined by the weight field.
# The range is 0-65535. Note that it is not widely implemented and
# should be set to zero.
attr_reader :priority
# A server selection mechanism.
#
# The weight field specifies a relative weight for entries with the
# same priority. Larger weights SHOULD be given a proportionately
# higher probability of being selected. The range of this number is
# 0-65535. Domain administrators SHOULD use Weight 0 when there
# isn't any server selection to do, to make the RR easier to read
# for humans (less noisy). Note that it is not widely implemented
# and should be set to zero.
attr_reader :weight
# The port on this target host of this service.
#
# The range is 0-65535.
2005-01-29 05:14:11 +00:00
2005-06-09 22:47:35 +00:00
attr_reader :port
# The domain name of the target host.
#
# A target of "." means that the service is decidedly not available
# at this domain.
attr_reader :target
def encode_rdata ( msg ) # :nodoc:
2005-01-29 05:14:11 +00:00
msg . put_pack ( " n " , @priority )
msg . put_pack ( " n " , @weight )
msg . put_pack ( " n " , @port )
2023-04-08 12:25:04 +00:00
msg . put_name ( @target , compress : false )
2005-01-29 05:14:11 +00:00
end
2005-06-09 22:47:35 +00:00
def self . decode_rdata ( msg ) # :nodoc:
2005-01-29 05:14:11 +00:00
priority , = msg . get_unpack ( " n " )
weight , = msg . get_unpack ( " n " )
port , = msg . get_unpack ( " n " )
target = msg . get_name
return self . new ( priority , weight , port , target )
end
end
[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
2023-11-24 10:35:26 +09:00
##
# 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
##
2023-12-14 14:20:18 +01:00
# The service parameters for the target host.
[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
2023-11-24 10:35:26 +09:00
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
2001-05-30 09:10:30 +00:00
end
end
end
2005-06-09 22:47:35 +00:00
##
# A Resolv::DNS IPv4 address.
2001-05-30 09:10:30 +00:00
class IPv4
2005-06-09 22:47:35 +00:00
##
# Regular expression IPv4 addresses must match.
2010-04-18 09:09:46 +00:00
Regex256 = / 0
| 1 ( ?: [ 0 - 9 ] [ 0 - 9 ] ?) ?
| 2 ( ?: [ 0 - 4 ] [ 0 - 9 ] ? | 5 [ 0 - 5 ] ?| [ 6 - 9 ] ) ?
| [ 3 - 9 ] [ 0 - 9 ] ? / x
Regex = / \ A( #{ Regex256 } ) \ .( #{ Regex256 } ) \ .( #{ Regex256 } ) \ .( #{ Regex256 } ) \ z /
2001-05-30 09:10:30 +00:00
def self . create ( arg )
case arg
when IPv4
return arg
when Regex
if ( 0 .. 255 ) === ( a = $1 . to_i ) &&
( 0 .. 255 ) === ( b = $2 . to_i ) &&
( 0 .. 255 ) === ( c = $3 . to_i ) &&
( 0 .. 255 ) === ( d = $4 . to_i )
return self . new ( [ a , b , c , d ] . pack ( " CCCC " ) )
else
raise ArgumentError . new ( " IPv4 address with invalid value: " + arg )
end
else
2004-03-29 07:54:38 +00:00
raise ArgumentError . new ( " cannot interpret as IPv4 address: #{ arg . inspect } " )
2001-05-30 09:10:30 +00:00
end
end
2005-06-09 22:47:35 +00:00
def initialize ( address ) # :nodoc:
2010-01-02 16:31:00 +00:00
unless address . kind_of? ( String )
raise ArgumentError , 'IPv4 address must be a string'
end
unless address . length == 4
raise ArgumentError , " IPv4 address expects 4 bytes but #{ address . length } bytes "
2001-05-30 09:10:30 +00:00
end
@address = address
end
2005-06-09 22:47:35 +00:00
##
2008-06-04 09:37:38 +00:00
# A String representation of this IPv4 address.
2005-06-09 22:47:35 +00:00
##
# The raw IPv4 address as a String.
2001-05-30 09:10:30 +00:00
attr_reader :address
2005-06-09 22:47:35 +00:00
def to_s # :nodoc:
2001-05-30 09:10:30 +00:00
return sprintf ( " %d.%d.%d.%d " , * @address . unpack ( " CCCC " ) )
end
2005-06-09 22:47:35 +00:00
def inspect # :nodoc:
2014-06-24 08:48:46 +00:00
return " # < #{ self . class } #{ self } > "
2001-11-01 05:11:24 +00:00
end
2005-06-09 22:47:35 +00:00
##
# Turns this IPv4 address into a Resolv::DNS::Name.
2001-05-30 09:10:30 +00:00
def to_name
2002-03-12 08:12:32 +00:00
return DNS :: Name . create (
'%d.%d.%d.%d.in-addr.arpa.' % @address . unpack ( 'CCCC' ) . reverse )
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
def == ( other ) # :nodoc:
2001-05-30 09:10:30 +00:00
return @address == other . address
end
2005-06-09 22:47:35 +00:00
def eql? ( other ) # :nodoc:
2001-05-30 09:10:30 +00:00
return self == other
end
2005-06-09 22:47:35 +00:00
def hash # :nodoc:
2001-05-30 09:10:30 +00:00
return @address . hash
end
end
2005-06-09 22:47:35 +00:00
##
# A Resolv::DNS IPv6 address.
2001-05-30 09:10:30 +00:00
class IPv6
2005-06-09 22:47:35 +00:00
##
# IPv6 address format a:b:c:d:e:f:g:h
2001-12-18 18:14:08 +00:00
Regex_8Hex = / \ A
( ?: [ 0 - 9 A - Fa - f ] { 1 , 4 } : ) { 7 }
2003-07-24 01:03:28 +00:00
[ 0 - 9 A - Fa - f ] { 1 , 4 }
2001-12-18 18:14:08 +00:00
\ z / x
2005-06-09 22:47:35 +00:00
##
# Compressed IPv6 address format a::b
2001-12-18 18:14:08 +00:00
Regex_CompressedHex = / \ A
( ( ?: [ 0 - 9 A - Fa - f ] { 1 , 4 } ( ?: : [ 0 - 9 A - Fa - f ] { 1 , 4 } ) * ) ?) ::
( ( ?: [ 0 - 9 A - Fa - f ] { 1 , 4 } ( ?: : [ 0 - 9 A - Fa - f ] { 1 , 4 } ) * ) ?)
\ z / x
2005-06-09 22:47:35 +00:00
##
# IPv4 mapped IPv6 address format a:b:c:d:e:f:w.x.y.z
2001-12-18 18:14:08 +00:00
Regex_6Hex4Dec = / \ A
( ( ?: [ 0 - 9 A - Fa - f ] { 1 , 4 } : ) { 6 , 6 } )
( \ d + ) \ . ( \ d + ) \ . ( \ d + ) \ . ( \ d + )
\ z / x
2005-06-09 22:47:35 +00:00
##
# Compressed IPv4 mapped IPv6 address format a::b:w.x.y.z
2001-12-18 18:14:08 +00:00
Regex_CompressedHex4Dec = / \ A
( ( ?: [ 0 - 9 A - Fa - f ] { 1 , 4 } ( ?: : [ 0 - 9 A - Fa - f ] { 1 , 4 } ) * ) ?) ::
( ( ?: [ 0 - 9 A - Fa - f ] { 1 , 4 } : ) * )
( \ d + ) \ . ( \ d + ) \ . ( \ d + ) \ . ( \ d + )
\ z / x
2020-08-24 12:39:08 -07:00
##
# IPv6 link local address format fe80:b:c:d:e:f:g:h%em1
Regex_8HexLinkLocal = / \ A
2020-11-09 16:16:30 +09:00
[ Ff ] [ Ee ] 80
2020-08-24 12:39:08 -07:00
( ?: : [ 0 - 9 A - Fa - f ] { 1 , 4 } ) { 7 }
2021-01-09 12:14:23 -08:00
%[ -0-9A-Za-z._~ ] +
2020-08-24 12:39:08 -07:00
\ z / x
##
# Compressed IPv6 link local address format fe80::b%em1
Regex_CompressedHexLinkLocal = / \ A
2020-11-09 16:16:30 +09:00
[ Ff ] [ Ee ] 80 :
2020-08-24 12:39:08 -07:00
( ?:
( ( ?: [ 0 - 9 A - Fa - f ] { 1 , 4 } ( ?: : [ 0 - 9 A - Fa - f ] { 1 , 4 } ) * ) ?) ::
( ( ?: [ 0 - 9 A - Fa - f ] { 1 , 4 } ( ?: : [ 0 - 9 A - Fa - f ] { 1 , 4 } ) * ) ?)
|
: ( ( ?: [ 0 - 9 A - Fa - f ] { 1 , 4 } ( ?: : [ 0 - 9 A - Fa - f ] { 1 , 4 } ) * ) ?)
) ?
2021-01-09 12:14:23 -08:00
: [ 0 - 9 A - Fa - f ] { 1 , 4 } %[ -0-9A-Za-z._~ ] +
2020-08-24 12:39:08 -07:00
\ z / x
2005-06-09 22:47:35 +00:00
##
# A composite IPv6 address Regexp.
2001-12-18 18:14:08 +00:00
Regex = /
2004-04-23 05:22:25 +00:00
( ?: #{Regex_8Hex}) |
( ?: #{Regex_CompressedHex}) |
( ?: #{Regex_6Hex4Dec}) |
2020-08-24 12:39:08 -07:00
( ?: #{Regex_CompressedHex4Dec}) |
( ?: #{Regex_8HexLinkLocal}) |
( ?: #{Regex_CompressedHexLinkLocal})
/ x
2001-05-30 09:10:30 +00:00
2005-06-09 22:47:35 +00:00
##
# Creates a new IPv6 address from +arg+ which may be:
#
# IPv6:: returns +arg+.
# String:: +arg+ must match one of the IPv6::Regex* constants
2001-05-30 09:10:30 +00:00
def self . create ( arg )
case arg
when IPv6
return arg
when String
2015-12-28 20:31:10 +00:00
address = '' . b
2001-05-30 09:10:30 +00:00
if Regex_8Hex =~ arg
arg . scan ( / [0-9A-Fa-f]+ / ) { | hex | address << [ hex . hex ] . pack ( 'n' ) }
elsif Regex_CompressedHex =~ arg
prefix = $1
suffix = $2
2015-12-28 20:31:10 +00:00
a1 = '' . b
a2 = '' . b
2001-05-30 09:10:30 +00:00
prefix . scan ( / [0-9A-Fa-f]+ / ) { | hex | a1 << [ hex . hex ] . pack ( 'n' ) }
suffix . scan ( / [0-9A-Fa-f]+ / ) { | hex | a2 << [ hex . hex ] . pack ( 'n' ) }
omitlen = 16 - a1 . length - a2 . length
address << a1 << " \0 " * omitlen << a2
elsif Regex_6Hex4Dec =~ arg
prefix , a , b , c , d = $1 , $2 . to_i , $3 . to_i , $4 . to_i , $5 . to_i
if ( 0 .. 255 ) === a && ( 0 .. 255 ) === b && ( 0 .. 255 ) === c && ( 0 .. 255 ) === d
prefix . scan ( / [0-9A-Fa-f]+ / ) { | hex | address << [ hex . hex ] . pack ( 'n' ) }
address << [ a , b , c , d ] . pack ( 'CCCC' )
else
raise ArgumentError . new ( " not numeric IPv6 address: " + arg )
end
elsif Regex_CompressedHex4Dec =~ arg
prefix , suffix , a , b , c , d = $1 , $2 , $3 . to_i , $4 . to_i , $5 . to_i , $6 . to_i
if ( 0 .. 255 ) === a && ( 0 .. 255 ) === b && ( 0 .. 255 ) === c && ( 0 .. 255 ) === d
2015-12-28 20:31:10 +00:00
a1 = '' . b
a2 = '' . b
2001-05-30 09:10:30 +00:00
prefix . scan ( / [0-9A-Fa-f]+ / ) { | hex | a1 << [ hex . hex ] . pack ( 'n' ) }
suffix . scan ( / [0-9A-Fa-f]+ / ) { | hex | a2 << [ hex . hex ] . pack ( 'n' ) }
omitlen = 12 - a1 . length - a2 . length
address << a1 << " \0 " * omitlen << a2 << [ a , b , c , d ] . pack ( 'CCCC' )
else
raise ArgumentError . new ( " not numeric IPv6 address: " + arg )
end
else
raise ArgumentError . new ( " not numeric IPv6 address: " + arg )
end
return IPv6 . new ( address )
else
2004-03-29 07:54:38 +00:00
raise ArgumentError . new ( " cannot interpret as IPv6 address: #{ arg . inspect } " )
2001-05-30 09:10:30 +00:00
end
end
2005-06-09 22:47:35 +00:00
def initialize ( address ) # :nodoc:
2001-05-30 09:10:30 +00:00
unless address . kind_of? ( String ) && address . length == 16
2003-04-21 21:14:08 +00:00
raise ArgumentError . new ( 'IPv6 address must be 16 bytes' )
2001-05-30 09:10:30 +00:00
end
@address = address
end
2005-06-09 22:47:35 +00:00
##
# The raw IPv6 address as a String.
2001-05-30 09:10:30 +00:00
attr_reader :address
2005-06-09 22:47:35 +00:00
def to_s # :nodoc:
2023-11-22 11:35:22 +01:00
sprintf ( " %x:%x:%x:%x:%x:%x:%x:%x " , * @address . unpack ( " nnnnnnnn " ) ) . sub ( / (^|:)0(:0)+(:|$) / , '::' )
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
def inspect # :nodoc:
2014-06-24 08:48:46 +00:00
return " # < #{ self . class } #{ self } > "
2001-11-01 05:11:24 +00:00
end
2005-06-09 22:47:35 +00:00
##
# Turns this IPv6 address into a Resolv::DNS::Name.
#--
# ip6.arpa should be searched too. [RFC3152]
2001-05-30 09:10:30 +00:00
def to_name
return DNS :: Name . new (
2007-12-26 13:50:31 +00:00
@address . unpack ( " H32 " ) [ 0 ] . split ( / / ) . reverse + [ 'ip6' , 'arpa' ] )
2001-05-30 09:10:30 +00:00
end
2005-06-09 22:47:35 +00:00
def == ( other ) # :nodoc:
2001-05-30 09:10:30 +00:00
return @address == other . address
end
2005-06-09 22:47:35 +00:00
def eql? ( other ) # :nodoc:
2001-05-30 09:10:30 +00:00
return self == other
end
2005-06-09 22:47:35 +00:00
def hash # :nodoc:
2001-05-30 09:10:30 +00:00
return @address . hash
end
end
2013-04-06 14:32:34 +00:00
##
# Resolv::MDNS is a one-shot Multicast DNS (mDNS) resolver. It blindly
# makes queries to the mDNS addresses without understanding anything about
# multicast ports.
#
# Information taken form the following places:
#
# * RFC 6762
class MDNS < DNS
##
# Default mDNS Port
Port = 5353
##
# Default IPv4 mDNS address
AddressV4 = '224.0.0.251'
##
# Default IPv6 mDNS address
AddressV6 = 'ff02::fb'
##
# Default mDNS addresses
Addresses = [
[ AddressV4 , Port ] ,
[ AddressV6 , Port ] ,
]
##
# Creates a new one-shot Multicast DNS (mDNS) resolver.
#
# +config_info+ can be:
#
# nil::
# Uses the default mDNS addresses
#
# Hash::
# Must contain :nameserver or :nameserver_port like
# Resolv::DNS#initialize.
def initialize ( config_info = nil )
if config_info then
super ( { nameserver_port : Addresses } . merge ( config_info ) )
else
super ( nameserver_port : Addresses )
end
end
##
# Iterates over all IP addresses for +name+ retrieved from the mDNS
# resolver, provided name ends with "local". If the name does not end in
# "local" no records will be returned.
#
# +name+ can be a Resolv::DNS::Name or a String. Retrieved addresses will
# be a Resolv::IPv4 or Resolv::IPv6
def each_address ( name )
name = Resolv :: DNS :: Name . create ( name )
2018-01-11 18:26:49 +00:00
return unless name [ - 1 ] . to_s == 'local'
2013-04-06 14:32:34 +00:00
super ( name )
end
def make_udp_requester # :nodoc:
nameserver_port = @config . nameserver_port
Requester :: MDNSOneShot . new ( * nameserver_port )
end
end
2013-04-06 14:52:48 +00:00
module LOC
##
# A Resolv::LOC::Size
class Size
Regex = / ^( \ d+ \ .* \ d*)[m]$ /
##
# Creates a new LOC::Size from +arg+ which may be:
#
# LOC::Size:: returns +arg+.
# String:: +arg+ must match the LOC::Size::Regex constant
def self . create ( arg )
case arg
when Size
return arg
when String
scalar = ''
if Regex =~ arg
scalar = [ ( ( $1 . to_f * ( 1e2 ) ) . to_i . to_s [ 0 ] . to_i * ( 2 ** 4 ) + ( ( $1 . to_f * ( 1e2 ) ) . to_i . to_s . length - 1 ) ) ] . pack ( " C " )
else
raise ArgumentError . new ( " not a properly formed Size string: " + arg )
end
return Size . new ( scalar )
else
raise ArgumentError . new ( " cannot interpret as Size: #{ arg . inspect } " )
end
end
def initialize ( scalar )
@scalar = scalar
end
##
# The raw size
attr_reader :scalar
def to_s # :nodoc:
s = @scalar . unpack ( " H2 " ) . join . to_s
return ( ( s [ 0 ] . to_i ) * ( 10 ** ( s [ 1 ] . to_i - 2 ) ) ) . to_s << " m "
end
def inspect # :nodoc:
2014-06-24 08:48:46 +00:00
return " # < #{ self . class } #{ self } > "
2013-04-06 14:52:48 +00:00
end
def == ( other ) # :nodoc:
return @scalar == other . scalar
end
def eql? ( other ) # :nodoc:
return self == other
end
def hash # :nodoc:
return @scalar . hash
end
end
##
# A Resolv::LOC::Coord
class Coord
Regex = / ^( \ d+) \ s( \ d+) \ s( \ d+ \ . \ d+) \ s([NESW])$ /
##
# Creates a new LOC::Coord from +arg+ which may be:
#
# LOC::Coord:: returns +arg+.
# String:: +arg+ must match the LOC::Coord::Regex constant
def self . create ( arg )
case arg
when Coord
return arg
when String
coordinates = ''
2016-11-05 15:19:21 +00:00
if Regex =~ arg && $1 . to_f < 180
m = $~
hemi = ( m [ 4 ] [ / [NE] / ] ) || ( m [ 4 ] [ / [SW] / ] ) ? 1 : - 1
coordinates = [ ( ( m [ 1 ] . to_i * ( 36e5 ) ) + ( m [ 2 ] . to_i * ( 6e4 ) ) +
( m [ 3 ] . to_f * ( 1e3 ) ) ) * hemi + ( 2 ** 31 ) ] . pack ( " N " )
orientation = m [ 4 ] [ / [NS] / ] ? 'lat' : 'lon'
2013-04-06 14:52:48 +00:00
else
raise ArgumentError . new ( " not a properly formed Coord string: " + arg )
end
return Coord . new ( coordinates , orientation )
else
raise ArgumentError . new ( " cannot interpret as Coord: #{ arg . inspect } " )
end
end
def initialize ( coordinates , orientation )
unless coordinates . kind_of? ( String )
raise ArgumentError . new ( " Coord must be a 32bit unsigned integer in hex format: #{ coordinates . inspect } " )
end
unless orientation . kind_of? ( String ) && orientation [ / ^lon$|^lat$ / ]
raise ArgumentError . new ( 'Coord expects orientation to be a String argument of "lat" or "lon"' )
end
@coordinates = coordinates
@orientation = orientation
end
##
# The raw coordinates
attr_reader :coordinates
## The orientation of the hemisphere as 'lat' or 'lon'
attr_reader :orientation
def to_s # :nodoc:
c = @coordinates . unpack ( " N " ) . join . to_i
val = ( c - ( 2 ** 31 ) ) . abs
fracsecs = ( val % 1e3 ) . to_i . to_s
val = val / 1e3
secs = ( val % 60 ) . to_i . to_s
val = val / 60
mins = ( val % 60 ) . to_i . to_s
degs = ( val / 60 ) . to_i . to_s
posi = ( c > = 2 ** 31 )
case posi
when true
hemi = @orientation [ / ^lat$ / ] ? " N " : " E "
else
hemi = @orientation [ / ^lon$ / ] ? " W " : " S "
end
return degs << " " << mins << " " << secs << " . " << fracsecs << " " << hemi
end
def inspect # :nodoc:
2014-06-24 08:48:46 +00:00
return " # < #{ self . class } #{ self } > "
2013-04-06 14:52:48 +00:00
end
def == ( other ) # :nodoc:
return @coordinates == other . coordinates
end
def eql? ( other ) # :nodoc:
return self == other
end
def hash # :nodoc:
return @coordinates . hash
end
end
##
# A Resolv::LOC::Alt
class Alt
Regex = / ^([+-]* \ d+ \ .* \ d*)[m]$ /
##
# Creates a new LOC::Alt from +arg+ which may be:
#
# LOC::Alt:: returns +arg+.
# String:: +arg+ must match the LOC::Alt::Regex constant
def self . create ( arg )
case arg
when Alt
return arg
when String
altitude = ''
if Regex =~ arg
altitude = [ ( $1 . to_f * ( 1e2 ) ) + ( 1e7 ) ] . pack ( " N " )
else
raise ArgumentError . new ( " not a properly formed Alt string: " + arg )
end
return Alt . new ( altitude )
else
raise ArgumentError . new ( " cannot interpret as Alt: #{ arg . inspect } " )
end
end
def initialize ( altitude )
@altitude = altitude
end
##
# The raw altitude
attr_reader :altitude
def to_s # :nodoc:
a = @altitude . unpack ( " N " ) . join . to_i
return ( ( a . to_f / 1e2 ) - 1e5 ) . to_s + " m "
end
def inspect # :nodoc:
2014-06-24 08:48:46 +00:00
return " # < #{ self . class } #{ self } > "
2013-04-06 14:52:48 +00:00
end
def == ( other ) # :nodoc:
return @altitude == other . altitude
end
def eql? ( other ) # :nodoc:
return self == other
end
def hash # :nodoc:
return @altitude . hash
end
end
end
2005-06-09 22:47:35 +00:00
##
# Default resolver to use for Resolv class methods.
2001-05-30 09:10:30 +00:00
DefaultResolver = self . new
2005-06-09 22:47:35 +00:00
2013-04-06 14:32:34 +00:00
##
# Replaces the resolvers in the default resolver with +new_resolvers+. This
# allows resolvers to be changed for resolv-replace.
def DefaultResolver . replace_resolvers new_resolvers
@resolvers = new_resolvers
end
2005-06-09 22:47:35 +00:00
##
# Address Regexp to use for matching IP addresses.
2004-04-23 05:22:25 +00:00
AddressRegex = / (?: #{ IPv4 :: Regex } )|(?: #{ IPv6 :: Regex } ) /
2005-06-09 22:47:35 +00:00
2001-05-30 09:10:30 +00:00
end