diff --git a/lib/.document b/lib/.document index 989f6c0ffb..b7180b5c7b 100644 --- a/lib/.document +++ b/lib/.document @@ -23,6 +23,7 @@ net observer.rb optionparser.rb pathname.rb +resolv.rb set.rb shellwords.rb singleton.rb diff --git a/lib/resolv.rb b/lib/resolv.rb index e55bb72654..a57245b716 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -1,239 +1,106 @@ -=begin -= resolv library -resolv.rb is a resolver library written in Ruby. -Since it is written in Ruby, it is thread-aware. -I.e. it can resolv many hostnames concurrently. - -It is possible to lookup various resources of DNS using DNS module directly. - -== example - p Resolv.getaddress("www.ruby-lang.org") - p Resolv.getname("210.251.121.214") - - Resolv::DNS.open {|dns| - p dns.getresources("www.ruby-lang.org", Resolv::DNS::Resource::IN::A).collect {|r| r.address} - p dns.getresources("ruby-lang.org", Resolv::DNS::Resource::IN::MX).collect {|r| [r.exchange.to_s, r.preference]} - } - -== Resolv class - -=== class methods ---- Resolv.getaddress(name) ---- Resolv.getaddresses(name) ---- Resolv.each_address(name) {|address| ...} - They lookups IP addresses of ((|name|)) which represents a hostname - as a string by default resolver. - - getaddress returns first entry of lookupped addresses. - getaddresses returns lookupped addresses as an array. - each_address iterates over lookupped addresses. - ---- Resolv.getname(address) ---- Resolv.getnames(address) ---- Resolv.each_name(address) {|name| ...} - lookups hostnames of ((|address|)) which represents IP address as a string. - - getname returns first entry of lookupped names. - getnames returns lookupped names as an array. - each_names iterates over lookupped names. - -== Resolv::Hosts class -hostname resolver using /etc/hosts format. - -=== class methods ---- Resolv::Hosts.new(hosts='/etc/hosts') - -=== methods ---- Resolv::Hosts#getaddress(name) ---- Resolv::Hosts#getaddresses(name) ---- Resolv::Hosts#each_address(name) {|address| ...} - address lookup methods. - ---- Resolv::Hosts#getname(address) ---- Resolv::Hosts#getnames(address) ---- Resolv::Hosts#each_name(address) {|name| ...} - hostnames lookup methods. - -== Resolv::DNS class -DNS stub resolver. - -=== class methods ---- Resolv::DNS.new(config_info=nil) - - ((|config_info|)) should be nil, a string or a hash. - If nil is given, /etc/resolv.conf and platform specific information is used. - If a string is given, it should be a filename which format is same as /etc/resolv.conf. - If a hash is given, it may contains information for nameserver, search and ndots as follows. - - Resolv::DNS.new({:nameserver=>["210.251.121.21"], :search=>["ruby-lang.org"], :ndots=>1}) - ---- Resolv::DNS.open(config_info=nil) ---- Resolv::DNS.open(config_info=nil) {|dns| ...} - -=== methods ---- Resolv::DNS#close - ---- Resolv::DNS#getaddress(name) ---- Resolv::DNS#getaddresses(name) ---- Resolv::DNS#each_address(name) {|address| ...} - address lookup methods. - - ((|name|)) must be a instance of Resolv::DNS::Name or String. Lookupped - address is represented as an instance of Resolv::IPv4 or Resolv::IPv6. - ---- Resolv::DNS#getname(address) ---- Resolv::DNS#getnames(address) ---- Resolv::DNS#each_name(address) {|name| ...} - hostnames lookup methods. - - ((|address|)) must be a instance of Resolv::IPv4, Resolv::IPv6 or String. - Lookupped name is represented as an instance of Resolv::DNS::Name. - ---- Resolv::DNS#getresource(name, typeclass) ---- Resolv::DNS#getresources(name, typeclass) ---- Resolv::DNS#each_resource(name, typeclass) {|resource| ...} - They lookup DNS resources of ((|name|)). - ((|name|)) must be a instance of Resolv::Name or String. - - ((|typeclass|)) should be one of follows: - * Resolv::DNS::Resource::IN::ANY - * Resolv::DNS::Resource::IN::NS - * Resolv::DNS::Resource::IN::CNAME - * Resolv::DNS::Resource::IN::SOA - * Resolv::DNS::Resource::IN::HINFO - * Resolv::DNS::Resource::IN::MINFO - * Resolv::DNS::Resource::IN::MX - * Resolv::DNS::Resource::IN::TXT - * Resolv::DNS::Resource::IN::ANY - * Resolv::DNS::Resource::IN::A - * Resolv::DNS::Resource::IN::WKS - * Resolv::DNS::Resource::IN::PTR - * Resolv::DNS::Resource::IN::AAAA - - Lookupped resource is represented as an instance of (a subclass of) - Resolv::DNS::Resource. - (Resolv::DNS::Resource::IN::A, etc.) - -== Resolv::DNS::Resource::IN::NS class ---- name -== Resolv::DNS::Resource::IN::CNAME class ---- name -== Resolv::DNS::Resource::IN::SOA class ---- mname ---- rname ---- serial ---- refresh ---- retry ---- expire ---- minimum -== Resolv::DNS::Resource::IN::HINFO class ---- cpu ---- os -== Resolv::DNS::Resource::IN::MINFO class ---- rmailbx ---- emailbx -== Resolv::DNS::Resource::IN::MX class ---- preference ---- exchange -== Resolv::DNS::Resource::IN::TXT class ---- data -== Resolv::DNS::Resource::IN::A class ---- address -== Resolv::DNS::Resource::IN::WKS class ---- address ---- protocol ---- bitmap -== Resolv::DNS::Resource::IN::PTR class ---- name -== Resolv::DNS::Resource::IN::AAAA class ---- address - -== Resolv::DNS::Name class - -=== class methods ---- Resolv::DNS::Name.create(name) - -=== methods ---- Resolv::DNS::Name#to_s - -== Resolv::DNS::Resource class - -== Resolv::IPv4 class -=== class methods ---- Resolv::IPv4.create(address) - -=== methods ---- Resolv::IPv4#to_s ---- Resolv::IPv4#to_name - -=== constants ---- Resolv::IPv4::Regex - regular expression for IPv4 address. - -== Resolv::IPv6 class -=== class methods ---- Resolv::IPv6.create(address) - -=== methods ---- Resolv::IPv6#to_s ---- Resolv::IPv6#to_name - -=== constants ---- Resolv::IPv6::Regex - regular expression for IPv6 address. - -== Bugs -* NIS is not supported. -* /etc/nsswitch.conf is not supported. -* IPv6 is not supported. - -=end - require 'socket' require 'fcntl' require 'timeout' require 'thread' +# Resolv is a thread-aware DNS resolver library written in Ruby. Resolv can +# handle multiple DNS requests concurrently without blocking. The ruby +# interpreter. +# +# See also resolv-replace.rb to replace the libc resolver with # Resolv. +# +# Resolv can look up various DNS resources using the DNS module directly. +# +# Examples: +# +# p Resolv.getaddress "www.ruby-lang.org" +# p Resolv.getname "210.251.121.214" +# +# Resolv::DNS.open do |dns| +# ress = dns.getresources "www.ruby-lang.org", Resolv::DNS::Resource::IN::A +# p ress.map { |r| r.address } +# ress = dns.getresources "ruby-lang.org", 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. +# * IPv6 is not supported. + class Resolv + + ## + # 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 Resolv using +resolvers+. + def initialize(resolvers=[Hosts.new, DNS.new]) @resolvers = resolvers 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 @@ -249,17 +116,26 @@ class Resolv } 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| @@ -271,11 +147,18 @@ class Resolv } end - class ResolvError < StandardError - end + ## + # Indicates a failure to resolve a name or address. - class ResolvTimeout < TimeoutError - end + class ResolvError < StandardError; end + + ## + # Indicates a timeout resolving a name or address. + + class ResolvTimeout < TimeoutError; end + + ## + # DNS::Hosts is a hostname resolver that uses the system hosts file. class Hosts if /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM @@ -285,13 +168,16 @@ class Resolv DefaultFileName = '/etc/hosts' end + ## + # Creates a new DNS::Hosts, using +filename+ for its data source. + def initialize(filename = DefaultFileName) @filename = filename @mutex = Mutex.new @initialized = nil end - def lazy_initialize + def lazy_initialize # :nodoc: @mutex.synchronize { unless @initialized @name2addr = {} @@ -322,17 +208,26 @@ class Resolv 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 if @name2addr.include?(name) @@ -340,17 +235,26 @@ class Resolv end 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+ retrived from the hosts file. + def each_name(address, &proc) lazy_initialize if @addr2name.include?(address) @@ -359,15 +263,39 @@ class Resolv end end + ## + # 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 - # STD0013 (RFC 1035, etc.) - # ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters + + ## + # Default DNS Port Port = 53 + + ## + # Default DNS UDP packet size + UDPSize = 512 + ## + # Group of DNS resolver threads + DNSThreadGroup = ThreadGroup.new + ## + # Creates a new DNS resolver. See Resolv::DNS.new for argument details. + # + # Yields the created DNS resolver to the block, if given, otherwise + # returns it. + def self.open(*args) dns = new(*args) return dns unless block_given? @@ -378,13 +306,28 @@ class Resolv 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. + # + # Example: + # + # Resolv::DNS.new(:nameserver => ['210.251.121.21'], + # :search => ['ruby-lang.org'], + # :ndots => 1) + def initialize(config_info=nil) @mutex = Mutex.new @config = Config.new(config_info) @initialized = nil end - def lazy_initialize + def lazy_initialize # :nodoc: @mutex.synchronize { unless @initialized @config.lazy_initialize @@ -401,6 +344,9 @@ class Resolv self end + ## + # Closes the DNS resolver. + def close @mutex.synchronize { if @initialized @@ -411,32 +357,70 @@ class Resolv } end + ## + # Gets the IP address of +name+ from the DNS resolver. + # + # +name+ can be a Resolv::DNS::Name or a String. Retrieved address will + # be a Resolv::IPv4 or Resolv::IPv6 + 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 Resolv::DNS::Name or a String. Retrieved addresses will + # be a Resolv::IPv4 or 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 Resolv::DNS::Name or a String. Retrieved addresses will + # be a Resolv::IPv4 or Resolv::IPv6 + def each_address(name) each_resource(name, Resource::IN::A) {|resource| yield resource.address} end + ## + # Gets the hostname for +address+ from the DNS resolver. + # + # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved + # name will be a Resolv::DNS::Name. + 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 Resolv::IPv4, Resolv::IPv6 or a String. Retrieved + # names will be 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 Resolv::IPv4, Resolv::IPv6 or a String. Retrieved + # names will be Resolv::DNS::Name instances. + def each_name(address) case address when Name @@ -451,17 +435,49 @@ class Resolv each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name} end + ## + # Look up the +typeclass+ DNS resource of +name+. + # + # +name+ must be a Resolv::DNS::Name or a String. + # + # +typeclass+ should be one of the following: + # + # * Resolv::DNS::Resource::IN::A + # * Resolv::DNS::Resource::IN::AAAA + # * Resolv::DNS::Resource::IN::ANY + # * Resolv::DNS::Resource::IN::ANY + # * Resolv::DNS::Resource::IN::CNAME + # * Resolv::DNS::Resource::IN::HINFO + # * Resolv::DNS::Resource::IN::MINFO + # * Resolv::DNS::Resource::IN::MX + # * Resolv::DNS::Resource::IN::NS + # * Resolv::DNS::Resource::IN::PTR + # * Resolv::DNS::Resource::IN::SOA + # * Resolv::DNS::Resource::IN::TXT + # * Resolv::DNS::Resource::IN::WKS + # + # Returned resource is represented as a Resolv::DNS::Resource instance, + # i.e. Resolv::DNS::Resource::IN::A. + 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) lazy_initialize q = Queue.new @@ -493,7 +509,7 @@ class Resolv end end - def extract_resources(msg, name, typeclass) + def extract_resources(msg, name, typeclass) # :nodoc: if typeclass < Resource::ANY n0 = Name.create(name) msg.each_answer {|n, ttl, data| @@ -524,7 +540,7 @@ class Resolv } end - class Requester + class Requester # :nodoc: def initialize @senders = {} end @@ -552,7 +568,7 @@ class Resolv end end - class Sender + class Sender # :nodoc: def initialize(msg, data, sock, queue) @msg = msg @data = data @@ -566,7 +582,7 @@ class Resolv end end - class UnconnectedUDP < Requester + class UnconnectedUDP < Requester # :nodoc: def initialize super() @sock = UDPSocket.new @@ -603,7 +619,7 @@ class Resolv Sender.new(request, data, @sock, host, port, queue) end - class Sender < Requester::Sender + class Sender < Requester::Sender # :nodoc: def initialize(msg, data, sock, host, port, queue) super(msg, data, sock, queue) @host = host @@ -616,7 +632,7 @@ class Resolv end end - class ConnectedUDP < Requester + class ConnectedUDP < Requester # :nodoc: def initialize(host, port=Port) super() @host = host @@ -654,14 +670,14 @@ class Resolv return @senders[id] = Sender.new(request, data, @sock, queue) end - class Sender < Requester::Sender + class Sender < Requester::Sender # :nodoc: def send @sock.send(@msg, 0) end end end - class TCP < Requester + class TCP < Requester # :nodoc: def initialize(host, port=Port) super() @host = host @@ -701,7 +717,7 @@ class Resolv return @senders[id] = Sender.new(request, data, @sock, queue) end - class Sender < Requester::Sender + class Sender < Requester::Sender # :nodoc: def send @sock.print(@msg) @sock.flush @@ -709,11 +725,14 @@ class Resolv end end + ## + # Indicates a problem with the DNS request. + class RequestError < StandardError end end - class Config + class Config # :nodoc: def initialize(config_info=nil) @mutex = Mutex.new @config_info = config_info @@ -883,14 +902,20 @@ class Resolv 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 + module OpCode # :nodoc: Query = 0 IQuery = 1 Status = 2 @@ -898,7 +923,7 @@ class Resolv Update = 5 end - module RCode + module RCode # :nodoc: NoError = 0 FormErr = 1 ServFail = 2 @@ -919,20 +944,26 @@ class Resolv 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 + module Label # :nodoc: def self.split(arg) labels = [] arg.scan(/[^\.]+/) {labels << Str.new($&)} return labels end - class Str + class Str # :nodoc: def initialize(string) @string = string @downcase = string.downcase @@ -961,7 +992,17 @@ class Resolv 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 @@ -973,26 +1014,33 @@ class Resolv end end - def initialize(labels, absolute=true) + def initialize(labels, absolute=true) # :nodoc: @labels = labels @absolute = absolute end - def inspect + def inspect # :nodoc: "#<#{self.class}: #{self.to_s}#{@absolute ? '.' : ''}>" end + ## + # True if this name is absolute. + def absolute? return @absolute end - def ==(other) + def ==(other) # :nodoc: return false unless Name === other return @labels == other.to_a && @absolute == other.absolute? end - alias eql? == - # tests subdomain-of relation. + alias eql? == # :nodoc: + + ## + # Returns true if +other+ is a subdomain. + # + # Example: # # domain = Resolv::DNS::Name.create("y.z") # p Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true @@ -1002,6 +1050,7 @@ class Resolv # p Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false # p 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? @@ -1010,36 +1059,39 @@ class Resolv return @labels[-other_len, other_len] == other.to_a end - def hash + def hash # :nodoc: return @labels.hash ^ @absolute.hash end - def to_a + def to_a # :nodoc: return @labels end - def length + def length # :nodoc: return @labels.length end - def [](i) + 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 Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z" # p Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z" - # + def to_s return @labels.join('.') end end - class Message + class Message # :nodoc: @@identifier = -1 def initialize(id = (@@identifier += 1) & 0xffff) @@ -1152,7 +1204,7 @@ class Resolv }.to_s end - class MessageEncoder + class MessageEncoder # :nodoc: def initialize @data = '' @names = {} @@ -1247,7 +1299,7 @@ class Resolv return o end - class MessageDecoder + class MessageDecoder # :nodoc: def initialize(data) @data = data @index = 0 @@ -1359,39 +1411,46 @@ class Resolv end end + ## + # A DNS query abstract class. + class Query - def encode_rdata(msg) + def encode_rdata(msg) # :nodoc: raise EncodeError.new("#{self.class} is query.") end - def self.decode_rdata(msg) + def self.decode_rdata(msg) # :nodoc: raise DecodeError.new("#{self.class} is query.") end end + ## + # A DNS resource abstract class. + class Resource < Query - ClassHash = {} - def encode_rdata(msg) + ClassHash = {} # :nodoc: + + def encode_rdata(msg) # :nodoc: raise NotImplementedError.new end - def self.decode_rdata(msg) + def self.decode_rdata(msg) # :nodoc: raise NotImplementedError.new end - def ==(other) + def ==(other) # :nodoc: return self.class == other.class && self.instance_variables == other.instance_variables && self.instance_variables.collect {|name| self.instance_eval name} == other.instance_variables.collect {|name| other.instance_eval name} end - def eql?(other) + def eql?(other) # :nodoc: return self == other end - def hash + def hash # :nodoc: h = 0 self.instance_variables.each {|name| h ^= self.instance_eval("#{name}.hash") @@ -1399,26 +1458,37 @@ class Resolv return h end - def self.get_class(type_value, class_value) + def self.get_class(type_value, class_value) # :nodoc: return ClassHash[[type_value, class_value]] || 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) + def encode_rdata(msg) # :nodoc: msg.put_bytes(data) end - def self.decode_rdata(msg) + def self.decode_rdata(msg) # :nodoc: return self.new(msg.get_bytes) end - def self.create(type_value, class_value) + def self.create(type_value, class_value) # :nodoc: c = Class.new(Generic) c.const_set(:TypeValue, type_value) c.const_set(:ClassValue, class_value) @@ -1428,34 +1498,60 @@ class Resolv 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) + def encode_rdata(msg) # :nodoc: msg.put_name(@name) end - def self.decode_rdata(msg) + def self.decode_rdata(msg) # :nodoc: return self.new(msg.get_name) end end # Standard (class generic) RRs - ClassValue = nil + + ClassValue = nil # :nodoc: + + ## + # An authoritative name server. class NS < DomainName - TypeValue = 2 + TypeValue = 2 # :nodoc: end + ## + # The canonical name for an alias. + class CNAME < DomainName - TypeValue = 5 + TypeValue = 5 # :nodoc: end + ## + # Start Of Authority resource. + class SOA < Resource - TypeValue = 6 + + 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 @@ -1466,15 +1562,52 @@ class Resolv @expire = expire @minimum = minimum end - attr_reader :mname, :rname, :serial, :refresh, :retry, :expire, :minimum - def encode_rdata(msg) + ## + # 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) + def self.decode_rdata(msg) # :nodoc: mname = msg.get_name rname = msg.get_name serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN') @@ -1483,106 +1616,172 @@ class Resolv end end + ## + # A Pointer to another DNS name. + class PTR < DomainName - TypeValue = 12 + TypeValue = 12 # :nodoc: end + ## + # Host Information resource. + class HINFO < Resource - TypeValue = 13 + + TypeValue = 13 # :nodoc: + + ## + # Creates a new HINFO running +os+ on +cpu+. def initialize(cpu, os) @cpu = cpu @os = os end - attr_reader :cpu, :os - def encode_rdata(msg) + ## + # 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) + 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 + + TypeValue = 14 # :nodoc: def initialize(rmailbx, emailbx) @rmailbx = rmailbx @emailbx = emailbx end - attr_reader :rmailbx, :emailbx - def encode_rdata(msg) + ## + # 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) + 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 + + TypeValue= 15 # :nodoc: + + ## + # Creates a new MX record with +preference+, accepting mail at + # +exchange+. def initialize(preference, exchange) @preference = preference @exchange = exchange end - attr_reader :preference, :exchange - def encode_rdata(msg) + ## + # 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) + 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 + + 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 first string from +strings+. + def data @strings[0] end - def encode_rdata(msg) + def encode_rdata(msg) # :nodoc: msg.put_string_list(@strings) end - def self.decode_rdata(msg) + def self.decode_rdata(msg) # :nodoc: strings = msg.get_string_list return self.new(*strings) end end + ## + # A Query type requesting any RR. + class ANY < Query - TypeValue = 255 + TypeValue = 255 # :nodoc: end - ClassInsensitiveTypes = [ + ClassInsensitiveTypes = [ # :nodoc: NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, ANY ] - # ARPA Internet specific RRs + ## + # module IN contains ARPA Internet specific RRs. + module IN - ClassValue = 1 + + ClassValue = 1 # :nodoc: ClassInsensitiveTypes.each {|s| c = Class.new(s) @@ -1592,40 +1791,74 @@ class Resolv self.const_set(s.name.sub(/.*::/, ''), c) } + ## + # IPv4 Address resource + class A < Resource - ClassHash[[TypeValue = 1, ClassValue = ClassValue]] = self + + ClassHash[[TypeValue = 1, ClassValue = ClassValue]] = self # :nodoc: + + ## + # Creates a new A for +address+. def initialize(address) @address = IPv4.create(address) end + + ## + # The Resolv::IPv4 address for this A. + attr_reader :address - def encode_rdata(msg) + def encode_rdata(msg) # :nodoc: msg.put_bytes(@address.address) end - def self.decode_rdata(msg) + def self.decode_rdata(msg) # :nodoc: return self.new(IPv4.new(msg.get_bytes(4))) end end + ## + # Well Known Service resource. + class WKS < Resource - ClassHash[[TypeValue = 11, ClassValue = ClassValue]] = self + + ClassHash[[TypeValue = 11, ClassValue = ClassValue]] = self # :nodoc: def initialize(address, protocol, bitmap) @address = IPv4.create(address) @protocol = protocol @bitmap = bitmap end - attr_reader :address, :protocol, :bitmap - def encode_rdata(msg) + ## + # 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) + def self.decode_rdata(msg) # :nodoc: address = IPv4.new(msg.get_bytes(4)) protocol, = msg.get_unpack("n") bitmap = msg.get_bytes @@ -1633,55 +1866,49 @@ class Resolv end end + ## + # An IPv6 address record. + class AAAA < Resource - ClassHash[[TypeValue = 28, ClassValue = ClassValue]] = self + + ClassHash[[TypeValue = 28, ClassValue = ClassValue]] = self # :nodoc: + + ## + # Creates a new AAAA for +address+. def initialize(address) @address = IPv6.create(address) end + + ## + # The Resolv::IPv6 address for this AAAA. + attr_reader :address - def encode_rdata(msg) + def encode_rdata(msg) # :nodoc: msg.put_bytes(@address.address) end - def self.decode_rdata(msg) + 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. - # - # The format is: - # _Service._Proto.Name TTL Class SRV Priority Weight Port Target - # - # The fields specific to SRV are defined in RFC 2782 as meaning: - # - +priority+ 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. - # - # - +weight+ 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. - # - # - +port+ The port on this target host of this service. The range is 0- - # 65535. - # - # - +target+ The domain name of the target host. A target of "." means - # that the service is decidedly not available at this domain. + class SRV < Resource - ClassHash[[TypeValue = 33, ClassValue = ClassValue]] = self + + ClassHash[[TypeValue = 33, ClassValue = 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 @@ -1689,16 +1916,49 @@ class Resolv @target = Name.create(target) end - attr_reader :priority, :weight, :port, :target + # 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. - def encode_rdata(msg) + 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) end - def self.decode_rdata(msg) + def self.decode_rdata(msg) # :nodoc: priority, = msg.get_unpack("n") weight, = msg.get_unpack("n") port, = msg.get_unpack("n") @@ -1706,12 +1966,18 @@ class Resolv return self.new(priority, weight, port, target) end end - end end end + ## + # A Resolv::DNS IPv4 address. + class IPv4 + + ## + # Regular expression IPv4 addresses must match. + Regex = /\A(\d+)\.(\d+)\.(\d+)\.(\d+)\z/ def self.create(arg) @@ -1732,68 +1998,102 @@ class Resolv end end - def initialize(address) + def initialize(address) # :nodoc: unless address.kind_of?(String) && address.length == 4 raise ArgumentError.new('IPv4 address must be 4 bytes') end @address = address end + + ## + # A String reperesentation of this IPv4 address. + + ## + # The raw IPv4 address as a String. + attr_reader :address - def to_s + def to_s # :nodoc: return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC")) end - def inspect + def inspect # :nodoc: return "#<#{self.class} #{self.to_s}>" end + ## + # Turns this IPv4 address into a Resolv::DNS::Name. + def to_name return DNS::Name.create( '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse) end - def ==(other) + def ==(other) # :nodoc: return @address == other.address end - def eql?(other) + def eql?(other) # :nodoc: return self == other end - def hash + def hash # :nodoc: return @address.hash end end + ## + # A 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 + ## + # A composite IPv6 address Regexp. + Regex = / (?:#{Regex_8Hex}) | (?:#{Regex_CompressedHex}) | (?:#{Regex_6Hex4Dec}) | (?:#{Regex_CompressedHex4Dec})/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 @@ -1840,15 +2140,19 @@ class Resolv end end - def initialize(address) + 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 + def to_s # :nodoc: address = sprintf("%X:%X:%X:%X:%X:%X:%X:%X", *@address.unpack("nnnnnnnn")) unless address.sub!(/(^|:)0(:0)+(:|$)/, '::') address.sub!(/(^|:)0(:|$)/, '::') @@ -1856,29 +2160,42 @@ class Resolv return address end - def inspect + def inspect # :nodoc: return "#<#{self.class} #{self.to_s}>" end + ## + # Turns this IPv6 address into a Resolv::DNS::Name. + #-- + # ip6.arpa should be searched too. [RFC3152] + def to_name - # ip6.arpa should be searched too. [RFC3152] return DNS::Name.new( @address.unpack("H32")[0].split(//).reverse + ['ip6', 'int']) end - def ==(other) + def ==(other) # :nodoc: return @address == other.address end - def eql?(other) + def eql?(other) # :nodoc: return self == other end - def hash + def hash # :nodoc: return @address.hash end end + ## + # Default resolver to use for Resolv class methods. + DefaultResolver = self.new + + ## + # Address Regexp to use for matching IP addresses. + AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/ + end +