Skip to content

Commit

Permalink
Added IPv4#split and reworked IPv4#subnet as per RFC3531 (closes ipad…
Browse files Browse the repository at this point in the history
  • Loading branch information
bluemonk committed May 15, 2011
1 parent bda1e2b commit 8599c87
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 64 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
CHANGED:: Removed extension methods and extension directory to facilitate integration with the stdlib
CHANGED:: Reworked IPv4#<=>, now intuitively sorts objects based on the prefix
CHANGED:: IPv4#supernet now returns "0.0.0.0/0" if supernetting with a prefix less than 1
CHANGED:: IPv4#subnet now accept a new prefix instead of number of subnets (as per RFC3531)
NEW:: IPv6#network
NEW:: Prefix128#host_prefix
NEW:: IPv6#broadcast_u128
NEW:: IPv6#each
NEW:: IPv6#<=>
NEW:: IPv4#split

== ipaddress 0.7.5

Expand Down
3 changes: 3 additions & 0 deletions lib/ipaddress.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ def self.valid_ipv6?(addr)
false
end

#
# Deprecate method
#
def self.deprecate(message = nil) # :nodoc:
message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
warn("DEPRECATION WARNING: #{message}")
Expand Down
73 changes: 46 additions & 27 deletions lib/ipaddress/ipv4.rb
Original file line number Diff line number Diff line change
Expand Up @@ -584,10 +584,10 @@ def reverse
alias_method :arpa, :reverse

#
# Subnetting a network
# Splits a network into different subnets
#
# If the IP Address is a network, it can be divided into
# multiple networks. If +self+ is not a network, the
# multiple networks. If +self+ is not a network, this
# method will calculate the network from the IP and then
# subnet it.
#
Expand All @@ -613,15 +613,19 @@ def reverse
# "172.16.10.64/26",
# "172.16.10.128/25"]
#
# Returns an array of IPAddress objects
# Returns an array of IPv4 objects
#
def subnet(subnets=2)
def split(subnets=2)
unless (1..(2**@prefix.host_prefix)).include? subnets
raise ArgumentError, "Value #{subnets} out of range"
end
calculate_subnets(subnets)
networks = subnet(newprefix(subnets))
until networks.size == subnets
networks = sum_first_found(networks)
end
return networks
end
alias_method :/, :subnet
alias_method :/, :split

#
# Returns a new IPv4 object from the supernetting
Expand Down Expand Up @@ -653,6 +657,37 @@ def supernet(new_prefix)
return self.class.new(@address+"/#{new_prefix}").network
end

#
# This method implements the subnetting function
# similar to the one described in RFC3531.
#
# By specifying a new prefix, the method calculates
# the network number for the given IPv4 object
# and calculates the subnets associated to the new
# prefix.
#
# For example, given the following network:
#
# ip = IPAddress "172.16.10.0/24"
#
# we can calculate the subnets with a /26 prefix
#
# ip.subnets(26).map{&:to_string)
# #=> ["172.16.10.0/26", "172.16.10.64/26",
# "172.16.10.128/26", "172.16.10.192/26"]
#
# The resulting number of subnets will of course always be
# a power of two.
#
def subnet(subprefix)
unless ((@prefix.to_i)..32).include? subprefix
raise ArgumentError, "New prefix must be between #@prefix and 32"
end
Array.new(2**(subprefix-@prefix.to_i)) do |i|
self.class.parse_u32(network_u32+(i*(2**(32-subprefix))), subprefix)
end
end

#
# Returns the difference between two IP addresses
# in unsigned int 32 bits format
Expand Down Expand Up @@ -934,28 +969,12 @@ def self.parse_classful(ip)
#
private

def power_of_2?(int)
Math::log2(int).to_i == Math::log2(int)
end

def closest_power_of_2(num, limit=32)
num.upto(limit) do |i|
return i if power_of_2?(i)
end
end

def calculate_subnets(subnets)
po2 = closest_power_of_2(subnets)
new_prefix = @prefix + Math::log2(po2).to_i
networks = Array.new
(0..po2-1).each do |i|
mul = i * (2**(32-new_prefix))
networks << self.class.parse_u32(network_u32+mul, new_prefix)
end
until networks.size == subnets
networks = sum_first_found(networks)
def newprefix(num)
num.upto(32) do |i|
if (a = Math::log2(i).to_i) == Math::log2(i)
return @prefix + a
end
end
return networks
end

def sum_first_found(arr)
Expand Down
2 changes: 1 addition & 1 deletion lib/ipaddress/prefix.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module IPAddress
# IPAddress::Prefix shouldn't be accesses directly, unless
# for particular needs.
#
class Prefix
class Prefix

include Comparable

Expand Down
87 changes: 51 additions & 36 deletions test/ipaddress/ipv4_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -377,42 +377,57 @@ def test_method_netmask_equal
assert_equal 24, ip.prefix.to_i
end

def test_method_subnet
assert_raise(ArgumentError) {@ip.subnet(0)}
assert_raise(ArgumentError) {@ip.subnet(257)}

arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
"172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27",
"172.16.10.192/27", "172.16.10.224/27"]
assert_equal arr, @network.subnet(8).map {|s| s.to_string}
arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
"172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27",
"172.16.10.192/26"]
assert_equal arr, @network.subnet(7).map {|s| s.to_string}
arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
"172.16.10.96/27", "172.16.10.128/26", "172.16.10.192/26"]
assert_equal arr, @network.subnet(6).map {|s| s.to_string}
arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
"172.16.10.96/27", "172.16.10.128/25"]
assert_equal arr, @network.subnet(5).map {|s| s.to_string}
arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26",
"172.16.10.192/26"]
assert_equal arr, @network.subnet(4).map {|s| s.to_string}
arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/25"]
assert_equal arr, @network.subnet(3).map {|s| s.to_string}
arr = ["172.16.10.0/25", "172.16.10.128/25"]
assert_equal arr, @network.subnet(2).map {|s| s.to_string}
arr = ["172.16.10.0/24"]
assert_equal arr, @network.subnet(1).map {|s| s.to_string}
end

def test_method_supernet
assert_raise(ArgumentError) {@ip.supernet(24)}
assert_equal "0.0.0.0/0", @ip.supernet(0).to_string
assert_equal "0.0.0.0/0", @ip.supernet(-2).to_string
assert_equal "172.16.10.0/23", @ip.supernet(23).to_string
assert_equal "172.16.8.0/22", @ip.supernet(22).to_string
end
def test_method_split
assert_raise(ArgumentError) {@ip.split(0)}
assert_raise(ArgumentError) {@ip.split(257)}

assert_equal @ip.network, @ip.split(1).first

arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
"172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27",
"172.16.10.192/27", "172.16.10.224/27"]
assert_equal arr, @network.split(8).map {|s| s.to_string}
arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
"172.16.10.96/27", "172.16.10.128/27", "172.16.10.160/27",
"172.16.10.192/26"]
assert_equal arr, @network.split(7).map {|s| s.to_string}
arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
"172.16.10.96/27", "172.16.10.128/26", "172.16.10.192/26"]
assert_equal arr, @network.split(6).map {|s| s.to_string}
arr = ["172.16.10.0/27", "172.16.10.32/27", "172.16.10.64/27",
"172.16.10.96/27", "172.16.10.128/25"]
assert_equal arr, @network.split(5).map {|s| s.to_string}
arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26",
"172.16.10.192/26"]
assert_equal arr, @network.split(4).map {|s| s.to_string}
arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/25"]
assert_equal arr, @network.split(3).map {|s| s.to_string}
arr = ["172.16.10.0/25", "172.16.10.128/25"]
assert_equal arr, @network.split(2).map {|s| s.to_string}
arr = ["172.16.10.0/24"]
assert_equal arr, @network.split(1).map {|s| s.to_string}
end

def test_method_subnet
assert_raise(ArgumentError) {@network.subnet(23)}
assert_raise(ArgumentError) {@network.subnet(33)}
assert_nothing_raised {@ip.subnet(30)}
arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26",
"172.16.10.192/26"]
assert_equal arr, @network.subnet(26).map {|s| s.to_string}
arr = ["172.16.10.0/25", "172.16.10.128/25"]
assert_equal arr, @network.subnet(25).map {|s| s.to_string}
arr = ["172.16.10.0/24"]
assert_equal arr, @network.subnet(24).map {|s| s.to_string}
end

def test_method_supernet
assert_raise(ArgumentError) {@ip.supernet(24)}
assert_equal "0.0.0.0/0", @ip.supernet(0).to_string
assert_equal "0.0.0.0/0", @ip.supernet(-2).to_string
assert_equal "172.16.10.0/23", @ip.supernet(23).to_string
assert_equal "172.16.8.0/22", @ip.supernet(22).to_string
end

def test_classmethod_parse_u32
@decimal_values.each do |addr,int|
Expand Down

0 comments on commit 8599c87

Please sign in to comment.