diff options
author | Marco Ceresa <ceresa@gmail.com> | 2010-01-19 11:42:16 +0000 |
---|---|---|
committer | Marco Ceresa <ceresa@gmail.com> | 2010-01-19 11:42:16 +0000 |
commit | 9030d410a3757558ef23ac2317f2fb82d5a1e324 (patch) | |
tree | a943e29731d8976f314a8876dec0049cf171d4f0 | |
parent | a6c67904f9daf29142f3e6f989445edd1390f579 (diff) | |
download | ipaddress-9030d410a3757558ef23ac2317f2fb82d5a1e324.tar.gz |
Added IPv6 support
-rw-r--r-- | README.rdoc | 12 | ||||
-rw-r--r-- | Rakefile | 18 | ||||
-rw-r--r-- | lib/ipaddress.rb | 2 | ||||
-rw-r--r-- | lib/ipaddress/ipv4.rb | 29 | ||||
-rw-r--r-- | lib/ipaddress/ipv6.rb | 162 | ||||
-rw-r--r-- | lib/ipaddress/prefix.rb | 34 | ||||
-rw-r--r-- | test/ipaddress/ipv4_test.rb | 20 | ||||
-rw-r--r-- | test/ipaddress/ipv6_test.rb | 125 | ||||
-rw-r--r-- | test/ipaddress/prefix_test.rb | 47 | ||||
-rw-r--r-- | test/test_helper.rb | 2 |
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 @@ -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')) |