diff options
-rw-r--r-- | README.rdoc | 66 | ||||
-rw-r--r-- | lib/ipaddress/extensions/extensions.rb | 12 | ||||
-rw-r--r-- | lib/ipaddress/ipv4.rb | 48 | ||||
-rw-r--r-- | test/ipaddress/ipv4_test.rb | 26 |
4 files changed, 110 insertions, 42 deletions
diff --git a/README.rdoc b/README.rdoc index 875fa6d..564c7f1 100644 --- a/README.rdoc +++ b/README.rdoc @@ -81,9 +81,9 @@ If you don't specify a prefix (or a subnet mask), then the library will create an object base on the CLASSFUL network from the given IP. Remember the CLASSFUL network are the following (RFC 791) - Class A, from 0.0.0.0 to 127.255.255.255 - Class B, from 128.0.0.0 to 191.255.255.255 - Class C, from 192.0.0.0 to 255.255.255.255 +* Class A, from 0.0.0.0 to 127.255.255.255 +* Class B, from 128.0.0.0 to 191.255.255.255 +* Class C, from 192.0.0.0 to 255.255.255.255 Since classful networks here are only considered to calculate the default prefix number, classes D and E are not considered. @@ -168,8 +168,8 @@ included in a range. When you specify an IPv4 address such as "172.16.10.1/24", you are actually handling two different information: - * The IP address itself, "172.16.10.1" - * The subnet mask which indicates the network +* The IP address itself, "172.16.10.1" +* The subnet mask which indicates the network The network number is the IP which has all zeroes in the host portion. In our example, because the prefix is 24, we identify our @@ -309,17 +309,51 @@ IPAddress includes a lot of useful methods to manipulate IPv4 and IPv6 networks and do some basic network design. === Subnetting - # Subnetting a network - # - # If the IP Address is a network, it can be divided into - # multiple networks. If +self+ is not a network, the - # method will calculate the network from the IP and then - # subnet it. - # - # If +subnets+ is an even number, the resulting networks will be - # divided evenly from the supernet. - # - # network = IPAddress("172.16.10.0/24") + +The process of subnetting is the division of a network into smaller +(in terms of hosts capacity) networks, called subnets, so that they +all share a common root, which is the starting network. + +For example, if the have network "172.16.10.0/24", we can subnet it +into 4 smaller subnets. The new prefix will be /26, because 4 is 2^2 +and therefore we add 2 bits to the network prefix (24+2=26). + +Subnetting is easy with IPAddress. Let's work out the last example: + + network = IPAddress("172.16.10.0/24") + + subnets = network / 4 + #=> [#<IPAddress::IPv4:0xb7b10e10 @octets=[172,16,10,0] [...] + #<IPAddress::IPv4:0xb7b0f1b4 @octets=[172,16,10,64] [...] + #<IPAddress::IPv4:0xb7b0e5ac @octets=[172,16,10,128] [...] + #<IPAddress::IPv4:0xb7b0e0c0 @octets=[172,16,10,192] [...]] + + subnets.map{|i| i.to_s} + #=> ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26", + "172.16.10.192/26"] + +You can also use method IPv4#subnets, which is an alias for +IPv4#/. Please note that you don't have to specify a network to +calculate a subnet: if the IPv4 object is a host IPv4, the method will +calculate the network number for that network and then subnet it. For +example: + + ip = IPAddress("172.16.10.58/24") + + ip.subnet(4).map{|i| i.to_s} + #=> ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26", + "172.16.10.192/26"] + +Usually, subnetting implies dividing a network to a number of subnets +which is a power of two: in this way, you can be sure that the network +will be divived evenly, and all the subnets will have the same number +of hosts. +IPAddress also handles un-even subnetting: if you specify any number +(up to the prefix limit), the network will be divided so that the +first power-of-two networks will be even, and all the rest will just +fill out the space. + + # network / 4 # implies map{|i| i.to_s} # #=> ["172.16.10.0/26", # "172.16.10.64/26", diff --git a/lib/ipaddress/extensions/extensions.rb b/lib/ipaddress/extensions/extensions.rb index ca73f9a..3090244 100644 --- a/lib/ipaddress/extensions/extensions.rb +++ b/lib/ipaddress/extensions/extensions.rb @@ -2,3 +2,15 @@ class << Math def log2(n); log(n) / log(2); end end +class Integer + def power_of_2? + Math::log2(self).to_i == Math::log2(self) + end + + def closest_power_of_2 + self.upto(32) do |i| + return i if i.power_of_2? + end + end +end + diff --git a/lib/ipaddress/ipv4.rb b/lib/ipaddress/ipv4.rb index 823d216..7c5bc0a 100644 --- a/lib/ipaddress/ipv4.rb +++ b/lib/ipaddress/ipv4.rb @@ -519,8 +519,8 @@ module IPAddress; # method will calculate the network from the IP and then # subnet it. # - # If +subnets+ is an even number, the resulting networks will be - # divided evenly from the supernet. + # If +subnets+ is an power of two number, the resulting + # networks will be divided evenly from the supernet. # # network = IPAddress("172.16.10.0/24") # network / 4 # implies map{|i| i.to_s} @@ -529,9 +529,9 @@ module IPAddress; # "172.16.10.128/26", # "172.16.10.192/26"] # - # If +num+ is a odd number, the supernet will be - # divided into num-1 networks with a even number of hosts and - # a last network with the remaining addresses. + # If +num+ is any other number, the supernet will be + # divided into some networks with a even number of hosts and + # other networks with the remaining addresses. # # network = IPAddress("172.16.10.0/24") # network / 3 # implies map{|i| i.to_s} @@ -539,18 +539,14 @@ module IPAddress; # "172.16.10.64/26", # "172.16.10.128/25"] # - # Returns an array of IPAddress objects, + # Returns an array of IPAddress objects # def subnet(subnets=2) unless (1..(2**(32-prefix.to_i))).include? subnets raise ArgumentError, "Value #{subnets} out of range" end - - if subnets.even? - return subnet_even(subnets) - else - return subnet_odd(subnets) - end + + calculate_subnets(subnets) end alias_method :/, :subnet @@ -820,23 +816,33 @@ module IPAddress; bits = bits_from_address(ip) CLASSFUL.each {|reg,prefix| return prefix if bits =~ reg} end - - def subnet_even(subnets) - new_prefix = @prefix.to_i + Math::log2(subnets).ceil + + def calculate_subnets(subnets) + po2 = subnets.closest_power_of_2 + new_prefix = @prefix.to_i + Math::log2(po2).to_i networks = Array.new - (0..subnets-1).each do |i| + (0..po2-1).each do |i| mul = i * (2**(32-new_prefix)) networks << IPAddress::IPv4.parse_u32(network_u32+mul, new_prefix) end + until networks.size == subnets + networks = sum_first_found(networks) + end return networks end - def subnet_odd(subnets) - networks = subnet_even(subnets+1) - networks[-2..-1] = IPAddress::IPv4.summarize(networks[-2],networks[-1]) - return networks + def sum_first_found(arr) + dup = arr.dup.reverse + dup.each_with_index do |obj,i| + a = [IPAddress::IPv4.summarize(obj,dup[i+1])].flatten + if a.size == 1 + dup[i..i+1] = a + return dup.reverse + end + end + return dup.reverse end - + end # class IPv4 end # module IPAddress diff --git a/test/ipaddress/ipv4_test.rb b/test/ipaddress/ipv4_test.rb index d84859a..d734921 100644 --- a/test/ipaddress/ipv4_test.rb +++ b/test/ipaddress/ipv4_test.rb @@ -301,14 +301,30 @@ class IPv4Test < Test::Unit::TestCase def test_method_subnet assert_raise(ArgumentError) {@ip.subnet(0)} assert_raise(ArgumentError) {@ip.subnet(257)} - - # Even subnets - arr = ["172.16.10.0/26", "172.16.10.64/26", "172.16.10.128/26", "172.16.10.192/26"] + + 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_s} + 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_s} + 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_s} + 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_s} + 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_s} - - # Odd subnets 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_s} + arr = ["172.16.10.0/25", "172.16.10.128/25"] + assert_equal arr, @network.subnet(2).map {|s| s.to_s} + arr = ["172.16.10.0/24"] + assert_equal arr, @network.subnet(1).map {|s| s.to_s} end def test_method_supernet |