lib/resolv.rb


DEFINITIONS

This source file includes following functions.


   1  =begin
   2  = resolv library
   3  resolv.rb is a resolver library written in Ruby.
   4  Since it is written in Ruby, it is thread-aware.
   5  I.e. it can resolv many hostnames concurrently.
   6  
   7  It is possible to lookup various resources of DNS using DNS module directly.
   8  
   9  == example
  10    Resolv.getaddress("www.ruby-lang.org")
  11    Resolv.getname("210.251.121.214")
  12  
  13    dns = Resolv::DNS.new
  14    dns.getresources("www.ruby-lang.org", Resolv::DNS::Resource::IN::A).collect {|r| r.address}
  15    dns.getresources("ruby-lang.org", Resolv::DNS::Resource::IN::MX).collect {|r| [r.exchange.to_s, r.preference]}
  16  
  17  == Resolv class
  18  
  19  === class methods
  20  --- Resolv.getaddress(name)
  21  --- Resolv.getaddresses(name)
  22  --- Resolv.each_address(name) {|address| ...}
  23      They lookups IP addresses of ((|name|)) which represents a hostname
  24      as a string by default resolver.
  25  
  26      getaddress returns first entry of lookupped addresses.
  27      getaddresses returns lookupped addresses as an array.
  28      each_address iterates over lookupped addresses.
  29  
  30  --- Resolv.getname(address)
  31  --- Resolv.getnames(address)
  32  --- Resolv.each_name(address) {|name| ...}
  33      lookups hostnames of ((|address|)) which represents IP address as a string.
  34  
  35      getname returns first entry of lookupped names.
  36      getnames returns lookupped names as an array.
  37      each_names iterates over lookupped names.
  38  
  39  == Resolv::Hosts class
  40  hostname resolver using /etc/hosts format.
  41  
  42  === class methods
  43  --- Resolv::Hosts.new(hosts='/etc/hosts')
  44  
  45  === methods
  46  --- Resolv::Hosts#getaddress(name)
  47  --- Resolv::Hosts#getaddresses(name)
  48  --- Resolv::Hosts#each_address(name) {|address| ...}
  49      address lookup methods.
  50  
  51  --- Resolv::Hosts#getname(address)
  52  --- Resolv::Hosts#getnames(address)
  53  --- Resolv::Hosts#each_name(address) {|name| ...}
  54      hostnames lookup methods.
  55  
  56  == Resolv::DNS class
  57  DNS stub resolver.
  58  
  59  === class methods
  60  --- Resolv::DNS.new(resolv_conf='/etc/resolv.conf')
  61  
  62  --- Resolv::DNS.open(resolv_conf='/etc/resolv.conf')
  63  --- Resolv::DNS.open(resolv_conf='/etc/resolv.conf') {|dns| ...}
  64  
  65  === methods
  66  --- Resolv::DNS#close
  67  
  68  --- Resolv::DNS#getaddress(name)
  69  --- Resolv::DNS#getaddresses(name)
  70  --- Resolv::DNS#each_address(name) {|address| ...}
  71      address lookup methods.
  72  
  73      ((|name|)) must be a instance of Resolv::DNS::Name or String.  Lookupped
  74      address is represented as an instance of Resolv::IPv4 or Resolv::IPv6.
  75  
  76  --- Resolv::DNS#getname(address)
  77  --- Resolv::DNS#getnames(address)
  78  --- Resolv::DNS#each_name(address) {|name| ...}
  79      hostnames lookup methods.
  80  
  81      ((|address|)) must be a instance of Resolv::IPv4, Resolv::IPv6 or String.
  82      Lookupped name is represented as an instance of Resolv::DNS::Name.
  83  
  84  --- Resolv::DNS#getresource(name, typeclass)
  85  --- Resolv::DNS#getresources(name, typeclass)
  86  --- Resolv::DNS#each_resource(name, typeclass) {|resource| ...}
  87      They lookup DNS resources of ((|name|)).
  88      ((|name|)) must be a instance of Resolv::Name or String.
  89  
  90      ((|typeclass|)) should be one of follows:
  91      * Resolv::DNS::Resource::IN::ANY
  92      * Resolv::DNS::Resource::IN::NS
  93      * Resolv::DNS::Resource::IN::CNAME
  94      * Resolv::DNS::Resource::IN::SOA
  95      * Resolv::DNS::Resource::IN::HINFO
  96      * Resolv::DNS::Resource::IN::MINFO
  97      * Resolv::DNS::Resource::IN::MX
  98      * Resolv::DNS::Resource::IN::TXT
  99      * Resolv::DNS::Resource::IN::ANY
 100      * Resolv::DNS::Resource::IN::A
 101      * Resolv::DNS::Resource::IN::WKS
 102      * Resolv::DNS::Resource::IN::PTR
 103      * Resolv::DNS::Resource::IN::AAAA
 104  
 105      Lookupped resource is represented as an instance of (a subclass of)
 106      Resolv::DNS::Resource. 
 107      (Resolv::DNS::Resource::IN::A, etc.)
 108  
 109  == Resolv::DNS::Resource::IN::NS class
 110  --- name
 111  == Resolv::DNS::Resource::IN::CNAME class
 112  --- name
 113  == Resolv::DNS::Resource::IN::SOA class
 114  --- mname
 115  --- rname
 116  --- serial
 117  --- refresh
 118  --- retry
 119  --- expire
 120  --- minimum
 121  == Resolv::DNS::Resource::IN::HINFO class
 122  --- cpu
 123  --- os
 124  == Resolv::DNS::Resource::IN::MINFO class
 125  --- rmailbx
 126  --- emailbx
 127  == Resolv::DNS::Resource::IN::MX class
 128  --- preference
 129  --- exchange
 130  == Resolv::DNS::Resource::IN::TXT class
 131  --- data
 132  == Resolv::DNS::Resource::IN::A class
 133  --- address
 134  == Resolv::DNS::Resource::IN::WKS class
 135  --- address
 136  --- protocol
 137  --- bitmap
 138  == Resolv::DNS::Resource::IN::PTR class
 139  --- name
 140  == Resolv::DNS::Resource::IN::AAAA class
 141  --- address
 142  
 143  == Resolv::DNS::Name class
 144  
 145  === class methods
 146  --- Resolv::DNS::Name.create(name)
 147  
 148  === methods
 149  --- Resolv::DNS::Name#to_s
 150  
 151  == Resolv::DNS::Resource class
 152  
 153  == Resolv::IPv4 class
 154  === class methods
 155  --- Resolv::IPv4.create(address)
 156  
 157  === methods
 158  --- Resolv::IPv4#to_s
 159  --- Resolv::IPv4#to_name
 160  
 161  === constants
 162  --- Resolv::IPv4::Regex
 163      regular expression for IPv4 address.
 164  
 165  == Resolv::IPv6 class
 166  === class methods
 167  --- Resolv::IPv6.create(address)
 168  
 169  === methods
 170  --- Resolv::IPv6#to_s
 171  --- Resolv::IPv6#to_name
 172  
 173  === constants
 174  --- Resolv::IPv6::Regex
 175      regular expression for IPv6 address.
 176  
 177  == Bugs
 178  * NIS is not supported.
 179  * /etc/nsswitch.conf is not supported.
 180  * IPv6 is not supported.
 181  
 182  =end
 183  
 184  require 'socket'
 185  require 'fcntl'
 186  require 'timeout'
 187  require 'thread'
 188  
 189  class Resolv
 190    def self.getaddress(name)
 191      DefaultResolver.getaddress(name)
 192    end
 193  
 194    def self.getaddresses(name)
 195      DefaultResolver.getaddresses(name)
 196    end
 197  
 198    def self.each_address(name, &block)
 199      DefaultResolver.each_address(name, &block)
 200    end
 201  
 202    def self.getname(address)
 203      DefaultResolver.getname(address)
 204    end
 205  
 206    def self.getnames(address)
 207      DefaultResolver.getnames(address)
 208    end
 209  
 210    def self.each_name(address, &proc)
 211      DefaultResolver.each_name(address, &proc)
 212    end
 213  
 214    def initialize(resolvers=[Hosts.new, DNS.new])
 215      @resolvers = resolvers
 216    end
 217  
 218    def getaddress(name)
 219      each_address(name) {|address| return address}
 220      raise ResolvError.new("no address for #{name}")
 221    end
 222  
 223    def getaddresses(name)
 224      ret = []
 225      each_address(name) {|address| ret << address}
 226      return ret
 227    end
 228  
 229    def each_address(name)
 230      if AddressRegex =~ name
 231        yield name
 232        return
 233      end
 234      yielded = false
 235      @resolvers.each {|r|
 236        r.each_address(name) {|address|
 237          yield address.to_s
 238          yielded = true
 239        }
 240        return if yielded
 241      }
 242    end
 243  
 244    def getname(address)
 245      each_name(address) {|name| return name}
 246      raise ResolvError.new("no name for #{address}")
 247    end
 248  
 249    def getnames(address)
 250      ret = []
 251      each_name(address) {|name| ret << name}
 252      return ret
 253    end
 254  
 255    def each_name(address)
 256      yielded = false
 257      @resolvers.each {|r|
 258        r.each_name(address) {|name|
 259          yield name.to_s
 260          yielded = true
 261        }
 262        return if yielded
 263      }
 264    end
 265  
 266    class ResolvError < StandardError
 267    end
 268  
 269    class ResolvTimeout < TimeoutError
 270    end
 271  
 272    class Hosts
 273      DefaultFileName = '/etc/hosts'
 274  
 275      def initialize(filename = DefaultFileName)
 276        @filename = filename
 277        @mutex = Mutex.new
 278        @initialized = nil
 279      end
 280  
 281      def lazy_initialize
 282        @mutex.synchronize {
 283          unless @initialized
 284            @name2addr = {}
 285            @addr2name = {}
 286            open(@filename) {|f|
 287              f.each {|line|
 288                line.sub!(/#.*/, '')
 289                addr, hostname, *aliases = line.split(/\s+/)
 290                next unless addr
 291                addr.untaint
 292                hostname.untaint
 293                @addr2name[addr] = [] unless @addr2name.include? addr
 294                @addr2name[addr] << hostname
 295                @addr2name[addr] += aliases
 296                @name2addr[hostname] = [] unless @name2addr.include? hostname
 297                @name2addr[hostname] << addr
 298                aliases.each {|n|
 299                  n.untaint
 300                  @name2addr[n] = [] unless @name2addr.include? n
 301                  @name2addr[n] << addr
 302                }
 303              }
 304            }
 305            @name2addr.each {|name, arr| arr.reverse!}
 306            @initialized = true
 307          end
 308        }
 309      end
 310  
 311      def getaddress(name)
 312        each_address(name) {|address| return address}
 313        raise ResolvError.new("#{@filename} has no name: #{name}")
 314      end
 315  
 316      def getaddresses(name)
 317        ret = []
 318        each_address(name) {|address| ret << address}
 319        return ret
 320      end
 321  
 322      def each_address(name, &proc)
 323        lazy_initialize
 324        if @name2addr.include?(name)
 325          @name2addr[name].each(&proc)
 326        end
 327      end
 328  
 329      def getname(address)
 330        each_name(address) {|name| return name}
 331        raise ResolvError.new("#{@filename} has no address: #{address}")
 332      end
 333  
 334      def getnames(address)
 335        ret = []
 336        each_name(address) {|name| ret << name}
 337        return ret
 338      end
 339  
 340      def each_name(address, &proc)
 341        lazy_initialize
 342        if @addr2name.include?(address)
 343          @addr2name[address].each(&proc)
 344        end
 345      end
 346    end
 347  
 348    class DNS
 349      # STD0013 (RFC 1035, etc.)
 350      # ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters
 351  
 352      Port = 53
 353      UDPSize = 512
 354  
 355      DNSThreadGroup = ThreadGroup.new
 356  
 357      def self.open(*args)
 358        dns = new(*args)
 359        return dns unless block_given?
 360        begin
 361          yield dns
 362        ensure
 363          dns.close
 364        end
 365      end
 366  
 367      def initialize(config="/etc/resolv.conf")
 368        @mutex = Mutex.new
 369        @config = Config.new(config)
 370        @initialized = nil
 371      end
 372  
 373      def lazy_initialize
 374        @mutex.synchronize {
 375          unless @initialized
 376            @config.lazy_initialize
 377  
 378            if nameserver = @config.single?
 379              @requester = Requester::ConnectedUDP.new(nameserver)
 380            else
 381              @requester = Requester::UnconnectedUDP.new
 382            end
 383  
 384            @initialized = true
 385          end
 386        }
 387      end
 388  
 389      def close
 390        @mutex.synchronize {
 391          if @initialized
 392            @requester.close if @requester
 393            @requester = nil
 394            @initialized = false
 395          end
 396        }
 397      end
 398  
 399      def getaddress(name)
 400        each_address(name) {|address| return address}
 401        raise ResolvError.new("DNS result has no information for #{name}")
 402      end
 403  
 404      def getaddresses(name)
 405        ret = []
 406        each_address(name) {|address| ret << address}
 407        return ret
 408      end
 409  
 410      def each_address(name)
 411        each_resource(name, Resource::IN::A) {|resource| yield resource.address}
 412      end
 413  
 414      def getname(address)
 415        each_name(address) {|name| return name}
 416        raise ResolvError.new("DNS result has no information for #{address}")
 417      end
 418  
 419      def getnames(address)
 420        ret = []
 421        each_name(address) {|name| ret << name}
 422        return ret
 423      end
 424  
 425      def each_name(address)
 426        case address
 427        when Name
 428          ptr = address
 429        when IPv4::Regex
 430          ptr = IPv4.create(address).to_name
 431        when IPv6::Regex
 432          ptr = IPv6.create(address).to_name
 433        else
 434          raise ResolvError.new("cannot interpret as address: #{address}")
 435        end
 436        each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name}
 437      end
 438  
 439      def getresource(name, typeclass)
 440        each_resource(name, typeclass) {|resource| return resource}
 441        raise ResolvError.new("DNS result has no information for #{name}")
 442      end
 443  
 444      def getresources(name, typeclass)
 445        ret = []
 446        each_resource(name, typeclass) {|resource| ret << resource}
 447        return ret
 448      end
 449  
 450      def each_resource(name, typeclass, &proc)
 451        lazy_initialize
 452        q = Queue.new
 453        senders = {}
 454        begin
 455          @config.resolv(name) {|candidate, tout, nameserver|
 456            msg = Message.new
 457            msg.rd = 1
 458            msg.add_question(candidate, typeclass)
 459            unless sender = senders[[candidate, nameserver]]
 460              sender = senders[[candidate, nameserver]] =
 461                @requester.sender(msg, candidate, q, nameserver)
 462            end
 463            sender.send
 464            reply = reply_name = nil
 465            timeout(tout, ResolvTimeout) { reply, reply_name = q.pop }
 466            case reply.rcode
 467            when RCode::NoError
 468              extract_resources(reply, reply_name, typeclass, &proc)
 469              return
 470            when RCode::NXDomain
 471              raise Config::NXDomain.new(reply_name.to_s)
 472            else
 473              raise Config::OtherResolvError.new(reply_name.to_s)
 474            end
 475          }
 476        ensure
 477          @requester.delete(q)
 478        end
 479      end
 480  
 481      def extract_resources(msg, name, typeclass)
 482        if typeclass < Resource::ANY
 483          n0 = Name.create(name)
 484          msg.each_answer {|n, ttl, data|
 485            yield data if n0 == n
 486          }
 487        end
 488        yielded = false
 489        n0 = Name.create(name)
 490        msg.each_answer {|n, ttl, data|
 491          if n0 == n
 492            case data
 493            when typeclass
 494              yield data
 495              yielded = true
 496            when Resource::CNAME
 497              n0 = data.name
 498            end
 499          end
 500        }
 501        return if yielded
 502        msg.each_answer {|n, ttl, data|
 503          if n0 == n
 504            case data
 505            when typeclass
 506              yield data
 507            end
 508          end
 509        }
 510      end
 511  
 512      class Requester
 513        def initialize
 514          @senders = {}
 515        end
 516  
 517        def close
 518          thread, sock, @thread, @sock = @thread, @sock
 519          begin
 520            if thread
 521              thread.kill
 522              thread.join
 523            end
 524          ensure
 525            sock.close if sock
 526          end
 527        end
 528  
 529        def delete(arg)
 530          case arg
 531          when Sender
 532            @senders.delete_if {|k, s| s == arg }
 533          when Queue
 534            @senders.delete_if {|k, s| s.queue == arg }
 535          else
 536            raise ArgumentError.new("neither Sender or Queue: #{arg}")
 537          end
 538        end
 539  
 540        class Sender
 541          def initialize(msg, data, sock, queue)
 542            @msg = msg
 543            @data = data
 544            @sock = sock
 545            @queue = queue
 546          end
 547          attr_reader :queue
 548  
 549          def recv(msg)
 550            @queue.push([msg, @data])
 551          end
 552        end
 553  
 554        class UnconnectedUDP < Requester
 555          def initialize
 556            super()
 557            @sock = UDPSocket.new
 558            @sock.fcntl(Fcntl::F_SETFD, 1)
 559            @id = {}
 560            @id.default = -1
 561            @thread = Thread.new {
 562              DNSThreadGroup.add Thread.current
 563              loop {
 564                reply, from = @sock.recvfrom(UDPSize)
 565                msg = begin
 566                  Message.decode(reply)
 567                rescue DecodeError
 568                  STDERR.print("DNS message decoding error: #{reply.inspect}\n")
 569                  next
 570                end
 571                if s = @senders[[[from[3],from[1]],msg.id]]
 572                  s.recv msg
 573                else
 574                  #STDERR.print("non-handled DNS message: #{msg.inspect} from #{from.inspect}\n")
 575                end
 576              }
 577            }
 578          end
 579  
 580          def sender(msg, data, queue, host, port=Port)
 581            service = [host, port]
 582            id = Thread.exclusive {
 583              @id[service] = (@id[service] + 1) & 0xffff
 584            }
 585            request = msg.encode
 586            request[0,2] = [id].pack('n')
 587            return @senders[[service, id]] =
 588              Sender.new(request, data, @sock, host, port, queue)
 589          end
 590  
 591          class Sender < Requester::Sender
 592            def initialize(msg, data, sock, host, port, queue)
 593              super(msg, data, sock, queue)
 594              @host = host
 595              @port = port
 596            end
 597  
 598            def send
 599              @sock.send(@msg, 0, @host, @port)
 600            end
 601          end
 602        end
 603  
 604        class ConnectedUDP < Requester
 605          def initialize(host, port=Port)
 606            super()
 607            @host = host
 608            @port = port
 609            @sock = UDPSocket.new
 610            @sock.connect(host, port)
 611            @sock.fcntl(Fcntl::F_SETFD, 1)
 612            @id = -1
 613            @thread = Thread.new {
 614              DNSThreadGroup.add Thread.current
 615              loop {
 616                reply = @sock.recv(UDPSize)
 617                msg = begin
 618                  Message.decode(reply)
 619                rescue DecodeError
 620                  STDERR.print("DNS message decoding error: #{reply.inspect}")
 621                  next
 622                end
 623                if s = @senders[msg.id]
 624                  s.recv msg
 625                else
 626                  #STDERR.print("non-handled DNS message: #{msg.inspect}")
 627                end
 628              }
 629            }
 630          end
 631  
 632          def sender(msg, data, queue, host=@host, port=@port)
 633            unless host == @host && port == @port
 634              raise RequestError.new("host/port don't match: #{host}:#{port}")
 635            end
 636            id = Thread.exclusive { @id = (@id + 1) & 0xffff }
 637            request = msg.encode
 638            request[0,2] = [id].pack('n')
 639            return @senders[id] = Sender.new(request, data, @sock, queue)
 640          end
 641  
 642          class Sender < Requester::Sender
 643            def send
 644              @sock.send(@msg, 0)
 645            end
 646          end
 647        end
 648  
 649        class TCP < Requester
 650          def initialize(host, port=Port)
 651            super()
 652            @host = host
 653            @port = port
 654            @sock = TCPSocket.new
 655            @sock.connect(host, port)
 656            @sock.fcntl(Fcntl::F_SETFD, 1)
 657            @id = -1
 658            @senders = {}
 659            @thread = Thread.new {
 660              DNSThreadGroup.add Thread.current
 661              loop {
 662                len = @sock.read(2).unpack('n')
 663                reply = @sock.read(len)
 664                msg = begin
 665                  Message.decode(reply)
 666                rescue DecodeError
 667                  STDERR.print("DNS message decoding error: #{reply.inspect}")
 668                  next
 669                end
 670                if s = @senders[msg.id]
 671                  s.push msg
 672                else
 673                  #STDERR.print("non-handled DNS message: #{msg.inspect}")
 674                end
 675              }
 676            }
 677          end
 678  
 679          def sender(msg, data, queue, host=@host, port=@port)
 680            unless host == @host && port == @port
 681              raise RequestError.new("host/port don't match: #{host}:#{port}")
 682            end
 683            id = Thread.exclusive { @id = (@id + 1) & 0xffff }
 684            request = msg.encode
 685            request[0,2] = [request.length, id].pack('nn')
 686            return @senders[id] = Sender.new(request, data, @sock, queue)
 687          end
 688  
 689          class Sender < Requester::Sender
 690            def send
 691              @sock.print(@msg)
 692              @sock.flush
 693            end
 694          end
 695        end
 696  
 697        class RequestError < StandardError
 698        end
 699      end
 700  
 701      class Config
 702        def initialize(filename="/etc/resolv.conf")
 703          @mutex = Mutex.new
 704          @filename = filename
 705          @initialized = nil
 706        end
 707  
 708        def lazy_initialize
 709          @mutex.synchronize {
 710            unless @initialized
 711              @nameserver = []
 712              @search = nil
 713              @ndots = 1
 714              begin
 715                open(@filename) {|f|
 716                  f.each {|line|
 717                    line.sub!(/[#;].*/, '')
 718                    keyword, *args = line.split(/\s+/)
 719                    args.each { |arg|
 720                      arg.untaint
 721                    }
 722                    next unless keyword
 723                    case keyword
 724                    when 'nameserver'
 725                      @nameserver += args
 726                    when 'domain'
 727                      @search = [Label.split(args[0])]
 728                    when 'search'
 729                      @search = args.map {|arg| Label.split(arg)}
 730                    end
 731                  }
 732                }
 733              rescue Errno::ENOENT
 734              end
 735  
 736              @nameserver = ['0.0.0.0'] if @nameserver.empty?
 737              unless @search
 738                hostname = Socket.gethostname
 739                if /\./ =~ hostname
 740                  @search = [Label.split($')]
 741                else
 742                  @search = [[]]
 743                end
 744              end
 745              @initialized = true
 746            end
 747          }
 748        end
 749  
 750        def single?
 751          lazy_initialize
 752          if @nameserver.length == 1
 753            return @nameserver[0]
 754          else
 755            return nil
 756          end
 757        end
 758  
 759        def generate_candidates(name)
 760          candidates = nil
 761          name = Name.create(name)
 762          if name.absolute?
 763            candidates = [name]
 764          else
 765            if @ndots <= name.length - 1
 766              candidates = [Name.new(name.to_a)]
 767            else
 768              candidates = []
 769            end
 770            candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)})
 771          end
 772          return candidates
 773        end
 774  
 775        InitialTimeout = 5
 776  
 777        def generate_timeouts
 778          ts = [InitialTimeout]
 779          ts << ts[-1] * 2 / @nameserver.length
 780          ts << ts[-1] * 2
 781          ts << ts[-1] * 2
 782          return ts
 783        end
 784  
 785        def resolv(name)
 786          candidates = generate_candidates(name)
 787          timeouts = generate_timeouts
 788          begin
 789            candidates.each {|candidate|
 790              begin
 791                timeouts.each {|tout|
 792                  @nameserver.each {|nameserver|
 793                    begin
 794                      yield candidate, tout, nameserver
 795                    rescue ResolvTimeout
 796                    end
 797                  }
 798                }
 799                raise ResolvError.new("DNS resolv timeout: #{name}")
 800              rescue NXDomain
 801              end
 802            }
 803          rescue OtherResolvError
 804            raise ResolvError.new("DNS error: #{$!.message}")
 805          end
 806          raise ResolvError.new("DNS resolv error: #{name}")
 807        end
 808  
 809        class NXDomain < ResolvError
 810        end
 811  
 812        class OtherResolvError < ResolvError
 813        end
 814      end
 815  
 816      module OpCode
 817        Query = 0
 818        IQuery = 1
 819        Status = 2
 820        Notify = 4
 821        Update = 5
 822      end
 823  
 824      module RCode
 825        NoError = 0
 826        FormErr = 1
 827        ServFail = 2
 828        NXDomain = 3
 829        NotImp = 4
 830        Refused = 5
 831        YXDomain = 6
 832        YXRRSet = 7
 833        NXRRSet = 8
 834        NotAuth = 9
 835        NotZone = 10
 836        BADVERS = 16
 837        BADSIG = 16
 838        BADKEY = 17
 839        BADTIME = 18
 840        BADMODE = 19
 841        BADNAME = 20
 842        BADALG = 21
 843      end
 844  
 845      class DecodeError < StandardError
 846      end
 847  
 848      class EncodeError < StandardError
 849      end
 850  
 851      module Label
 852        def self.split(arg)
 853          labels = []
 854          arg.scan(/[^\.]+/) {labels << Str.new($&)}
 855          return labels
 856        end
 857  
 858        class Str
 859          def initialize(string)
 860            @string = string
 861            @downcase = string.downcase
 862          end
 863          attr_reader :string, :downcase
 864  
 865          def to_s
 866            return @string
 867          end
 868  
 869          def inspect
 870            return "#<#{self.class} #{self.to_s}>"
 871          end
 872  
 873          def ==(other)
 874            return @downcase == other.downcase
 875          end
 876  
 877          def eql?(other)
 878            return self == other
 879          end
 880  
 881          def hash
 882            return @downcase.hash
 883          end
 884        end
 885      end
 886  
 887      class Name
 888        def self.create(arg)
 889          case arg
 890          when Name
 891            return arg
 892          when String
 893            return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
 894          else
 895            raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
 896          end
 897        end
 898  
 899        def initialize(labels, absolute=true)
 900          @labels = labels
 901          @absolute = absolute
 902        end
 903  
 904        def absolute?
 905          return @absolute
 906        end
 907  
 908        def ==(other)
 909          return @labels == other.to_a && @absolute == other.absolute?
 910        end
 911        alias eql? ==
 912  
 913        def hash
 914          return @labels.hash ^ @absolute.hash
 915        end
 916  
 917        def to_a
 918          return @labels
 919        end
 920  
 921        def length
 922          return @labels.length
 923        end
 924  
 925        def [](i)
 926          return @labels[i]
 927        end
 928  
 929        def to_s
 930          return @labels.join('.')
 931        end
 932      end
 933  
 934      class Message
 935        @@identifier = -1
 936  
 937        def initialize(id = (@@identifier += 1) & 0xffff)
 938          @id = id
 939          @qr = 0
 940          @opcode = 0
 941          @aa = 0
 942          @tc = 0
 943          @rd = 0 # recursion desired
 944          @ra = 0 # recursion available
 945          @rcode = 0
 946          @question = []
 947          @answer = []
 948          @authority = []
 949          @additional = []
 950        end
 951  
 952        attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode
 953        attr_reader :question, :answer, :authority, :additional
 954  
 955        def ==(other)
 956          return @id == other.id &&
 957                 @qr == other.qr &&
 958                 @opcode == other.opcode &&
 959                 @aa == other.aa &&
 960                 @tc == other.tc &&
 961                 @rd == other.rd &&
 962                 @ra == other.ra &&
 963                 @rcode == other.rcode &&
 964                 @question == other.question &&
 965                 @answer == other.answer &&
 966                 @authority == other.authority &&
 967                 @additional == other.additional
 968        end
 969  
 970        def add_question(name, typeclass)
 971          @question << [Name.create(name), typeclass]
 972        end
 973  
 974        def each_question
 975          @question.each {|name, typeclass|
 976            yield name, typeclass
 977          }
 978        end
 979  
 980        def add_answer(name, ttl, data)
 981          @answer << [Name.create(name), ttl, data]
 982        end
 983  
 984        def each_answer
 985          @answer.each {|name, ttl, data|
 986            yield name, ttl, data
 987          }
 988        end
 989  
 990        def add_authority(name, ttl, data)
 991          @authority << [Name.create(name), ttl, data]
 992        end
 993  
 994        def each_authority
 995          @authority.each {|name, ttl, data|
 996            yield name, ttl, data
 997          }
 998        end
 999  
1000        def add_additional(name, ttl, data)
1001          @additional << [Name.create(name), ttl, data]
1002        end
1003  
1004        def each_additional
1005          @additional.each {|name, ttl, data|
1006            yield name, ttl, data
1007          }
1008        end
1009  
1010        def each_resource
1011          each_answer {|name, ttl, data| yield name, ttl, data}
1012          each_authority {|name, ttl, data| yield name, ttl, data}
1013          each_additional {|name, ttl, data| yield name, ttl, data}
1014        end
1015  
1016        def encode
1017          return MessageEncoder.new {|msg|
1018            msg.put_pack('nnnnnn',
1019              @id,
1020              (@qr & 1) << 15 |
1021              (@opcode & 15) << 11 |
1022              (@aa & 1) << 10 |
1023              (@tc & 1) << 9 |
1024              (@rd & 1) << 8 |
1025              (@ra & 1) << 7 |
1026              (@rcode & 15),
1027              @question.length,
1028              @answer.length,
1029              @authority.length,
1030              @additional.length)
1031            @question.each {|q|
1032              name, typeclass = q
1033              msg.put_name(name)
1034              msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue)
1035            }
1036            [@answer, @authority, @additional].each {|rr|
1037              rr.each {|r|
1038                name, ttl, data = r
1039                msg.put_name(name)
1040                msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl)
1041                msg.put_length16 {data.encode_rdata(msg)}
1042              }
1043            }
1044          }.to_s
1045        end
1046  
1047        class MessageEncoder
1048          def initialize
1049            @data = ''
1050            @names = {}
1051            yield self
1052          end
1053  
1054          def to_s
1055            return @data
1056          end
1057  
1058          def put_bytes(d)
1059            @data << d
1060          end
1061  
1062          def put_pack(template, *d)
1063            @data << d.pack(template)
1064          end
1065  
1066          def put_length16
1067            length_index = @data.length
1068            @data << "\0\0"
1069            data_start = @data.length
1070            yield
1071            data_end = @data.length
1072            @data[length_index, 2] = [data_end - data_start].pack("n")
1073          end
1074  
1075          def put_string(d)
1076            self.put_pack("C", d.length)
1077            @data << d
1078          end
1079  
1080          def put_name(d)
1081            put_labels(d.to_a)
1082          end
1083  
1084          def put_labels(d)
1085            d.each_index {|i|
1086              domain = d[i..-1]
1087              if idx = @names[domain]
1088                self.put_pack("n", 0xc000 | idx)
1089                return
1090              else
1091                @names[domain] = @data.length
1092                self.put_label(d[i])
1093              end
1094            }
1095            @data << "\0"
1096          end
1097  
1098          def put_label(d)
1099            self.put_string(d.string)
1100          end
1101        end
1102  
1103        def Message.decode(m)
1104          o = Message.new(0)
1105          MessageDecoder.new(m) {|msg|
1106            id, flag, qdcount, ancount, nscount, arcount =
1107              msg.get_unpack('nnnnnn')
1108            o.id = id
1109            o.qr = (flag >> 15) & 1
1110            o.opcode = (flag >> 11) & 15
1111            o.aa = (flag >> 10) & 1
1112            o.tc = (flag >> 9) & 1
1113            o.rd = (flag >> 8) & 1
1114            o.ra = (flag >> 7) & 1
1115            o.rcode = flag & 15
1116            (1..qdcount).each {
1117              name, typeclass = msg.get_question
1118              o.add_question(name, typeclass)
1119            }
1120            (1..ancount).each {
1121              name, ttl, data = msg.get_rr
1122              o.add_answer(name, ttl, data)
1123            }
1124            (1..nscount).each {
1125              name, ttl, data = msg.get_rr
1126              o.add_authority(name, ttl, data)
1127            }
1128            (1..arcount).each {
1129              name, ttl, data = msg.get_rr
1130              o.add_additional(name, ttl, data)
1131            }
1132          }
1133          return o
1134        end
1135  
1136        class MessageDecoder
1137          def initialize(data)
1138            @data = data
1139            @index = 0
1140            @limit = data.length
1141            yield self
1142          end
1143  
1144          def get_length16
1145            len, = self.get_unpack('n')
1146            save_limit = @limit
1147            @limit = @index + len
1148            d = yield len
1149            if @index < @limit
1150              raise DecodeError.new("junk exist")
1151            elsif @limit < @index
1152              raise DecodeError.new("limit exceed")
1153            end
1154            @limit = save_limit
1155            return d
1156          end
1157  
1158          def get_bytes(len = @limit - @index)
1159            d = @data[@index, len]
1160            @index += len
1161            return d
1162          end
1163  
1164          def get_unpack(template)
1165            len = 0
1166            template.each_byte {|byte|
1167              case byte
1168              when ?c, ?C
1169                len += 1
1170              when ?n
1171                len += 2
1172              when ?N
1173                len += 4
1174              else
1175                raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'")
1176              end
1177            }
1178            raise DecodeError.new("limit exceed") if @limit < @index + len
1179            arr = @data.unpack("@#{@index}#{template}")
1180            @index += len
1181            return arr
1182          end
1183  
1184          def get_string
1185            len = @data[@index]
1186            raise DecodeError.new("limit exceed") if @limit < @index + 1 + len
1187            d = @data[@index + 1, len]
1188            @index += 1 + len
1189            return d
1190          end
1191  
1192          def get_name
1193            return Name.new(self.get_labels)
1194          end
1195  
1196          def get_labels(limit=nil)
1197            limit = @index if !limit || @index < limit
1198            d = []
1199            while true
1200              case @data[@index]
1201              when 0
1202                @index += 1
1203                return d
1204              when 192..255
1205                idx = self.get_unpack('n')[0] & 0x3fff
1206                if limit <= idx
1207                  raise DecodeError.new("non-backward name pointer")
1208                end
1209                save_index = @index
1210                @index = idx
1211                d += self.get_labels(limit)
1212                @index = save_index
1213                return d
1214              else
1215                d << self.get_label
1216              end
1217            end
1218            return d
1219          end
1220  
1221          def get_label
1222            return Label::Str.new(self.get_string)
1223          end
1224  
1225          def get_question
1226            name = self.get_name
1227            type, klass = self.get_unpack("nn")
1228            return name, Resource.get_class(type, klass)
1229          end
1230  
1231          def get_rr
1232            name = self.get_name
1233            type, klass, ttl = self.get_unpack('nnN')
1234            typeclass = Resource.get_class(type, klass)
1235            return name, ttl, self.get_length16 {typeclass.decode_rdata(self)}
1236          end
1237        end
1238      end
1239  
1240      class Query
1241        def encode_rdata(msg)
1242          raise EncodeError.new("#{self.type} is query.") 
1243        end
1244  
1245        def self.decode_rdata(msg)
1246          raise DecodeError.new("#{self.type} is query.") 
1247        end
1248      end
1249  
1250      class Resource < Query
1251        ClassHash = {}
1252  
1253        def encode_rdata(msg)
1254          raise NotImplementedError.new
1255        end
1256  
1257        def self.decode_rdata(msg)
1258          raise NotImplementedError.new
1259        end
1260  
1261        def ==(other)
1262          return self.type == other.type &&
1263            self.instance_variables == other.instance_variables &&
1264            self.instance_variables.collect {|name| self.instance_eval name} ==
1265              other.instance_variables.collect {|name| other.instance_eval name}
1266        end
1267  
1268        def eql?(other)
1269          return self == other
1270        end
1271  
1272        def hash
1273          h = 0
1274          self.instance_variables.each {|name|
1275            h += self.instance_eval("#{name}.hash")
1276          }
1277          return h
1278        end
1279  
1280        def self.get_class(type_value, class_value)
1281          return ClassHash[[type_value, class_value]] ||
1282                 Generic.create(type_value, class_value)
1283        end
1284  
1285        class Generic < Resource
1286          def initialize(data)
1287            @data = data
1288          end
1289          attr_reader :data
1290  
1291          def encode_rdata(msg)
1292            msg.put_bytes(data)
1293          end
1294  
1295          def self.decode_rdata(msg)
1296            return self.new(msg.get_bytes)
1297          end
1298  
1299          def self.create(type_value, class_value)
1300            c = Class.new(Generic)
1301            c.const_set(:TypeValue, type_value)
1302            c.const_set(:ClassValue, class_value)
1303            Generic.const_set("Type#{type_value}_Class#{class_value}", c)
1304            ClassHash[[type_value, class_value]] = c
1305            return c
1306          end
1307        end
1308  
1309        class DomainName < Resource
1310          def initialize(name)
1311            @name = name
1312          end
1313          attr_reader :name
1314  
1315          def encode_rdata(msg)
1316            msg.put_name(@name)
1317          end
1318  
1319          def self.decode_rdata(msg)
1320            return self.new(msg.get_name)
1321          end
1322        end
1323  
1324        # Standard (class generic) RRs
1325        ClassValue = nil
1326  
1327        class NS < DomainName
1328          TypeValue = 2
1329        end
1330  
1331        class CNAME < DomainName
1332          TypeValue = 5
1333        end
1334  
1335        class SOA < Resource
1336          TypeValue = 6
1337  
1338          def initialize(mname, rname, serial, refresh, retry_, expire, minimum)
1339            @mname = mname
1340            @rname = rname
1341            @serial = serial
1342            @refresh = refresh
1343            @retry = retry_
1344            @expire = expire
1345            @minimum = minimum
1346          end
1347          attr_reader :mname, :rname, :serial, :refresh, :retry, :expire, :minimum
1348  
1349          def encode_rdata(msg)
1350            msg.put_name(@mname)
1351            msg.put_name(@rname)
1352            msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum)
1353          end
1354  
1355          def self.decode_rdata(msg)
1356            mname = msg.get_name
1357            rname = msg.get_name
1358            serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN')
1359            return self.new(
1360              mname, rname, serial, refresh, retry_, expire, minimum)
1361          end
1362        end
1363  
1364        class PTR < DomainName
1365          TypeValue = 12
1366        end
1367  
1368        class HINFO < Resource
1369          TypeValue = 13
1370  
1371          def initialize(cpu, os)
1372            @cpu = cpu
1373            @os = os
1374          end
1375          attr_reader :cpu, :os
1376  
1377          def encode_rdata(msg)
1378            msg.put_string(@cpu)
1379            msg.put_string(@os)
1380          end
1381  
1382          def self.decode_rdata(msg)
1383            cpu = msg.get_string
1384            os = msg.get_string
1385            return self.new(cpu, os)
1386          end
1387        end
1388  
1389        class MINFO < Resource
1390          TypeValue = 14
1391  
1392          def initialize(rmailbx, emailbx)
1393            @rmailbx = rmailbx
1394            @emailbx = emailbx
1395          end
1396          attr_reader :rmailbx, :emailbx
1397  
1398          def encode_rdata(msg)
1399            msg.put_name(@rmailbx)
1400            msg.put_name(@emailbx)
1401          end
1402  
1403          def self.decode_rdata(msg)
1404            rmailbx = msg.get_string
1405            emailbx = msg.get_string
1406            return self.new(rmailbx, emailbx)
1407          end
1408        end
1409  
1410        class MX < Resource
1411          TypeValue= 15
1412  
1413          def initialize(preference, exchange)
1414            @preference = preference
1415            @exchange = exchange
1416          end
1417          attr_reader :preference, :exchange
1418  
1419          def encode_rdata(msg)
1420            msg.put_pack('n', @preference)
1421            msg.put_name(@exchange)
1422          end
1423  
1424          def self.decode_rdata(msg)
1425            preference, = msg.get_unpack('n')
1426            exchange = msg.get_name
1427            return self.new(preference, exchange)
1428          end
1429        end
1430  
1431        class TXT < Resource
1432          TypeValue = 16
1433  
1434          def initialize(data)
1435            @data = data
1436          end
1437          attr_reader :data
1438  
1439          def encode_rdata(msg)
1440            msg.put_string(@data)
1441          end
1442  
1443          def self.decode_rdata(msg)
1444            data = msg.get_string
1445            return self.new(data)
1446          end
1447        end
1448  
1449        class ANY < Query
1450          TypeValue = 255
1451        end
1452  
1453        ClassInsensitiveTypes = [
1454          NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, ANY
1455        ]
1456  
1457        # ARPA Internet specific RRs
1458        module IN
1459          ClassValue = 1
1460  
1461          ClassInsensitiveTypes.each {|s|
1462            c = Class.new(s)
1463            c.const_set(:TypeValue, s::TypeValue)
1464            c.const_set(:ClassValue, ClassValue)
1465            ClassHash[[s::TypeValue, ClassValue]] = c
1466            self.const_set(s.name.sub(/.*::/, ''), c)
1467          }
1468  
1469          class A < Resource
1470            ClassHash[[TypeValue = 1, ClassValue = ClassValue]] = self
1471  
1472            def initialize(address)
1473              @address = IPv4.create(address)
1474            end
1475            attr_reader :address
1476  
1477            def encode_rdata(msg)
1478              msg.put_bytes(@address.address)
1479            end
1480  
1481            def self.decode_rdata(msg)
1482              return self.new(IPv4.new(msg.get_bytes(4)))
1483            end
1484          end
1485  
1486          class WKS < Resource
1487            ClassHash[[TypeValue = 11, ClassValue = ClassValue]] = self
1488  
1489            def initialize(address, protocol, bitmap)
1490              @address = IPv4.create(address)
1491              @protocol = protocol
1492              @bitmap = bitmap
1493            end
1494            attr_reader :address, :protocol, :bitmap
1495  
1496            def encode_rdata(msg)
1497              msg.put_bytes(@address.address)
1498              msg.put_pack("n", @protocol)
1499              msg.put_bytes(@bitmap)
1500            end
1501  
1502            def self.decode_rdata(msg)
1503              address = IPv4.new(msg.get_bytes(4))
1504              protocol, = msg.get_unpack("n")
1505              bitmap = msg.get_bytes
1506              return self.new(address, protocol, bitmap)
1507            end
1508          end
1509  
1510          class AAAA < Resource
1511            ClassHash[[TypeValue = 28, ClassValue = ClassValue]] = self
1512  
1513            def initialize(address)
1514              @address = IPv6.create(address)
1515            end
1516            attr_reader :address
1517  
1518            def encode_rdata(msg)
1519              msg.put_bytes(@address.address)
1520            end
1521  
1522            def self.decode_rdata(msg)
1523              return self.new(IPv6.new(msg.get_bytes(16)))
1524            end
1525          end
1526        end
1527      end
1528    end
1529  
1530    class IPv4
1531      Regex = /\A(\d+)\.(\d+)\.(\d+)\.(\d+)\z/
1532  
1533      def self.create(arg)
1534        case arg
1535        when IPv4
1536          return arg
1537        when Regex
1538          if (0..255) === (a = $1.to_i) &&
1539             (0..255) === (b = $2.to_i) &&
1540             (0..255) === (c = $3.to_i) &&
1541             (0..255) === (d = $4.to_i)
1542            return self.new([a, b, c, d].pack("CCCC"))
1543          else
1544            raise ArgumentError.new("IPv4 address with invalid value: " + arg)
1545          end
1546        else
1547          raise ArgumentError.new("cannot interprete as IPv4 address: #{arg.inspect}")
1548        end
1549      end
1550  
1551      def initialize(address)
1552        unless address.kind_of?(String) && address.length == 4
1553          raise ArgumentError.new('IPv4 address muse be 4 bytes')
1554        end
1555        @address = address
1556      end
1557      attr_reader :address
1558  
1559      def to_s
1560        return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC"))
1561      end
1562  
1563      def inspect
1564        return "#<#{self.class} #{self.to_s}>"
1565      end
1566  
1567      def to_name
1568        return DNS::Name.create(
1569          '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse)
1570      end
1571  
1572      def ==(other)
1573        return @address == other.address
1574      end
1575  
1576      def eql?(other)
1577        return self == other
1578      end
1579  
1580      def hash
1581        return @address.hash
1582      end
1583    end
1584  
1585    class IPv6
1586      Regex_8Hex = /\A
1587        (?:[0-9A-Fa-f]{1,4}:){7}
1588           [0-9A-Fa-f]{1,4}
1589        \z/x
1590  
1591      Regex_CompressedHex = /\A
1592        ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
1593        ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
1594        \z/x
1595  
1596      Regex_6Hex4Dec = /\A
1597        ((?:[0-9A-Fa-f]{1,4}:){6,6})
1598        (\d+)\.(\d+)\.(\d+)\.(\d+)
1599        \z/x
1600  
1601      Regex_CompressedHex4Dec = /\A
1602        ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
1603        ((?:[0-9A-Fa-f]{1,4}:)*)
1604        (\d+)\.(\d+)\.(\d+)\.(\d+)
1605        \z/x
1606  
1607      Regex = /
1608        (?:#{Regex_8Hex.source}) |
1609        (?:#{Regex_CompressedHex.source}) |
1610        (?:#{Regex_6Hex4Dec.source}) |
1611        (?:#{Regex_CompressedHex4Dec.source})/x
1612  
1613      def self.create(arg)
1614        case arg
1615        when IPv6
1616          return arg
1617        when String
1618          address = ''
1619          if Regex_8Hex =~ arg
1620            arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
1621          elsif Regex_CompressedHex =~ arg
1622            prefix = $1
1623            suffix = $2
1624            a1 = ''
1625            a2 = ''
1626            prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
1627            suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
1628            omitlen = 16 - a1.length - a2.length
1629            address << a1 << "\0" * omitlen << a2
1630          elsif Regex_6Hex4Dec =~ arg
1631            prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i
1632            if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
1633              prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
1634              address << [a, b, c, d].pack('CCCC')
1635            else
1636              raise ArgumentError.new("not numeric IPv6 address: " + arg)
1637            end
1638          elsif Regex_CompressedHex4Dec =~ arg
1639            prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i
1640            if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
1641              a1 = ''
1642              a2 = ''
1643              prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
1644              suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
1645              omitlen = 12 - a1.length - a2.length
1646              address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC')
1647            else
1648              raise ArgumentError.new("not numeric IPv6 address: " + arg)
1649            end
1650          else
1651            raise ArgumentError.new("not numeric IPv6 address: " + arg)
1652          end
1653          return IPv6.new(address)
1654        else
1655          raise ArgumentError.new("cannot interprete as IPv6 address: #{arg.inspect}")
1656        end
1657      end
1658  
1659      def initialize(address)
1660        unless address.kind_of?(String) && address.length == 16
1661          raise ArgumentError.new('IPv6 address muse be 16 bytes')
1662        end
1663        @address = address
1664      end
1665      attr_reader :address
1666  
1667      def to_s
1668        address = sprintf("%X:%X:%X:%X:%X:%X:%X:%X", *@address.unpack("nnnnnnnn"))
1669        unless address.sub!(/(^|:)0(:0)+(:|$)/, '::')
1670          address.sub!(/(^|:)0(:|$)/, '::')
1671        end
1672        return address
1673      end
1674  
1675      def inspect
1676        return "#<#{self.class} #{self.to_s}>"
1677      end
1678  
1679      def to_name
1680        # ip6.arpa should be searched too. [RFC3152]
1681        return DNS::Name.new(
1682          @address.unpack("H32")[0].split(//).reverse + ['ip6', 'int'])
1683      end
1684  
1685      def ==(other)
1686        return @address == other.address
1687      end
1688  
1689      def eql?(other)
1690        return self == other
1691      end
1692  
1693      def hash
1694        return @address.hash
1695      end
1696    end
1697  
1698    DefaultResolver = self.new
1699    AddressRegex = /(?:#{IPv4::Regex.source})|(?:#{IPv6::Regex.source})/
1700  end