public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/ruby] f44: Update to Ruby 4.0.7
@ 2026-09-17 14:01 Mamoru TASAKA
  0 siblings, 0 replies; only message in thread
From: Mamoru TASAKA @ 2026-09-17 14:01 UTC (permalink / raw)
  To: git-commits

A new commit has been pushed.

Repo   : rpms/ruby
Branch : f44
Commit : d2201e13ea046fdb8c871762fbfc07dc8562f1bc
Author : Mamoru TASAKA <mtasaka@fedoraproject.org>
Date   : 2026-09-17T22:58:49+09:00
Stats  : +3595/-1007 in 4 file(s)
URL    : https://src.fedoraproject.org/rpms/ruby/c/d2201e13ea046fdb8c871762fbfc07dc8562f1bc?branch=f44

Log:
Update to Ruby 4.0.7

---
diff --git a/ruby-4.0.7-rubygems-pr9877-stop-vendoring-resolv.patch b/ruby-4.0.7-rubygems-pr9877-stop-vendoring-resolv.patch
new file mode 100644
index 0000000..9c1b3f7
--- /dev/null
+++ b/ruby-4.0.7-rubygems-pr9877-stop-vendoring-resolv.patch
@@ -0,0 +1,3579 @@
+From 0b8e0b1c28bc839095e2c0e884313f0f5ef1fc53 Mon Sep 17 00:00:00 2001
+From: Hiroshi SHIBATA <hsbt@ruby-lang.org>
+Date: Wed, 16 Sep 2026 09:34:09 +0900
+Subject: [PATCH] [ruby/rubygems] Stop vendoring resolv for two regexps
+
+Vendored net/http loaded the whole 3500-line DNS resolver only to match
+Resolv::IPv4::Regex and Resolv::IPv6::Regex when deciding whether an
+address may be sent as a Server Name Indication. Inline equivalent
+regexps instead, so that a resolver RubyGems never resolves with is no
+longer shipped and no longer shows up as an outdated copy in security
+audits of the Ruby tarball.
+
+https://bugs.ruby-lang.org/issues/22317
+
+https://github.com/ruby/rubygems/commit/9f2bd5f66c
+
+Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
+---
+ lib/rubygems/vendor/net-http/lib/net/http.rb |   26 +-
+ lib/rubygems/vendor/resolv/lib/resolv.rb     | 3508 ------------------
+ 2 files changed, 24 insertions(+), 3510 deletions(-)
+ delete mode 100644 lib/rubygems/vendor/resolv/lib/resolv.rb
+
+diff --git a/lib/rubygems/vendor/net-http/lib/net/http.rb b/lib/rubygems/vendor/net-http/lib/net/http.rb
+index 4800cd25f13c21..b205e49734eebf 100644
+--- a/lib/rubygems/vendor/net-http/lib/net/http.rb
++++ b/lib/rubygems/vendor/net-http/lib/net/http.rb
+@@ -22,7 +22,6 @@
+ 
+ require_relative '../../../net-protocol/lib/net/protocol'
+ require_relative '../../../uri/lib/uri'
+-require_relative '../../../resolv/lib/resolv'
+ autoload :OpenSSL, 'openssl'
+ 
+ module Gem::Net   #:nodoc:
+@@ -1531,6 +1530,29 @@ def use_ssl=(flag)
+ 
+     SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc:
+ 
++    # Resolv::IPv4::Regex and Resolv::IPv6::Regex, copied from resolv 0.7.1 rather
++    # than required, so that net/http does not load a DNS resolver just to keep an
++    # IP address literal out of the Server Name Indication.
++    # https://github.com/ruby/resolv/blob/v0.7.1/lib/resolv.rb
++    ipv4_octet = /0|1(?:[0-9][0-9]?)?|2(?:[0-4][0-9]?|5[0-5]?|[6-9])?|[3-9][0-9]?/
++    hex16 = /[0-9A-Fa-f]{1,4}/
++    hex16_group = /(?:#{hex16}(?::#{hex16})*)?/
++    dotted_quad = /\d+\.\d+\.\d+\.\d+/
++    zone_id = /%[-0-9A-Za-z._~]+/
++
++    IPV4_ADDRESS = /\A(?:#{ipv4_octet})\.(?:#{ipv4_octet})\.(?:#{ipv4_octet})\.(?:#{ipv4_octet})\z/ # :nodoc:
++
++    IPV6_ADDRESS = /\A(?:
++        (?:#{hex16}:){7}#{hex16}                                            # a:b:c:d:e:f:g:h
++      | #{hex16_group}::#{hex16_group}                                      # a::b
++      | (?:#{hex16}:){6}#{dotted_quad}                                      # a:b:c:d:e:f:w.x.y.z
++      | #{hex16_group}::(?:#{hex16}:)*#{dotted_quad}                        # a::b:w.x.y.z
++      | [Ff][Ee]80(?::#{hex16}){7}#{zone_id}                                # fe80:b:c:d:e:f:g:h%em1
++      | [Ff][Ee]80:(?:#{hex16_group}::#{hex16_group}|:#{hex16_group})?:#{hex16}#{zone_id} # fe80::b%em1
++    )\z/x # :nodoc:
++
++    private_constant :IPV4_ADDRESS, :IPV6_ADDRESS
++
+     # Sets or returns the path to a CA certification file in PEM format.
+     attr_accessor :ca_file
+ 
+@@ -1714,7 +1736,7 @@ def connect
+ 
+         # Server Name Indication (SNI) RFC 3546/6066
+         case @address
+-        when Gem::Resolv::IPv4::Regex, Gem::Resolv::IPv6::Regex
++        when IPV4_ADDRESS, IPV6_ADDRESS
+           # don't set SNI, as IP addresses in SNI is not valid
+           # per RFC 6066, section 3.
+ 
+diff --git a/lib/rubygems/vendor/resolv/lib/resolv.rb b/lib/rubygems/vendor/resolv/lib/resolv.rb
+deleted file mode 100644
+index f922c731b05026..00000000000000
+--- a/lib/rubygems/vendor/resolv/lib/resolv.rb
++++ /dev/null
+@@ -1,3499 +0,0 @@
+-# frozen_string_literal: true
+-
+-require 'socket'
+-require_relative '../../../vendored_timeout'
+-require 'io/wait'
+-require_relative '../../../vendored_securerandom'
+-require 'rbconfig'
+-
+-# Gem::Resolv is a thread-aware DNS resolver library written in Ruby.  Gem::Resolv can
+-# handle multiple DNS requests concurrently without blocking the entire Ruby
+-# interpreter.
+-#
+-# See also resolv-replace.rb to replace the libc resolver with Gem::Resolv.
+-#
+-# Gem::Resolv can look up various DNS resources using the DNS module directly.
+-#
+-# Examples:
+-#
+-#   p Gem::Resolv.getaddress "www.ruby-lang.org"
+-#   p Gem::Resolv.getname "210.251.121.214"
+-#
+-#   Gem::Resolv::DNS.open do |dns|
+-#     ress = dns.getresources "www.ruby-lang.org", Gem::Resolv::DNS::Resource::IN::A
+-#     p ress.map(&:address)
+-#     ress = dns.getresources "ruby-lang.org", Gem::Resolv::DNS::Resource::IN::MX
+-#     p ress.map { |r| [r.exchange.to_s, r.preference] }
+-#   end
+-#
+-#
+-# == Bugs
+-#
+-# * NIS is not supported.
+-# * /etc/nsswitch.conf is not supported.
+-
+-class Gem::Resolv
+-
+-  # The version string
+-  VERSION = "0.7.0"
+-
+-  ##
+-  # Looks up the first IP address for +name+.
+-
+-  def self.getaddress(name)
+-    DefaultResolver.getaddress(name)
+-  end
+-
+-  ##
+-  # Looks up all IP address for +name+.
+-
+-  def self.getaddresses(name)
+-    DefaultResolver.getaddresses(name)
+-  end
+-
+-  ##
+-  # Iterates over all IP addresses for +name+.
+-
+-  def self.each_address(name, &block)
+-    DefaultResolver.each_address(name, &block)
+-  end
+-
+-  ##
+-  # Looks up the hostname of +address+.
+-
+-  def self.getname(address)
+-    DefaultResolver.getname(address)
+-  end
+-
+-  ##
+-  # Looks up all hostnames for +address+.
+-
+-  def self.getnames(address)
+-    DefaultResolver.getnames(address)
+-  end
+-
+-  ##
+-  # Iterates over all hostnames for +address+.
+-
+-  def self.each_name(address, &proc)
+-    DefaultResolver.each_name(address, &proc)
+-  end
+-
+-  ##
+-  # Creates a new Gem::Resolv using +resolvers+.
+-  #
+-  # 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
+-
+-    @resolvers = case resolvers
+-    when Hash, nil
+-      [Hosts.new, DNS.new(DNS::Config.default_config_hash.merge(resolvers || {}))]
+-    else
+-      resolvers
+-    end
+-  end
+-
+-  ##
+-  # Looks up the first IP address for +name+.
+-
+-  def getaddress(name)
+-    each_address(name) {|address| return address}
+-    raise ResolvError.new("no address for #{name}")
+-  end
+-
+-  ##
+-  # Looks up all IP address for +name+.
+-
+-  def getaddresses(name)
+-    ret = []
+-    each_address(name) {|address| ret << address}
+-    return ret
+-  end
+-
+-  ##
+-  # Iterates over all IP addresses for +name+.
+-
+-  def each_address(name)
+-    if AddressRegex =~ name
+-      yield name
+-      return
+-    end
+-    yielded = false
+-    @resolvers.each {|r|
+-      r.each_address(name) {|address|
+-        yield address.to_s
+-        yielded = true
+-      }
+-      return if yielded
+-    }
+-  end
+-
+-  ##
+-  # Looks up the hostname of +address+.
+-
+-  def getname(address)
+-    each_name(address) {|name| return name}
+-    raise ResolvError.new("no name for #{address}")
+-  end
+-
+-  ##
+-  # Looks up all hostnames for +address+.
+-
+-  def getnames(address)
+-    ret = []
+-    each_name(address) {|name| ret << name}
+-    return ret
+-  end
+-
+-  ##
+-  # Iterates over all hostnames for +address+.
+-
+-  def each_name(address)
+-    yielded = false
+-    @resolvers.each {|r|
+-      r.each_name(address) {|name|
+-        yield name.to_s
+-        yielded = true
+-      }
+-      return if yielded
+-    }
+-  end
+-
+-  ##
+-  # Indicates a failure to resolve a name or address.
+-
+-  class ResolvError < StandardError; end
+-
+-  ##
+-  # Indicates a timeout resolving a name or address.
+-
+-  class ResolvTimeout < Gem::Timeout::Error; end
+-
+-  ##
+-  # Gem::Resolv::Hosts is a hostname resolver that uses the system hosts file.
+-
+-  class Hosts
+-    if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/
+-      begin
+-        require 'win32/resolv' unless defined?(Win32::Resolv)
+-        hosts = Win32::Resolv.get_hosts_path || IO::NULL
+-      rescue LoadError
+-      end
+-    end
+-    # The default file name for host names
+-    DefaultFileName = hosts || '/etc/hosts'
+-
+-    ##
+-    # Creates a new Gem::Resolv::Hosts, using +filename+ for its data source.
+-
+-    def initialize(filename = DefaultFileName)
+-      @filename = filename
+-      @mutex = Thread::Mutex.new
+-      @initialized = nil
+-    end
+-
+-    def lazy_initialize # :nodoc:
+-      @mutex.synchronize {
+-        unless @initialized
+-          @name2addr = {}
+-          @addr2name = {}
+-          File.open(@filename, 'rb') {|f|
+-            f.each {|line|
+-              line.sub!(/#.*/, '')
+-              addr, *hostnames = line.split(/\s+/)
+-              next unless addr
+-              (@addr2name[addr] ||= []).concat(hostnames)
+-              hostnames.each {|hostname| (@name2addr[hostname] ||= []) << addr}
+-            }
+-          }
+-          @name2addr.each {|name, arr| arr.reverse!}
+-          @initialized = true
+-        end
+-      }
+-      self
+-    end
+-
+-    ##
+-    # Gets the IP address of +name+ from the hosts file.
+-
+-    def getaddress(name)
+-      each_address(name) {|address| return address}
+-      raise ResolvError.new("#{@filename} has no name: #{name}")
+-    end
+-
+-    ##
+-    # Gets all IP addresses for +name+ from the hosts file.
+-
+-    def getaddresses(name)
+-      ret = []
+-      each_address(name) {|address| ret << address}
+-      return ret
+-    end
+-
+-    ##
+-    # Iterates over all IP addresses for +name+ retrieved from the hosts file.
+-
+-    def each_address(name, &proc)
+-      lazy_initialize
+-      @name2addr[name]&.each(&proc)
+-    end
+-
+-    ##
+-    # Gets the hostname of +address+ from the hosts file.
+-
+-    def getname(address)
+-      each_name(address) {|name| return name}
+-      raise ResolvError.new("#{@filename} has no address: #{address}")
+-    end
+-
+-    ##
+-    # Gets all hostnames for +address+ from the hosts file.
+-
+-    def getnames(address)
+-      ret = []
+-      each_name(address) {|name| ret << name}
+-      return ret
+-    end
+-
+-    ##
+-    # Iterates over all hostnames for +address+ retrieved from the hosts file.
+-
+-    def each_name(address, &proc)
+-      lazy_initialize
+-      @addr2name[address]&.each(&proc)
+-    end
+-  end
+-
+-  ##
+-  # Gem::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.
+-
+-  class DNS
+-
+-    ##
+-    # Default DNS Port
+-
+-    Port = 53
+-
+-    ##
+-    # Default DNS UDP packet size
+-
+-    UDPSize = 512
+-
+-    ##
+-    # Creates a new DNS resolver.  See Gem::Resolv::DNS.new for argument details.
+-    #
+-    # Yields the created DNS resolver to the block, if given, otherwise
+-    # returns it.
+-
+-    def self.open(*args)
+-      dns = new(*args)
+-      return dns unless block_given?
+-      begin
+-        yield dns
+-      ensure
+-        dns.close
+-      end
+-    end
+-
+-    ##
+-    # Creates a new DNS resolver.
+-    #
+-    # +config_info+ can be:
+-    #
+-    # nil:: Uses /etc/resolv.conf.
+-    # String:: Path to a file using /etc/resolv.conf's format.
+-    # Hash:: Must contain :nameserver, :search and :ndots keys.
+-    # :nameserver_port can be used to specify port number of nameserver address.
+-    # :raise_timeout_errors can be used to raise timeout errors
+-    # as exceptions instead of treating the same as an NXDOMAIN response.
+-    #
+-    # 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]]
+-    #
+-    # Example:
+-    #
+-    #   Gem::Resolv::DNS.new(:nameserver => ['210.251.121.21'],
+-    #                   :search => ['ruby-lang.org'],
+-    #                   :ndots => 1)
+-
+-    def initialize(config_info=nil)
+-      @mutex = Thread::Mutex.new
+-      @config = Config.new(config_info)
+-      @initialized = nil
+-    end
+-
+-    # 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
+-
+-    def lazy_initialize # :nodoc:
+-      @mutex.synchronize {
+-        unless @initialized
+-          @config.lazy_initialize
+-          @initialized = true
+-        end
+-      }
+-      self
+-    end
+-
+-    ##
+-    # Closes the DNS resolver.
+-
+-    def close
+-      @mutex.synchronize {
+-        if @initialized
+-          @initialized = false
+-        end
+-      }
+-    end
+-
+-    ##
+-    # Gets the IP address of +name+ from the DNS resolver.
+-    #
+-    # +name+ can be a Gem::Resolv::DNS::Name or a String.  Retrieved address will
+-    # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6
+-
+-    def getaddress(name)
+-      each_address(name) {|address| return address}
+-      raise ResolvError.new("DNS result has no information for #{name}")
+-    end
+-
+-    ##
+-    # Gets all IP addresses for +name+ from the DNS resolver.
+-    #
+-    # +name+ can be a Gem::Resolv::DNS::Name or a String.  Retrieved addresses will
+-    # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6
+-
+-    def getaddresses(name)
+-      ret = []
+-      each_address(name) {|address| ret << address}
+-      return ret
+-    end
+-
+-    ##
+-    # Iterates over all IP addresses for +name+ retrieved from the DNS
+-    # resolver.
+-    #
+-    # +name+ can be a Gem::Resolv::DNS::Name or a String.  Retrieved addresses will
+-    # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6
+-
+-    def each_address(name)
+-      if use_ipv6?
+-        each_resource(name, Resource::IN::AAAA) {|resource| yield resource.address}
+-      end
+-      each_resource(name, Resource::IN::A) {|resource| yield resource.address}
+-    end
+-
+-    def use_ipv6? # :nodoc:
+-      @config.lazy_initialize unless @config.instance_variable_get(:@initialized)
+-
+-      use_ipv6 = @config.use_ipv6?
+-      unless use_ipv6.nil?
+-        return use_ipv6
+-      end
+-
+-      begin
+-        list = Socket.ip_address_list
+-      rescue NotImplementedError
+-        return true
+-      end
+-      list.any? {|a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? }
+-    end
+-    private :use_ipv6?
+-
+-    ##
+-    # Gets the hostname for +address+ from the DNS resolver.
+-    #
+-    # +address+ must be a Gem::Resolv::IPv4, Gem::Resolv::IPv6 or a String.  Retrieved
+-    # name will be a Gem::Resolv::DNS::Name.
+-
+-    def getname(address)
+-      each_name(address) {|name| return name}
+-      raise ResolvError.new("DNS result has no information for #{address}")
+-    end
+-
+-    ##
+-    # Gets all hostnames for +address+ from the DNS resolver.
+-    #
+-    # +address+ must be a Gem::Resolv::IPv4, Gem::Resolv::IPv6 or a String.  Retrieved
+-    # names will be Gem::Resolv::DNS::Name instances.
+-
+-    def getnames(address)
+-      ret = []
+-      each_name(address) {|name| ret << name}
+-      return ret
+-    end
+-
+-    ##
+-    # Iterates over all hostnames for +address+ retrieved from the DNS
+-    # resolver.
+-    #
+-    # +address+ must be a Gem::Resolv::IPv4, Gem::Resolv::IPv6 or a String.  Retrieved
+-    # names will be Gem::Resolv::DNS::Name instances.
+-
+-    def each_name(address)
+-      case address
+-      when Name
+-        ptr = address
+-      when IPv4, IPv6
+-        ptr = address.to_name
+-      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
+-      each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name}
+-    end
+-
+-    ##
+-    # Look up the +typeclass+ DNS resource of +name+.
+-    #
+-    # +name+ must be a Gem::Resolv::DNS::Name or a String.
+-    #
+-    # +typeclass+ should be one of the following:
+-    #
+-    # * Gem::Resolv::DNS::Resource::IN::A
+-    # * Gem::Resolv::DNS::Resource::IN::AAAA
+-    # * Gem::Resolv::DNS::Resource::IN::ANY
+-    # * Gem::Resolv::DNS::Resource::IN::CNAME
+-    # * Gem::Resolv::DNS::Resource::IN::HINFO
+-    # * Gem::Resolv::DNS::Resource::IN::MINFO
+-    # * Gem::Resolv::DNS::Resource::IN::MX
+-    # * Gem::Resolv::DNS::Resource::IN::NS
+-    # * Gem::Resolv::DNS::Resource::IN::PTR
+-    # * Gem::Resolv::DNS::Resource::IN::SOA
+-    # * Gem::Resolv::DNS::Resource::IN::TXT
+-    # * Gem::Resolv::DNS::Resource::IN::WKS
+-    #
+-    # Returned resource is represented as a Gem::Resolv::DNS::Resource instance,
+-    # i.e. Gem::Resolv::DNS::Resource::IN::A.
+-
+-    def getresource(name, typeclass)
+-      each_resource(name, typeclass) {|resource| return resource}
+-      raise ResolvError.new("DNS result has no information for #{name}")
+-    end
+-
+-    ##
+-    # Looks up all +typeclass+ DNS resources for +name+.  See #getresource for
+-    # argument details.
+-
+-    def getresources(name, typeclass)
+-      ret = []
+-      each_resource(name, typeclass) {|resource| ret << resource}
+-      return ret
+-    end
+-
+-    ##
+-    # Iterates over all +typeclass+ DNS resources for +name+.  See
+-    # #getresource for argument details.
+-
+-    def each_resource(name, typeclass, &proc)
+-      fetch_resource(name, typeclass) {|reply, reply_name|
+-        extract_resources(reply, reply_name, typeclass, &proc)
+-      }
+-    end
+-
+-    # :stopdoc:
+-
+-    def fetch_resource(name, typeclass)
+-      lazy_initialize
+-      truncated = {}
+-      requesters = {}
+-      udp_requester = begin
+-        make_udp_requester
+-      rescue Errno::EACCES
+-        # fall back to TCP
+-      end
+-      senders = {}
+-
+-      begin
+-        @config.resolv(name) do |candidate, tout, nameserver, port|
+-          msg = Message.new
+-          msg.rd = 1
+-          msg.add_question(candidate, typeclass)
+-
+-          requester = requesters.fetch([nameserver, port]) do
+-            if !truncated[candidate] && udp_requester
+-              udp_requester
+-            else
+-              requesters[[nameserver, port]] = make_tcp_requester(nameserver, port)
+-            end
+-          end
+-
+-          unless sender = senders[[candidate, requester, nameserver, port]]
+-            sender = requester.sender(msg, candidate, nameserver, port)
+-            next if !sender
+-            senders[[candidate, requester, nameserver, port]] = sender
+-          end
+-          reply, reply_name = requester.request(sender, tout)
+-          case reply.rcode
+-          when RCode::NoError
+-            if reply.tc == 1 and not Requester::TCP === requester
+-              # Retry via TCP:
+-              truncated[candidate] = true
+-              redo
+-            else
+-              yield(reply, reply_name)
+-            end
+-            return
+-          when RCode::NXDomain
+-            raise Config::NXDomain.new(reply_name.to_s)
+-          else
+-            raise Config::OtherResolvError.new(reply_name.to_s)
+-          end
+-        end
+-      ensure
+-        udp_requester&.close
+-        requesters.each_value { |requester| requester&.close }
+-      end
+-    end
+-
+-    def make_udp_requester # :nodoc:
+-      nameserver_port = @config.nameserver_port
+-      if nameserver_port.length == 1
+-        Requester::ConnectedUDP.new(*nameserver_port[0])
+-      else
+-        Requester::UnconnectedUDP.new(*nameserver_port)
+-      end
+-    end
+-
+-    def make_tcp_requester(host, port) # :nodoc:
+-      return Requester::TCP.new(host, port)
+-    rescue Errno::ECONNREFUSED
+-      # Treat a refused TCP connection attempt to a nameserver like a timeout,
+-      # as Gem::Resolv::DNS::Config#resolv considers ResolvTimeout exceptions as a
+-      # hint to try the next nameserver:
+-      raise ResolvTimeout
+-    end
+-
+-    def extract_resources(msg, name, typeclass) # :nodoc:
+-      if typeclass < Resource::ANY
+-        n0 = Name.create(name)
+-        msg.each_resource {|n, ttl, data|
+-          yield data if n0 == n
+-        }
+-      end
+-      yielded = false
+-      n0 = Name.create(name)
+-      msg.each_resource {|n, ttl, data|
+-        if n0 == n
+-          case data
+-          when typeclass
+-            yield data
+-            yielded = true
+-          when Resource::CNAME
+-            n0 = data.name
+-          end
+-        end
+-      }
+-      return if yielded
+-      msg.each_resource {|n, ttl, data|
+-        if n0 == n
+-          case data
+-          when typeclass
+-            yield data
+-          end
+-        end
+-      }
+-    end
+-
+-    def self.random(arg) # :nodoc:
+-      begin
+-        Gem::SecureRandom.random_number(arg)
+-      rescue NotImplementedError
+-        rand(arg)
+-      end
+-    end
+-
+-    RequestID = {} # :nodoc:
+-    RequestIDMutex = Thread::Mutex.new # :nodoc:
+-
+-    def self.allocate_request_id(host, port) # :nodoc:
+-      id = nil
+-      RequestIDMutex.synchronize {
+-        h = (RequestID[[host, port]] ||= {})
+-        begin
+-          id = random(0x0000..0xffff)
+-        end while h[id]
+-        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
+-
+-    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
+-        port = random(1024..65535)
+-        udpsock.bind(bind_host, port)
+-      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).
+-        retry
+-      end
+-    end
+-
+-    class Requester # :nodoc:
+-      def initialize
+-        @senders = {}
+-        @socks = nil
+-      end
+-
+-      def request(sender, tout)
+-        start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+-        timelimit = start + tout
+-        begin
+-          sender.send
+-        rescue Errno::EHOSTUNREACH, # multi-homed IPv6 may generate this
+-               Errno::ENETUNREACH
+-          raise ResolvTimeout
+-        end
+-        while true
+-          before_select = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+-          timeout = timelimit - before_select
+-          if timeout <= 0
+-            raise ResolvTimeout
+-          end
+-          if @socks.size == 1
+-            select_result = @socks[0].wait_readable(timeout) ? [ @socks ] : nil
+-          else
+-            select_result = IO.select(@socks, nil, nil, timeout)
+-          end
+-          if !select_result
+-            after_select = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+-            next if after_select < timelimit
+-            raise ResolvTimeout
+-          end
+-          begin
+-            reply, from = recv_reply(select_result[0])
+-          rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD
+-                 Errno::ECONNRESET # Windows
+-            # No name server running on the server?
+-            # Don't wait anymore.
+-            raise ResolvTimeout
+-          end
+-          begin
+-            msg = Message.decode(reply)
+-          rescue DecodeError
+-            next # broken DNS message ignored
+-          end
+-          if sender == sender_for(from, msg)
+-            break
+-          else
+-            # unexpected DNS message ignored
+-          end
+-        end
+-        return msg, sender.data
+-      end
+-
+-      def sender_for(addr, msg)
+-        @senders[[addr,msg.id]]
+-      end
+-
+-      def close
+-        socks = @socks
+-        @socks = nil
+-        socks&.each(&:close)
+-      end
+-
+-      class Sender # :nodoc:
+-        def initialize(msg, data, sock)
+-          @msg = msg
+-          @data = data
+-          @sock = sock
+-        end
+-      end
+-
+-      class UnconnectedUDP < Requester # :nodoc:
+-        def initialize(*nameserver_port)
+-          super()
+-          @nameserver_port = nameserver_port
+-          @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)
+-              rescue Errno::EAFNOSUPPORT, Errno::EPROTONOSUPPORT
+-                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)
+-            }
+-          }
+-          self
+-        end
+-
+-        def recv_reply(readable_socks)
+-          lazy_initialize
+-          reply, from = readable_socks[0].recvfrom(UDPSize)
+-          return reply, [from[3],from[1]]
+-        end
+-
+-        def sender(msg, data, host, port=Port)
+-          host = Addrinfo.ip(host).ip_address
+-          lazy_initialize
+-          sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"]
+-          return nil if !sock
+-          service = [host, port]
+-          id = DNS.allocate_request_id(host, port)
+-          request = msg.encode
+-          request[0,2] = [id].pack('n')
+-          return @senders[[service, id]] =
+-            Sender.new(request, data, sock, host, port)
+-        end
+-
+-        def close
+-          @mutex.synchronize {
+-            if @initialized
+-              super
+-              @senders.each_key {|service, id|
+-                DNS.free_request_id(service[0], service[1], id)
+-              }
+-              @initialized = false
+-            end
+-          }
+-        end
+-
+-        class Sender < Requester::Sender # :nodoc:
+-          def initialize(msg, data, sock, host, port)
+-            super(msg, data, sock)
+-            @host = host
+-            @port = port
+-          end
+-          attr_reader :data
+-
+-          def send
+-            raise "@sock is nil." if @sock.nil?
+-            @sock.send(@msg, 0, @host, @port)
+-          end
+-        end
+-      end
+-
+-      class ConnectedUDP < Requester # :nodoc:
+-        def initialize(host, port=Port)
+-          super()
+-          @host = host
+-          @port = port
+-          @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
+-        end
+-
+-        def recv_reply(readable_socks)
+-          lazy_initialize
+-          reply = readable_socks[0].recv(UDPSize)
+-          return reply, nil
+-        end
+-
+-        def sender(msg, data, host=@host, port=@port)
+-          lazy_initialize
+-          unless host == @host && port == @port
+-            raise RequestError.new("host/port don't match: #{host}:#{port}")
+-          end
+-          id = DNS.allocate_request_id(@host, @port)
+-          request = msg.encode
+-          request[0,2] = [id].pack('n')
+-          return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
+-        end
+-
+-        def close
+-          @mutex.synchronize do
+-            if @initialized
+-              super
+-              @senders.each_key {|from, id|
+-                DNS.free_request_id(@host, @port, id)
+-              }
+-              @initialized = false
+-            end
+-          end
+-        end
+-
+-        class Sender < Requester::Sender # :nodoc:
+-          def send
+-            raise "@sock is nil." if @sock.nil?
+-            @sock.send(@msg, 0)
+-          end
+-          attr_reader :data
+-        end
+-      end
+-
+-      class MDNSOneShot < UnconnectedUDP # :nodoc:
+-        def sender(msg, data, host, port=Port)
+-          lazy_initialize
+-          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)
+-          lazy_initialize
+-          @senders[msg.id]
+-        end
+-      end
+-
+-      class TCP < Requester # :nodoc:
+-        def initialize(host, port=Port)
+-          super()
+-          @host = host
+-          @port = port
+-          sock = TCPSocket.new(@host, @port)
+-          @socks = [sock]
+-          @senders = {}
+-        end
+-
+-        def recv_reply(readable_socks)
+-          len = readable_socks[0].read(2).unpack('n')[0]
+-          reply = @socks[0].read(len)
+-          return reply, nil
+-        end
+-
+-        def sender(msg, data, host=@host, port=@port)
+-          unless host == @host && port == @port
+-            raise RequestError.new("host/port don't match: #{host}:#{port}")
+-          end
+-          id = DNS.allocate_request_id(@host, @port)
+-          request = msg.encode
+-          request[0,2] = [request.length, id].pack('nn')
+-          return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
+-        end
+-
+-        class Sender < Requester::Sender # :nodoc:
+-          def send
+-            @sock.print(@msg)
+-            @sock.flush
+-          end
+-          attr_reader :data
+-        end
+-
+-        def close
+-          super
+-          @senders.each_key {|from,id|
+-            DNS.free_request_id(@host, @port, id)
+-          }
+-        end
+-      end
+-
+-      ##
+-      # Indicates a problem with the DNS request.
+-
+-      class RequestError < StandardError
+-      end
+-    end
+-
+-    class Config # :nodoc:
+-      def initialize(config_info=nil)
+-        @mutex = Thread::Mutex.new
+-        @config_info = config_info
+-        @initialized = nil
+-        @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"
+-            t > 0.0 or raise ArgumentError, "timeout=#{t} must be positive"
+-          end
+-          @timeouts = values
+-        else
+-          @timeouts = nil
+-        end
+-      end
+-
+-      def Config.parse_resolv_conf(filename)
+-        nameserver = []
+-        search = nil
+-        ndots = 1
+-        File.open(filename, 'rb') {|f|
+-          f.each {|line|
+-            line.sub!(/[#;].*/, '')
+-            keyword, *args = line.split(/\s+/)
+-            next unless keyword
+-            case keyword
+-            when 'nameserver'
+-              nameserver.concat(args.each(&:freeze))
+-            when 'domain'
+-              next if args.empty?
+-              search = [args[0].freeze]
+-            when 'search'
+-              next if args.empty?
+-              search = args.each(&:freeze)
+-            when 'options'
+-              args.each {|arg|
+-                case arg
+-                when /\Andots:(\d+)\z/
+-                  ndots = $1.to_i
+-                end
+-              }
+-            end
+-          }
+-        }
+-        return { :nameserver => nameserver.freeze, :search => search.freeze, :ndots => ndots.freeze }.freeze
+-      end
+-
+-      def Config.default_config_hash(filename="/etc/resolv.conf")
+-        if File.exist? filename
+-          Config.parse_resolv_conf(filename)
+-        elsif 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
+-        else
+-          {}
+-        end
+-      end
+-
+-      def lazy_initialize
+-        @mutex.synchronize {
+-          unless @initialized
+-            @nameserver_port = []
+-            @use_ipv6 = nil
+-            @search = nil
+-            @ndots = 1
+-            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]]
+-              end
+-              if String === config_hash[:search]
+-                config_hash[:search] = [config_hash[:search]]
+-              end
+-            else
+-              raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}")
+-            end
+-            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
+-            if config_hash.include? :use_ipv6
+-              @use_ipv6 = config_hash[:use_ipv6]
+-            end
+-            @search = config_hash[:search] if config_hash.include? :search
+-            @ndots = config_hash[:ndots] if config_hash.include? :ndots
+-            @raise_timeout_errors = config_hash[:raise_timeout_errors]
+-
+-            if @nameserver_port.empty?
+-              @nameserver_port << ['0.0.0.0', Port]
+-            end
+-            if @search
+-              @search = @search.map {|arg| Label.split(arg) }
+-            else
+-              hostname = Socket.gethostname
+-              if /\./ =~ hostname
+-                @search = [Label.split($')]
+-              else
+-                @search = [[]]
+-              end
+-            end
+-
+-            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}")
+-            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
+-
+-            @initialized = true
+-          end
+-        }
+-        self
+-      end
+-
+-      def single?
+-        lazy_initialize
+-        if @nameserver_port.length == 1
+-          return @nameserver_port[0]
+-        else
+-          return nil
+-        end
+-      end
+-
+-      def nameserver_port
+-        @nameserver_port
+-      end
+-
+-      def use_ipv6?
+-        @use_ipv6
+-      end
+-
+-      def generate_candidates(name)
+-        candidates = nil
+-        name = Name.create(name)
+-        if name.absolute?
+-          candidates = [name]
+-        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)})
+-          fname = Name.create("#{name}.")
+-          if !candidates.include?(fname)
+-            candidates << fname
+-          end
+-        end
+-        return candidates
+-      end
+-
+-      InitialTimeout = 5
+-
+-      def generate_timeouts
+-        ts = [InitialTimeout]
+-        ts << ts[-1] * 2 / @nameserver_port.length
+-        ts << ts[-1] * 2
+-        ts << ts[-1] * 2
+-        return ts
+-      end
+-
+-      def resolv(name)
+-        candidates = generate_candidates(name)
+-        timeouts = @timeouts || generate_timeouts
+-        timeout_error = false
+-        begin
+-          candidates.each {|candidate|
+-            begin
+-              timeouts.each {|tout|
+-                @nameserver_port.each {|nameserver, port|
+-                  begin
+-                    yield candidate, tout, nameserver, port
+-                  rescue ResolvTimeout
+-                  end
+-                }
+-              }
+-              timeout_error = true
+-              raise ResolvError.new("DNS resolv timeout: #{name}")
+-            rescue NXDomain
+-            end
+-          }
+-        rescue ResolvError
+-          raise if @raise_timeout_errors && timeout_error
+-        end
+-      end
+-
+-      ##
+-      # Indicates no such domain was found.
+-
+-      class NXDomain < ResolvError
+-      end
+-
+-      ##
+-      # Indicates some other unhandled resolver error was encountered.
+-
+-      class OtherResolvError < ResolvError
+-      end
+-    end
+-
+-    module OpCode # :nodoc:
+-      Query = 0
+-      IQuery = 1
+-      Status = 2
+-      Notify = 4
+-      Update = 5
+-    end
+-
+-    module RCode # :nodoc:
+-      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
+-
+-    ##
+-    # Indicates that the DNS response was unable to be decoded.
+-
+-    class DecodeError < StandardError
+-    end
+-
+-    ##
+-    # Indicates that the DNS request was unable to be encoded.
+-
+-    class EncodeError < StandardError
+-    end
+-
+-    module Label # :nodoc:
+-      def self.split(arg)
+-        labels = []
+-        arg.scan(/[^\.]+/) {labels << Str.new($&)}
+-        return labels
+-      end
+-
+-      class Str # :nodoc:
+-        def initialize(string)
+-          @string = string
+-          # case insensivity of DNS labels doesn't apply non-ASCII characters. [RFC 4343]
+-          # This assumes @string is given in ASCII compatible encoding.
+-          @downcase = string.b.downcase
+-        end
+-        attr_reader :string, :downcase
+-
+-        def to_s
+-          return @string
+-        end
+-
+-        def inspect
+-          return "#<#{self.class} #{self}>"
+-        end
+-
+-        def ==(other)
+-          return self.class == other.class && @downcase == other.downcase
+-        end
+-
+-        def eql?(other)
+-          return self == other
+-        end
+-
+-        def hash
+-          return @downcase.hash
+-        end
+-      end
+-    end
+-
+-    ##
+-    # A representation of a DNS name.
+-
+-    class Name
+-
+-      ##
+-      # Creates a new DNS name from +arg+.  +arg+ can be:
+-      #
+-      # Name:: returns +arg+.
+-      # String:: Creates a new Name.
+-
+-      def self.create(arg)
+-        case arg
+-        when Name
+-          return arg
+-        when String
+-          return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
+-        else
+-          raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
+-        end
+-      end
+-
+-      def initialize(labels, absolute=true) # :nodoc:
+-        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
+-        }
+-        @labels = labels
+-        @absolute = absolute
+-      end
+-
+-      def inspect # :nodoc:
+-        "#<#{self.class}: #{self}#{@absolute ? '.' : ''}>"
+-      end
+-
+-      ##
+-      # True if this name is absolute.
+-
+-      def absolute?
+-        return @absolute
+-      end
+-
+-      def ==(other) # :nodoc:
+-        return false unless Name === other
+-        return false unless @absolute == other.absolute?
+-        return @labels == other.to_a
+-      end
+-
+-      alias eql? == # :nodoc:
+-
+-      ##
+-      # Returns true if +other+ is a subdomain.
+-      #
+-      # Example:
+-      #
+-      #   domain = Gem::Resolv::DNS::Name.create("y.z")
+-      #   p Gem::Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true
+-      #   p Gem::Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true
+-      #   p Gem::Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false
+-      #   p Gem::Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false
+-      #   p Gem::Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false
+-      #   p Gem::Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false
+-      #
+-
+-      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
+-
+-      def hash # :nodoc:
+-        return @labels.hash ^ @absolute.hash
+-      end
+-
+-      def to_a # :nodoc:
+-        return @labels
+-      end
+-
+-      def length # :nodoc:
+-        return @labels.length
+-      end
+-
+-      def [](i) # :nodoc:
+-        return @labels[i]
+-      end
+-
+-      ##
+-      # returns the domain name as a string.
+-      #
+-      # The domain name doesn't have a trailing dot even if the name object is
+-      # absolute.
+-      #
+-      # Example:
+-      #
+-      #   p Gem::Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z"
+-      #   p Gem::Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z"
+-
+-      def to_s
+-        return @labels.join('.')
+-      end
+-    end
+-
+-    class Message # :nodoc:
+-      @@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
+-
+-      class MessageEncoder # :nodoc:
+-        def initialize
+-          @data = ''.dup
+-          @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
+-
+-        def put_string_list(ds)
+-          ds.each {|d|
+-            self.put_string(d)
+-          }
+-        end
+-
+-        def put_name(d, compress: true)
+-          put_labels(d.to_a, compress: compress)
+-        end
+-
+-        def put_labels(d, compress: true)
+-          d.each_index {|i|
+-            domain = d[i..-1]
+-            if compress && idx = @names[domain]
+-              self.put_pack("n", 0xc000 | idx)
+-              return
+-            else
+-              if @data.length < 0x4000
+-                @names[domain] = @data.length
+-              end
+-              self.put_label(d[i])
+-            end
+-          }
+-          @data << "\0"
+-        end
+-
+-        def put_label(d)
+-          self.put_string(d.to_s)
+-        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
+-          o.tc = (flag >> 9) & 1
+-          o.rcode = flag & 15
+-          return o unless o.tc.zero?
+-
+-          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
+-
+-      class MessageDecoder # :nodoc:
+-        def initialize(data)
+-          @data = data
+-          @index = 0
+-          @limit = data.bytesize
+-          yield self
+-        end
+-
+-        def inspect
+-          "\#<#{self.class}: #{@data.byteslice(0, @index).inspect} #{@data.byteslice(@index..-1).inspect}>"
+-        end
+-
+-        def get_length16
+-          len, = self.get_unpack('n')
+-          save_limit = @limit
+-          @limit = @index + len
+-          d = yield(len)
+-          if @index < @limit
+-            raise DecodeError.new("junk exists")
+-          elsif @limit < @index
+-            raise DecodeError.new("limit exceeded")
+-          end
+-          @limit = save_limit
+-          return d
+-        end
+-
+-        def get_bytes(len = @limit - @index)
+-          raise DecodeError.new("limit exceeded") if @limit < @index + len
+-          d = @data.byteslice(@index, len)
+-          @index += len
+-          return d
+-        end
+-
+-        def get_unpack(template)
+-          len = 0
+-          template.each_byte {|byte|
+-            byte = "%c" % byte
+-            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
+-          }
+-          raise DecodeError.new("limit exceeded") if @limit < @index + len
+-          arr = @data.unpack("@#{@index}#{template}")
+-          @index += len
+-          return arr
+-        end
+-
+-        def get_string
+-          raise DecodeError.new("limit exceeded") if @limit <= @index
+-          len = @data.getbyte(@index)
+-          raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len
+-          d = @data.byteslice(@index + 1, len)
+-          @index += 1 + len
+-          return d
+-        end
+-
+-        def get_string_list
+-          strings = []
+-          while @index < @limit
+-            strings << self.get_string
+-          end
+-          strings
+-        end
+-
+-        def get_list
+-          [].tap do |values|
+-            while @index < @limit
+-              values << yield
+-            end
+-          end
+-        end
+-
+-        def get_name
+-          return Name.new(self.get_labels)
+-        end
+-
+-        def get_labels
+-          prev_index = @index
+-          save_index = nil
+-          d = []
+-          size = -1
+-          while true
+-            raise DecodeError.new("limit exceeded") if @limit <= @index
+-            case @data.getbyte(@index)
+-            when 0
+-              @index += 1
+-              if save_index
+-                @index = save_index
+-              end
+-              return d
+-            when 192..255
+-              idx = self.get_unpack('n')[0] & 0x3fff
+-              if prev_index <= idx
+-                raise DecodeError.new("non-backward name pointer")
+-              end
+-              prev_index = idx
+-              if !save_index
+-                save_index = @index
+-              end
+-              @index = idx
+-            else
+-              l = self.get_label
+-              d << l
+-              size += 1 + l.string.bytesize
+-              raise DecodeError.new("name label data exceed 255 octets") if size > 255
+-            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)
+-          res = self.get_length16 do
+-            begin
+-              typeclass.decode_rdata self
+-            rescue => e
+-              raise DecodeError, e.message, e.backtrace
+-            end
+-          end
+-          res.instance_variable_set :@ttl, ttl
+-          return name, ttl, res
+-        end
+-      end
+-    end
+-
+-    ##
+-    # SvcParams for service binding RRs. [RFC9460]
+-
+-    class SvcParams
+-      include Enumerable
+-
+-      ##
+-      # Create a list of SvcParams with the given initial content.
+-      #
+-      # +params+ has to be an enumerable of +SvcParam+s.
+-      # If its content has +SvcParam+s with the duplicate key,
+-      # the one appears last takes precedence.
+-
+-      def initialize(params = [])
+-        @params = {}
+-
+-        params.each do |param|
+-          add param
+-        end
+-      end
+-
+-      ##
+-      # Get SvcParam for the given +key+ in this list.
+-
+-      def [](key)
+-        @params[canonical_key(key)]
+-      end
+-
+-      ##
+-      # Get the number of SvcParams in this list.
+-
+-      def count
+-        @params.count
+-      end
+-
+-      ##
+-      # Get whether this list is empty.
+-
+-      def empty?
+-        @params.empty?
+-      end
+-
+-      ##
+-      # Add the SvcParam +param+ to this list, overwriting the existing one with the same key.
+-
+-      def add(param)
+-        @params[param.class.key_number] = param
+-      end
+-
+-      ##
+-      # Remove the +SvcParam+ with the given +key+ and return it.
+-
+-      def delete(key)
+-        @params.delete(canonical_key(key))
+-      end
+-
+-      ##
+-      # Enumerate the +SvcParam+s in this list.
+-
+-      def each(&block)
+-        return enum_for(:each) unless block
+-        @params.each_value(&block)
+-      end
+-
+-      def encode(msg) # :nodoc:
+-        @params.keys.sort.each do |key|
+-          msg.put_pack('n', key)
+-          msg.put_length16 do
+-            @params.fetch(key).encode(msg)
+-          end
+-        end
+-      end
+-
+-      def self.decode(msg) # :nodoc:
+-        params = msg.get_list do
+-          key, = msg.get_unpack('n')
+-          msg.get_length16 do
+-            SvcParam::ClassHash[key].decode(msg)
+-          end
+-        end
+-
+-        return self.new(params)
+-      end
+-
+-      private
+-
+-      def canonical_key(key) # :nodoc:
+-        case key
+-        when Integer
+-          key
+-        when /\Akey(\d+)\z/
+-          Integer($1)
+-        when Symbol
+-          SvcParam::ClassHash[key].key_number
+-        else
+-          raise TypeError, 'key must be either String or Symbol'
+-        end
+-      end
+-    end
+-
+-    ##
+-    # Base class for SvcParam. [RFC9460]
+-
+-    class SvcParam
+-
+-      ##
+-      # Get the presentation name of the SvcParamKey.
+-
+-      def self.key_name
+-        const_get(:KeyName)
+-      end
+-
+-      ##
+-      # Get the registered number of the SvcParamKey.
+-
+-      def self.key_number
+-        const_get(:KeyNumber)
+-      end
+-
+-      ClassHash = Hash.new do |h, key| # :nodoc:
+-        case key
+-        when Integer
+-          Generic.create(key)
+-        when /\Akey(?<key>\d+)\z/
+-          Generic.create(key.to_int)
+-        when Symbol
+-          raise KeyError, "unknown key #{key}"
+-        else
+-          raise TypeError, 'key must be either String or Symbol'
+-        end
+-      end
+-
+-      ##
+-      # Generic SvcParam abstract class.
+-
+-      class Generic < SvcParam
+-
+-        ##
+-        # SvcParamValue in wire-format byte string.
+-
+-        attr_reader :value
+-
+-        ##
+-        # Create generic SvcParam
+-
+-        def initialize(value)
+-          @value = value
+-        end
+-
+-        def encode(msg) # :nodoc:
+-          msg.put_bytes(@value)
+-        end
+-
+-        def self.decode(msg) # :nodoc:
+-          return self.new(msg.get_bytes)
+-        end
+-
+-        def self.create(key_number)
+-          c = Class.new(Generic)
+-          key_name = :"key#{key_number}"
+-          c.const_set(:KeyName, key_name)
+-          c.const_set(:KeyNumber, key_number)
+-          self.const_set(:"Key#{key_number}", c)
+-          ClassHash[key_name] = ClassHash[key_number] = c
+-          return c
+-        end
+-      end
+-
+-      ##
+-      # "mandatory" SvcParam -- Mandatory keys in service binding RR
+-
+-      class Mandatory < SvcParam
+-        KeyName = :mandatory
+-        KeyNumber = 0
+-        ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
+-
+-        ##
+-        # Mandatory keys.
+-
+-        attr_reader :keys
+-
+-        ##
+-        # Initialize "mandatory" ScvParam.
+-
+-        def initialize(keys)
+-          @keys = keys.map(&:to_int)
+-        end
+-
+-        def encode(msg) # :nodoc:
+-          @keys.sort.each do |key|
+-            msg.put_pack('n', key)
+-          end
+-        end
+-
+-        def self.decode(msg) # :nodoc:
+-          keys = msg.get_list { msg.get_unpack('n')[0] }
+-          return self.new(keys)
+-        end
+-      end
+-
+-      ##
+-      # "alpn" SvcParam -- Additional supported protocols
+-
+-      class ALPN < SvcParam
+-        KeyName = :alpn
+-        KeyNumber = 1
+-        ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
+-
+-        ##
+-        # Supported protocol IDs.
+-
+-        attr_reader :protocol_ids
+-
+-        ##
+-        # Initialize "alpn" ScvParam.
+-
+-        def initialize(protocol_ids)
+-          @protocol_ids = protocol_ids.map(&:to_str)
+-        end
+-
+-        def encode(msg) # :nodoc:
+-          msg.put_string_list(@protocol_ids)
+-        end
+-
+-        def self.decode(msg) # :nodoc:
+-          return self.new(msg.get_string_list)
+-        end
+-      end
+-
+-      ##
+-      # "no-default-alpn" SvcParam -- No support for default protocol
+-
+-      class NoDefaultALPN < SvcParam
+-        KeyName = :'no-default-alpn'
+-        KeyNumber = 2
+-        ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
+-
+-        def encode(msg) # :nodoc:
+-          # no payload
+-        end
+-
+-        def self.decode(msg) # :nodoc:
+-          return self.new
+-        end
+-      end
+-
+-      ##
+-      # "port" SvcParam -- Port for alternative endpoint
+-
+-      class Port < SvcParam
+-        KeyName = :port
+-        KeyNumber = 3
+-        ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
+-
+-        ##
+-        # Port number.
+-
+-        attr_reader :port
+-
+-        ##
+-        # Initialize "port" ScvParam.
+-
+-        def initialize(port)
+-          @port = port.to_int
+-        end
+-
+-        def encode(msg) # :nodoc:
+-          msg.put_pack('n', @port)
+-        end
+-
+-        def self.decode(msg) # :nodoc:
+-          port, = msg.get_unpack('n')
+-          return self.new(port)
+-        end
+-      end
+-
+-      ##
+-      # "ipv4hint" SvcParam -- IPv4 address hints
+-
+-      class IPv4Hint < SvcParam
+-        KeyName = :ipv4hint
+-        KeyNumber = 4
+-        ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
+-
+-        ##
+-        # Set of IPv4 addresses.
+-
+-        attr_reader :addresses
+-
+-        ##
+-        # Initialize "ipv4hint" ScvParam.
+-
+-        def initialize(addresses)
+-          @addresses = addresses.map {|address| IPv4.create(address) }
+-        end
+-
+-        def encode(msg) # :nodoc:
+-          @addresses.each do |address|
+-            msg.put_bytes(address.address)
+-          end
+-        end
+-
+-        def self.decode(msg) # :nodoc:
+-          addresses = msg.get_list { IPv4.new(msg.get_bytes(4)) }
+-          return self.new(addresses)
+-        end
+-      end
+-
+-      ##
+-      # "ipv6hint" SvcParam -- IPv6 address hints
+-
+-      class IPv6Hint < SvcParam
+-        KeyName = :ipv6hint
+-        KeyNumber = 6
+-        ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
+-
+-        ##
+-        # Set of IPv6 addresses.
+-
+-        attr_reader :addresses
+-
+-        ##
+-        # Initialize "ipv6hint" ScvParam.
+-
+-        def initialize(addresses)
+-          @addresses = addresses.map {|address| IPv6.create(address) }
+-        end
+-
+-        def encode(msg) # :nodoc:
+-          @addresses.each do |address|
+-            msg.put_bytes(address.address)
+-          end
+-        end
+-
+-        def self.decode(msg) # :nodoc:
+-          addresses = msg.get_list { IPv6.new(msg.get_bytes(16)) }
+-          return self.new(addresses)
+-        end
+-      end
+-
+-      ##
+-      # "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
+-    end
+-
+-    ##
+-    # A DNS query abstract class.
+-
+-    class Query
+-      def encode_rdata(msg) # :nodoc:
+-        raise EncodeError.new("#{self.class} is query.")
+-      end
+-
+-      def self.decode_rdata(msg) # :nodoc:
+-        raise DecodeError.new("#{self.class} is query.")
+-      end
+-    end
+-
+-    ##
+-    # A DNS resource abstract class.
+-
+-    class Resource < Query
+-
+-      ##
+-      # Remaining Time To Live for this Resource.
+-
+-      attr_reader :ttl
+-
+-      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
+-
+-      def encode_rdata(msg) # :nodoc:
+-        raise NotImplementedError.new
+-      end
+-
+-      def self.decode_rdata(msg) # :nodoc:
+-        raise NotImplementedError.new
+-      end
+-
+-      def ==(other) # :nodoc:
+-        return false unless self.class == other.class
+-        s_ivars = self.instance_variables
+-        s_ivars.sort!
+-        s_ivars.delete :@ttl
+-        o_ivars = other.instance_variables
+-        o_ivars.sort!
+-        o_ivars.delete :@ttl
+-        return s_ivars == o_ivars &&
+-          s_ivars.collect {|name| self.instance_variable_get name} ==
+-            o_ivars.collect {|name| other.instance_variable_get name}
+-      end
+-
+-      def eql?(other) # :nodoc:
+-        return self == other
+-      end
+-
+-      def hash # :nodoc:
+-        h = 0
+-        vars = self.instance_variables
+-        vars.delete :@ttl
+-        vars.each {|name|
+-          h ^= self.instance_variable_get(name).hash
+-        }
+-        return h
+-      end
+-
+-      def self.get_class(type_value, class_value) # :nodoc:
+-        cache = :"Type#{type_value}_Class#{class_value}"
+-
+-        return (const_defined?(cache) && const_get(cache)) ||
+-               Generic.create(type_value, class_value)
+-      end
+-
+-      ##
+-      # A generic resource abstract class.
+-
+-      class Generic < Resource
+-
+-        ##
+-        # Creates a new generic resource.
+-
+-        def initialize(data)
+-          @data = data
+-        end
+-
+-        ##
+-        # Data for this generic resource.
+-
+-        attr_reader :data
+-
+-        def encode_rdata(msg) # :nodoc:
+-          msg.put_bytes(data)
+-        end
+-
+-        def self.decode_rdata(msg) # :nodoc:
+-          return self.new(msg.get_bytes)
+-        end
+-
+-        def self.create(type_value, class_value) # :nodoc:
+-          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
+-
+-      ##
+-      # Domain Name resource abstract class.
+-
+-      class DomainName < Resource
+-
+-        ##
+-        # Creates a new DomainName from +name+.
+-
+-        def initialize(name)
+-          @name = name
+-        end
+-
+-        ##
+-        # The name of this DomainName.
+-
+-        attr_reader :name
+-
+-        def encode_rdata(msg) # :nodoc:
+-          msg.put_name(@name)
+-        end
+-
+-        def self.decode_rdata(msg) # :nodoc:
+-          return self.new(msg.get_name)
+-        end
+-      end
+-
+-      # Standard (class generic) RRs
+-
+-      ClassValue = nil # :nodoc:
+-
+-      ##
+-      # An authoritative name server.
+-
+-      class NS < DomainName
+-        TypeValue = 2 # :nodoc:
+-      end
+-
+-      ##
+-      # The canonical name for an alias.
+-
+-      class CNAME < DomainName
+-        TypeValue = 5 # :nodoc:
+-      end
+-
+-      ##
+-      # Start Of Authority resource.
+-
+-      class SOA < Resource
+-
+-        TypeValue = 6 # :nodoc:
+-
+-        ##
+-        # Creates a new SOA record.  See the attr documentation for the
+-        # details of each argument.
+-
+-        def initialize(mname, rname, serial, refresh, retry_, expire, minimum)
+-          @mname = mname
+-          @rname = rname
+-          @serial = serial
+-          @refresh = refresh
+-          @retry = retry_
+-          @expire = expire
+-          @minimum = minimum
+-        end
+-
+-        ##
+-        # 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:
+-          msg.put_name(@mname)
+-          msg.put_name(@rname)
+-          msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum)
+-        end
+-
+-        def self.decode_rdata(msg) # :nodoc:
+-          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
+-
+-      ##
+-      # A Pointer to another DNS name.
+-
+-      class PTR < DomainName
+-        TypeValue = 12 # :nodoc:
+-      end
+-
+-      ##
+-      # Host Information resource.
+-
+-      class HINFO < Resource
+-
+-        TypeValue = 13 # :nodoc:
+-
+-        ##
+-        # Creates a new HINFO running +os+ on +cpu+.
+-
+-        def initialize(cpu, os)
+-          @cpu = cpu
+-          @os = os
+-        end
+-
+-        ##
+-        # CPU architecture for this resource.
+-
+-        attr_reader :cpu
+-
+-        ##
+-        # Operating system for this resource.
+-
+-        attr_reader :os
+-
+-        def encode_rdata(msg) # :nodoc:
+-          msg.put_string(@cpu)
+-          msg.put_string(@os)
+-        end
+-
+-        def self.decode_rdata(msg) # :nodoc:
+-          cpu = msg.get_string
+-          os = msg.get_string
+-          return self.new(cpu, os)
+-        end
+-      end
+-
+-      ##
+-      # Mailing list or mailbox information.
+-
+-      class MINFO < Resource
+-
+-        TypeValue = 14 # :nodoc:
+-
+-        def initialize(rmailbx, emailbx)
+-          @rmailbx = rmailbx
+-          @emailbx = emailbx
+-        end
+-
+-        ##
+-        # 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:
+-          msg.put_name(@rmailbx)
+-          msg.put_name(@emailbx)
+-        end
+-
+-        def self.decode_rdata(msg) # :nodoc:
+-          rmailbx = msg.get_string
+-          emailbx = msg.get_string
+-          return self.new(rmailbx, emailbx)
+-        end
+-      end
+-
+-      ##
+-      # Mail Exchanger resource.
+-
+-      class MX < Resource
+-
+-        TypeValue= 15 # :nodoc:
+-
+-        ##
+-        # Creates a new MX record with +preference+, accepting mail at
+-        # +exchange+.
+-
+-        def initialize(preference, exchange)
+-          @preference = preference
+-          @exchange = exchange
+-        end
+-
+-        ##
+-        # The preference for this MX.
+-
+-        attr_reader :preference
+-
+-        ##
+-        # The host of this MX.
+-
+-        attr_reader :exchange
+-
+-        def encode_rdata(msg) # :nodoc:
+-          msg.put_pack('n', @preference)
+-          msg.put_name(@exchange)
+-        end
+-
+-        def self.decode_rdata(msg) # :nodoc:
+-          preference, = msg.get_unpack('n')
+-          exchange = msg.get_name
+-          return self.new(preference, exchange)
+-        end
+-      end
+-
+-      ##
+-      # Unstructured text resource.
+-
+-      class TXT < Resource
+-
+-        TypeValue = 16 # :nodoc:
+-
+-        def initialize(first_string, *rest_strings)
+-          @strings = [first_string, *rest_strings]
+-        end
+-
+-        ##
+-        # Returns an Array of Strings for this TXT record.
+-
+-        attr_reader :strings
+-
+-        ##
+-        # Returns the concatenated string from +strings+.
+-
+-        def data
+-          @strings.join("")
+-        end
+-
+-        def encode_rdata(msg) # :nodoc:
+-          msg.put_string_list(@strings)
+-        end
+-
+-        def self.decode_rdata(msg) # :nodoc:
+-          strings = msg.get_string_list
+-          return self.new(*strings)
+-        end
+-      end
+-
+-      ##
+-      # Location resource
+-
+-      class LOC < Resource
+-
+-        TypeValue = 29 # :nodoc:
+-
+-        def initialize(version, ssize, hprecision, vprecision, latitude, longitude, altitude)
+-          @version    = version
+-          @ssize      = Gem::Resolv::LOC::Size.create(ssize)
+-          @hprecision = Gem::Resolv::LOC::Size.create(hprecision)
+-          @vprecision = Gem::Resolv::LOC::Size.create(vprecision)
+-          @latitude   = Gem::Resolv::LOC::Coord.create(latitude)
+-          @longitude  = Gem::Resolv::LOC::Coord.create(longitude)
+-          @altitude   = Gem::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,
+-            Gem::Resolv::LOC::Size.new(ssize),
+-            Gem::Resolv::LOC::Size.new(hprecision),
+-            Gem::Resolv::LOC::Size.new(vprecision),
+-            Gem::Resolv::LOC::Coord.new(latitude,"lat"),
+-            Gem::Resolv::LOC::Coord.new(longitude,"lon"),
+-            Gem::Resolv::LOC::Alt.new(altitude)
+-          )
+-        end
+-      end
+-
+-      ##
+-      # A Query type requesting any RR.
+-
+-      class ANY < Query
+-        TypeValue = 255 # :nodoc:
+-      end
+-
+-      ##
+-      # 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
+-
+-        ##
+-        # Flags for this property:
+-        # - 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
+-
+-      ClassInsensitiveTypes = [ # :nodoc:
+-        NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, LOC, ANY, CAA
+-      ]
+-
+-      ##
+-      # module IN contains ARPA Internet specific RRs.
+-
+-      module IN
+-
+-        ClassValue = 1 # :nodoc:
+-
+-        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)
+-        }
+-
+-        ##
+-        # IPv4 Address resource
+-
+-        class A < Resource
+-          TypeValue = 1
+-          ClassValue = IN::ClassValue
+-          ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
+-
+-          ##
+-          # Creates a new A for +address+.
+-
+-          def initialize(address)
+-            @address = IPv4.create(address)
+-          end
+-
+-          ##
+-          # The Gem::Resolv::IPv4 address for this A.
+-
+-          attr_reader :address
+-
+-          def encode_rdata(msg) # :nodoc:
+-            msg.put_bytes(@address.address)
+-          end
+-
+-          def self.decode_rdata(msg) # :nodoc:
+-            return self.new(IPv4.new(msg.get_bytes(4)))
+-          end
+-        end
+-
+-        ##
+-        # Well Known Service resource.
+-
+-        class WKS < Resource
+-          TypeValue = 11
+-          ClassValue = IN::ClassValue
+-          ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
+-
+-          def initialize(address, protocol, bitmap)
+-            @address = IPv4.create(address)
+-            @protocol = protocol
+-            @bitmap = bitmap
+-          end
+-
+-          ##
+-          # 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:
+-            msg.put_bytes(@address.address)
+-            msg.put_pack("n", @protocol)
+-            msg.put_bytes(@bitmap)
+-          end
+-
+-          def self.decode_rdata(msg) # :nodoc:
+-            address = IPv4.new(msg.get_bytes(4))
+-            protocol, = msg.get_unpack("n")
+-            bitmap = msg.get_bytes
+-            return self.new(address, protocol, bitmap)
+-          end
+-        end
+-
+-        ##
+-        # An IPv6 address record.
+-
+-        class AAAA < Resource
+-          TypeValue = 28
+-          ClassValue = IN::ClassValue
+-          ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
+-
+-          ##
+-          # Creates a new AAAA for +address+.
+-
+-          def initialize(address)
+-            @address = IPv6.create(address)
+-          end
+-
+-          ##
+-          # The Gem::Resolv::IPv6 address for this AAAA.
+-
+-          attr_reader :address
+-
+-          def encode_rdata(msg) # :nodoc:
+-            msg.put_bytes(@address.address)
+-          end
+-
+-          def self.decode_rdata(msg) # :nodoc:
+-            return self.new(IPv6.new(msg.get_bytes(16)))
+-          end
+-        end
+-
+-        ##
+-        # SRV resource record defined in RFC 2782
+-        #
+-        # These records identify the hostname and port that a service is
+-        # available at.
+-
+-        class SRV < Resource
+-          TypeValue = 33
+-          ClassValue = IN::ClassValue
+-          ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
+-
+-          # Create a SRV resource record.
+-          #
+-          # See the documentation for #priority, #weight, #port and #target
+-          # for +priority+, +weight+, +port and +target+ respectively.
+-
+-          def initialize(priority, weight, port, target)
+-            @priority = priority.to_int
+-            @weight = weight.to_int
+-            @port = port.to_int
+-            @target = Name.create(target)
+-          end
+-
+-          # 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.
+-
+-          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:
+-            msg.put_pack("n", @priority)
+-            msg.put_pack("n", @weight)
+-            msg.put_pack("n", @port)
+-            msg.put_name(@target, compress: false)
+-          end
+-
+-          def self.decode_rdata(msg) # :nodoc:
+-            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
+-
+-        ##
+-        # Common implementation for SVCB-compatible resource records.
+-
+-        class ServiceBinding
+-
+-          ##
+-          # Create a service binding resource record.
+-
+-          def initialize(priority, target, params = [])
+-            @priority = priority.to_int
+-            @target = Name.create(target)
+-            @params = SvcParams.new(params)
+-          end
+-
+-          ##
+-          # The priority of this target host.
+-          #
+-          # The range is 0-65535.
+-          # If set to 0, this RR is in AliasMode. Otherwise, it is in ServiceMode.
+-
+-          attr_reader :priority
+-
+-          ##
+-          # The domain name of the target host.
+-
+-          attr_reader :target
+-
+-          ##
+-          # The service parameters for the target host.
+-
+-          attr_reader :params
+-
+-          ##
+-          # Whether this RR is in AliasMode.
+-
+-          def alias_mode?
+-            self.priority == 0
+-          end
+-
+-          ##
+-          # Whether this RR is in ServiceMode.
+-
+-          def service_mode?
+-            !alias_mode?
+-          end
+-
+-          def encode_rdata(msg) # :nodoc:
+-            msg.put_pack("n", @priority)
+-            msg.put_name(@target, compress: false)
+-            @params.encode(msg)
+-          end
+-
+-          def self.decode_rdata(msg) # :nodoc:
+-            priority, = msg.get_unpack("n")
+-            target    = msg.get_name
+-            params    = SvcParams.decode(msg)
+-            return self.new(priority, target, params)
+-          end
+-        end
+-
+-        ##
+-        # SVCB resource record [RFC9460]
+-
+-        class SVCB < ServiceBinding
+-          TypeValue = 64
+-          ClassValue = IN::ClassValue
+-          ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
+-        end
+-
+-        ##
+-        # HTTPS resource record [RFC9460]
+-
+-        class HTTPS < ServiceBinding
+-          TypeValue = 65
+-          ClassValue = IN::ClassValue
+-          ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
+-        end
+-      end
+-    end
+-  end
+-
+-  ##
+-  # A Gem::Resolv::DNS IPv4 address.
+-
+-  class IPv4
+-
+-    Regex256 = /0
+-               |1(?:[0-9][0-9]?)?
+-               |2(?:[0-4][0-9]?|5[0-5]?|[6-9])?
+-               |[3-9][0-9]?/x # :nodoc:
+-
+-    ##
+-    # Regular expression IPv4 addresses must match.
+-    Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/
+-
+-    ##
+-    # Creates a new IPv4 address from +arg+ which may be:
+-    #
+-    # IPv4:: returns +arg+.
+-    # String:: +arg+ must match the IPv4::Regex constant
+-
+-    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
+-        raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}")
+-      end
+-    end
+-
+-    def initialize(address) # :nodoc:
+-      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"
+-      end
+-      @address = address
+-    end
+-
+-    ##
+-    # A String representation of this IPv4 address.
+-
+-    ##
+-    # The raw IPv4 address as a String.
+-
+-    attr_reader :address
+-
+-    def to_s # :nodoc:
+-      return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC"))
+-    end
+-
+-    def inspect # :nodoc:
+-      return "#<#{self.class} #{self}>"
+-    end
+-
+-    ##
+-    # Turns this IPv4 address into a Gem::Resolv::DNS::Name.
+-
+-    def to_name
+-      return DNS::Name.create(
+-        '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse)
+-    end
+-
+-    def ==(other) # :nodoc:
+-      return @address == other.address
+-    end
+-
+-    def eql?(other) # :nodoc:
+-      return self == other
+-    end
+-
+-    def hash # :nodoc:
+-      return @address.hash
+-    end
+-  end
+-
+-  ##
+-  # A Gem::Resolv::DNS IPv6 address.
+-
+-  class IPv6
+-
+-    ##
+-    # IPv6 address format a:b:c:d:e:f:g:h
+-    Regex_8Hex = /\A
+-      (?:[0-9A-Fa-f]{1,4}:){7}
+-         [0-9A-Fa-f]{1,4}
+-      \z/x
+-
+-    ##
+-    # Compressed IPv6 address format a::b
+-
+-    Regex_CompressedHex = /\A
+-      ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
+-      ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
+-      \z/x
+-
+-    ##
+-    # IPv4 mapped IPv6 address format a:b:c:d:e:f:w.x.y.z
+-
+-    Regex_6Hex4Dec = /\A
+-      ((?:[0-9A-Fa-f]{1,4}:){6,6})
+-      (\d+)\.(\d+)\.(\d+)\.(\d+)
+-      \z/x
+-
+-    ##
+-    # Compressed IPv4 mapped IPv6 address format a::b:w.x.y.z
+-
+-    Regex_CompressedHex4Dec = /\A
+-      ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
+-      ((?:[0-9A-Fa-f]{1,4}:)*)
+-      (\d+)\.(\d+)\.(\d+)\.(\d+)
+-      \z/x
+-
+-    ##
+-    # IPv6 link local address format fe80:b:c:d:e:f:g:h%em1
+-    Regex_8HexLinkLocal = /\A
+-      [Ff][Ee]80
+-      (?::[0-9A-Fa-f]{1,4}){7}
+-      %[-0-9A-Za-z._~]+
+-      \z/x
+-
+-    ##
+-    # Compressed IPv6 link local address format fe80::b%em1
+-
+-    Regex_CompressedHexLinkLocal = /\A
+-      [Ff][Ee]80:
+-      (?:
+-        ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
+-        ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
+-        |
+-        :((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
+-      )?
+-      :[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+
+-      \z/x
+-
+-    ##
+-    # A composite IPv6 address Regexp.
+-
+-    Regex = /
+-      (?:#{Regex_8Hex}) |
+-      (?:#{Regex_CompressedHex}) |
+-      (?:#{Regex_6Hex4Dec}) |
+-      (?:#{Regex_CompressedHex4Dec}) |
+-      (?:#{Regex_8HexLinkLocal}) |
+-      (?:#{Regex_CompressedHexLinkLocal})
+-      /x
+-
+-    ##
+-    # Creates a new IPv6 address from +arg+ which may be:
+-    #
+-    # IPv6:: returns +arg+.
+-    # String:: +arg+ must match one of the IPv6::Regex* constants
+-
+-    def self.create(arg)
+-      case arg
+-      when IPv6
+-        return arg
+-      when String
+-        address = ''.b
+-        if Regex_8Hex =~ arg
+-          arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
+-        elsif Regex_CompressedHex =~ arg
+-          prefix = $1
+-          suffix = $2
+-          a1 = ''.b
+-          a2 = ''.b
+-          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
+-            a1 = ''.b
+-            a2 = ''.b
+-            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
+-        raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}")
+-      end
+-    end
+-
+-    def initialize(address) # :nodoc:
+-      unless address.kind_of?(String) && address.length == 16
+-        raise ArgumentError.new('IPv6 address must be 16 bytes')
+-      end
+-      @address = address
+-    end
+-
+-    ##
+-    # The raw IPv6 address as a String.
+-
+-    attr_reader :address
+-
+-    def to_s # :nodoc:
+-      sprintf("%x:%x:%x:%x:%x:%x:%x:%x", *@address.unpack("nnnnnnnn")).sub(/(^|:)0(:0)+(:|$)/, '::')
+-    end
+-
+-    def inspect # :nodoc:
+-      return "#<#{self.class} #{self}>"
+-    end
+-
+-    ##
+-    # Turns this IPv6 address into a Gem::Resolv::DNS::Name.
+-    #--
+-    # ip6.arpa should be searched too. [RFC3152]
+-
+-    def to_name
+-      return DNS::Name.new(
+-        @address.unpack("H32")[0].split(//).reverse + ['ip6', 'arpa'])
+-    end
+-
+-    def ==(other) # :nodoc:
+-      return @address == other.address
+-    end
+-
+-    def eql?(other) # :nodoc:
+-      return self == other
+-    end
+-
+-    def hash # :nodoc:
+-      return @address.hash
+-    end
+-  end
+-
+-  ##
+-  # Gem::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
+-    #   Gem::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 Gem::Resolv::DNS::Name or a String.  Retrieved addresses will
+-    # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6
+-
+-    def each_address(name)
+-      name = Gem::Resolv::DNS::Name.create(name)
+-
+-      return unless name[-1].to_s == 'local'
+-
+-      super(name)
+-    end
+-
+-    def make_udp_requester # :nodoc:
+-      nameserver_port = @config.nameserver_port
+-      Requester::MDNSOneShot.new(*nameserver_port)
+-    end
+-
+-  end
+-
+-  module LOC # :nodoc:
+-
+-    ##
+-    # A Gem::Resolv::LOC::Size
+-
+-    class Size
+-
+-      # Regular expression LOC size must match.
+-
+-      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
+-
+-      # Internal use; use self.create.
+-      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:
+-        return "#<#{self.class} #{self}>"
+-      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 Gem::Resolv::LOC::Coord
+-
+-    class Coord
+-
+-      # Regular expression LOC Coord must match.
+-
+-      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 = ''
+-          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'
+-          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
+-
+-      # Internal use; use self.create.
+-      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:
+-        return "#<#{self.class} #{self}>"
+-      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 Gem::Resolv::LOC::Alt
+-
+-    class Alt
+-
+-      # Regular expression LOC Alt must match.
+-
+-      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
+-
+-      # Internal use; use self.create.
+-      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:
+-        return "#<#{self.class} #{self}>"
+-      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
+-
+-  ##
+-  # Default resolver to use for Gem::Resolv class methods.
+-
+-  DefaultResolver = self.new
+-
+-  ##
+-  # 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
+-
+-  ##
+-  # Address Regexp to use for matching IP addresses.
+-
+-  AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/
+-
+-end

diff --git a/ruby-4_0-pr18528-update-resolv-0_7_2.patch b/ruby-4_0-pr18528-update-resolv-0_7_2.patch
deleted file mode 100644
index e49f0d2..0000000
--- a/ruby-4_0-pr18528-update-resolv-0_7_2.patch
+++ /dev/null
@@ -1,984 +0,0 @@
-From 88ba1d4e1370f4221e060e4ed53ef191834e5838 Mon Sep 17 00:00:00 2001
-From: Hiroshi SHIBATA <hsbt@ruby-lang.org>
-Date: Thu, 27 Aug 2026 12:31:49 +0900
-Subject: [PATCH] Bump up resolv-0.7.2 for Ruby 4.0
-
-Backport of the August 2026 resolv security release.
-
-CVE-2026-80212
-CVE-2026-80213
-
-Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
----
- lib/resolv.rb                     | 114 +++++++++--
- test/resolv/test_dns.rb           | 319 ++++++++++++++++++++++++++++++
- test/resolv/test_resource.rb      |  61 ++++++
- test/resolv/test_resource_leak.rb | 101 ++++++++++
- 4 files changed, 582 insertions(+), 13 deletions(-)
- create mode 100644 test/resolv/test_resource_leak.rb
-
-diff --git a/lib/resolv.rb b/lib/resolv.rb
-index 0e62aaf8510496..a2fe692b38f6cf 100644
---- a/lib/resolv.rb
-+++ b/lib/resolv.rb
-@@ -35,7 +35,7 @@
- class Resolv
- 
-   # The version string
--  VERSION = "0.7.0"
-+  VERSION = "0.7.2"
- 
-   ##
-   # Looks up the first IP address for +name+.
-@@ -487,13 +487,18 @@ def each_name(address)
-     # * Resolv::DNS::Resource::IN::A
-     # * Resolv::DNS::Resource::IN::AAAA
-     # * Resolv::DNS::Resource::IN::ANY
-+    # * Resolv::DNS::Resource::IN::CAA
-     # * Resolv::DNS::Resource::IN::CNAME
-     # * Resolv::DNS::Resource::IN::HINFO
-+    # * Resolv::DNS::Resource::IN::HTTPS
-+    # * Resolv::DNS::Resource::IN::LOC
-     # * 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::SRV
-+    # * Resolv::DNS::Resource::IN::SVCB
-     # * Resolv::DNS::Resource::IN::TXT
-     # * Resolv::DNS::Resource::IN::WKS
-     #
-@@ -721,7 +726,8 @@ def request(sender, tout)
-           begin
-             reply, from = recv_reply(select_result[0])
-           rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD
--                 Errno::ECONNRESET # Windows
-+                 Errno::ECONNRESET, # Windows
-+                 EOFError
-             # No name server running on the server?
-             # Don't wait anymore.
-             raise ResolvTimeout
-@@ -930,8 +936,11 @@ def initialize(host, port=Port)
-         end
- 
-         def recv_reply(readable_socks)
--          len = readable_socks[0].read(2).unpack('n')[0]
-+          len_data = readable_socks[0].read(2)
-+          raise EOFError if len_data.nil? || len_data.bytesize != 2
-+          len = len_data.unpack('n')[0]
-           reply = @socks[0].read(len)
-+          raise EOFError if reply.nil? || reply.bytesize != len
-           return reply, nil
-         end
- 
-@@ -1244,6 +1253,13 @@ def self.split(arg)
- 
-       class Str # :nodoc:
-         def initialize(string)
-+          # A label is limited to 63 octets. [RFC 1035 2.3.4] Checking it here
-+          # makes it an invariant of the object: every label, however it was
-+          # built, fits in its length octet and cannot wrap it. Callers turn
-+          # this into the error their own contract promises.
-+          if string.bytesize > 63
-+            raise ArgumentError, "DNS label is too long (#{string.bytesize} bytes, max 63): #{string.inspect}"
-+          end
-           @string = string
-           # case insensivity of DNS labels doesn't apply non-ASCII characters. [RFC 4343]
-           # This assumes @string is given in ASCII compatible encoding.
-@@ -1289,7 +1305,26 @@ def self.create(arg)
-         when Name
-           return arg
-         when String
--          return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
-+          # A hostname is runtime data rather than a programming mistake, so
-+          # both size limits surface as ResolvError to stay rescuable alongside
-+          # the rest of name resolution. The type check below is a caller
-+          # mistake and keeps raising ArgumentError.
-+          begin
-+            labels = Label.split(arg)
-+          rescue ArgumentError => e
-+            raise ResolvError.new(e.message)
-+          end
-+          # Label::Str enforces the per-label limit. Only the total is knowable
-+          # here, and it counts the encoded form, so size starts at 1 for the
-+          # root label's terminating zero octet. [RFC 1035 2.3.4, 3.1]
-+          size = 1
-+          labels.each do |label|
-+            size += 1 + label.string.bytesize
-+            if size > 255
-+              raise ResolvError.new("DNS name is too long (#{size} octets, max 255): #{arg.inspect}")
-+            end
-+          end
-+          return Name.new(labels, /\.\z/ =~ arg ? true : false)
-         else
-           raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
-         end
-@@ -1411,12 +1446,24 @@ def ==(other)
-                @rd == other.rd &&
-                @ra == other.ra &&
-                @rcode == other.rcode &&
--               @question == other.question &&
-+               question_equal?(other.question) &&
-                @answer == other.answer &&
-                @authority == other.authority &&
-                @additional == other.additional
-       end
- 
-+      # A question holds the resource class itself, and decoding creates a fresh
-+      # class for each unknown type, so the classes cannot be compared by
-+      # identity alone.
-+      private def question_equal?(other_question) # :nodoc:
-+        return false unless @question.length == other_question.length
-+        @question.zip(other_question) {|(name, typeclass), (o_name, o_typeclass)|
-+          return false unless name == o_name &&
-+                              Resource::Generic.type_class_equal?(typeclass, o_typeclass)
-+        }
-+        return true
-+      end
-+
-       def add_question(name, typeclass)
-         @question << [Name.create(name), typeclass]
-       end
-@@ -1523,8 +1570,15 @@ def put_length16
-         end
- 
-         def put_string(d)
--          self.put_pack("C", d.length)
--          @data << d
-+          s = d.to_s
-+          # A character-string is prefixed by a single length octet, so it can
-+          # hold at most 255 octets. [RFC 1035 3.3] Reject anything longer to
-+          # avoid silently truncating the length to its low 8 bits (mod 256).
-+          if s.bytesize > 255
-+            raise ArgumentError, "character-string is too long (#{s.bytesize} bytes, max 255): #{s.inspect}"
-+          end
-+          self.put_pack("C", s.bytesize)
-+          @data << s
-         end
- 
-         def put_string_list(ds)
-@@ -1554,7 +1608,17 @@ def put_labels(d, compress: true)
-         end
- 
-         def put_label(d)
--          self.put_string(d.to_s)
-+          s = d.to_s
-+          # Label::Str applies this limit when a label is built, so what is left
-+          # for here is a raw string handed straight to put_labels. The two ways
-+          # an over-long label goes wrong differ: 64 to 255 octets write a length
-+          # octet in the reserved or compression pointer range, and 256 or more
-+          # wrap it mod 256. Either way the encoded name stops being the name the
-+          # caller asked for. [RFC 1035 2.3.4, 4.1.4]
-+          if s.bytesize > 63
-+            raise ArgumentError, "DNS label is too long (#{s.bytesize} bytes, max 63): #{s.inspect}"
-+          end
-+          self.put_string(s)
-         end
-       end
- 
-@@ -1680,7 +1744,9 @@ def get_labels
-           prev_index = @index
-           save_index = nil
-           d = []
--          size = -1
-+          # size counts the encoded form, so it starts at 1 for the root
-+          # label's terminating zero octet. [RFC 1035 3.1]
-+          size = 1
-           while true
-             raise DecodeError.new("limit exceeded") if @limit <= @index
-             case @data.getbyte(@index)
-@@ -1711,6 +1777,11 @@ def get_labels
- 
-         def get_label
-           return Label::Str.new(self.get_string)
-+        rescue ArgumentError => e
-+          # A length octet of 64..191 is reserved rather than a label length,
-+          # but this decoder used to read it as one. [RFC 1035 4.1.4] Report it
-+          # the way the rest of a malformed message is reported.
-+          raise DecodeError.new(e.message)
-         end
- 
-         def get_question
-@@ -1898,8 +1969,9 @@ def self.create(key_number)
-           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
-+          # Not registered in a constant or in ClassHash. ClassHash creates a
-+          # class for every unknown SvcParamKey, so registering them
-+          # permanently would let a malicious response exhaust memory.
-           return c
-         end
-       end
-@@ -2206,12 +2278,28 @@ def self.decode_rdata(msg) # :nodoc:
-           return self.new(msg.get_bytes)
-         end
- 
-+        # create makes a fresh class for each decoded resource, so the type and
-+        # class values have to be compared instead of the class itself.
-+        def self.type_class_equal?(klass, other) # :nodoc:
-+          return true if klass.equal?(other)
-+          Generic > klass && Generic > other &&
-+            klass::TypeValue == other::TypeValue &&
-+            klass::ClassValue == other::ClassValue
-+        end
-+
-+        def ==(other) # :nodoc:
-+          return other.is_a?(Generic) &&
-+                 Generic.type_class_equal?(self.class, other.class) &&
-+                 @data == other.data
-+        end
-+
-         def self.create(type_value, class_value) # :nodoc:
-           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
-+          # Not registered in a constant or in ClassHash. get_class creates a
-+          # class for every unknown (type, class) pair, so registering them
-+          # permanently would let a malicious response exhaust memory.
-           return c
-         end
-       end
-diff --git a/test/resolv/test_dns.rb b/test/resolv/test_dns.rb
-index d5d2648e1bc649..34ae4aeb9a33c3 100644
---- a/test/resolv/test_dns.rb
-+++ b/test/resolv/test_dns.rb
-@@ -636,6 +636,205 @@ def test_too_long_address
-     end
-   end
- 
-+  # A DNS label is limited to 63 octets. [RFC 1035 2.3.4] Writing a longer label
-+  # through the label path must raise instead of overflowing the length octet.
-+  def test_put_label_rejects_label_over_63_octets
-+    Resolv::DNS::Message::MessageEncoder.new {|msg|
-+      assert_nothing_raised { msg.put_label("a" * 63) }
-+      assert_raise_with_message(ArgumentError, /DNS label is too long/) do
-+        msg.put_label("a" * 64)
-+      end
-+    }
-+    # put_labels drives put_label, so the same guard applies to the name path.
-+    Resolv::DNS::Message::MessageEncoder.new {|msg|
-+      assert_raise_with_message(ArgumentError, /DNS label is too long/) do
-+        msg.put_labels(["a" * 64])
-+      end
-+    }
-+  end
-+
-+  # The per-label limit is an invariant of Label::Str, so no label object can
-+  # exist that would overflow its length octet. [RFC 1035 2.3.4]
-+  def test_label_str_rejects_label_over_63_octets
-+    assert_nothing_raised { Resolv::DNS::Label::Str.new("a" * 63) }
-+    assert_raise_with_message(ArgumentError, /DNS label is too long/) do
-+      Resolv::DNS::Label::Str.new("a" * 64)
-+    end
-+  end
-+
-+  # Every way of building a name goes through Label::Str, so the paths that
-+  # skip Name.create are covered too.
-+  def test_label_length_is_enforced_on_every_construction_path
-+    assert_raise_with_message(ArgumentError, /DNS label is too long/) do
-+      Resolv::DNS::Name.new(["a" * 64])
-+    end
-+    assert_raise_with_message(ArgumentError, /DNS label is too long/) do
-+      Resolv::DNS::Label.split("a" * 64)
-+    end
-+    # Config#generate_candidates appends search domains with Name.new, and the
-+    # search list itself comes from Label.split, so a resolv.conf carrying an
-+    # over-long label is rejected when the config is read.
-+    config = Resolv::DNS::Config.new(nameserver: ['127.0.0.1'],
-+                                     search: ["a" * 64], ndots: 1)
-+    assert_raise_with_message(ArgumentError, /DNS label is too long/) do
-+      config.lazy_initialize
-+    end
-+  end
-+
-+  def test_name_create_rejects_too_long_label
-+    assert_nothing_raised { Resolv::DNS::Name.create("a" * 63) }
-+    assert_raise_with_message(Resolv::ResolvError, /DNS label is too long/) do
-+      Resolv::DNS::Name.create("a" * 64)
-+    end
-+  end
-+
-+  def test_name_create_rejects_too_long_name
-+    # Five 63-octet labels total 321 encoded octets, over the 255 octet limit,
-+    # while each individual label is still valid.
-+    too_long = (["a" * 63] * 5).join(".")
-+    assert_raise_with_message(Resolv::ResolvError, /DNS name is too long/) do
-+      Resolv::DNS::Name.create(too_long)
-+    end
-+  end
-+
-+  # A hostname is runtime data, so an over-long one has to stay rescuable the
-+  # way the rest of name resolution is. It reaches Name.create through
-+  # Config#generate_candidates, which runs outside Config#resolv's own rescue.
-+  def test_oversized_name_is_rescuable_as_resolv_error
-+    dns = Resolv::DNS.new(nameserver_port: [['127.0.0.1', 53]])
-+    assert_raise(Resolv::ResolvError) { dns.getaddress("a" * 64) }
-+    assert_raise(Resolv::ResolvError) { dns.getaddress((["a" * 63] * 5).join(".")) }
-+  ensure
-+    dns&.close
-+  end
-+
-+  # A length octet of 64..191 is reserved, not a label length, but this decoder
-+  # read it as one and accepted labels no encoder should ever produce.
-+  # [RFC 1035 4.1.4] Rejecting them has to look like any other malformed
-+  # message, so the caller's rescue DecodeError still covers it.
-+  def test_decode_rejects_label_over_63_octets
-+    message = ->(n) {
-+      [0, 0x8180, 1, 0, 0, 0].pack("n*") +
-+        [n].pack("C") + ("a" * n) + "\0" + [1, 1].pack("nn")
-+    }
-+    assert_nothing_raised { Resolv::DNS::Message.decode(message.call(63)) }
-+    [64, 100, 191].each do |n|
-+      assert_raise_with_message(Resolv::DNS::DecodeError, /DNS label is too long/) do
-+        Resolv::DNS::Message.decode(message.call(n))
-+      end
-+    end
-+  end
-+
-+  # The type check is a caller mistake rather than runtime data, so it keeps
-+  # raising ArgumentError.
-+  def test_name_create_still_raises_argument_error_for_wrong_type
-+    assert_raise_with_message(ArgumentError, /cannot interpret as DNS name/) do
-+      Resolv::DNS::Name.create(123)
-+    end
-+  end
-+
-+  # The 255 octet limit counts the encoded form, including each label's length
-+  # octet and the root label's terminating zero octet. [RFC 1035 2.3.4, 3.1]
-+  # So the longest legal name encodes to exactly 255 octets.
-+  def test_name_create_total_length_boundary
-+    at_limit = (["a" * 63] * 3 + ["a" * 61]).join(".")
-+    name = Resolv::DNS::Name.create(at_limit)
-+    encoded = Resolv::DNS::Message::MessageEncoder.new {|msg| msg.put_name(name) }.to_s
-+    assert_equal(255, encoded.bytesize, "longest legal name encodes to 255 octets")
-+
-+    over_limit = (["a" * 63] * 3 + ["a" * 62]).join(".")
-+    assert_raise_with_message(Resolv::ResolvError, /DNS name is too long/) do
-+      Resolv::DNS::Name.create(over_limit)
-+    end
-+
-+    # Four 63-octet labels encode to 257 octets. Counting the presentation
-+    # form instead of the encoded form lets these two extra octets through.
-+    assert_raise_with_message(Resolv::ResolvError, /DNS name is too long/) do
-+      Resolv::DNS::Name.create((["a" * 63] * 4).join("."))
-+    end
-+  end
-+
-+  # The decoder enforces the same limit, counted the same way.
-+  def test_get_labels_total_length_boundary
-+    encode = ->(labels) {
-+      Resolv::DNS::Message::MessageEncoder.new {|msg|
-+        msg.put_labels(labels.map {|l| Resolv::DNS::Label::Str.new(l) })
-+      }.to_s
-+    }
-+
-+    at_limit = encode.call(["a" * 63] * 3 + ["a" * 61])
-+    assert_equal(255, at_limit.bytesize)
-+    Resolv::DNS::Message::MessageDecoder.new(at_limit) {|msg|
-+      assert_equal(4, msg.get_labels.length)
-+    }
-+
-+    over_limit = encode.call(["a" * 63] * 4)
-+    assert_equal(257, over_limit.bytesize)
-+    assert_raise_with_message(Resolv::DNS::DecodeError, /name label data exceed 255 octets/) do
-+      Resolv::DNS::Message::MessageDecoder.new(over_limit) {|msg| msg.get_labels }
-+    end
-+  end
-+
-+  # A single 262-octet label whose bytes start with "target\x03com\x00". The
-+  # old encoder wrote the length octet as 262 & 0xff == 6, so the wire bytes
-+  # decoded to the unrelated name "target.com" (query name confusion /
-+  # allowlist bypass).
-+  def test_encoder_rejects_label_length_wrap
-+    poc_label = "target".b + "\x03com\x00".b + ("a".b * 251)
-+    assert_equal(262, poc_label.bytesize)
-+    assert_equal(6, poc_label.bytesize & 0xff, "precondition: the length octet wraps to 6")
-+
-+    # The bytes the buggy encoder would have emitted really do decode to a
-+    # different name. This is the vulnerability being fixed.
-+    wrapped = [poc_label.bytesize & 0xff].pack("C") + poc_label
-+    Resolv::DNS::Message::MessageDecoder.new(wrapped) {|msg|
-+      assert_equal("target.com", msg.get_labels.map(&:to_s).join("."))
-+    }
-+
-+    # The fixed encoder refuses to emit it instead of silently wrapping, so it
-+    # can no longer produce "target.com" from this input.
-+    Resolv::DNS::Message::MessageEncoder.new {|msg|
-+      assert_raise_with_message(ArgumentError, /DNS label is too long/) do
-+        msg.put_label(poc_label)
-+      end
-+    }
-+    assert_raise_with_message(Resolv::ResolvError, /DNS label is too long/) do
-+      Resolv::DNS::Name.create(poc_label)
-+    end
-+  end
-+
-+  # A character-string (e.g. TXT rdata) is prefixed by a single length octet and
-+  # may legitimately be up to 255 octets, so the 63 octet label limit must not
-+  # leak into put_string. [RFC 1035 3.3]
-+  def test_put_string_allows_character_string_up_to_255
-+    [64, 200, 255].each do |n|
-+      s = "a" * n
-+      m = Resolv::DNS::Message::MessageEncoder.new {|msg| msg.put_string(s) }
-+      encoded = m.to_s
-+      assert_equal(n, encoded.getbyte(0), "length octet for #{n} byte string")
-+      assert_equal(n + 1, encoded.bytesize)
-+      Resolv::DNS::Message::MessageDecoder.new(encoded) {|msg|
-+        assert_equal(s, msg.get_string)
-+      }
-+    end
-+  end
-+
-+  def test_txt_record_roundtrip_with_long_character_strings
-+    txt = Resolv::DNS::Resource::IN::TXT.new("a" * 255, "b" * 64)
-+    m = Resolv::DNS::Message.new(0)
-+    m.add_answer("example.com.", 3600, txt)
-+    decoded = Resolv::DNS::Message.decode(m.encode)
-+    _, _, res = decoded.answer.first
-+    assert_equal(["a" * 255, "b" * 64], res.strings)
-+  end
-+
-+  # put_string still guards against the length octet wrapping past 255 octets.
-+  def test_put_string_rejects_over_255_octets
-+    assert_raise_with_message(ArgumentError, /character-string is too long/) do
-+      Resolv::DNS::Message::MessageEncoder.new {|msg| msg.put_string("a" * 256) }
-+    end
-+  end
-+
-   def assert_no_fd_leak
-     socket = assert_throw(self) do |tag|
-       Resolv::DNS.stub(:bind_random_port, ->(s, *) {throw(tag, s)}) do
-@@ -822,4 +1021,124 @@ def test_multiple_servers_with_timeout_and_truncated_tcp_fallback
-       end
-     end
-   end
-+
-+  def test_tcp_connection_closed_before_length
-+    with_tcp('127.0.0.1', 0) do |t|
-+      _, server_port, _, server_address = t.addr
-+
-+      server_thread = Thread.new do
-+        ct = t.accept
-+        ct.recv(512)
-+        ct.close
-+      end
-+
-+      client_thread = Thread.new do
-+        requester = Resolv::DNS::Requester::TCP.new(server_address, server_port)
-+        begin
-+          msg = Resolv::DNS::Message.new
-+          msg.add_question('example.org', Resolv::DNS::Resource::IN::A)
-+          sender = requester.sender(msg, msg)
-+          assert_raise(Resolv::ResolvTimeout) do
-+            requester.request(sender, 2)
-+          end
-+        ensure
-+          requester.close
-+        end
-+      end
-+
-+      server_thread.join
-+      client_thread.join
-+    end
-+  end
-+
-+  def test_tcp_connection_closed_after_length
-+    with_tcp('127.0.0.1', 0) do |t|
-+      _, server_port, _, server_address = t.addr
-+
-+      server_thread = Thread.new do
-+        ct = t.accept
-+        ct.recv(512)
-+        ct.send([100].pack('n'), 0)
-+        ct.close
-+      end
-+
-+      client_thread = Thread.new do
-+        requester = Resolv::DNS::Requester::TCP.new(server_address, server_port)
-+        begin
-+          msg = Resolv::DNS::Message.new
-+          msg.add_question('example.org', Resolv::DNS::Resource::IN::A)
-+          sender = requester.sender(msg, msg)
-+          assert_raise(Resolv::ResolvTimeout) do
-+            requester.request(sender, 2)
-+          end
-+        ensure
-+          requester.close
-+        end
-+      end
-+
-+      server_thread.join
-+      client_thread.join
-+    end
-+  end
-+
-+  def test_tcp_connection_closed_with_partial_length_prefix
-+    with_tcp('127.0.0.1', 0) do |t|
-+      _, server_port, _, server_address = t.addr
-+
-+      server_thread = Thread.new do
-+        ct = t.accept
-+        ct.recv(512)
-+        ct.write "A" # 1 byte
-+        ct.close
-+      end
-+
-+      client_thread = Thread.new do
-+        requester = Resolv::DNS::Requester::TCP.new(server_address, server_port)
-+        begin
-+          msg = Resolv::DNS::Message.new
-+          msg.add_question('example.org', Resolv::DNS::Resource::IN::A)
-+          sender = requester.sender(msg, msg)
-+          assert_raise(Resolv::ResolvTimeout) do
-+            requester.request(sender, 2)
-+          end
-+        ensure
-+          requester.close
-+        end
-+      end
-+
-+      server_thread.join
-+      client_thread.join
-+    end
-+  end
-+
-+  def test_tcp_connection_closed_with_partial_message_body
-+    with_tcp('127.0.0.1', 0) do |t|
-+      _, server_port, _, server_address = t.addr
-+
-+      server_thread = Thread.new do
-+        ct = t.accept
-+        ct.recv(512)
-+        ct.write([10].pack('n')) # length 10
-+        ct.write "12345" # 5 bytes (partial)
-+        ct.close
-+      end
-+
-+      client_thread = Thread.new do
-+        requester = Resolv::DNS::Requester::TCP.new(server_address, server_port)
-+        begin
-+          msg = Resolv::DNS::Message.new
-+          msg.add_question('example.org', Resolv::DNS::Resource::IN::A)
-+          sender = requester.sender(msg, msg)
-+          assert_raise(Resolv::ResolvTimeout) do
-+            requester.request(sender, 2)
-+          end
-+        ensure
-+          requester.close
-+        end
-+      end
-+
-+      server_thread.join
-+      client_thread.join
-+    end
-+  end
- end
-diff --git a/test/resolv/test_resource.rb b/test/resolv/test_resource.rb
-index 434380236e6721..b4e56ce4bb8ff5 100644
---- a/test/resolv/test_resource.rb
-+++ b/test/resolv/test_resource.rb
-@@ -24,6 +24,67 @@ def test_coord
-     Resolv::LOC::Coord.create('1 2 1.1 N')
-   end
- 
-+  # Decoding an unknown (type, class) pair builds a fresh class every time, so
-+  # equality must not rest on the class identity.
-+  def test_generic_equality
-+    wire = generic_answer(40000, "\x01\x02\x03")
-+    rr1 = decode_generic(wire)
-+    rr2 = decode_generic(wire)
-+
-+    assert_not_same rr1.class, rr2.class
-+    assert_equal rr1, rr2
-+    assert rr1.eql?(rr2)
-+    assert_equal rr1.hash, rr2.hash
-+    assert_equal Resolv::DNS::Message.decode(wire), Resolv::DNS::Message.decode(wire)
-+  end
-+
-+  # Any descendant counts, not just a class create returned.
-+  def test_generic_equality_between_descendants
-+    generic = Resolv::DNS::Resource::Generic
-+    direct = generic.create(40000, 60000)
-+    descendant = Class.new(generic.create(40000, 60000))
-+
-+    assert_equal direct.new("\x01\x02\x03"), descendant.new("\x01\x02\x03")
-+    assert_equal descendant.new("\x01\x02\x03"), direct.new("\x01\x02\x03")
-+    assert_equal generic.new("\x01\x02\x03"), generic.new("\x01\x02\x03")
-+    assert_not_equal direct.new("\x01\x02\x03"),
-+      Class.new(generic.create(40001, 60000)).new("\x01\x02\x03")
-+  end
-+
-+  def test_generic_inequality
-+    rr = decode_generic(generic_answer(40000, "\x01\x02\x03"))
-+
-+    assert_not_equal rr, decode_generic(generic_answer(40001, "\x01\x02\x03"))
-+    assert_not_equal rr, decode_generic(generic_answer(40000, "\x09\x09\x09"))
-+    assert_not_equal rr, Resolv::DNS::Resource::IN::A.new("192.168.0.1")
-+  end
-+
-+  # A question holds the resource class itself, so it needs the same treatment.
-+  def test_generic_question_equality
-+    wire = generic_question(40000)
-+
-+    assert_equal Resolv::DNS::Message.decode(wire), Resolv::DNS::Message.decode(wire)
-+    assert_not_equal Resolv::DNS::Message.decode(wire),
-+      Resolv::DNS::Message.decode(generic_question(40001))
-+  end
-+
-+  private def header(qdcount, ancount)
-+    "\x00\x00\x00\x00".b + [qdcount, ancount, 0, 0].pack('nnnn')
-+  end
-+
-+  private def generic_answer(type, rdata)
-+    rdata = rdata.b
-+    (header(0, 1) + "\x00".b + [type, 60000, 0, rdata.bytesize].pack('nnNn') + rdata).b
-+  end
-+
-+  private def generic_question(type)
-+    (header(1, 0) + "\x07example\x03com\x00".b + [type, 60000].pack('nn')).b
-+  end
-+
-+  private def decode_generic(wire)
-+    Resolv::DNS::Message.decode(wire).answer.first[2]
-+  end
-+
-   def test_srv_no_compress
-     # Domain name in SRV RDATA should not be compressed
-     issue29 = 'https://github.com/ruby/resolv/issues/29'
-diff --git a/test/resolv/test_resource_leak.rb b/test/resolv/test_resource_leak.rb
-new file mode 100644
-index 00000000000000..4628a3d2857677
---- /dev/null
-+++ b/test/resolv/test_resource_leak.rb
-@@ -0,0 +1,101 @@
-+# frozen_string_literal: false
-+require 'test/unit'
-+require 'resolv'
-+
-+# Decoding a response with unknown (type, class) pairs or unknown SvcParamKeys
-+# used to register a generated class permanently, so a malicious response could
-+# exhaust memory even after the response was discarded.
-+class TestResolvResourceLeak < Test::Unit::TestCase
-+  # Number of dynamically-registered "Type<n>_Class<n>" constants on +mod+.
-+  def type_const_count(mod)
-+    mod.constants(false).count { |c| c.to_s.match?(/\AType\d+_Class\d+\z/) }
-+  end
-+
-+  def svcparam_key_const_count
-+    Resolv::DNS::SvcParam::Generic.constants(false).count { |c| c.to_s.match?(/\AKey\d+\z/) }
-+  end
-+
-+  # A DNS response whose answer section holds +count+ RRs, each with a distinct
-+  # unknown (type, class) pair.
-+  def unknown_typeclass_response(count)
-+    body = "".b
-+    count.times do |i|
-+      type  = 40000 + i
-+      klass = 60000
-+      rdata = "\x01\x02\x03".b
-+      body << "\x00".b                                    # NAME = root
-+      body << [type, klass, 0, rdata.bytesize].pack('nnNn')
-+      body << rdata
-+    end
-+    header = "\x00\x00\x00\x00".b + [0, count, 0, 0].pack('nnnn')
-+    (header + body).b
-+  end
-+
-+  # An SVCB RR (type 64) carrying +count+ distinct unknown SvcParamKeys.
-+  def unknown_svcparam_response(count)
-+    rdata = "".b
-+    rdata << [1].pack('n')                                # SvcPriority
-+    rdata << "\x03foo\x07example\x03com\x00".b            # TargetName
-+    count.times do |i|
-+      key = 1000 + i
-+      val = "x".b
-+      rdata << [key, val.bytesize].pack('nn') << val
-+    end
-+    header = "\x00\x00\x00\x00".b + [0, 1, 0, 0].pack('nnnn')
-+    name   = "\x07example\x03com\x00".b
-+    rr     = name + [64, 1, 0, rdata.bytesize].pack('nnNn') + rdata
-+    (header + rr).b
-+  end
-+
-+  def test_unknown_typeclass_does_not_leak_classes
-+    resource = Resolv::DNS::Resource
-+    generic  = Resolv::DNS::Resource::Generic
-+
-+    before_resource = type_const_count(resource)
-+    before_generic  = type_const_count(generic)
-+
-+    [100, 1000].each do |count|
-+      msg = unknown_typeclass_response(count)
-+      3.times { Resolv::DNS::Message.decode(msg) }
-+    end
-+    GC.start
-+
-+    assert_equal before_resource, type_const_count(resource),
-+      'decoding unknown (type, class) RRs must not register new Resource constants'
-+    assert_equal before_generic, type_const_count(generic),
-+      'decoding unknown (type, class) RRs must not register new Generic constants'
-+  end
-+
-+  def test_unknown_svcparam_key_does_not_leak_classes
-+    class_hash = Resolv::DNS::SvcParam::ClassHash
-+
-+    before_consts = svcparam_key_const_count
-+    before_hash   = class_hash.size
-+
-+    [100, 1000].each do |count|
-+      msg = unknown_svcparam_response(count)
-+      3.times { Resolv::DNS::Message.decode(msg) }
-+    end
-+    GC.start
-+
-+    assert_equal before_consts, svcparam_key_const_count,
-+      'decoding unknown SvcParamKeys must not register new Generic constants'
-+    assert_equal before_hash, class_hash.size,
-+      'decoding unknown SvcParamKeys must not grow SvcParam::ClassHash'
-+  end
-+
-+  # Dropping the permanent registration must not break decoding of the unknown
-+  # values themselves.
-+  def test_unknown_values_still_decode
-+    msg = Resolv::DNS::Message.decode(unknown_typeclass_response(3))
-+    assert_equal 3, msg.answer.size
-+    _, _, rr = msg.answer.first
-+    assert_kind_of Resolv::DNS::Resource::Generic, rr
-+    assert_equal "\x01\x02\x03".b, rr.data
-+
-+    msg = Resolv::DNS::Message.decode(unknown_svcparam_response(3))
-+    _, _, svcb = msg.answer.first
-+    assert_equal 3, svcb.params.count
-+    assert_equal "x".b, svcb.params[:key1000].value
-+  end
-+end
---- ruby-4.0.6.orig/lib/rubygems/vendor/resolv/lib/resolv.rb	2026-07-14 09:22:10.000000000 +0900
-+++ ruby-4.0.6/lib/rubygems/vendor/resolv/lib/resolv.rb	2026-09-03 17:16:18.543025164 +0900
-@@ -35,7 +35,7 @@
- class Gem::Resolv
- 
-   # The version string
--  VERSION = "0.7.0"
-+  VERSION = "0.7.2"
- 
-   ##
-   # Looks up the first IP address for +name+.
-@@ -487,13 +487,18 @@ def each_name(address)
-     # * Gem::Resolv::DNS::Resource::IN::A
-     # * Gem::Resolv::DNS::Resource::IN::AAAA
-     # * Gem::Resolv::DNS::Resource::IN::ANY
-+    # * Gem::Resolv::DNS::Resource::IN::CAA
-     # * Gem::Resolv::DNS::Resource::IN::CNAME
-     # * Gem::Resolv::DNS::Resource::IN::HINFO
-+    # * Gem::Resolv::DNS::Resource::IN::HTTPS
-+    # * Gem::Resolv::DNS::Resource::IN::LOC
-     # * Gem::Resolv::DNS::Resource::IN::MINFO
-     # * Gem::Resolv::DNS::Resource::IN::MX
-     # * Gem::Resolv::DNS::Resource::IN::NS
-     # * Gem::Resolv::DNS::Resource::IN::PTR
-     # * Gem::Resolv::DNS::Resource::IN::SOA
-+    # * Gem::Resolv::DNS::Resource::IN::SRV
-+    # * Gem::Resolv::DNS::Resource::IN::SVCB
-     # * Gem::Resolv::DNS::Resource::IN::TXT
-     # * Gem::Resolv::DNS::Resource::IN::WKS
-     #
-@@ -721,7 +726,8 @@ def request(sender, tout)
-           begin
-             reply, from = recv_reply(select_result[0])
-           rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD
--                 Errno::ECONNRESET # Windows
-+                 Errno::ECONNRESET, # Windows
-+                 EOFError
-             # No name server running on the server?
-             # Don't wait anymore.
-             raise ResolvTimeout
-@@ -930,8 +936,11 @@ def initialize(host, port=Port)
-         end
- 
-         def recv_reply(readable_socks)
--          len = readable_socks[0].read(2).unpack('n')[0]
-+          len_data = readable_socks[0].read(2)
-+          raise EOFError if len_data.nil? || len_data.bytesize != 2
-+          len = len_data.unpack('n')[0]
-           reply = @socks[0].read(len)
-+          raise EOFError if reply.nil? || reply.bytesize != len
-           return reply, nil
-         end
- 
-@@ -1244,6 +1253,13 @@ def self.split(arg)
- 
-       class Str # :nodoc:
-         def initialize(string)
-+          # A label is limited to 63 octets. [RFC 1035 2.3.4] Checking it here
-+          # makes it an invariant of the object: every label, however it was
-+          # built, fits in its length octet and cannot wrap it. Callers turn
-+          # this into the error their own contract promises.
-+          if string.bytesize > 63
-+            raise ArgumentError, "DNS label is too long (#{string.bytesize} bytes, max 63): #{string.inspect}"
-+          end
-           @string = string
-           # case insensivity of DNS labels doesn't apply non-ASCII characters. [RFC 4343]
-           # This assumes @string is given in ASCII compatible encoding.
-@@ -1289,7 +1305,26 @@ def self.create(arg)
-         when Name
-           return arg
-         when String
--          return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
-+          # A hostname is runtime data rather than a programming mistake, so
-+          # both size limits surface as ResolvError to stay rescuable alongside
-+          # the rest of name resolution. The type check below is a caller
-+          # mistake and keeps raising ArgumentError.
-+          begin
-+            labels = Label.split(arg)
-+          rescue ArgumentError => e
-+            raise ResolvError.new(e.message)
-+          end
-+          # Label::Str enforces the per-label limit. Only the total is knowable
-+          # here, and it counts the encoded form, so size starts at 1 for the
-+          # root label's terminating zero octet. [RFC 1035 2.3.4, 3.1]
-+          size = 1
-+          labels.each do |label|
-+            size += 1 + label.string.bytesize
-+            if size > 255
-+              raise ResolvError.new("DNS name is too long (#{size} octets, max 255): #{arg.inspect}")
-+            end
-+          end
-+          return Name.new(labels, /\.\z/ =~ arg ? true : false)
-         else
-           raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
-         end
-@@ -1411,12 +1446,24 @@ def ==(other)
-                @rd == other.rd &&
-                @ra == other.ra &&
-                @rcode == other.rcode &&
--               @question == other.question &&
-+               question_equal?(other.question) &&
-                @answer == other.answer &&
-                @authority == other.authority &&
-                @additional == other.additional
-       end
- 
-+      # A question holds the resource class itself, and decoding creates a fresh
-+      # class for each unknown type, so the classes cannot be compared by
-+      # identity alone.
-+      private def question_equal?(other_question) # :nodoc:
-+        return false unless @question.length == other_question.length
-+        @question.zip(other_question) {|(name, typeclass), (o_name, o_typeclass)|
-+          return false unless name == o_name &&
-+                              Resource::Generic.type_class_equal?(typeclass, o_typeclass)
-+        }
-+        return true
-+      end
-+
-       def add_question(name, typeclass)
-         @question << [Name.create(name), typeclass]
-       end
-@@ -1523,8 +1570,15 @@ def put_length16
-         end
- 
-         def put_string(d)
--          self.put_pack("C", d.length)
--          @data << d
-+          s = d.to_s
-+          # A character-string is prefixed by a single length octet, so it can
-+          # hold at most 255 octets. [RFC 1035 3.3] Reject anything longer to
-+          # avoid silently truncating the length to its low 8 bits (mod 256).
-+          if s.bytesize > 255
-+            raise ArgumentError, "character-string is too long (#{s.bytesize} bytes, max 255): #{s.inspect}"
-+          end
-+          self.put_pack("C", s.bytesize)
-+          @data << s
-         end
- 
-         def put_string_list(ds)
-@@ -1554,7 +1608,17 @@ def put_labels(d, compress: true)
-         end
- 
-         def put_label(d)
--          self.put_string(d.to_s)
-+          s = d.to_s
-+          # Label::Str applies this limit when a label is built, so what is left
-+          # for here is a raw string handed straight to put_labels. The two ways
-+          # an over-long label goes wrong differ: 64 to 255 octets write a length
-+          # octet in the reserved or compression pointer range, and 256 or more
-+          # wrap it mod 256. Either way the encoded name stops being the name the
-+          # caller asked for. [RFC 1035 2.3.4, 4.1.4]
-+          if s.bytesize > 63
-+            raise ArgumentError, "DNS label is too long (#{s.bytesize} bytes, max 63): #{s.inspect}"
-+          end
-+          self.put_string(s)
-         end
-       end
- 
-@@ -1680,7 +1744,9 @@ def get_labels
-           prev_index = @index
-           save_index = nil
-           d = []
--          size = -1
-+          # size counts the encoded form, so it starts at 1 for the root
-+          # label's terminating zero octet. [RFC 1035 3.1]
-+          size = 1
-           while true
-             raise DecodeError.new("limit exceeded") if @limit <= @index
-             case @data.getbyte(@index)
-@@ -1711,6 +1777,11 @@ def get_labels
- 
-         def get_label
-           return Label::Str.new(self.get_string)
-+        rescue ArgumentError => e
-+          # A length octet of 64..191 is reserved rather than a label length,
-+          # but this decoder used to read it as one. [RFC 1035 4.1.4] Report it
-+          # the way the rest of a malformed message is reported.
-+          raise DecodeError.new(e.message)
-         end
- 
-         def get_question
-@@ -1898,8 +1969,9 @@ def self.create(key_number)
-           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
-+          # Not registered in a constant or in ClassHash. ClassHash creates a
-+          # class for every unknown SvcParamKey, so registering them
-+          # permanently would let a malicious response exhaust memory.
-           return c
-         end
-       end
-@@ -2206,12 +2278,28 @@ def self.decode_rdata(msg) # :nodoc:
-           return self.new(msg.get_bytes)
-         end
- 
-+        # create makes a fresh class for each decoded resource, so the type and
-+        # class values have to be compared instead of the class itself.
-+        def self.type_class_equal?(klass, other) # :nodoc:
-+          return true if klass.equal?(other)
-+          Generic > klass && Generic > other &&
-+            klass::TypeValue == other::TypeValue &&
-+            klass::ClassValue == other::ClassValue
-+        end
-+
-+        def ==(other) # :nodoc:
-+          return other.is_a?(Generic) &&
-+                 Generic.type_class_equal?(self.class, other.class) &&
-+                 @data == other.data
-+        end
-+
-         def self.create(type_value, class_value) # :nodoc:
-           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
-+          # Not registered in a constant or in ClassHash. get_class creates a
-+          # class for every unknown (type, class) pair, so registering them
-+          # permanently would let a malicious response exhaust memory.
-           return c
-         end
-       end
-

diff --git a/ruby.spec b/ruby.spec
index a2b36e1..1846059 100644
--- a/ruby.spec
+++ b/ruby.spec
@@ -1,6 +1,6 @@
 %global major_version 4
 %global minor_version 0
-%global teeny_version 6
+%global teeny_version 7
 %global major_minor_version %{major_version}.%{minor_version}
 
 %global ruby_version %{major_minor_version}.%{teeny_version}
@@ -36,19 +36,18 @@
 ## BUNDLED_GEMS_VERSIONS
 
 # Bundled libraries versions
-%global rubygems_version 4.0.16
+%global rubygems_version 4.0.20
 %global rubygems_molinillo_version 0.8.0
 %global rubygems_net_http_version 0.7.0
 %global rubygems_net_protocol_version 0.2.2
 %global rubygems_optparse_version 0.8.0
-%global rubygems_resolv_version 0.7.2
 %global rubygems_securerandom_version 0.4.1
 %global rubygems_timeout_version 0.4.4
 %global rubygems_tsort_version 0.2.0
 %global rubygems_uri_version 1.1.1
 
 # Default gems.
-%global bundler_version 4.0.16
+%global bundler_version 4.0.20
 %global bundler_connection_pool_version 2.5.4
 %global bundler_fileutils_version 1.8.0
 %global bundler_net_http_persistent_version 4.0.6
@@ -62,7 +61,7 @@
 %global did_you_mean_version 2.0.0
 %global digest_version 3.2.1
 %global english_version 0.8.1
-%global erb_version 6.0.1.1
+%global erb_version 6.0.7
 %global error_highlight_version 0.7.2
 %global etc_version 1.4.6
 %global fcntl_version 1.3.0
@@ -189,7 +188,7 @@
 Summary: An interpreter of object-oriented scripting language
 Name: ruby
 Version: %{ruby_version}%{?development_release}
-Release: 38%{?dist}
+Release: 39%{?dist}
 # Licenses, which are likely not included in binary RPMs:
 # Apache-2.0:
 #   benchmark/gc/redblack.rb
@@ -298,10 +297,12 @@ Patch8: ruby-4.0.1-Support-customizable-rustc_flags-for-rustc-builds.patch
 # Fix error with `gem install --document=rdoc,ri`
 # Fixed in rdoc 7.1.0 but not in 7.0.4
 Patch9: rdoc-pr1531-fix-mutilple-document-installation.patch
-# Backport from ruby_4_0 branch to update resolv to 0.7.2 (fixes CVE-2026-80212 CVE-2026-80213)
-# https://github.com/ruby/ruby/pull/18528
-# Also copied the patch to apply the fix also for vendored resolv
-Patch10: ruby-4_0-pr18528-update-resolv-0_7_2.patch
+# Stop vendoring resolv for two regexps to stop showing up as an outdated copy in security
+# audits of the Ruby tarball
+# https://github.com/ruby/ruby/commit/0b8e0b1c28bc839095e2c0e884313f0f5ef1fc53
+# ref: https://github.com/ruby/rubygems/pull/9877/commits/9f2bd5f66cd7e71ee140efb68c19a3a2661720aa
+# This change is included in rubygems 4.0.21
+Patch10: ruby-4.0.7-rubygems-pr9877-stop-vendoring-resolv.patch
 
 Requires: %{name}-libs%{?_isa} = %{version}-%{release}
 %{?with_rubypick:Suggests: rubypick}
@@ -409,7 +410,6 @@ Version:    %{rubygems_version}
 #   lib/rubygems/net-http/
 #   lib/rubygems/net-protocol/
 #   lib/rubygems/optparse/
-#   lib/rubygems/resolv/
 #   lib/rubygems/timeout/
 #   lib/rubygems/tsort/
 # MIT: lib/rubygems/resolver/molinillo
@@ -428,7 +428,6 @@ Provides:   bundled(rubygem-molinillo) = %{rubygems_molinillo_version}
 Provides:   bundled(rubygem-net-http) = %{rubygems_net_http_version}
 Provides:   bundled(rubygem-net-protocol) = %{rubygems_net_protocol_version}
 Provides:   bundled(rubygem-optparse) = %{rubygems_optparse_version}
-Provides:   bundled(rubygem-resolv) = %{rubygems_resolv_version}
 Provides:   bundled(rubygem-securerandom) = %{rubygems_securerandom_version}
 Provides:   bundled(rubygem-timeout) = %{rubygems_timeout_version}
 Provides:   bundled(rubygem-tsort) = %{rubygems_tsort_version}
@@ -1052,16 +1051,6 @@ make -C %{_vpath_builddir} -s runruby TESTRUN_SCRIPT="-e \" \
   exit 1 if Gem::OptionParser::Version != '%{rubygems_optparse_version}'; \
 \""
 
-# Resolv.
-make -C %{_vpath_builddir} -s runruby TESTRUN_SCRIPT="-e \" \
-  module Gem; end; \
-  require 'rbconfig'; \
-  require 'rubygems/vendor/resolv/lib/resolv'; \
-  puts '%%{rubygems_resolv_version}: %{rubygems_resolv_version}'; \
-  puts %Q[Gem::Resolv::VERSION: #{Gem::Resolv::VERSION}]; \
-  exit 1 if Gem::Resolv::VERSION != '%{rubygems_resolv_version}'; \
-\""
-
 # SecureRandom.
 make -C %{_vpath_builddir} -s runruby TESTRUN_SCRIPT="-e \" \
   module Gem; module Random; end; end; \
@@ -1952,6 +1941,10 @@ make -C %{_vpath_builddir} runruby TESTRUN_SCRIPT=" \
 
 
 %changelog
+* Thu Sep 17 2026 Mamoru TASAKA <mtasaka@fedoraproject.org> - 4.0.7-39
+- Update to Ruby 4.0.7
+- Backport rubygems side change to stop vendoring resolv in rubygems tree
+
 * Thu Sep 03 2026 Mamoru TASAKA <mtasaka@fedoraproject.org> - 4.0.6-38
 - Backport upstream patch to update resolv to 0.7.2
 - Resolves: CVE-2026-80212 (rhbz#2527308)

diff --git a/sources b/sources
index 8415dfc..c24ae13 100644
--- a/sources
+++ b/sources
@@ -1 +1 @@
-SHA512 (ruby-4.0.6.tar.xz) = c9c391675a098b5a27dd687571ab0a164c788cbb8ddcbab6d17a3e8b3c4ba389346b2e6d7807d38db3e74d225c70e378a296d60d0f465cb0fcec9702242c83c6
+SHA512 (ruby-4.0.7.tar.xz) = d1d345c25bff43aa86f536b22cd04746a7d3372077bf47ec75ef1820c0b43d0dbe3573dd5e5c55ee517796f46f0cdbd22c7506aebd06581241eefb5ffd9d2e5f

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-09-17 14:01 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-17 14:01 [rpms/ruby] f44: Update to Ruby 4.0.7 Mamoru TASAKA

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox