lib/ipaddress/ipv4.rb in ipaddress-0.5.0 vs lib/ipaddress/ipv4.rb in ipaddress-0.6.0

- old
+ new

@@ -1,6 +1,5 @@ -require 'ipaddress/ipbase' require 'ipaddress/prefix' module IPAddress; # # =Name @@ -13,11 +12,11 @@ # # =Description # # Class IPAddress::IPv4 is used to handle IPv4 type addresses. # - class IPv4 < IPBase + class IPv4 include IPAddress include Enumerable include Comparable @@ -100,10 +99,11 @@ # # Returns the address portion of the IPv4 object # as a string. # # ip = IPAddress("172.16.100.4/22") + # # ip.address # #=> "172.16.100.4" # def address @address @@ -112,12 +112,14 @@ # # Returns the prefix portion of the IPv4 object # as a IPAddress::Prefix32 object # # ip = IPAddress("172.16.100.4/22") + # # ip.prefix # #=> 22 + # # ip.prefix.class # #=> IPAddress::Prefix32 # def prefix @prefix @@ -130,14 +132,16 @@ # to an object created with IPv4::parse_u32 or # if the object was created using the classful # mask. # # ip = IPAddress("172.16.100.4") + # # puts ip # #=> 172.16.100.4/16 # # ip.prefix = 22 + # # puts ip # #=> 172.16.100.4/22 # def prefix=(num) @prefix = Prefix32.new(num) @@ -145,33 +149,50 @@ # # Returns the address as an array of decimal values # # ip = IPAddress("172.16.100.4") + # # ip.octets # #=> [172, 16, 100, 4] # def octets @octets end # + # Returns a string with the address portion of + # the IPv4 object + # + # ip = IPAddress("172.16.100.4/22") + # + # ip.to_s + # #=> "172.16.100.4" + # + def to_s + @address + end + + # # Returns a string with the IP address in canonical # form. # # ip = IPAddress("172.16.100.4/22") - # ip.to_s + # + # ip.to_string # #=> "172.16.100.4/22" # - def to_s + def to_string "#@address/#@prefix" end + # # Returns the prefix as a string in IP format # # ip = IPAddress("172.16.100.4/22") + # # ip.netmask # #=> "255.255.252.0" # def netmask @prefix.to_ip @@ -181,14 +202,16 @@ # Like IPv4#prefix=, this method allow you to # change the prefix / netmask of an IP address # object. # # ip = IPAddress("172.16.100.4") + # # puts ip # #=> 172.16.100.4/16 # # ip.netmask = "255.255.252.0" + # # puts ip # #=> 172.16.100.4/22 # def netmask=(addr) @prefix = Prefix32.parse_netmask(addr) @@ -201,10 +224,11 @@ # This method is identical to the C function # inet_pton to create a 32 bits address family # structure. # # ip = IPAddress("10.0.0.0/8") + # # ip.to_u32 # #=> 167772160 # def to_u32 data.unpack("N").first @@ -214,10 +238,11 @@ # # Returns the address portion of an IPv4 object # in a network byte order format. # # ip = IPAddress("172.16.10.1/24") + # # ip.data # #=> "\254\020\n\001" # # It is usually used to include an IP address # in a data packet to be sent over a socket @@ -235,10 +260,11 @@ # # Returns the octet specified by index # # ip = IPAddress("172.16.100.50/24") + # # ip[0] # #=> 172 # ip[1] # #=> 16 # ip[2] @@ -254,10 +280,11 @@ # # 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 data.unpack("B*").first @@ -265,25 +292,28 @@ # # Returns the broadcast address for the given IP. # # ip = IPAddress("172.16.10.64/24") + # # ip.broadcast.to_s - # #=> "172.16.10.255/24" + # #=> "172.16.10.255" # def broadcast self.class.parse_u32(broadcast_u32, @prefix) end # # Checks 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 @@ -292,12 +322,13 @@ # # Returns a new IPv4 object with the network number # for the given IP. # # ip = IPAddress("172.16.10.64/24") + # # ip.network.to_s - # #=> "172.16.10.0/24" + # #=> "172.16.10.0" # def network self.class.parse_u32(network_u32, @prefix) end @@ -307,19 +338,21 @@ # # 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.to_s - # #=> "192.168.100.1/24" + # #=> "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.to_s - # #=> "192.168.100.1/24" + # #=> "192.168.100.1" # def first self.class.parse_u32(network_u32+1, @prefix) end @@ -327,34 +360,37 @@ # Like its sibling method IPv4#first, this method # returns a new IPv4 object 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. + # host IP address is 192.168.100.254 # # ip = IPAddress("192.168.100.0/24") + # # ip.last.to_s - # #=> "192.168.100.254/24" + # #=> "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.to_s - # #=> "192.168.100.254/24" + # #=> "192.168.100.254" # def last self.class.parse_u32(broadcast_u32-1, @prefix) end # # Iterates over all the hosts IP addresses for the given # network (or IP address). # - # ip = IPaddress("10.0.0.1/29") + # ip = IPAddress("10.0.0.1/29") + # # ip.each do |i| - # p i + # p i.to_s # end # #=> "10.0.0.1" # #=> "10.0.0.2" # #=> "10.0.0.3" # #=> "10.0.0.4" @@ -372,11 +408,12 @@ # network (or IP address). # # The object yielded is a new IPv4 object created # from the iteration. # - # ip = IPaddress("10.0.0.1/29") + # ip = IPAddress("10.0.0.1/29") + # # ip.each do |i| # p i.address # end # #=> "10.0.0.0" # #=> "10.0.0.1" @@ -404,12 +441,12 @@ # "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" + # ip2 = IPAddress "172.16.0.1/16" + # ip3 = IPAddress "10.100.100.1/16" # # ip1 < ip2 # #=> true # ip1 < ip3 # #=> false @@ -432,11 +469,12 @@ # # Returns the number of IP addresses included # in the network. It also counts the network # address and the broadcast address. # - # ip = IPaddress("10.0.0.1/29") + # ip = IPAddress("10.0.0.1/29") + # # ip.size # #=> 8 # def size broadcast_u32 - network_u32 + 1 @@ -444,11 +482,12 @@ # # Returns an array with the IP addresses of # all the hosts in the network. # - # ip = IPaddress("10.0.0.1/29") + # ip = IPAddress("10.0.0.1/29") + # # ip.hosts.map {|i| i.address} # #=> ["10.0.0.1", # #=> "10.0.0.2", # #=> "10.0.0.3", # #=> "10.0.0.4", @@ -460,11 +499,12 @@ end # # Returns the network number in Unsigned 32bits format # - # ip = IPaddress("10.0.0.1/29") + # ip = IPAddress("10.0.0.1/29") + # # ip.network_u32 # #=> 167772160 # def network_u32 to_u32 & @prefix.to_u32 @@ -472,10 +512,11 @@ # # 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 @@ -488,10 +529,11 @@ # object. # # ip = IPAddress("192.168.10.100/24") # # addr = IPAddress("192.168.10.102/24") + # # ip.include? addr # #=> true # # ip.include? IPAddress("172.16.0.48/16") # #=> false @@ -503,16 +545,18 @@ # # 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 + alias_method :arpa, :reverse # # Subnetting a network # # If the IP Address is a network, it can be divided into @@ -522,33 +566,34 @@ # # 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} + # + # network / 4 # implies map{|i| i.to_string} # #=> ["172.16.10.0/26", # "172.16.10.64/26", # "172.16.10.128/26", # "172.16.10.192/26"] # # 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} + # + # network / 3 # implies map{|i| i.to_string} # #=> ["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 - calculate_subnets(subnets) end alias_method :/, :subnet # @@ -562,17 +607,17 @@ # # ip = IPAddress("172.16.10.0/24") # # you can supernet it with a new /23 prefix # - # ip.supernet(23).to_s + # ip.supernet(23).to_string # #=> "172.16.10.0/23" # # However if you supernet it with a /22 prefix, the # network address will change: # - # ip.supernet(22).to_s + # ip.supernet(22).to_string # #=> "172.16.8.0/22" # def supernet(new_prefix) raise ArgumentError, "Can't supernet a /1 network" if new_prefix < 1 raise ArgumentError, "New prefix must be smaller than existing prefix" if new_prefix >= @prefix.to_i @@ -581,10 +626,18 @@ # # Returns the difference between two IP addresses # in unsigned int 32 bits format # + # Example: + # + # ip1 = IPAddress("172.16.10.0/24") + # ip2 = IPAddress("172.16.11.0/24") + # + # puts ip1 - ip2 + # #=> 256 + # def -(oth) return (to_u32 - oth.to_u32).abs end # @@ -594,72 +647,83 @@ # # Example: # # ip1 = IPAddress("172.16.10.1/24") # ip2 = IPAddress("172.16.11.2/24") - # puts ip1 + ip2 - # #=>"172.16.10.0/23" # + # p (ip1 + ip2).map {|i| i.to_string} + # #=> ["172.16.10.0/23"] + # # If the networks are not contiguous, returns # the two network numbers from the objects # + # ip1 = IPAddress("10.0.0.1/24") + # ip2 = IPAddress("10.0.2.1/24") + # + # p (ip1 + ip2).map {|i| i.to_string} + # #=> ["10.0.0.0/24","10.0.2.0/24"] + # def +(oth) - self.class.summarize(self,oth) + aggregate(*[self,oth].sort.map{|i| i.network}) end # # Checks whether the ip address belongs to a # RFC 791 CLASS A network, no matter # what the subnet mask is. # # Example: # # ip = IPAddress("10.0.0.1/24") + # # ip.a? # #=> true # def a? - CLASSFUL.index(8) === bits + CLASSFUL.key(8) === bits end # # Checks whether the ip address belongs to a # RFC 791 CLASS B network, no matter # what the subnet mask is. # # Example: # # ip = IPAddress("172.16.10.1/24") + # # ip.b? # #=> true # def b? - CLASSFUL.index(16) === bits + CLASSFUL.key(16) === bits end # # Checks whether the ip address belongs to a # RFC 791 CLASS C network, no matter # what the subnet mask is. # # Example: # # ip = IPAddress("192.168.1.1/30") + # # ip.c? # #=> true # def c? - CLASSFUL.index(24) === bits + CLASSFUL.key(24) === bits end # # Return the ip address in a format compatible # with the IPv6 Mapped IPv4 addresses # # Example: # # ip = IPAddress("172.16.10.1/24") + # # ip.to_ipv6 # #=> "ac10:0a01" # def to_ipv6 "%.4x:%.4x" % [to_u32].pack("N").unpack("nn") @@ -668,18 +732,20 @@ # # Creates a new IPv4 object from an # unsigned 32bits integer. # # ip = IPAddress::IPv4::parse_u32(167772160) + # # ip.prefix = 8 - # ip.to_s + # ip.to_string # #=> "10.0.0.0/8" # # The +prefix+ parameter is optional: # # ip = IPAddress::IPv4::parse_u32(167772160, 8) - # ip.to_s + # + # ip.to_string # #=> "10.0.0.0/8" # def self.parse_u32(u32, prefix=nil) ip = [u32].pack("N").unpack("C4").join(".") if prefix @@ -697,11 +763,11 @@ # is represented with the binary "\254\020\n\001". # # ip = IPAddress::IPv4::parse_data "\254\020\n\001" # ip.prefix = 24 # - # ip.to_s + # ip.to_string # #=> "172.16.10.1/24" # def self.parse_data(str) self.new str.unpack("C4").join(".") end @@ -711,14 +777,14 @@ # returns a new object # # Example: # # str = "foobar172.16.10.1barbaz" - # ip = self.extract str + # ip = IPAddress::IPv4::extract str # # ip.to_s - # #=> "172.16.10.1/16" + # #=> "172.16.10.1" # def self.extract(str) self.new REGEXP.match(str).to_s end @@ -768,40 +834,42 @@ # # ip1 = IPAddress("10.0.0.1/24") # ip2 = IPAddress("10.0.1.1/24") # ip3 = IPAddress("10.0.2.1/24") # ip4 = IPAddress("10.0.3.1/24") - # IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).to_s + # + # IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).to_string # #=> "10.0.0.0/22", # # But the following networks can't be summarized in a single network: # # ip1 = IPAddress("10.0.1.1/24") # ip2 = IPAddress("10.0.2.1/24") # ip3 = IPAddress("10.0.3.1/24") # ip4 = IPAddress("10.0.4.1/24") - # IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).map{|i| i.to_s} + # + # IPAddress::IPv4::summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string} # #=> ["10.0.1.0/24","10.0.2.0/23","10.0.4.0/24"] # def self.summarize(*args) # one network? no need to summarize - return args.flatten.first if args.size == 1 + return [args.first.network] if args.size == 1 - result, arr, last = [], args.sort, args.sort.last.network - arr.each_cons(2) do |x,y| - snet = x.supernet(x.prefix.to_i-1) - if snet.include? y - result << snet - else - result << x.network unless result.any?{|i| i.include? x} - end + i = 0 + result = args.dup.sort.map{|ip| ip.network} + while i < result.size-1 + sum = result[i] + result[i+1] + result[i..i+1] = sum.first if sum.size == 1 + i += 1 end - result << last unless result.any?{|i| i.include? last} + result.flatten! if result.size == args.size + # nothing more to summarize return result else + # keep on summarizing return self.summarize(*result) end end # @@ -813,16 +881,16 @@ ip.split(".").map{|i| i.to_i}.pack("C4").unpack("B*").first end def prefix_from_ip(ip) bits = bits_from_address(ip) - CLASSFUL.each {|reg,prefix| return prefix if bits =~ reg} + CLASSFUL.each {|reg,prefix| return Prefix32.new(prefix) if bits =~ reg} end def calculate_subnets(subnets) po2 = subnets.closest_power_of_2 - new_prefix = @prefix.to_i + Math::log2(po2).to_i + new_prefix = @prefix + Math::log2(po2).to_i networks = Array.new (0..po2-1).each do |i| mul = i * (2**(32-new_prefix)) networks << IPAddress::IPv4.parse_u32(network_u32+mul, new_prefix) end @@ -840,9 +908,24 @@ dup[i..i+1] = a return dup.reverse end end return dup.reverse + end + + def aggregate(ip1,ip2) + if ip1.include? ip2 + return [ip1] + else + snet = ip1.supernet(ip1.prefix-1) + arr1 = ip1.subnet(2**(ip2.prefix-ip1.prefix)).map{|i| i.to_string} + arr2 = snet.subnet(2**(ip2.prefix-snet.prefix)).map{|i| i.to_string} + if (arr2 - [ip2.to_string] - arr1).empty? + return [snet] + else + return [ip1, ip2] + end + end end end # class IPv4 end # module IPAddress