Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -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 [#<IP::V4 192.168.0.0/25>,
sn.split [#<IP::V4 192.168.0.0/25>,
#<IP::V4 192.168.0.128/25>] (2 evenly divided subnets)
sn.divide_by_subnets(3) [#<IP::V4 192.168.0.0/26>,
#<IP::V4 192.168.0.64/26>,
#<IP::V4 192.168.0.128/26>,
sn.divide_by_subnets(3) [#<IP::V4 192.168.0.0/26>,
#<IP::V4 192.168.0.64/26>,
#<IP::V4 192.168.0.128/26>,
#<IP::V4 192.168.0.192/26>] (4 evenly divided subnets)
#keep in mind this always takes into account a network and broadcast address
sn.divide_by_hosts(100) [#<IP::V4 192.168.0.0/25>,
sn.divide_by_hosts(100) [#<IP::V4 192.168.0.0/25>,
#<IP::V4 192.168.0.128/25>] (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::V4 1.2.0.0/15>]
IP.new('1.2.0.0').deaggregate(IP.new('1.4.255.255'))
=> [#<IP::V4 1.2.0.0/15>, #<IP::V4 1.4.0.0/16>]
IP.new('2001:db8:85a3:8d3::').deaggregate(IP.new('2001:0db8:85a3:08d3:ffff:ffff:ffff:ffff'))
=> [#<IP::V6 2001:db8:85a3:8d3::/64>]
IP.new('2001:db8:85a3:8d3::').deaggregate(IP.new('2001:db8:85a3:8d3:1::'))
=> [#<IP::V6 2001:db8:85a3:8d3::/80>, #<IP::V6 2001:db8:85a3:8d3:1::>]

* Convert to and from a compact Array representation

Expand Down
36 changes: 31 additions & 5 deletions lib/ip/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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::V4 1.2.0.0/15>]
# IP.new('1.2.0.0').deaggregate(IP.new('1.4.255.255'))
# => [#<IP::V4 1.2.0.0/15>, #<IP::V4 1.4.0.0/16>]
# IP.new('2001:db8:85a3:8d3::').deaggregate(IP.new('2001:0db8:85a3:08d3:ffff:ffff:ffff:ffff'))
# => [#<IP::V6 2001:db8:85a3:8d3::/64>]
# IP.new('2001:db8:85a3:8d3::').deaggregate(IP.new('2001:db8:85a3:8d3:1::'))
# => [#<IP::V6 2001:db8:85a3:8d3::/80>, #<IP::V6 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
Expand Down Expand Up @@ -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."
Expand Down Expand Up @@ -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"
Expand Down
10 changes: 10 additions & 0 deletions test/ip_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down