summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarco Ceresa <ceresa@gmail.com>2009-12-09 11:44:29 +0000
committerMarco Ceresa <ceresa@gmail.com>2009-12-09 11:44:29 +0000
commitc6bbaffb29518a500e0cafdf01d804e510d92b57 (patch)
tree8e68a4e93bc4a5f1ecf63dcbc8db6df6a1b5dd4b
parent60d8137669d4ff7d64274cd2788c9daaa1741191 (diff)
downloadipaddress-c6bbaffb29518a500e0cafdf01d804e510d92b57.tar.gz
Initial import
-rw-r--r--lib/ipaddress/extensions/extensions.rb4
-rw-r--r--lib/ipaddress/ip.rb80
-rw-r--r--lib/ipaddress/ipaddress.rb88
-rw-r--r--lib/ipaddress/ipbase.rb62
-rw-r--r--lib/ipaddress/ipv4.rb564
-rw-r--r--lib/ipaddress/prefix.rb60
-rw-r--r--test/ipaddress/ipbase_test.rb28
-rw-r--r--test/ipaddress/ipv4_test.rb258
-rw-r--r--test/ipaddress/prefix_test.rb98
9 files changed, 1242 insertions, 0 deletions
diff --git a/lib/ipaddress/extensions/extensions.rb b/lib/ipaddress/extensions/extensions.rb
new file mode 100644
index 0000000..ca73f9a
--- /dev/null
+++ b/lib/ipaddress/extensions/extensions.rb
@@ -0,0 +1,4 @@
+class << Math
+ def log2(n); log(n) / log(2); end
+end
+
diff --git a/lib/ipaddress/ip.rb b/lib/ipaddress/ip.rb
new file mode 100644
index 0000000..8fa9df3
--- /dev/null
+++ b/lib/ipaddress/ip.rb
@@ -0,0 +1,80 @@
+
+
+class IPAddress
+
+ #
+ # Checks if the given string is a valid IP address,
+ # either IPv4 or IPv6
+ #
+ # Example:
+ #
+ # IPAddress::valid? "2002::1"
+ # #=> true
+ #
+ # IPAddress::valid? "10.0.0.256"
+ # #=> false
+ #
+ def self.valid?(addr)
+ valid_ipv4?(addr) || valid_ipv6?(addr)
+ end
+
+ def self.valid_ipv4?(addr)
+ if /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/ =~ addr
+ return $~.captures.all? {|i| i.to_i < 256}
+ end
+ false
+ end
+
+ def self.valid_ipv6?(addr)
+ # IPv6 (normal)
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*\Z/ =~ addr
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ addr
+ return true if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ addr
+ # IPv6 (IPv4 compat)
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:/ =~ addr && valid_ipv4?($')
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ addr && valid_ipv4?($')
+ return true if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ addr && valid_ipv4?($')
+ false
+ end
+
+ #
+ # Returns the netmask length in prefix format
+ #
+ # ip = IPAddress.new("10.1.1.1/23")
+ # ip.prefix
+ # #=> 23
+ #
+ def prefix
+ @octects.pack("C"*@octects.size).unpack("B*").first.count "1"
+ end
+
+ #
+ # Returns the IP address in human readable form
+ #
+ # ip.to_s
+ # #=> "172.16.11.40/24"
+ #
+ def to_s
+ @octests.
+
+ end
+
+ @ip = str.split(".").pack("CCCC").unpack("N")
+
+ #
+ # Check whether an IP represents a network or
+ # a host.
+ #
+ # For instance, 10.0.0.130/25 is the secondo host
+ # in the 10.0.0.128/25 network.
+ #
+ # ip = IPAddress.new("10.0.0.128/25")
+ # ip.network?
+ # #=> true
+ #
+ def network?
+ @ip &
+
+end
+
+
diff --git a/lib/ipaddress/ipaddress.rb b/lib/ipaddress/ipaddress.rb
new file mode 100644
index 0000000..351f11b
--- /dev/null
+++ b/lib/ipaddress/ipaddress.rb
@@ -0,0 +1,88 @@
+
+
+class IPAddress
+
+ #
+ # Checks if the given string is a valid IP address,
+ # either IPv4 or IPv6
+ #
+ # Example:
+ #
+ # IPAddress::valid? "2002::1"
+ # #=> true
+ #
+ # IPAddress::valid? "10.0.0.256"
+ # #=> false
+ #
+ def self.valid?(addr)
+ valid_ipv4?(addr) || valid_ipv6?(addr)
+ end
+
+ def self.valid_ipv4?(addr)
+ if /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/ =~ addr
+ return $~.captures.all? {|i| i.to_i < 256}
+ end
+ false
+ end
+
+ def self.valid_ipv6?(addr)
+ # IPv6 (normal)
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*\Z/ =~ addr
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ addr
+ return true if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ addr
+ # IPv6 (IPv4 compat)
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:/ =~ addr && valid_ipv4?($')
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ addr && valid_ipv4?($')
+ return true if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ addr && valid_ipv4?($')
+ false
+ end
+
+ #
+ # Returns the netmask length in prefix format
+ #
+ # ip = IPAddress.new("10.1.1.1/23")
+ # ip.prefix
+ # #=> 23
+ #
+ def prefix
+ @octects.pack("C"*@octects.size).unpack("B*").first.count "1"
+ end
+
+ #
+ # Returns the IP address in human readable form
+ #
+ # ip.to_s
+ # #=> "172.16.11.40/24"
+ #
+ # ip6.to_s
+ # #=> "FF80::1/64"
+ #
+ def to_s
+ @octests.
+
+ end
+
+ @ip = str.split(".").pack("CCCC").unpack("N")
+
+ #
+ # Check whether an IP represents a network or
+ # a host.
+ #
+ # For instance, 10.0.0.130/25 is the secondo host
+ # in the 10.0.0.128/25 network.
+ #
+ # ip = IPAddress("10.0.0.128/25")
+ # ip.network?
+ # #=> true
+ #
+ def network?
+ @ip &
+ end
+
+ def initialize(str)
+ @ip, @prefix = str.split "/"
+ end
+
+end
+
+
diff --git a/lib/ipaddress/ipbase.rb b/lib/ipaddress/ipbase.rb
new file mode 100644
index 0000000..eb0aa93
--- /dev/null
+++ b/lib/ipaddress/ipbase.rb
@@ -0,0 +1,62 @@
+require 'ipaddress/extensions/extensions'
+
+module IPAddress
+
+ #
+ # Checks if the given string is a valid IP address,
+ # either IPv4 or IPv6
+ #
+ # Example:
+ #
+ # IPAddress::valid? "2002::1"
+ # #=> true
+ #
+ # IPAddress::valid? "10.0.0.256"
+ # #=> false
+ #
+ def self.valid?(addr)
+ valid_ipv4?(addr) || valid_ipv6?(addr)
+ end
+
+ def self.valid_ipv4?(addr)
+ if /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/ =~ addr
+ return $~.captures.all? {|i| i.to_i < 256}
+ end
+ false
+ end
+
+ #
+ # Checks if the argument is a valid IPv4 netmark
+ # expressed in dotted decimal format.
+ #
+ # IPAddress.valid_ipv4_netmask? "255.255.0.0"
+ # #=> true
+ #
+ def self.valid_ipv4_netmask?(addr)
+ arr = addr.split(".").map{|i| i.to_i}.pack("CCCC").unpack("B*").first.scan(/01/)
+ valid_ipv4?(addr) && arr.empty?
+ end
+
+
+
+ def self.valid_ipv6?(addr)
+ # IPv6 (normal)
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*\Z/ =~ addr
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ addr
+ return true if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ addr
+ # IPv6 (IPv4 compat)
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:/ =~ addr && valid_ipv4?($')
+ return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ addr && valid_ipv4?($')
+ return true if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ addr && valid_ipv4?($')
+ false
+ end
+
+ class IPBase
+ end
+
+end # module IPAddress
+
+
+
+
+
diff --git a/lib/ipaddress/ipv4.rb b/lib/ipaddress/ipv4.rb
new file mode 100644
index 0000000..13d271e
--- /dev/null
+++ b/lib/ipaddress/ipv4.rb
@@ -0,0 +1,564 @@
+require 'ipaddress/ipbase'
+require 'ipaddress/prefix'
+
+#
+# =Name
+#
+# Net::DNS::Resolver - DNS resolver class
+#
+# =Synopsis
+#
+# require 'net/dns/resolver'
+#
+# =Description
+#
+# The Net::DNS::Resolver class implements a complete DNS resolver written
+# in pure Ruby, without a single C line of code. It has all of the
+# tipical properties of an evoluted resolver, and a bit of OO which
+# comes from having used Ruby.
+#
+# This project started as a porting of the Net::DNS Perl module,
+# written by Martin Fuhr, but turned out (in the last months) to be
+# an almost complete rewriting. Well, maybe some of the features of
+# the Perl version are still missing, but guys, at least this is
+# readable code!
+#
+# FIXME
+#
+# =Environment
+#
+# The Following Environment variables can also be used to configure
+# the resolver:
+#
+# * +RES_NAMESERVERS+: A space-separated list of nameservers to query.
+#
+# # Bourne Shell
+# $ RES_NAMESERVERS="192.168.1.1 192.168.2.2 192.168.3.3"
+# $ export RES_NAMESERVERS
+#
+# # C Shell
+# % setenv RES_NAMESERVERS "192.168.1.1 192.168.2.2 192.168.3.3"
+#
+# * +RES_SEARCHLIST+: A space-separated list of domains to put in the
+# search list.
+#
+# # Bourne Shell
+# $ RES_SEARCHLIST="example.com sub1.example.com sub2.example.com"
+# $ export RES_SEARCHLIST
+#
+# # C Shell
+# % setenv RES_SEARCHLIST "example.com sub1.example.com sub2.example.com"
+#
+# * +LOCALDOMAIN+: The default domain.
+#
+# # Bourne Shell
+# $ LOCALDOMAIN=example.com
+# $ export LOCALDOMAIN
+#
+# # C Shell
+# % setenv LOCALDOMAIN example.com
+#
+# * +RES_OPTIONS+: A space-separated list of resolver options to set.
+# Options that take values are specified as option:value.
+#
+# # Bourne Shell
+# $ RES_OPTIONS="retrans:3 retry:2 debug"
+# $ export RES_OPTIONS
+#
+# # C Shell
+# % setenv RES_OPTIONS "retrans:3 retry:2 debug"
+#
+
+
+module IPAddress; class IPv4 < IPBase
+
+ include IPAddress
+ include Enumerable
+ include Comparable
+
+ #
+ # This Hash contains the prefix values for Classful networks
+ #
+ # Note that classes C, D and E will all have a default
+ # prefix of /24 or 255.255.255.0
+ #
+ CLASSFUL = {
+ /^0../ => 8, # Class A, from 0.0.0.0 to 127.255.255.255
+ /^10./ => 16, # Class B, from 128.0.0.0 to 191.255.255.255
+ /^110/ => 24 # Class C, D and E, from 192.0.0.0 to 255.255.255.254
+ }
+
+
+ #
+ # Creates a new IPv4 address.
+ #
+ # An IPv4 address can be expressed in any of the following forms:
+ #
+ # * 10.1.1.1
+ # *
+ def initialize(str)
+ ip, netmask = str.split("/")
+
+ # Check the ip and remove white space
+ if IPAddress.valid_ipv4?(ip)
+ @address = ip.strip
+ else
+ raise ArgumentError, "Invalid IP #{ip.inspect}"
+ end
+
+ # Check the netmask
+ if netmask # netmask is defined
+ netmask.strip!
+ if netmask =~ /^\d{1,2}$/ # netmask in cidr format
+ @prefix = Prefix.new(netmask.to_i)
+ elsif IPAddress.valid_ipv4_netmask?(netmask) # netmask in IP format
+ @prefix = Prefix.parse_netmask(netmask)
+ else # invalid netmask
+ raise ArgumentError, "Invalid netmask #{netmask}"
+ end
+ else # netmask is nil, reverting to defaul classful mask
+ @prefix = prefix_from_ip(@address)
+ end
+
+ # Array formed with the IP octets
+ @octets = @address.split(".").map{|i| i.to_i}
+
+ end # def initialize
+
+ def address
+ @address
+ end
+
+ def prefix
+ @prefix
+ end
+
+ def prefix=(num)
+ @prefix = Prefix.new(num)
+ end
+
+ def octets
+ @octets
+ end
+
+ def to_s
+ "#@address/#@prefix"
+ end
+
+ def netmask
+ @prefix.to_ip
+ end
+
+ def netmask=(addr)
+ @prefix = Prefix.parse_netmask(addr)
+ end
+
+ def to_u32
+ @octets.pack("CCCC").unpack("N").first
+ end
+
+ #
+ # Returns the octet specified by index
+ #
+ # ip = IPAddress("172.16.100.50/24")
+ # ip[0]
+ # #=> 172
+ # ip[1]
+ # #=> 16
+ # ip[2]
+ # #=> 100
+ # ip[3]
+ # #=> 50
+ #
+ def [](index)
+ @octets[index]
+ end
+ alias_method :octet, :[]
+
+ #
+ # Returns the address portion of an IP in binary format,
+ # as a string containing a sequence of 0 and 1
+ #
+ # ip = IPAddress("127.0.0.1")
+ # ip.bits
+ # #=> "01111111000000000000000000000001"
+ #
+ def bits
+ @octets.pack("CCCC").unpack("B*").first
+ end
+
+ #
+ # Returns the broadcast address for the given IP.
+ #
+ # ip = IPAddress("172.16.10.64/24")
+ # ip.broadcast
+ # #=> "172.16.10.255"
+ #
+ def broadcast
+ [broadcast_u32].pack("N").unpack("CCCC").join(".")
+ end
+
+ #
+ # Check if the IP address is actually a network
+ #
+ # ip = IPAddress("172.16.10.64/24")
+ # ip.network?
+ # #=> false
+ #
+ # ip = IPAddress("172.16.10.64/26")
+ # ip.network?
+ # #=> true
+ #
+ def network?
+ to_u32 | @prefix.to_u32 == @prefix.to_u32
+ end
+
+ #
+ # Returns the network number for the given IP.
+ #
+ # ip = IPAddress("172.16.10.64/24")
+ # ip.network
+ # #=> "172.16.10.0"
+ #
+ def network
+ [network_u32].pack("N").unpack("CCCC").join(".")
+ end
+
+ #
+ # This method takes the object network number (if it's
+ # not already a network) and returns a string with the
+ # first host IP address in the range.
+ #
+ # Example: given the 192.168.100.0/24 network, the first
+ # host IP address is 192.168.100.1.
+ #
+ # ip = IPAddress("192.168.100.0/24")
+ # ip.first
+ # #=> "192.168.100.1"
+ #
+ # The object IP doesn't need to be a network: the method
+ # automatically gets the network number from it
+ #
+ # ip = IPAddress("192.168.100.50/24")
+ # ip.first
+ # #=> "192.168.100.1"
+ #
+ def first
+ [network_u32 + 1].pack("N").unpack("CCCC").join(".")
+ end
+
+ #
+ # Like its sibling method IPv4#last, this method takes
+ # the object network number (if it's
+ # not already a network) and returns a string with the
+ # last host IP address in the range.
+ #
+ # Example: given the 192.168.100.0/24 network, the last
+ # host IP address is 192.168.100.1.
+ #
+ # ip = IPAddress("192.168.100.0/24")
+ # ip.last
+ # #=> "192.168.100.254"
+ #
+ # The object IP doesn't need to be a network: the method
+ # automatically gets the network number from it
+ #
+ # ip = IPAddress("192.168.100.50/24")
+ # ip.last
+ # #=> "192.168.100.254"
+ #
+ def last
+ [broadcast_u32 - 1].pack("N").unpack("CCCC").join(".")
+ end
+
+ #
+ # Iterates over all the hosts IP addresses for the given
+ # network (or IP address).
+ #
+ # ip = IPaddress("10.0.0.1/29")
+ # ip.each do |i|
+ # p i
+ # end
+ # #=> "10.0.0.1"
+ # #=> "10.0.0.2"
+ # #=> "10.0.0.3"
+ # #=> "10.0.0.4"
+ # #=> "10.0.0.5"
+ # #=> "10.0.0.6"
+ #
+ def each_host
+ hosts.each do |i|
+ yield i
+ end
+ end
+
+ #
+ # Iterates over all the IP addresses for the given
+ # network (or IP address).
+ #
+ # ip = IPaddress("10.0.0.1/29")
+ # ip.each do |i|
+ # p i
+ # end
+ # #=> "10.0.0.0"
+ # #=> "10.0.0.1"
+ # #=> "10.0.0.2"
+ # #=> "10.0.0.3"
+ # #=> "10.0.0.4"
+ # #=> "10.0.0.5"
+ # #=> "10.0.0.6"
+ # #=> "10.0.0.7"
+ #
+ def each
+ (network_u32..broadcast_u32).each do |i|
+ yield [i].pack("N").unpack("CCCC").join(".")
+ end
+ end
+
+ #
+ # Spaceship operator to compare IP addresses
+ #
+ # An IP address is considered to be minor if it
+ # has a greater prefix (thus smaller hosts
+ # portion) and a smaller u32 value.
+ #
+ # For example, "10.100.100.1/8" is smaller than
+ # "172.16.0.1/16", but it's bigger than "10.100.100.1/16".
+ #
+ # Example:
+ #
+ # ip1 = IPAddress "10.100.100.1/8"
+ # ip2 = IPAddress ""172.16.0.1/16"
+ # ip3 = IPAddress ""10.100.100.1/16"
+ #
+ # ip1 < ip2
+ # #=> true
+ # ip1 < ip3
+ # #=> false
+ #
+ def <=>(oth)
+ if to_u32 > oth.to_u32
+ return 1
+ elsif to_u32 < oth.to_u32
+ return -1
+ else
+ if prefix < oth.prefix
+ return 1
+ elsif prefix > oth.prefix
+ return -1
+ end
+ end
+ return 0
+ end
+
+ #
+ # Returns the number of IP addresses included
+ # in the network. It also counts the network
+ # number and the broadcast address.
+ #
+ # ip = IPaddress("10.0.0.1/29")
+ # ip.size
+ # #=> 8
+ #
+ def size
+ to_a.size
+ end
+
+ # Returns an array with the IP addresses of
+ # all the hosts in the network.
+ #
+ #
+ # ip = IPaddress("10.0.0.1/29")
+ # ip.hosts
+ # #=> ["10.0.0.1",
+ # #=> "10.0.0.2",
+ # #=> "10.0.0.3",
+ # #=> "10.0.0.4",
+ # #=> "10.0.0.5",
+ # #=> "10.0.0.6"]
+ def hosts
+ to_a[1..-2]
+ end
+
+ #
+ # Returns the network number in Unsigned 32bits format
+ #
+ # ip = IPaddress("10.0.0.1/29")
+ # ip.network_u32
+ # #=> 167772160
+ #
+ def network_u32
+ to_u32 & @prefix.to_u32
+ end
+
+ #
+ # Returns the broadcast address in Unsigned 32bits format
+ #
+ # ip = IPaddress("10.0.0.1/29")
+ # ip.broadcast_u32
+ # #=> 167772167
+ #
+ def broadcast_u32
+ [to_u32 | ~@prefix.to_u32].pack("N").unpack("N").first
+ end
+
+ #
+ # Checks whether a subnet includes the given IP address.
+ #
+ # Accepts either string with the IP or and IPAddress::IPv4
+ # object.
+ #
+ # ip = IPAddress("192.168.10.100/24")
+ #
+ # addr = IPAddress("192.168.10.102/24")
+ # ip.include? addr
+ # #=> true
+ #
+ # ip.include? "172.16.0.48"
+ # #=> false
+ #
+ def include?(oth)
+ to_a.include?(oth.to_s.split("/").first)
+ end
+
+ #
+ # Returns the IP address in in-addr.arpa format
+ # for DNS lookups
+ #
+ # ip = IPAddress("172.16.100.50/24")
+ # ip.reverse
+ # #=> "50.100.16.172.in-addr.arpa"
+ #
+ def reverse
+ @octets.reverse.join(".") + ".in-addr.arpa"
+ end
+
+ #
+ # 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 +num+ is an even number, the resulting networks will be
+ # divided evenly from the supernet.
+ #
+ # network = IPAddress("172.16.10.0/24")
+ # network / 4
+ # #=> ["172.16.10.0/26",
+ # "172.16.10.64/26",
+ # "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.
+ #
+ # network = IPAddress("172.16.10.0/24")
+ # network / 3
+ # #=> ["172.16.10.0/26",
+ # "172.16.10.64/26",
+ # "172.16.10.128/25"]
+ #
+ # 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
+ end
+ alias_method :/, :subnet
+
+ def supernet(new_prefix)
+ raise ArgumentError, "Can't supernet a /1 network" if prefix < 1
+ IPv4.new(@address+"/#{new_prefix}").network
+ end
+
+
+ def -(oth)
+ return (to_u32 - oth.to_u32).abs
+ end
+
+ #
+ # Creates a new IPv4 object from an
+ # unsigned 32bits integer.
+ #
+ # ip = IPAddress::IPv4::parse_u32(167772160)
+ # ip.prefix = 8
+ # ip.to_s
+ # #=> "10.0.0.0/8"
+ #
+ # The +prefix+ parameter is optional:
+ #
+ # ip = IPAddress::IPv4::parse_u32(167772160, 8)
+ # ip.to_s
+ # #=> "10.0.0.0/8"
+ #
+ def self.parse_u32(u32, prefix=nil)
+ ip = [u32].pack("N").unpack("CCCC").join(".")
+ if prefix
+ IPAddress::IPv4.new(ip+"/#{prefix}")
+ else
+ IPAddress::IPv4.new(ip)
+ end
+ end
+
+ def self.summarize(*args)
+ arr = args.sort
+
+ end
+
+
+
+ #
+ # private methods
+ #
+
+ private
+
+ def prefix_from_netmask(netmask)
+ octets = netmask.split(".").map{|i| i.to_i}
+ octets.pack("C"*octets.size).unpack("B*").first.count "1"
+ end
+
+ def netmask_from_prefix(prefix)
+ bits = "1" * prefix + "0" * (32 - prefix)
+ bits.pack("B*").unpack("CCCC").join(".")
+ end
+
+ def bits_from_address(ip)
+ ip.split(".").map{|i| i.to_i}.pack("CCCC").unpack("B*").first
+ end
+
+ def prefix_from_ip(ip)
+ 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
+ networks = Array.new
+ (0..subnets-1).each do |i|
+ mul = i * (2**(32-new_prefix))
+ networks << IPAddress::IPv4.parse_u32(network_u32+mul, new_prefix)
+ 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
+ end
+
+
+end; end # module IPAddress; class IPv4
+
diff --git a/lib/ipaddress/prefix.rb b/lib/ipaddress/prefix.rb
new file mode 100644
index 0000000..125bba8
--- /dev/null
+++ b/lib/ipaddress/prefix.rb
@@ -0,0 +1,60 @@
+module IPAddress
+ class Prefix
+
+ include Comparable
+
+ attr_reader :prefix
+
+ MASK = 0xffffffff
+ SIZE = 32
+
+ def initialize(num)
+ @prefix = num.to_i
+ end
+
+ def bits
+ # "1" * @prefix + "0" * (SIZE - @prefix)
+ sprintf "%0#{SIZE}b", (MASK & ~(MASK >> @prefix))
+ end
+
+ def to_u32
+ [bits].pack("B*").unpack("N").first
+ end
+
+ def to_ip
+ [bits].pack("B*").unpack("CCCC").join(".")
+ end
+
+ def to_s
+ "#@prefix"
+ end
+ alias_method :inspect, :to_s
+
+ def to_i
+ @prefix
+ end
+
+ def octets
+ to_ip.split(".").map{|i| i.to_i}
+ end
+
+ def [](index)
+ octets[index]
+ end
+
+ def hostmask
+ [~to_u32].pack("N").unpack("CCCC").join(".")
+ end
+
+ def <=>(oth)
+ @prefix <=> oth.to_i
+ end
+
+ def self.parse_netmask(netmask)
+ octets = netmask.split(".").map{|i| i.to_i}
+ num = octets.pack("C"*octets.size).unpack("B*").first.count "1"
+ return IPAddress::Prefix.new(num)
+ end
+
+ end # class Prefix
+end # module IPAddress
diff --git a/test/ipaddress/ipbase_test.rb b/test/ipaddress/ipbase_test.rb
new file mode 100644
index 0000000..c5cfe5a
--- /dev/null
+++ b/test/ipaddress/ipbase_test.rb
@@ -0,0 +1,28 @@
+require 'test_helper'
+
+class IpaddressTest < Test::Unit::TestCase
+
+ must "be valid ip" do
+ assert_equal true, IPAddress::valid?("10.0.0.1")
+ assert_equal true, IPAddress::valid?("10.0.0.0")
+ assert_equal true, IPAddress::valid?("2002::1")
+ assert_equal true, IPAddress::valid?("dead:beef:cafe:babe::f0ad")
+ end
+
+ must "be valid netmask" do
+ assert_equal true, IPAddress::valid_ipv4_netmask?("255.255.255.0")
+ end
+
+ must "be invalid netmask" do
+ assert_equal false, IPAddress::valid_ipv4_netmask?("10.0.0.1")
+ end
+
+ must "be invalid" do
+ assert_equal false, IPAddress::valid?("10.0.0.256")
+ assert_equal false, IPAddress::valid?("10.0.0.0.0")
+ assert_equal false, IPAddress::valid?("10.0.0")
+ assert_equal false, IPAddress::valid?("10.0")
+ assert_equal false, IPAddress::valid?("2002:::1")
+ end
+
+end
diff --git a/test/ipaddress/ipv4_test.rb b/test/ipaddress/ipv4_test.rb
new file mode 100644
index 0000000..1fc0898
--- /dev/null
+++ b/test/ipaddress/ipv4_test.rb
@@ -0,0 +1,258 @@
+require 'test_helper'
+
+class IPv4Test < Test::Unit::TestCase
+
+ def setup
+ @klass = IPAddress::IPv4
+
+ @valid_ipv4 = {
+ "10.0.0.0" => ["10.0.0.0", 8],
+ "10.0.0.1" => ["10.0.0.1", 8],
+ "10.0.0.1/24" => ["10.0.0.1", 24],
+ "10.0.0.1/255.255.255.0" => ["10.0.0.1", 24]}
+
+ @invalid_ipv4 = ["10.0.0.256",
+ "10.0.0.0.0",
+ "10.0.0",
+ "10.0"]
+
+ @valid_ipv4_range = ["10.0.0.1-254",
+ "10.0.1-254.0",
+ "10.1-254.0.0"]
+
+ @netmask_values = {
+ "10.0.0.0/8" => "255.0.0.0",
+ "172.16.0.0/16" => "255.255.0.0",
+ "192.168.0.0/24" => "255.255.255.0",
+ "192.168.100.4/30" => "255.255.255.252"}
+
+ @decimal_values ={
+ "10.0.0.0/8" => 167772160,
+ "172.16.0.0/16" => 2886729728,
+ "192.168.0.0/24" => 3232235520,
+ "192.168.100.4/30" => 3232261124}
+
+ @ip = @klass.new("172.16.10.1/24")
+ @network = @klass.new("172.16.10.0/24")
+
+ @broadcast = {
+ "10.0.0.0/8" => "10.255.255.255",
+ "172.16.0.0/16" => "172.16.255.255",
+ "192.168.0.0/24" => "192.168.0.255",
+ "192.168.100.4/30" => "192.168.100.7"}
+
+ @networks = {
+ "10.5.4.3/8" => "10.0.0.0",
+ "172.16.5.4/16" => "172.16.0.0",
+ "192.168.4.3/24" => "192.168.4.0",
+ "192.168.100.5/30" => "192.168.100.4"}
+ end
+
+ def test_initialize
+ @valid_ipv4.keys.each do |i|
+ ip = @klass.new(i)
+ assert_instance_of @klass, ip
+ end
+ end
+
+ def test_initialize_format_error
+ @invalid_ipv4.each do |i|
+ assert_raise(ArgumentError) {@klass.new(i)}
+ end
+ end
+
+ def test_attributes
+ @valid_ipv4.each do |arg,attr|
+ ip = @klass.new(arg)
+ assert_equal attr.first, ip.address
+ assert_equal attr.last, ip.prefix.to_i
+ end
+ end
+
+ def test_octets
+ ip = @klass.new("10.1.2.3/8")
+ assert_equal ip.octets, [10,1,2,3]
+ end
+
+ def test_initialize_should_require_ip
+ assert_raise(ArgumentError) { @klass.new }
+ end
+
+ def test_method_to_s
+ @valid_ipv4.each do |arg,attr|
+ ip = @klass.new(arg)
+ assert_equal attr.join("/"), ip.to_s
+ end
+ end
+
+ def test_netmask
+ @netmask_values.each do |addr,mask|
+ ip = @klass.new(addr)
+ assert_equal mask, ip.netmask
+ end
+ end
+
+ def test_method_to_u32
+ @decimal_values.each do |addr,int|
+ ip = @klass.new(addr)
+ assert_equal int, ip.to_u32
+ end
+ end
+
+ def test_method_network?
+ assert_equal true, @network.network?
+ assert_equal false, @ip.network?
+ end
+
+ def test_method_broadcast
+ @broadcast.each do |addr,bcast|
+ ip = @klass.new(addr)
+ assert_equal bcast, ip.broadcast
+ end
+ end
+
+ def test_method_network
+ @networks.each do |addr,net|
+ ip = @klass.new addr
+ assert_equal net, ip.network
+ end
+ end
+
+ def test_method_bits
+ ip = @klass.new("127.0.0.1")
+ assert_equal ip.bits, "01111111000000000000000000000001"
+ end
+
+ def test_method_first
+ ip = @klass.new("192.168.100.0/24")
+ assert_equal "192.168.100.1", ip.first
+ ip = @klass.new("192.168.100.50/24")
+ assert_equal "192.168.100.1", ip.first
+ end
+
+ def test_method_last
+ ip = @klass.new("192.168.100.0/24")
+ assert_equal "192.168.100.254", ip.last
+ ip = @klass.new("192.168.100.50/24")
+ assert_equal "192.168.100.254", ip.last
+ end
+
+ def test_method_each_host
+ ip = @klass.new("10.0.0.1/29")
+ arr = []
+ ip.each_host {|i| arr << i}
+ expected = ["10.0.0.1","10.0.0.2","10.0.0.3","10.0.0.4","10.0.0.5","10.0.0.6"]
+ assert_equal expected, arr
+ end
+
+ def test_method_each
+ ip = @klass.new("10.0.0.1/29")
+ arr = []
+ ip.each {|i| arr << i}
+ expected = ["10.0.0.0","10.0.0.1","10.0.0.2","10.0.0.3","10.0.0.4","10.0.0.5","10.0.0.6","10.0.0.7"]
+ assert_equal expected, arr
+ end
+
+ def test_method_size
+ ip = @klass.new("10.0.0.1/29")
+ assert_equal 8, ip.size
+ end
+
+ def test_method_hosts
+ ip = @klass.new("10.0.0.1/29")
+ expected = ["10.0.0.1","10.0.0.2","10.0.0.3","10.0.0.4","10.0.0.5","10.0.0.6"]
+ assert_equal expected, ip.hosts
+ end
+
+ def test_method_network_u32
+ assert_equal 2886732288, @ip.network_u32
+ end
+
+ def test_method_broadcast_u32
+ assert_equal 2886732543, @ip.broadcast_u32
+ end
+
+ def test_method_include?
+ ip = @klass.new("192.168.10.100/24")
+ addr = @klass.new("192.168.10.102/24")
+ assert_equal true, ip.include?(addr)
+ assert_equal false, ip.include?("172.16.0.48")
+ end
+
+ def test_method_octet
+ assert_equal 172, @ip[0]
+ assert_equal 16, @ip[1]
+ assert_equal 10, @ip[2]
+ assert_equal 1, @ip[3]
+ end
+
+ def test_method_reverse
+ assert_equal "1.10.16.172.in-addr.arpa", @ip.reverse
+ end
+
+ def test_method_comparabble
+ ip1 = @klass.new("10.1.1.1/8")
+ ip2 = @klass.new("10.1.1.1/16")
+ ip3 = @klass.new("172.16.1.1/14")
+ ip4 = @klass.new("10.1.1.1/8")
+
+ # ip1 should be major than ip2
+ assert_equal true, ip1 > ip2
+ assert_equal false, ip1 < ip2
+ # ip2 should be minor than ip3
+ assert_equal true, ip2 < ip3
+ assert_equal false, ip2 > ip3
+ # ip1 should be minor than ip3
+ assert_equal true, ip1 < ip3
+ assert_equal false, ip1 > ip3
+ # ip1 should be equal to itself
+ assert_equal true, ip1 == ip1
+ # ip1 should be equal to ip4
+ assert_equal true, ip1 == ip4
+ # test sorting
+ arr = ["10.1.1.1/16","10.1.1.1/8","172.16.1.1/14"]
+ assert_equal arr, [ip1,ip2,ip3].sort.map{|s| s.to_s}
+ end
+
+
+ def test_method_netmask_equal
+ ip = @klass.new("10.1.1.1/16")
+ assert_equal 16, ip.prefix.to_i
+ ip.netmask = "255.255.255.0"
+ 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/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}
+ # assert_equal ip.subnet(3), ["172.16.10.0/26",
+# "172.16.10.64/26",
+# "172.16.10.128/25"]
+ end
+
+ def test_method_supernet
+ assert_equal "172.16.8.0/23", @ip.supernet(23)
+ end
+
+ def test_classmethod_parse_u32
+ @decimal_values.each do |addr,int|
+ ip = @klass.parse_u32(int)
+ ip.prefix = addr.split("/").last.to_i
+ assert_equal ip.to_s, addr
+ end
+ end
+
+ def test_classmethod_summarize
+ ip1 = @klass.new("172.16.10.1/24")
+ ip2 = @klass.new("172.16.11.2/24")
+# assert_equal "172.16.10.1/23", @klass.summarize(ip1,ip2).to_s
+ end
+
+
+
+end # class IPv4Test
+
+
diff --git a/test/ipaddress/prefix_test.rb b/test/ipaddress/prefix_test.rb
new file mode 100644
index 0000000..a2a2ffa
--- /dev/null
+++ b/test/ipaddress/prefix_test.rb
@@ -0,0 +1,98 @@
+require 'test_helper'
+
+class PrefixTest < Test::Unit::TestCase
+
+ def setup
+ @netmask8 = "255.0.0.0"
+ @netmask16 = "255.255.0.0"
+ @netmask24 = "255.255.255.0"
+ @netmask30 = "255.255.255.252"
+ @netmasks = [@netmask8,@netmask16,@netmask24,@netmask30]
+
+ @prefix_hash = {
+ "255.0.0.0" => 8,
+ "255.255.0.0" => 16,
+ "255.255.255.0" => 24,
+ "255.255.255.252" => 30}
+
+ @octets_hash = {
+ [255,0,0,0] => 8,
+ [255,255,0,0] => 16,
+ [255,255,255,0] => 24,
+ [255,255,255,252] => 30}
+
+ @u32_hash = {
+ 8 => 4278190080,
+ 16 => 4294901760,
+ 24 => 4294967040,
+ 30 => 4294967292}
+
+ @klass = IPAddress::Prefix
+ end
+
+ def test_attributes
+ @prefix_hash.values.each do |num|
+ prefix = @klass.new(num)
+ assert_equal num, prefix.prefix
+ end
+ end
+
+ def test_parse_netmask
+ @prefix_hash.each do |netmask, num|
+ prefix = @klass.parse_netmask(netmask)
+ assert_equal num, prefix.prefix
+ end
+ end
+
+ def test_method_to_ip
+ @prefix_hash.each do |netmask, num|
+ prefix = @klass.new(num)
+ assert_equal netmask, prefix.to_ip
+ end
+ end
+
+ def test_method_to_s
+ prefix = @klass.new(8)
+ assert_equal "8", prefix.to_s
+ end
+
+ def test_method_bits
+ prefix = @klass.new(16)
+ str = "1"*16 + "0"*16
+ assert_equal str, prefix.bits
+ end
+
+ def test_method_to_u32
+ @u32_hash.each do |num,u32|
+ assert_equal u32, @klass.new(num).to_u32
+ end
+ end
+
+ def test_initialize
+ assert_instance_of @klass, @klass.new(8)
+ end
+
+ def test_method_octets
+ @octets_hash.each do |arr,pref|
+ prefix = @klass.new(pref)
+ assert_equal prefix.octets, arr
+ end
+ end
+
+ def test_method_brackets
+ @octets_hash.each do |arr,pref|
+ prefix = @klass.new(pref)
+ arr.each_with_index do |oct,index|
+ assert_equal prefix[index], oct
+ end
+ end
+ end
+
+ def test_method_hostmask
+ prefix = @klass.new(8)
+ assert_equal "0.255.255.255", prefix.hostmask
+ end
+
+end # class PrefixTest
+
+