Hello

I've seen there is some work done on ipaddr, for the ruby version 1.8,
I have also made a module for manipulating ip addresses (provided
in attachement) and I would be more than happy if we can find a
way to merge the two together (before it is too late)

Particularly for the following points:
 - having sub classes IPv4, IPv6 (and IPv6::Compatible) instead
   of one class doing it all (fall better in the object oriented design)
 - when creating an address from a string, being able to select
   how the string should be interpreted (ipv4 or ipv4 mapped)
    (OrderStrict, OrderCompatibility, OrderIPv6Only, OrderIPv4Only)
   This will give finer control on the type of addresses that
   should be created (and could allow to easily swap to IPv6 only
   or remover IPv4 mapped addresses (there are already some talk about it))
 - keeping some regular expression to test if a string match an ip address
    (IPv6StrictRegex, IPv6Regex, IPv6LooseRegex, ...)
 - having some commonly used constant defined (Loopback)
 - the method name 'reverse' for ipaddr seems confusing when compared
   to class string or array
 - scope_id or prefixlen are not directly part of an ip address
   and so should not be included in the class, if someone want/need
   them a new class should be created, this could be in the
   case of scope_id a class called SockAddr.
 - the | or & operator has some usefullness when dealing with netmask,
   but netmask and IPv4 classes (A, B, C, ..) have been depretiated in
   favor of prefixlen. now for the operators '<<' '>>' and '~', I should
   say that I have difficulties to find a real use for them.

I have also a new version of the resolver library able to work with UDP
or TCP connections and automatically fallback on TCP when dealing with
truncated packets. This library is at the core of one of our project,
and if you know the person dealing with the ruby resolver I would like
to see with him if it is possible to find a way to also merge the two
together.

Sincerly.

# $Id: address.rb,v 1.6 2003/04/01 15:42:16 sdalu Exp $

# 
# AUTHOR   : Stephane D'Alu <sdalu / nic.fr>
# CREATED  : 2002/07/19 07:28:13
#
# COPYRIGHT: AFNIC (c) 2003
# LICENSE  : RUBY
# CONTACT  : 
#
# $Revision: 1.6 $ 
# $Date: 2003/04/01 15:42:16 $
#
# INSPIRED BY:
#   - the ruby file: resolv.rb 
#
# CONTRIBUTORS: (see also CREDITS file)
#
#

require 'address/common'
require 'address/ipv4'
require 'address/ipv6'


##
## All addresses object are immutables
##
class Address
    #
    # Address detection order (between IPv4/IPv6)
    #
    OrderStrict        = [ Address::IPv4, Address::IPv6 ]
    OrderCompatibility = [ Address::IPv6::Compatibility ]
    OrderIPv6Only      = [ Address::IPv6 ]
    OrderIPv4Only      = [ Address::IPv4 ]
    OrderDefault       = OrderStrict


    # Check if a string as a valid address representation 
    #  and respect the address priority order
    def self.is_valid(addr, order=OrderDefault)
	order.each { |klass|
	    return true if addr =~ klass::Regex }
	false
    end

    # Try to convert a string into any address (and respect the
    #  address priority order)
    def self.create(arg, order=OrderDefault)
	order.each { |klass|
	    begin
		return klass::create(arg)
	    rescue InvalidAddress
	    end
	}
	raise InvalidAddress, "can't interpret #{arg.inspect} as address"
    end
end


# $Id: common.rb,v 1.3 2003/04/03 17:11:43 sdalu Exp $

# 
# AUTHOR   : Stephane D'Alu <sdalu / nic.fr>
# CREATED  : 2002/07/19 07:28:13
#
# COPYRIGHT: AFNIC (c) 2003
# LICENSE  : RUBY
# CONTACT  : 
#
# $Revision: 1.3 $ 
# $Date: 2003/04/03 17:11:43 $
#
# INSPIRED BY:
#   - the ruby file: resolv.rb 
#
# CONTRIBUTORS: (see also CREDITS file)
#
#


##
## Basic definition of an address
##
class Address
    class InvalidAddress < ArgumentError
    end

    attr_reader :address

    def namespace   ; ""                              ; end
    def to_name     ; to_dnsform + "." + namespace    ; end

    def inspect     ; "#<#{self.class} #{self.to_s}>" ; end
    def hash        ; @address.hash                   ; end
    def eql?(other) ; @address == other.address       ; end
    alias == eql?
end
# $Id: ipv4.rb,v 1.11 2003/04/03 17:11:43 sdalu Exp $

# 
# AUTHOR   : Stephane D'Alu <sdalu / nic.fr>
# CREATED  : 2002/07/19 07:28:13
#
# COPYRIGHT: AFNIC (c) 2003
# LICENSE  : RUBY
# CONTACT  : 
#
# $Revision: 1.11 $ 
# $Date: 2003/04/03 17:11:43 $
#
# INSPIRED BY:
#   - the ruby file: resolv.rb 
#
# CONTRIBUTORS: (see also CREDITS file)
#
#

require 'socket'
require 'address/common'

class Address
    ##
    ## IPv4 address
    ##
    class IPv4 < Address
	Regex	= /\A(\d+)\.(\d+)\.(\d+)\.(\d+)\z/
	
	def self.is_valid(str)
	    str =~ Regex
	end

	def self.create(arg)
	    case arg
	    when IPv4
		return arg
	    when Regex
		if (0..255) === (a = $1.to_i) &&
		   (0..255) === (b = $2.to_i) &&
		   (0..255) === (c = $3.to_i) &&
		   (0..255) === (d = $4.to_i)
		    return self::new([a, b, c, d].pack("CCCC").untaint.freeze)
		else
		    raise InvalidAddress, 
			"IPv4 address with invalid value: #{arg}"
		end
	    else
		raise InvalidAddress, 
		    "can't interprete as IPv4 address: #{arg.inspect}"
	    end
	end
	
	def initialize(address)
	    unless (address.instance_of?(String) && 
		    address.length == 4 && address.frozen?)
		raise ArgumentError,
		    "IPv4 raw address must be a 4 byte frozen string"
	    end
	    @address = address
	    freeze
	end
	
	def private?
	    # 10.0.0.0     -  10.255.255.255   (10/8       prefix)
	    # 172.16.0.0   -  172.31.255.255   (172.16/12  prefix)
	    # 192.168.0.0  -  192.168.255.255  (192.168/16 prefix)
	    bytes = @address.unpack("CCCC")
	    return (((bytes[0] == 10))                            ||
		    ((bytes[0] == 172) && (bytes[1]&0xf0 == 16))  ||
		    ((bytes[0] == 192) && (bytes[1] == 168)))
	end

	def prefix(size=nil)
	    if size.nil?
		# TODO
		raise RuntimeError, "Not Implemented Yet"
	    else
		if size > @address.size * 8
		    raise ArgumentError, "prefix size too big"
		end
		bytes, bits_shift = size / 8, 8 - (size % 8)
		address = @address.slice(0, bytes) + 
		    ("\0" * (@address.size - bytes))
		address[bytes] = (@address[bytes] >> bits_shift) << bits_shift
		IPv4::new(address.freeze)
	    end
	end

	def to_s
	    "%d.%d.%d.%d" % @address.unpack("CCCC")
	end
	
	def to_dnsform
	    "%d.%d.%d.%d" % @address.unpack('CCCC').reverse
	end

	def protocol  ; Socket::AF_INET ; end
	def namespace ; "in-addr.arpa." ; end


	##
	## IPv4 Loopback
	##
	Loopback = IPv4::create("127.0.0.1")
    end
end
# $Id: ipv6.rb,v 1.11 2003/04/03 17:11:44 sdalu Exp $

# 
# AUTHOR   : Stephane D'Alu <sdalu / nic.fr>
# CREATED  : 2002/07/19 07:28:13
#
# COPYRIGHT: AFNIC (c) 2003
# LICENSE  : RUBY
# CONTACT  : 
#
# $Revision: 1.11 $ 
# $Date: 2003/04/03 17:11:44 $
#
# INSPIRED BY:
#   - the ruby file: resolv.rb 
#
# CONTRIBUTORS: (see also CREDITS file)
#
#

require 'socket'
require 'address/common'
require 'address/ipv4'


class Address
    ##
    ## IPv6 address
    ##
    class IPv6 < Address
	private
	Regex_8Hex = /\A
            (?:[0-9A-Fa-f]{1,4}:){7}
	    [0-9A-Fa-f]{1,4}
            \z/x
	
	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
	
	Regex_6Hex4Dec = /\A
            ((?:[0-9A-Fa-f]{1,4}:){6,6})
            (\d+)\.(\d+)\.(\d+)\.(\d+)
            \z/x
	
	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

	Regex_4Dec = /\A
            (\d+)\.(\d+)\.(\d+)\.(\d+)
            \z/x

	public
	IPv6Regex = /
            (?:#{Regex_8Hex.source})              |
            (?:#{Regex_CompressedHex.source})     |
            (?:#{Regex_6Hex4Dec.source})          |
            (?:#{Regex_CompressedHex4Dec.source})
            /x

	IPv6StrictRegex = /
            (?:#{Regex_8Hex.source})              |
            (?:#{Regex_CompressedHex.source})
            /x

	IPv6LooseRegex = /
            (?:#{Regex_8Hex.source})              |
            (?:#{Regex_CompressedHex.source})     |
            (?:#{Regex_6Hex4Dec.source})          |
            (?:#{Regex_CompressedHex4Dec.source}) |
            (?:#{Regex_4Dec.source})
            /x
	
	Regex = IPv6Regex



	def self.hex_pack(str, data="")
	    str.scan(/[0-9A-Fa-f]+/) { |hex| data << [hex.hex].pack('n') }
	    data
	end

	def self.is_valid(str, opt=Regex)
	    str =~ opt
	end

	def self.create(arg, opt=Regex)
	    case arg
	    when IPv6
		return arg
	    when IPv4
		return self.create("::ffff:#{arg.to_s}")
	    when String
		address = ''

		# According to the option, select the test that
		# should be performed
		test = if    opt == IPv6StrictRegex then 1
		       elsif opt == IPv6Regex       then 2
		       elsif opt == IPv6LooseRegex  then 3
		       else  raise ArgumentError, "unknown option"
		       end

		# Test: a:b:c:d:e:f:g:h
		if    (test >= 1) && Regex_8Hex             =~ arg
		    hex_pack(arg, address)

		# Test: a:b:c::d:e:f
		elsif (test >= 1) && Regex_CompressedHex    =~ arg
		    prefix, suffix = $1, $2
		    a1 = hex_pack(prefix)
		    a2 = hex_pack(suffix)
		    omitlen = 16 - a1.length - a2.length
		    address << a1 << "\0" * omitlen << a2

		# Test: a:b:c:d:e:f:g.h.i.j
		elsif (test >= 2) && Regex_6Hex4Dec =~ arg
		    prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i
		    if (0..255) === a && (0..255) === b && 
		       (0..255) === c && (0..255) === d
			hex_pack(prefix, address)
			address << [a, b, c, d].pack('CCCC')
		    end

		# Test: a::b:c:d.e.f.g
		elsif (test >= 2) && Regex_CompressedHex4Dec =~ arg
		    prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i
		    if (0..255) === a && (0..255) === b && 
		       (0..255) === c && (0..255) === d
			a1 = hex_pack(prefix)
			a2 = hex_pack(suffix)
			omitlen = 12 - a1.length - a2.length
			address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC')
		    end

		# Test: a.b.c.d
		elsif (test >= 3) && Regex_4Dec              =~ arg
		    a, b, c, d = $1.to_i, $2.to_i, $3.to_i, $4.to_i
		    if (0..255) === a && (0..255) === b && 
		       (0..255) === c && (0..255) === d
			address << "\0" * 10 << "\377" * 2 << [a, b, c, d].pack('CCCC')
		    end
		end

		# Check if conversion succed
		if address.length != 16
		    raise InvalidAddress, 
			"IPv6 address with invalid value: #{arg}"
		end

		# Return new address
		return IPv6::new(address.untaint.freeze)
	    else
		raise InvalidAddress,
		    "can't interprete as IPv6 address: #{arg.inspect}"
	    end
	end
	
	def initialize(address)
	    unless (address.instance_of?(String) && 
		    address.length == 16 && address.frozen?)
		raise Argument,
		    "IPv6 raw address must be a 16 byte frozen string"
	    end
	    @address = address
	    freeze
	end
	
	def private?
	    return false
	end

	def prefix(size=nil)
	    if size.nil?
		prefix(64)
	    else
		if size > @address.size * 8
		    raise ArgumentError, "prefix size too big"
		end
		bytes, bits_shift = size / 8, 8 - (size % 8)
		address = @address.slice(0, bytes) + 
		    ("\0" * (@address.size - bytes))
		address[bytes] = (@address[bytes] >> bits_shift) << bits_shift
		IPv6::new(address.freeze)
	    end
	end

	def to_s
	    address = "%X:%X:%X:%X:%X:%X:%X:%X" % @address.unpack("nnnnnnnn")
	    unless address.sub!(/(^|:)0(:0)+(:|$)/, '::')
		address.sub!(/(^|:)0(:|$)/, '::')
	    end
	    address
	end
	
	def to_dnsform
	    @address.unpack("H32")[0].split(//).reverse.join(".")
	end

	def protocol  ; Socket::AF_INET6 ; end
	def namespace ; "ip6.arpa."      ; end

	##
	## IPv6 address
	##  (allow creation of IPv4 mapped address by default)
	##
	class Compatibility < IPv6
	    def self.create(arg, opt=IPv6LooseRegex)
		IPv6::create(arg, opt)
	    end
	end

	##
	## IPv6 Loopback
	## 
	Loopback = IPv6::create("::1")
    end
end