diff --git a/README.rdoc b/README.rdoc index cb6be78..8e93cd1 100644 --- a/README.rdoc +++ b/README.rdoc @@ -72,19 +72,29 @@ Docs:: http://deploy2.github.com/ruby-ip/ * Advanced Subnet Operations sn = IP.new('192.168.0.0/24') ip = IP.new('192.168.0.48/32') - sn.split [#, + sn.split [#, #] (2 evenly divided subnets) - sn.divide_by_subnets(3) [#, - #, - #, + sn.divide_by_subnets(3) [#, + #, + #, #] (4 evenly divided subnets) #keep in mind this always takes into account a network and broadcast address - sn.divide_by_hosts(100) [#, + sn.divide_by_hosts(100) [#, #] (128 hosts each) ip = IP.new('192.168.0.48/32') ip.is_in?(sn) - => true - + => true + +* Deaggregate subnets from range + + IP.new('1.2.0.0').deaggregate(IP.new('1.3.255.255')) + => [#] + IP.new('1.2.0.0').deaggregate(IP.new('1.4.255.255')) + => [#, #] + IP.new('2001:db8:85a3:8d3::').deaggregate(IP.new('2001:0db8:85a3:08d3:ffff:ffff:ffff:ffff')) + => [#] + IP.new('2001:db8:85a3:8d3::').deaggregate(IP.new('2001:db8:85a3:8d3:1::')) + => [#, #] * Convert to and from a compact Array representation diff --git a/lib/ip/base.rb b/lib/ip/base.rb index 6ed5a71..42a65cc 100644 --- a/lib/ip/base.rb +++ b/lib/ip/base.rb @@ -95,7 +95,7 @@ def to_hex # (Removing the last element makes them Comparable, as nil.<=> doesn't exist) def to_a @ctx ? [self.class::PROTO, @addr, @pfxlen, @ctx] : - [self.class::PROTO, @addr, @pfxlen] + [self.class::PROTO, @addr, @pfxlen] end # Return an array representation of the address, with 3 or 4 elements @@ -104,7 +104,7 @@ def to_a # ["v4", "01020304", 28, "context"] def to_ah @ctx ? [self.class::PROTO, to_hex, @pfxlen, @ctx] : - [self.class::PROTO, to_hex, @pfxlen] + [self.class::PROTO, to_hex, @pfxlen] end # Change the prefix length. If nil, the maximum is used (32 or 128) @@ -193,7 +193,7 @@ def to_irange # Here I turn it into 1.2.3.0..1.2.3.255. Which is better? def to_range self.class.new(@addr & ~mask, self.class::ADDR_BITS, @ctx) .. - self.class.new(@addr | mask, self.class::ADDR_BITS, @ctx) + self.class.new(@addr | mask, self.class::ADDR_BITS, @ctx) end # test if the address is in the provided subnet def is_in?(subnet) @@ -228,7 +228,7 @@ def divide_by_subnets(number_subnets) end nets = new_nets break if number_subnets <= nets.length && - nets[0].pfxlen <= (self.class::ADDR_BITS - 1) + nets[0].pfxlen <= (self.class::ADDR_BITS - 1) end nets end @@ -238,7 +238,7 @@ def divide_by_hosts(number_hosts) nets = [] nets << self while number_hosts <= (nets[0].split[0].size - 2) && - nets[0].pfxlen <= (self.class::ADDR_BITS - 1) + nets[0].pfxlen <= (self.class::ADDR_BITS - 1) new_nets = [] nets.each do |net| new_nets = new_nets | net.split @@ -248,6 +248,30 @@ def divide_by_hosts(number_hosts) nets end + # deaggregate address range + # IP.new('1.2.0.0').deaggregate(IP.new('1.3.255.255')) + # => [#] + # IP.new('1.2.0.0').deaggregate(IP.new('1.4.255.255')) + # => [#, #] + # IP.new('2001:db8:85a3:8d3::').deaggregate(IP.new('2001:0db8:85a3:08d3:ffff:ffff:ffff:ffff')) + # => [#] + # IP.new('2001:db8:85a3:8d3::').deaggregate(IP.new('2001:db8:85a3:8d3:1::')) + # => [#, #] + def deaggregate(other) + nets = [] + base = self.to_i + while (base <= other.to_i) do + step = 0 + while ((base | (1 << step)) != base) do + break if ((base | (((~0) & self.class::ADDR_MAX) >> (self.class::ADDR_BITS-1-step))) > other.to_i) + step += 1 + end + nets << self.class.new(base, self.class::ADDR_BITS-step, @ctx) + base += 1 << step + end + nets + end + # The number of IP addresses in subnet # IP.new("1.2.3.4/24").size => 256 def size @@ -325,6 +349,7 @@ class V4 < IP class << self; alias_method :new, :orig_new; end PROTO = 'v4'.freeze PROTO_TO_CLASS[PROTO] = self + ADDR_MAX = 4294967295 ADDR_BITS = 32 MASK = (1 << ADDR_BITS) - 1 ARPA = ".in-addr.arpa." @@ -367,6 +392,7 @@ class V6 < IP class << self; alias_method :new, :orig_new; end PROTO = 'v6'.freeze PROTO_TO_CLASS[PROTO] = self + ADDR_MAX = 340282366920938463463374607431768211455 ADDR_BITS = 128 MASK = (1 << ADDR_BITS) - 1 ARPA = ".ip6.arpa" diff --git a/test/ip_test.rb b/test/ip_test.rb index 6a69ed3..ae013e7 100644 --- a/test/ip_test.rb +++ b/test/ip_test.rb @@ -198,6 +198,16 @@ class IPTest < Minitest::Test IP.new('1.2.3.0/24').divide_by_hosts(68) end + it 'deaggregates address range to subnet' do + assert_equal IP.new('1.2.0.0').deaggregate(IP.new('1.3.255.255')), + [IP.new('1.2.0.0/15')] + end + + it 'deaggregates address range to subnets' do + assert_equal IP.new('1.2.0.0').deaggregate(IP.new('1.4.255.255')), + [IP.new('1.2.0.0/15'), IP.new('1.4.0.0/16')] + end + it 'has size' do assert_equal 256, @addr.size end