summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarco Ceresa <ceresa@gmail.com>2010-01-19 11:42:16 +0000
committerMarco Ceresa <ceresa@gmail.com>2010-01-19 11:42:16 +0000
commit9030d410a3757558ef23ac2317f2fb82d5a1e324 (patch)
treea943e29731d8976f314a8876dec0049cf171d4f0
parenta6c67904f9daf29142f3e6f989445edd1390f579 (diff)
downloadipaddress-9030d410a3757558ef23ac2317f2fb82d5a1e324.tar.gz
Added IPv6 support
-rw-r--r--README.rdoc12
-rw-r--r--Rakefile18
-rw-r--r--lib/ipaddress.rb2
-rw-r--r--lib/ipaddress/ipv4.rb29
-rw-r--r--lib/ipaddress/ipv6.rb162
-rw-r--r--lib/ipaddress/prefix.rb34
-rw-r--r--test/ipaddress/ipv4_test.rb20
-rw-r--r--test/ipaddress/ipv6_test.rb125
-rw-r--r--test/ipaddress/prefix_test.rb47
-rw-r--r--test/test_helper.rb2
10 files changed, 423 insertions, 28 deletions
diff --git a/README.rdoc b/README.rdoc
index c850627..9017e97 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -5,3 +5,15 @@ Description goes here.
== Copyright
Copyright (c) 2009 Marco Ceresa. See LICENSE for details.
+
+
+=IPv6
+
+== Special addresses
+
+=== Unspecified address
+
+=== Loopback address
+
+
+ \ No newline at end of file
diff --git a/Rakefile b/Rakefile
index d4f44fa..ba08997 100644
--- a/Rakefile
+++ b/Rakefile
@@ -62,4 +62,20 @@ task :console do
sh "irb -rubygems -I lib -r ipaddress.rb"
end
-
+desc "Look for TODO and FIXME tags in the code"
+task :todo do
+ def egrep(pattern)
+ Dir['**/*.rb'].each do |fn|
+ count = 0
+ open(fn) do |f|
+ while line = f.gets
+ count += 1
+ if line =~ pattern
+ puts "#{fn}:#{count}:#{line}"
+ end
+ end
+ end
+ end
+ end
+ egrep /(FIXME|TODO|TBD)/
+end
diff --git a/lib/ipaddress.rb b/lib/ipaddress.rb
index a6fe2da..4752e01 100644
--- a/lib/ipaddress.rb
+++ b/lib/ipaddress.rb
@@ -3,7 +3,7 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
require 'ipaddress/ipbase'
require 'ipaddress/ipv4'
-
+require 'ipaddress/ipv6'
def IPAddress(str)
if str.include? "-"
diff --git a/lib/ipaddress/ipv4.rb b/lib/ipaddress/ipv4.rb
index 2ca0d49..5621a97 100644
--- a/lib/ipaddress/ipv4.rb
+++ b/lib/ipaddress/ipv4.rb
@@ -33,7 +33,6 @@ module IPAddress;
/^10./ => 16, # Class B, from 128.0.0.0 to 191.255.255.255
/^110/ => 24 # Class C, D and E, from 192.0.0.0 to 255.255.255.254
}
-
#
# Creates a new IPv4 address object.
@@ -203,18 +202,30 @@ module IPAddress;
# ip.to_u32
# #=> 167772160
#
+ def to_u32
+ data.unpack("N").first
+ end
+ alias_method :to_i, :to_u32
+
+ # Returns the address portion of an IPv4 object
+ # in a network byte order format.
+ #
+ # ip = IPAddress("172.16.10.1/24")
+ # ip.data
+ # #=> "\254\020\n\001"
+ #
# It is usually used to include an IP address
# in a data packet to be sent over a socket
#
# a = Socket.open(params) # socket details here
# ip = IPAddress("10.1.1.0/24")
- # binary_data = ["Address: "].pack("a*") + ip.to_u32
+ # binary_data = ["Address: "].pack("a*") + ip.data
#
# # Send binary data
# a.puts binary_data
#
- def to_u32
- @octets.pack("CCCC").unpack("N").first
+ def data
+ @octets.pack("CCCC")
end
#
@@ -244,7 +255,7 @@ module IPAddress;
# #=> "01111111000000000000000000000001"
#
def bits
- @octets.pack("CCCC").unpack("B*").first
+ data.unpack("B*").first
end
#
@@ -601,6 +612,14 @@ module IPAddress;
end
#
+ # Docs here
+ # TODO
+ #
+ def self.parse_data(str)
+ self.new str.unpack("CCCC").join(".")
+ end
+
+ #
# Summarization (or aggregation) is the process when two or more
# networks are taken together to check if a supernet, including all
# and only these networks, exists. If it exists then this supernet
diff --git a/lib/ipaddress/ipv6.rb b/lib/ipaddress/ipv6.rb
new file mode 100644
index 0000000..ebb2d03
--- /dev/null
+++ b/lib/ipaddress/ipv6.rb
@@ -0,0 +1,162 @@
+require 'ipaddress/ipbase'
+require 'ipaddress/prefix'
+
+#
+# =Name
+#
+# IPAddress::IPv6 - IP version 6 address manipulation library
+#
+# =Synopsis
+#
+# require 'ipaddress'
+#
+# =Description
+#
+# This library provides a complete
+#
+#
+module IPAddress;
+ class IPv6 < IPBase
+
+ include IPAddress
+ include Enumerable
+ include Comparable
+
+ IN6FORMAT = ("%.4x:"*8).chop
+
+ def initialize(str)
+ ip, netmask = str.split("/")
+
+ # Check the ip and remove white space
+ if IPAddress.valid_ipv6?(ip)
+ @groups = self.class.groups(ip)
+ @address = IN6FORMAT % @groups
+ @compressed = compress_address
+ else
+ raise ArgumentError, "Invalid IP #{ip.inspect}"
+ end
+
+ # Check the prefix
+ if netmask =~ /^\d{1,3}$/
+ @prefix = Prefix128.new(netmask.to_i)
+ else
+ @prefix = Prefix128.new(128)
+ end
+
+ end # def initialize
+
+
+ def address
+ @address
+ end
+
+ def groups
+ @groups
+ end
+
+ def prefix
+ @prefix
+ end
+
+ def to_string
+ "#@address/#@prefix"
+ end
+
+ def to_s
+ "#{compressed}/#@prefix"
+ end
+
+ def to_i
+ to_hex.hex
+ end
+
+ def to_hex
+ hexs.to_s
+ end
+
+ def data
+ @groups.pack("n8")
+ end
+
+ def hexs
+ @address.split(":")
+ end
+
+ def compressed
+ @compressed
+ end
+
+ def unspecified?
+ @prefix == 128 and @compressed == "::"
+ end
+
+ def loopback?
+ @prefix == 128 and @compressed == "::1"
+ end
+
+ #
+ # Expands an IPv6 address in the canocical form
+ #
+ # IPAddress::IPv6.expand "2001:0DB8:0:CD30::"
+ # #=> "2001:0DB8:0000:CD30:0000:0000:0000:0000"
+ #
+ def self.expand(str)
+ self.new(str).address
+ end
+
+ def self.compress(str)
+ self.new(str).compressed
+ end
+
+ def self.groups(str)
+ l, r = if str =~ /^(.*)::(.*)$/
+ [$1,$2].map {|i| i.split ":"}
+ else
+ [str.split(":"),[]]
+ end
+ (l + Array.new(8-l.size-r.size, '0') + r).map {|i| i.hex}
+ end
+
+ def self.parse_data(str)
+ self.new(IN6FORMAT % str.unpack("n8"))
+ end
+
+ private
+
+ def compress_address
+ str = @groups.map{|i| i.to_s 16}.join ":"
+ loop do
+ break if str.sub!(/\A0:0:0:0:0:0:0:0\Z/, '::')
+ break if str.sub!(/\b0:0:0:0:0:0:0\b/, ':')
+ break if str.sub!(/\b0:0:0:0:0:0\b/, ':')
+ break if str.sub!(/\b0:0:0:0:0\b/, ':')
+ break if str.sub!(/\b0:0:0:0\b/, ':')
+ break if str.sub!(/\b0:0:0\b/, ':')
+ break if str.sub!(/\b0:0\b/, ':')
+ break
+ end
+ str.sub(/:{3,}/, '::')
+ end
+
+ end # class IPv6
+
+class IPAddress::IPv6::Unspecified < IPAddress::IPv6
+ def initialize
+ @address = ("0000:"*8).chop
+ @groups = Array.new(8,0)
+ @prefix = Prefix128.new(128)
+ end
+end
+
+class IPAddress::IPv6::Loopback < IPAddress::IPv6
+ def initialize
+ @address = ("0000:"*7)+"0001"
+ @groups = Array.new(7,0).push(1)
+ @prefix = Prefix128.new(128)
+ end
+end
+
+
+
+end # module IPAddress
+
diff --git a/lib/ipaddress/prefix.rb b/lib/ipaddress/prefix.rb
index 61109ff..5726272 100644
--- a/lib/ipaddress/prefix.rb
+++ b/lib/ipaddress/prefix.rb
@@ -1,4 +1,5 @@
module IPAddress
+
class Prefix
include Comparable
@@ -9,11 +10,6 @@ module IPAddress
@prefix = num.to_i
end
- def bits
- "1" * @prefix + "0" * (@size - @prefix)
- #sprintf "%0#@sizeb",(@mask & ~(@mask >> @prefix))
- end
-
def to_s
"#@prefix"
end
@@ -29,15 +25,20 @@ module IPAddress
end # class Prefix
+
class Prefix32 < Prefix
def initialize(num)
- raise ArgumentError unless (1..32).include? num
- @mask = 0xffffffff
- @size = 32
+ unless (1..32).include? num
+ raise ArgumentError, "Prefix must be in range 1..128, got: #{num}"
+ end
super(num)
end
+ def bits
+ "1" * @prefix + "0" * (32 - @prefix)
+ end
+
def to_ip
[bits].pack("B*").unpack("CCCC").join(".")
end
@@ -69,19 +70,20 @@ module IPAddress
class Prefix128 < Prefix
def initialize(num)
- raise ArgumentError unless (1..128).include? num
- @mask = 0xffffffffffffffffffffffffffffffff
- @size = 128
+ unless (1..128).include? num
+ raise ArgumentError, "Prefix must be in range 1..128, got: #{num}"
+ end
super(num)
end
+ def bits
+ "1" * @prefix + "0" * (128 - @prefix)
+ end
+
def to_u128
- [bits].pack("B*").unpack("N4").first
+ eval "0b#{bits}.to_i"
end
-
- end
-
-
+ end # class Prefix123 < Prefix
end # module IPAddress
diff --git a/test/ipaddress/ipv4_test.rb b/test/ipaddress/ipv4_test.rb
index a43bf16..e8b299f 100644
--- a/test/ipaddress/ipv4_test.rb
+++ b/test/ipaddress/ipv4_test.rb
@@ -1,7 +1,7 @@
require 'test_helper'
class IPv4Test < Test::Unit::TestCase
-
+
def setup
@klass = IPAddress::IPv4
@@ -53,6 +53,13 @@ class IPv4Test < Test::Unit::TestCase
ip = @klass.new(i)
assert_instance_of @klass, ip
end
+ assert_instance_of IPAddress::Prefix32, @ip.prefix
+ assert_raise (ArgumentError) do
+ @klass.new
+ end
+ assert_nothing_raised do
+ @klass.new "10.0.0.0/8"
+ end
end
def test_initialize_format_error
@@ -78,6 +85,10 @@ class IPv4Test < Test::Unit::TestCase
def test_initialize_should_require_ip
assert_raise(ArgumentError) { @klass.new }
end
+
+ def test_method_data
+ assert_equal "\254\020\n\001", @ip.data
+ end
def test_method_to_s
@valid_ipv4.each do |arg,attr|
@@ -312,7 +323,12 @@ class IPv4Test < Test::Unit::TestCase
end
-
+ def test_classmethod_parse_data
+ ip = @klass.parse_data "\254\020\n\001"
+ assert_instance_of @klass, ip
+ assert_equal "172.16.10.1", ip.address
+ assert_equal "172.16.10.1/16", ip.to_s
+ end
end # class IPv4Test
diff --git a/test/ipaddress/ipv6_test.rb b/test/ipaddress/ipv6_test.rb
new file mode 100644
index 0000000..afc3b45
--- /dev/null
+++ b/test/ipaddress/ipv6_test.rb
@@ -0,0 +1,125 @@
+require 'test_helper'
+
+class IPv6Test < Test::Unit::TestCase
+
+ def setup
+ @klass = IPAddress::IPv6
+
+ @compress_addr = {
+ "2001:db8:0000:0000:0008:0800:200c:417a" => "2001:db8::8:800:200c:417a",
+ "2001:db8:0:0:8:800:200c:417a" => "2001:db8::8:800:200c:417a",
+ "ff01:0:0:0:0:0:0:101" => "ff01::101",
+ "0:0:0:0:0:0:0:1" => "::1",
+ "0:0:0:0:0:0:0:0" => "::"}
+
+ @ip = @klass.new "2001:db8::8:800:200c:417a/64"
+ end
+
+
+ def test_attribute_address
+ addr = "2001:0db8:0000:0000:0008:0800:200c:417a"
+ assert_equal addr, @ip.address
+ end
+
+ def test_initialize
+ assert_equal 128, @klass.new("::").prefix
+ assert_equal false, "write the initialize tests!!"
+ end
+
+ def test_attribute_groups
+ arr = [8193,3512,0,0,8,2048,8204,16762]
+ assert_equal arr, @ip.groups
+ end
+
+ def test_method_hexs
+ arr = "2001:0db8:0000:0000:0008:0800:200c:417a".split(":")
+ assert_equal arr, @ip.hexs
+ end
+
+ def test_method_to_i
+ bigint = 42540766411282592856906245548098208122
+ assert_equal bigint, @ip.to_i
+ end
+
+ def test_method_to_hex
+ hex = "20010db80000000000080800200c417a"
+ assert_equal hex, @ip.to_hex
+ end
+
+ def test_method_to_s
+ assert_equal "2001:db8::8:800:200c:417a/64", @ip.to_s
+ end
+
+ def test_method_to_string
+ str = "2001:0db8:0000:0000:0008:0800:200c:417a/64"
+ assert_equal str, @ip.to_string
+ end
+
+ def test_method_data
+ str = " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
+ assert_equal str, @ip.data
+ end
+
+ def test_method_compressed
+ assert_equal "1:1:1::1", @klass.new("1:1:1:0:0:0:0:1").compressed
+ assert_equal "1:0:1::1", @klass.new("1:0:1:0:0:0:0:1").compressed
+ assert_equal "1:0:0:1::1", @klass.new("1:0:0:1:0:0:0:1").compressed
+ assert_equal "1::1:0:0:1", @klass.new("1:0:0:0:1:0:0:1").compressed
+ assert_equal "1::1", @klass.new("1:0:0:0:0:0:0:1").compressed
+ end
+
+ def test_method_unspecified?
+ assert_equal true, @klass.new("::").unspecified?
+ assert_equal false, @ip.unspecified?
+ end
+
+ def test_method_loopback?
+ assert_equal true, @klass.new("::1").loopback?
+ assert_equal false, @ip.loopback?
+ end
+
+ def test_classmethod_expand
+ compressed = "2001:db8:0:cd30::"
+ expanded = "2001:0db8:0000:cd30:0000:0000:0000:0000"
+ assert_equal expanded, @klass.expand(compressed)
+ assert_not_equal expanded, @klass.expand("2001:0db8:0:cd3")
+ assert_not_equal expanded, @klass.expand("2001:0db8::cd30")
+ assert_not_equal expanded, @klass.expand("2001:0db8::cd3")
+ end
+
+ def test_classmethod_compress
+ compressed = "2001:db8:0:cd30::"
+ expanded = "2001:0db8:0000:cd30:0000:0000:0000:0000"
+ assert_equal compressed, @klass.compress(expanded)
+ assert_not_equal compressed, @klass.compress("2001:0db8:0:cd3")
+ assert_not_equal compressed, @klass.compress("2001:0db8::cd30")
+ assert_not_equal compressed, @klass.compress("2001:0db8::cd3")
+ end
+
+# def test_classmethod_create_unpecified
+# unspec = @klass.create_unspecified
+# assert_equal "::", unspec.address
+# assert_equal 128, unspec.prefix
+# assert_equal true, unspec.unspecified?
+# assert_instance_of @klass, unspec.class
+# end
+
+# def test_classmethod_create_loopback
+# loopb = @klass.create_loopback
+# assert_equal "::1", loopb.address
+# assert_equal 128, loopb.prefix
+# assert_equal true, loopb.loopback?
+# assert_instance_of @klass, loopb.class
+# end
+
+ def test_classmethod_parse_data
+ str = " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
+ ip = @klass.parse_data str
+ assert_instance_of @klass, ip
+ assert_equal "2001:0db8:0000:0000:0008:0800:200c:417a", ip.address
+ assert_equal "2001:db8::8:800:200c:417a/128", ip.to_s
+ end
+
+end # class IPv4Test
+
+
diff --git a/test/ipaddress/prefix_test.rb b/test/ipaddress/prefix_test.rb
index 7e588d9..a2eed42 100644
--- a/test/ipaddress/prefix_test.rb
+++ b/test/ipaddress/prefix_test.rb
@@ -1,6 +1,6 @@
require 'test_helper'
-class PrefixTest < Test::Unit::TestCase
+class Prefix32Test < Test::Unit::TestCase
def setup
@netmask8 = "255.0.0.0"
@@ -67,8 +67,14 @@ class PrefixTest < Test::Unit::TestCase
assert_equal u32, @klass.new(num).to_u32
end
end
-
+
def test_initialize
+ assert_raise (ArgumentError) do
+ @klass.new 33
+ end
+ assert_nothing_raised do
+ @klass.new 8
+ end
assert_instance_of @klass, @klass.new(8)
end
@@ -93,6 +99,41 @@ class PrefixTest < Test::Unit::TestCase
assert_equal "0.255.255.255", prefix.hostmask
end
-end # class PrefixTest
+end # class Prefix32Test
+class Prefix128Test < Test::Unit::TestCase
+
+ def setup
+ @u128_hash = {
+ 32 => 340282366841710300949110269838224261120,
+ 64 => 340282366920938463444927863358058659840,
+ 96 => 340282366920938463463374607427473244160,
+ 126 => 340282366920938463463374607431768211452}
+
+ @klass = IPAddress::Prefix128
+ end
+
+ def test_initialize
+ assert_raise (ArgumentError) do
+ @klass.new 129
+ end
+ assert_nothing_raised do
+ @klass.new 64
+ end
+ assert_instance_of @klass, @klass.new(64)
+ end
+
+ def test_method_bits
+ prefix = @klass.new(64)
+ str = "1"*64 + "0"*64
+ assert_equal str, prefix.bits
+ end
+
+ def test_method_to_u32
+ @u128_hash.each do |num,u128|
+ assert_equal u128, @klass.new(num).to_u128
+ end
+ end
+
+end # class Prefix128Test
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 8f478e1..146f472 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -1,5 +1,7 @@
require 'rubygems'
require 'test/unit'
+require 'ruby-prof'
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))