Introduction

IPAdmin arose from a work-related project to create a Rails IP 
Administration package. I needed a back-end module that could easily
handle such advanced tasks as automating the subnetting/supernetting
of IP space, performing calculations on IP CIDR blocks, and other
various tasks. At the current time there were no modules that could
do any of the things that I needed, so I set out to create my own. 
Since it proved to be fairly useful to me, I decided to share the
code with the Ruby community. 

I apologize in advance for the short release cycles, but I am making
changes on a constant basis since this is a very active project.
I tend to post new releases to rubyforge since it is a very easy
way for me to distribute my changes to my co-workers.

I have added things that I find immediately useful for me. I am
open to suggestions if there is something that I could add to make
your life easier. Comments are also welcome (positive ones in particular).  

Dustin Spinhirne

Copyright (c) 2006 Dustin Spinhirne - http://www.spinhirne.com
Licensed under the same terms as Ruby, No Warranty is provided.

CIDR:

A class & series of methods for creating and manipulating CIDR network
addresses. Both IPv4 and IPv6 are supported.

This class accepts a CIDR address in (x.x.x.x/yy or xxxx::/yy) format for
IPv4 and IPv6, or (x.x.x.x/y.y.y.y) for IPv4. An optional tag hash may be 
provided with each CIDR as a way of adding custom labels to the object.

Upon initialization, the IP version is auto-detected and assigned to the 
object. The original IP/Netmask passed within the CIDR is stored and then 
used to determine the confines of the CIDR block. Various properties of the
CIDR block are accessible via several different methods. There are also
methods for modifying the CIDR or creating new derivative CIDR's.

An example CIDR object is as follows:
   IPAdmin::CIDR.new(:CIDR => '192.168.1.20/24')

This would create a CIDR object (192.168.1.0/24) with the following properties:
   version = 4
   base network = 192.168.1.0
   ip address = 192.168.1.20
   netmask = /24 (255.255.255.0)
   size = 256 IP addresses
   broadcast = 192.168.1.255

You can see how the CIDR object is based around the entire IP space
defined by the provided IP/Netmask pair, and not necessarily the individual
IP address itself.

Examples:

#!/usr/bin/ruby

require 'rubygems'
require_gem 'ipadmin'

puts "IPAdmin::CIDR"
print "\n"

# new
cidr4 = IPAdmin::CIDR.new(:CIDR => '192.168.1.0/24')
cidr4 = IPAdmin::CIDR.new(:CIDR => '192.168.1.1',
                          :Netmask => '255.255.255.0',
                          :Tag => {'test' => 'cidr4 tag'})
cidr6 = IPAdmin::CIDR.new(:CIDR => 'fec0::1/64')

cidr4_2 = IPAdmin::CIDR.new(:CIDR => '192.168.1.0/26')
cidr6_2 = IPAdmin::CIDR.new(:CIDR => 'fec0::0/96')

# reader/writer
puts "reader/writer"
puts "cidr4 tag '#{cidr4.tag['test']}'"
cidr4.tag['test'] = 'modified cidr4 tag'
puts "updated cidr4 tag '#{cidr4.tag['test']}'"
puts "cidr4 version #{cidr4.version}"
puts "cidr6 version #{cidr6.version}"
print "\n"  

# arpa
puts "arpa"
puts "arpa for #{cidr4.desc()} is #{cidr4.arpa}"
puts "arpa for #{cidr6.desc(:Short => true)} is #{cidr6.arpa}"
print "\n"

# bits
puts "bits"
puts "cidr4 netmask in bits #{cidr4.bits()}"
puts "cidr6 netmask in bits #{cidr6.bits()}"
print "\n"

# contains?
puts "contains"
puts "#{cidr4.desc} contains #{cidr4_2.desc}" if ( cidr4.contains?(cidr4_2) )
puts "#{cidr6.desc} contains #{cidr6_2.desc(:Short => true)}" if ( cidr6.contains?(cidr6_2) )
print "\n"

# desc
puts "desc"
puts "cidr4 description #{cidr4.desc()}"
puts "cidr6 description #{cidr6.desc()}"
puts "cidr6 short-hand description #{cidr6.desc(:Short => true)}"
print "\n"

# enumerate
puts "enumerate"
puts "first 4 cidr4 addresses (bitstep 32)"
cidr4.enumerate(:Limit => 4, :Bitstep => 32).each {|x| puts "  #{x}"}

puts "first 4 cidr6 addresses (bitstep 32)"
cidr6.enumerate(:Limit => 4, :Bitstep => 32, :Objectify => true).each {|x| puts "  #{x.desc}"}
print "\n"

# hostmask_ext
puts "hostmask_ext"
puts "cidr4 extended hostmask #{cidr4.hostmask_ext()}"
print "\n" 

# ip
puts "ip"
puts "cidr4 ip #{cidr4.ip()}"
puts "cidr6 short-hand ip #{cidr6.ip(:Short => true)}"
print "\n"

# last
puts "last"
puts "cidr4 last ip #{cidr4.last()}"
puts "cidr6 last ip #{cidr6.last(:Short => true)}"
print "\n"

# multicast_mac
mcast = IPAdmin::CIDR.new(:CIDR => '224.0.0.6')
mcast2 = IPAdmin::CIDR.new(:CIDR => 'ff00::abcd')
puts "multicast_mac"
puts "#{mcast.ip} multicast mac is #{mcast.multicast_mac()}"
puts "#{mcast2.ip} multicast mac is #{mcast2.multicast_mac()}"
print "\n"

# netmask
puts "netmask"
puts "cidr4 netmask in CIDR format #{cidr4.netmask()}"
puts "cidr6 netmask in CIDR format #{cidr6.netmask()}"
print "\n"

# netmask_ext
puts "netmask_ext"
puts "cidr4 extended netmask #{cidr4.netmask_ext()}"
print "\n"

# network
puts "network"
puts "cidr4 network address #{cidr4.network()}"
puts "cidr6 network address #{cidr6.network(:Short => true)}"
print "\n"

# next_ip
puts "next_ip"
puts "cidr4 next ip #{cidr4.next_ip()}"
puts "cidr6 next ip #{cidr6.next_ip(:Short => true)}"
print "\n"

# next_subnet
puts "next_subnet"
puts "cidr4 next subnet #{cidr4.next_subnet()}"
puts "cidr6 next subnet #{cidr6.next_subnet(:Short => true)}"
print "\n"

# nth
puts "nth"
puts "cidr4 1st ip is #{cidr4.nth(:Index => 1)}"
puts "cidr6 1st ip is #{(cidr6.nth(:Index => 1, :Objectify => true)).base}"
print "\n"

# packed_hostmask
puts "packed_hostmask"
puts "cidr4 packed_hostmask is #{cidr4.packed_hostmask.to_s(16)}"
puts "cidr6 packed_hostmask is #{cidr6.packed_hostmask.to_s(16)}"
print "\n"

# packed_netmask
puts "packed_netmask"
puts "cidr4 packed_netmask is #{cidr4.packed_netmask.to_s(16)}"
puts "cidr6 packed_netmask is #{cidr6.packed_netmask.to_s(16)}"
print "\n"

# packed_network
puts "packed_network"
puts "cidr4 packed_network is #{cidr4.packed_network.to_s(16)}"
puts "cidr6 packed_network is #{cidr6.packed_network.to_s(16)}"
print "\n"

# range
puts "range"
cidr4.range(:Indexes => [20,0], :Bitstep => 5).each {|x| puts x}
cidr6.range(:Indexes => [20,0], :Bitstep => 5).each {|x| puts x}
print "\n" 

# remainder
puts "remainder"
cidr4_2 = IPAdmin::CIDR.new(:CIDR => '192.168.1.64/26')
puts "The remainder of #{cidr4.desc} after subtracting #{cidr4_2.desc} is:"
cidr4.remainder(:Exclude => cidr4_2).each {|x| puts x}
print "\n" 

# resize!
puts "resize!"
cidr4.resize!(:Subnet => 25)
cidr6.resize!(:Subnet => 65)
puts "cidr4 resized is #{cidr4.desc}"
puts "cidr6 resized is #{cidr6.desc}"
print "\n"

# size
puts "size"
puts "cidr4 size is #{cidr4.size()}"
puts "cidr6 size is #{cidr6.size()}"
print "\n"

# subnet
puts "subnet"
puts "#{cidr4.desc} subnetted into at least 3 /28 ranges"
cidr4.subnet(:Subnet => 28, :MinCount => 3).each {|x| puts "  #{x}"} 

puts "#{cidr6.desc(:Short => true)} subnetted into at least 4 /67 ranges"
cidr6.subnet(:Subnet => 67, :MinCount => 4, :Short => true).each {|x| puts "  #{x}"}

EUI:

A class & series of methods for creating and manipulating Extended Unique Identifier
(EUI) addresses. Two types of address formats are supported EUI-48 and EUI-64. The 
most common use for this class will be to manipulate MAC addresses (which are essentially
a type of EUI-48 address). 

EUI addresses are separated into two parts, the 
Organizationally Unique Identifier (OUI) and the Extended Identifier (EI). The OUI
is assigned by the IEEE and is used to identify a particular hardware manufacturer.
The EI is assigned by the hardware manufacturer as a per device unique address.

Probably the most useful feature of this class, and thus the reason it was created,
is to help automate certain address assignments within IP. For example, IPv6
Link Local addresses use MAC addresses for IP auto-assignment and multicast MAC addresses
are determined based on the multicast IP address.

Examples:

#!/usr/bin/ruby

require 'rubygems'
require_gem 'ipadmin'

puts "IPAdmin::EUI"
print "\n"

eui1 = IPAdmin::EUI.new(:EUI => 'aa-bb-cc-dd-ee-ff')
eui2 = IPAdmin::EUI.new(:EUI => '12-34-56-78-9a-bc-de-f0')

# oui
puts "oui"
puts "OUI 1 is #{eui1.oui}"
puts "OUI 2 is #{eui2.oui}"
print "\n"

# ei
puts "ei"
puts "EI 1 is #{eui1.ei}"
puts "EI 2 is #{eui2.ei}"
print "\n"

# address
puts "address"
puts "address 1 is #{eui1.address(:Delimiter => '.')}"
puts "address 2 is #{eui2.address(:Delimiter => '.')}"
print "\n"

# link local
puts "link local"
puts "IPv6 link local 1 is #{eui1.link_local(:Short => true)}"
puts "IPv6 link local 2 is #{eui2.link_local(:Short => true)}"
print "\n"

# type
puts "type"
puts "eui 1 type is #{eui1.type}"
puts "eui 2 type is #{eui2.type}"

Tree:

A class & series of methods for creating and manipulating IP-based
heirarchical trees. Both IPv4 and IPv6 are supported.

Tree's are useful for creating mini 'routing tables' of CIDR address space.
Using a tree, you can quickly determine the 'route' of another CIDR via
the standard longest-match algorithm.

Tree's are not dynamic, in that if you modify any of the CIDR objects
contained within, the Tree will not automatically adjust itself accordingly.
For example if you have a tree like the following:

   192.168.0.0/24
       192.168.0.0/26
   192.168.1.0/24

You decide to resize 192.168.0.0/24 to 192.168.0.0/23. The tree will now
be incorrect:

   192.168.0.0/23
       192.168.0.0/26
   192.168.1.0/24

You would need to remove and re-add the CIDR 192.168.0.0/23 in order for the
tree to rebuild itself properly.

Examples:

#!/usr/bin/ruby

require 'rubygems'
require_gem 'ipadmin'

puts "IPAdmin::Tree"
print "\n"

cidr4 = [IPAdmin::CIDR.new(:CIDR => '192.168.1.0/24'),
         IPAdmin::CIDR.new(:CIDR => '192.168.1.0/26'),
         IPAdmin::CIDR.new(:CIDR => '192.168.1.64/26'),
         IPAdmin::CIDR.new(:CIDR => '192.168.1.128/26'),
         IPAdmin::CIDR.new(:CIDR => '192.168.1.192/26'),
         IPAdmin::CIDR.new(:CIDR => '192.168.1.0/27'),
         IPAdmin::CIDR.new(:CIDR => '192.168.1.0/28'),
         IPAdmin::CIDR.new(:CIDR => '192.168.1.16/28'),
         IPAdmin::CIDR.new(:CIDR => '192.168.1.16/29'),
         IPAdmin::CIDR.new(:CIDR => '192.168.1.32/27')]

cidr6 = [IPAdmin::CIDR.new(:CIDR => 'fec0::/64'),
         IPAdmin::CIDR.new(:CIDR => 'fec0::/66'),
         IPAdmin::CIDR.new(:CIDR => 'fec0::4000:0:0:0/66'),
         IPAdmin::CIDR.new(:CIDR => 'fec0::8000:0:0:0/66'),
         IPAdmin::CIDR.new(:CIDR => 'fec0::c000:0:0:0/66'),
         IPAdmin::CIDR.new(:CIDR => 'fec0::c000:0:0:0/67'),
         IPAdmin::CIDR.new(:CIDR => 'fec0::/67'),
         IPAdmin::CIDR.new(:CIDR => 'fec0::2000:0:0:0/67')]

# new
tree4 = IPAdmin::Tree.new(:Version => 4)
tree6 = IPAdmin::Tree.new(:Version => 6)

# add
puts "add"
cidr4.each do |x| 
   puts "adding #{x.desc}..."
   tree4.add(x)
end
cidr6.each do |x| 
   puts "adding #{x.desc}..."
   tree6.add(x)
end
print "\n"

 # dump
puts "dump"
puts "dumping & printing tree4"
dumped = tree4.dump()
dumped.each do |val|
   obj = val[:Object]
   depth = val[:Depth]
   if (depth > 0)
       indent = " " * (depth*3)
       puts indent << obj.desc()
   else
       puts obj.desc()
   end
end
print "\n"
puts "dumping & printing tree6"
dumped = tree6.dump()
dumped.each do |val|
   obj = val[:Object]
   depth = val[:Depth]
   if (depth > 0)
       indent = " " * (depth*3)
       puts indent << obj.desc()
   else
       puts obj.desc()
   end
end
print "\n"

# find space
puts "find_space with at least 28 IP's in it"
puts "available space with at least 28 IPs"
space = tree4.find_space(:IPCount => 28)
space.each do |obj|
   puts "  #{obj.desc}"
end
print "\n"
puts "available /67 space"
space = tree6.find_space(:Subnet => 67)
space.each do |obj|
   puts "  #{obj.desc}"
end
print "\n"

# remove
puts "remove"
puts "removing #{cidr4[8].desc}"
tree4.remove(cidr4[8])
puts tree4.show()
print "\n"
puts "removing #{cidr6[5].desc}"
tree6.remove(cidr6[5])
puts tree6.show()
print "\n"

# prune
puts "prune"
puts "pruning #{cidr4[4].desc}"
tree4.prune(cidr4[4])
puts tree4.show()
print "\n"
puts "pruning #{cidr6[1].desc}"
tree6.prune(cidr6[1])
puts tree6.show()
print "\n"

 # collapse
puts "collapse"
puts "collapsed tree4 is..."
new_tree4 = tree4.collapse()
puts new_tree4.show()
print "\n"
puts "collapsed tree6 is..."
new_tree6 = tree6.collapse()
puts new_tree6.show()

IPAdmin General Methods

A collection of general purpose methods that dont really fit within
a particular class.

Examples:

#!/usr/bin/ruby

require 'rubygems'
require_gem 'ipadmin'

puts "IPAdmin Methods"
print "\n"

# validate ip
puts "validate ip"
puts "192.168.1.0 is valid" if ( IPAdmin.validate_ip_addr(:IP => '192.168.1.0') )
puts "fec0::0 is valid" if ( IPAdmin.validate_ip_addr(:IP => 'fec0::0') )
puts "::ffff:10.1.0.1 is valid" if ( IPAdmin.validate_ip_addr(:IP => '::ffff:10.1.0.1') )
print "\n"

# validate netmask
puts "validate netmask"
puts "255.255.255.0 is valid" if (IPAdmin.validate_ip_netmask(:Netmask => '255.255.255.0') )
puts "/24 is valid" if ( IPAdmin.validate_ip_netmask(:Netmask => '/24') )
puts "/64 is valid" if ( IPAdmin.validate_ip_netmask(:Netmask => 64, :Version => 6) )

cidr4_1 = IPAdmin::CIDR.new(:CIDR => '192.168.1.0/24')
cidr4_2 = IPAdmin::CIDR.new(:CIDR => '192.168.1.0/25')
cidr4_3 = IPAdmin::CIDR.new(:CIDR => '192.168.1.50')
cidr6_1 = IPAdmin::CIDR.new(:CIDR => 'fec0::0/10')
cidr6_2 = IPAdmin::CIDR.new(:CIDR => 'fec0::0/64')
print "\n"

# compare
puts "compare"
comp1 = IPAdmin.compare(cidr4_1,cidr4_2)
comp2 = IPAdmin.compare(cidr6_1,cidr6_2)
puts "#{(comp1[0]).desc} is the supernet of #{(comp1[1]).desc}"
puts "#{(comp2[0]).desc} is the supernet of #{(comp2[1]).desc}"

cidr4_1 = IPAdmin::CIDR.new(:CIDR => '192.168.1.0/24')
cidr4_2 = IPAdmin::CIDR.new(:CIDR => '192.168.0.0/24')
cidr6_1 = IPAdmin::CIDR.new(:CIDR => 'fec0::0/128')
cidr6_2 = IPAdmin::CIDR.new(:CIDR => 'fec0::1/128')
print "\n"

# range
puts "range"
list = IPAdmin.range(:Boundaries => [cidr4_1,cidr4_3], :Bitstep => 20 )
puts "ip's between #{cidr4_1.base} and #{cidr4_3.base} (bitstep of 20)"
list.each do |x|
 puts "  #{x}"
end
print "\n"

# shorten
puts "shorten"
puts "shorthand notation for  #{cidr6_1.network()} is #{IPAdmin.shorten(cidr6_1.network)}"
print "\n"

# unshorten
puts "unshorten"
puts "expanded notation for  fec0:: is #{IPAdmin.unshorten('fec0::')}"