Patch to 1.8.6 resolv.rb
From:
Gary Wright <gwtmp01@...>
Date:
2007-11-07 22:08:59 UTC
List:
ruby-core #13273
At RubyConf I got motivated to help out on documentation. I dove
into resolv.rb and found that much of the docs are already in the
1.9 version as well as some patches. Eric Hodel, who I think added
the docs, asked in [ruby-core:5205] for the changes to be back
ported. It appears that the changes up to revision 10135 can be
trivially merged with the 1.8.6 version resolv.rb. Changes after
that point are 1.9 specific (String#ord etc).
I did the merge on my local copy, reviewed the diffs, and ran some
tests and everything looks ok.
I'm assuming someone with commit access can run the svn merge, but
the diff that results is included in this message.
I've already found and fixed some IPv6 bugs but I'll post those
patches separately.
Gary Wright
Index: lib/resolv.rb
===================================================================
--- lib/resolv.rb (revision 13825)
+++ lib/resolv.rb (working copy)
@@ -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 @@
}
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,12 +147,19 @@
}
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
require 'win32/resolv'
@@ -285,13 +168,16 @@
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 @@
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 @@
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 @@
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 @@
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 @@
self
end
+ ##
+ # Closes the DNS resolver.
+
def close
@mutex.synchronize {
if @initialized
@@ -411,32 +357,70 @@
}
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 @@
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 @@
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 @@
}
end
- class Requester
+ class Requester # :nodoc:
def initialize
@senders = {}
end
@@ -552,7 +568,7 @@
end
end
- class Sender
+ class Sender # :nodoc:
def initialize(msg, data, sock, queue)
@msg = msg
@data = data
@@ -566,7 +582,7 @@
end
end
- class UnconnectedUDP < Requester
+ class UnconnectedUDP < Requester # :nodoc:
def initialize
super()
@sock = UDPSocket.new
@@ -603,7 +619,7 @@
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 @@
end
end
- class ConnectedUDP < Requester
+ class ConnectedUDP < Requester # :nodoc:
def initialize(host, port=Port)
super()
@host = host
@@ -654,14 +670,14 @@
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 @@
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 @@
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 @@
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 @@
Update = 5
end
- module RCode
+ module RCode # :nodoc:
NoError = 0
FormErr = 1
ServFail = 2
@@ -919,20 +944,26 @@
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 @@
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,27 +1014,34 @@
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
# p Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain)
#=> true
@@ -1002,6 +1050,7 @@
# 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 @@
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 @@
}.to_s
end
- class MessageEncoder
+ class MessageEncoder # :nodoc:
def initialize
@data = ''
@names = {}
@@ -1247,7 +1299,7 @@
return o
end
- class MessageDecoder
+ class MessageDecoder # :nodoc:
def initialize(data)
@data = data
@index = 0
@@ -1354,71 +1406,104 @@
name = self.get_name
type, klass, ttl = self.get_unpack('nnN')
typeclass = Resource.get_class(type, klass)
- return name, ttl, self.get_length16 {typeclass.decode_rdata
(self)}
+ res = self.get_length16 { typeclass.decode_rdata self }
+ res.instance_variable_set :@ttl, ttl
+ return name, ttl, res
end
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)
+ ##
+ # Remaining Time To Live for this Resource.
+
+ attr_reader :ttl
+
+ 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)
- 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}
+ def ==(other) # :nodoc:
+ return false unless self.class == other.class
+ s_ivars = self.instance_variables
+ s_ivars.sort!
+ s_ivars.delete "@ttl"
+ o_ivars = other.instance_variables
+ o_ivars.sort!
+ o_ivars.delete "@ttl"
+ return s_ivars == o_ivars &&
+ s_ivars.collect {|name| self.instance_variable_get name} ==
+ o_ivars.collect {|name| other.instance_variable_get name}
end
- def eql?(other)
+ 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")
+ vars = self.instance_variables
+ vars.delete "@ttl"
+ vars.each {|name|
+ h ^= self.instance_variable_get(name).hash
}
return h
end
- def self.get_class(type_value, class_value)
+ 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,35 +1513,61 @@
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
@rname = rname
@@ -1466,15 +1577,52 @@
@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,107 +1631,173 @@
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)
c.const_set(:TypeValue, s::TypeValue)
@@ -1592,40 +1806,76 @@
self.const_set(s.name.sub(/.*::/, ''), c)
}
+ ##
+ # IPv4 Address resource
+
class A < Resource
- ClassHash[[TypeValue = 1, ClassValue = ClassValue]] = self
+ TypeValue = 1
+ ClassValue = IN::ClassValue
+ ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
+ ##
+ # Creates a new A for +address+.
+
def initialize(address)
@address = IPv4.create(address)
end
+
+ ##
+ # The 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
+ TypeValue = 11
+ ClassValue = IN::ClassValue
+ ClassHash[[TypeValue, 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 +1883,51 @@
end
end
+ ##
+ # An IPv6 address record.
+
class AAAA < Resource
- ClassHash[[TypeValue = 28, ClassValue = ClassValue]] = self
+ TypeValue = 28
+ ClassValue = IN::ClassValue
+ ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
+ ##
+ # Creates a new AAAA for +address+.
+
def initialize(address)
@address = IPv6.create(address)
end
+
+ ##
+ # The 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
+ TypeValue = 33
+ ClassValue = IN::ClassValue
+ ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
# Create a SRV resource record.
+ #
+ # See the documentation for #priority, #weight, #port and
#target
+ # for +priority+, +weight+, +port and +target+ respectively.
+
def initialize(priority, weight, port, target)
@priority = priority.to_int
@weight = weight.to_int
@@ -1689,16 +1935,49 @@
@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 +1985,18 @@
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 +2017,102 @@
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 +2159,19 @@
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 +2179,42 @@
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
+