diff options
author | Marco Ceresa <ceresa@gmail.com> | 2009-12-09 11:44:29 +0000 |
---|---|---|
committer | Marco Ceresa <ceresa@gmail.com> | 2009-12-09 11:44:29 +0000 |
commit | c6bbaffb29518a500e0cafdf01d804e510d92b57 (patch) | |
tree | 8e68a4e93bc4a5f1ecf63dcbc8db6df6a1b5dd4b | |
parent | 60d8137669d4ff7d64274cd2788c9daaa1741191 (diff) | |
download | ipaddress-c6bbaffb29518a500e0cafdf01d804e510d92b57.tar.gz |
Initial import
-rw-r--r-- | lib/ipaddress/extensions/extensions.rb | 4 | ||||
-rw-r--r-- | lib/ipaddress/ip.rb | 80 | ||||
-rw-r--r-- | lib/ipaddress/ipaddress.rb | 88 | ||||
-rw-r--r-- | lib/ipaddress/ipbase.rb | 62 | ||||
-rw-r--r-- | lib/ipaddress/ipv4.rb | 564 | ||||
-rw-r--r-- | lib/ipaddress/prefix.rb | 60 | ||||
-rw-r--r-- | test/ipaddress/ipbase_test.rb | 28 | ||||
-rw-r--r-- | test/ipaddress/ipv4_test.rb | 258 | ||||
-rw-r--r-- | test/ipaddress/prefix_test.rb | 98 |
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 + + |