IPaddr design
From:
"Stephane D'Alu" <Stephane.DAlu@...>
Date:
2003-04-24 11:59:44 UTC
List:
ruby-core #987
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.
Attachments (4)
address.rb
(1.31 KB, text/x-ruby)
# $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
common.rb
(800 Bytes, text/x-ruby)
# $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
ipv4.rb
(2.34 KB, text/x-ruby)
# $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
ipv6.rb
(5.29 KB, text/x-ruby)
# $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