From: Stephane D'Alu Date: 2003-04-24T20:59:44+09:00 Subject: IPaddr design --lCAWRPmW1mITcIfM Content-Type: text/plain; charset=us-ascii Content-Disposition: inline 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. --lCAWRPmW1mITcIfM Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="address.rb" # $Id: address.rb,v 1.6 2003/04/01 15:42:16 sdalu Exp $ # # AUTHOR : Stephane D'Alu # 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 --lCAWRPmW1mITcIfM Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="common.rb" # $Id: common.rb,v 1.3 2003/04/03 17:11:43 sdalu Exp $ # # AUTHOR : Stephane D'Alu # 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 --lCAWRPmW1mITcIfM Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="ipv4.rb" # $Id: ipv4.rb,v 1.11 2003/04/03 17:11:43 sdalu Exp $ # # AUTHOR : Stephane D'Alu # 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 --lCAWRPmW1mITcIfM Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="ipv6.rb" # $Id: ipv6.rb,v 1.11 2003/04/03 17:11:44 sdalu Exp $ # # AUTHOR : Stephane D'Alu # 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 --lCAWRPmW1mITcIfM--