<commit_before>require 'resolv'

module Celluloid
  module IO
    # Asynchronous DNS resolver using Celluloid::IO::UDPSocket
    class DNSResolver
      RESOLV_CONF = '/etc/resolv.conf'
      DNS_PORT    = 53

      @mutex = Mutex.new
      @identifier = 1

      def self.generate_id
        @mutex.synchronize { @identifier = (@identifier + 1) & 0xFFFF }
      end

      def self.nameservers(config = RESOLV_CONF)
        File.read(config).scan(/^\s*nameserver\s+([0-9.:]+)/).flatten
      end

      def initialize
        @nameservers = self.class.nameservers

        # TODO: fall back on other nameservers if the first one is unavailable
        @server = @nameservers.first

        # The non-blocking secret sauce is here, as this is actually a
        # Celluloid::IO::UDPSocket
        @socket = UDPSocket.new
      end

      def resolve(hostname)
        if host = resolve_hostname(hostname)
          unless ip_address = resolve_host(host)
            raise Resolv::ResolvError, "invalid entry in hosts file: #{host}"
          end
          return ip_address
        end

        query = build_query(hostname)
        @socket.send query.encode, 0, @server, DNS_PORT
        data, _ = @socket.recvfrom(512)
        response = Resolv::DNS::Message.decode(data)

        addrs = []
        # The answer might include IN::CNAME entries so filters them out
        # to include IN::A & IN::AAAA entries only.
        response.each_answer { |name, ttl, value| addrs << value.address if value.respond_to?(:address) }

        return if addrs.empty?
        return addrs.first if addrs.size == 1
        addrs
      end

      private

      def resolve_hostname(hostname)
        # Resolv::Hosts#getaddresses pushes onto a stack
        # so since we want the first occurance, simply
        # pop off the stack.
        resolv.getaddresses(hostname).pop rescue nil
      end

      def resolv
        @resolv ||= Resolv::Hosts.new
      end

      def build_query(hostname)
        Resolv::DNS::Message.new.tap do |query|
          query.id = self.class.generate_id
          query.rd = 1<commit_msg>Add Windows and IPv6 DNSResolver support<commit_after>require 'ipaddr'
require 'resolv'

module Celluloid
  module IO
    # Asynchronous DNS resolver using Celluloid::IO::UDPSocket
    class DNSResolver
      # Maximum UDP packet we'll accept
      MAX_PACKET_SIZE = 512
      RESOLV_CONF     = '/etc/resolv.conf'
      DNS_PORT        = 53

      @mutex = Mutex.new
      @identifier = 1

      def self.generate_id
        @mutex.synchronize { @identifier = (@identifier + 1) & 0xFFFF }
      end

      def self.nameservers(config = RESOLV_CONF)
        Resolv::DNS::Config.default_config_hash(config)[:nameserver]
      end

      def initialize
        @nameservers = self.class.nameservers

        @server = IPAddr.new(@nameservers.sample)

        # The non-blocking secret sauce is here, as this is actually a
        # Celluloid::IO::UDPSocket
        @socket = UDPSocket.new(@server.family)
      end

      def resolve(hostname)
        if host = resolve_hostname(hostname)
          unless ip_address = resolve_host(host)
            raise Resolv::ResolvError, "invalid entry in hosts file: #{host}"
          end
          return ip_address
        end

        query = build_query(hostname)
        @socket.send query.encode, 0, @server.to_s, DNS_PORT
        data, _ = @socket.recvfrom(MAX_PACKET_SIZE)
        response = Resolv::DNS::Message.decode(data)

        addrs = []
        # The answer might include IN::CNAME entries so filters them out
        # to include IN::A & IN::AAAA entries only.
        response.each_answer { |name, ttl, value| addrs << value.address if value.respond_to?(:address) }

        return if addrs.empty?
        return addrs.first if addrs.size == 1
        addrs
      end

      private

      def resolve_hostname(hostname)
        # Resolv::Hosts#getaddresses pushes onto a stack
        # so since we want the first occurance, simply
        # pop off the stack.
        resolv.getaddresses(hostname).pop rescue nil
      end

      def resolv
        @resolv ||= Resolv::Hosts.new
      end

      def build_query(hostname)
        Resolv::DNS::Message.new.tap do |query|
          query.id = self.class.generate_id
          query.rd = 1
{'fraction_non_alphanumeric': 0.08945167286245354, 'fraction_numerical': 0.005808550185873606, 'mean_word_length': 2.2150858849887975, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7464797', 'n_tokens_mistral': 1324, 'n_tokens_neox': 1291, 'n_words': 343}