ipaddress2-gem/ipaddress_2

View on GitHub
lib/ipaddress_2/ipv6.rb

Summary

Maintainability
D
2 days
Test Coverage
require 'ipaddress_2/prefix'

module IPAddress;
  #
  # =Name
  #
  # IPAddress::IPv6 - IP version 6 address manipulation library
  #
  # =Synopsis
  #
  #    require 'ipaddress'
  #
  # =Description
  #
  # Class IPAddress::IPv6 is used to handle IPv6 type addresses.
  #
  # == IPv6 addresses
  #
  # IPv6 addresses are 128 bits long, in contrast with IPv4 addresses
  # which are only 32 bits long. An IPv6 address is generally written as
  # eight groups of four hexadecimal digits, each group representing 16
  # bits or two octect. For example, the following is a valid IPv6
  # address:
  #
  #   2001:0db8:0000:0000:0008:0800:200c:417a
  #
  # Letters in an IPv6 address are usually written downcase, as per
  # RFC. You can create a new IPv6 object using uppercase letters, but
  # they will be converted.
  #
  # === Compression
  #
  # Since IPv6 addresses are very long to write, there are some
  # semplifications and compressions that you can use to shorten them.
  #
  # * Leading zeroes: all the leading zeroes within a group can be
  #   omitted: "0008" would become "8"
  #
  # * A string of consecutive zeroes can be replaced by the string
  #   "::". This can be only applied once.
  #
  # Using compression, the IPv6 address written above can be shorten into
  # the following, equivalent, address
  #
  #   2001:db8::8:800:200c:417a
  #
  # This short version is often used in human representation.
  #
  # === Network Mask
  #
  # As we used to do with IPv4 addresses, an IPv6 address can be written
  # using the prefix notation to specify the subnet mask:
  #
  #   2001:db8::8:800:200c:417a/64
  #
  # The /64 part means that the first 64 bits of the address are
  # representing the network portion, and the last 64 bits are the host
  # portion.
  #
  #
  class IPv6

    include IPAddress
    include Enumerable
    include Comparable


    #
    # Format string to pretty print IPv6 addresses
    #
    IN6FORMAT = ("%.4x:"*8).chop

    #
    # Creates a new IPv6 address object.
    #
    # An IPv6 address can be expressed in any of the following forms:
    #
    # * "2001:0db8:0000:0000:0008:0800:200C:417A": IPv6 address with no compression
    # * "2001:db8:0:0:8:800:200C:417A": IPv6 address with leading zeros compression
    # * "2001:db8::8:800:200C:417A": IPv6 address with full compression
    #
    # In all these 3 cases, a new IPv6 address object will be created, using the default
    # subnet mask /128
    #
    # You can also specify the subnet mask as with IPv4 addresses:
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    def initialize(str)
      raise ArgumentError, "Nil IP" unless str
      ip, netmask = str.split("/")

      if str =~ /:.+\./
        raise ArgumentError, "Please use #{self.class}::Mapped for IPv4 mapped addresses"
      end

      if IPAddress.valid_ipv6?(ip)
        @groups = self.class.groups(ip)
        @address = IN6FORMAT % @groups
        @compressed = compress_address
      else
        raise ArgumentError, "Invalid IP #{ip.inspect}"
      end

      @prefix = Prefix128.new(netmask ? netmask : 128)
      @allocator = 0

    end # def initialize

    #
    # Returns the IPv6 address in uncompressed form:
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    #   ip6.address
    #     #=> "2001:0db8:0000:0000:0008:0800:200c:417a"
    #
    def address
      @address
    end

    # When serializing to JSON format, just use the string representation
    #
    #   ip = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    #   ip.as_json
    #     #=> "2001:db8::8:800:200c:417a/64"
    #
    def as_json
      to_string
    end

    #
    # Returns an array with the 16 bits groups in decimal 
    # format:
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    #   ip6.groups
    #     #=> [8193, 3512, 0, 0, 8, 2048, 8204, 16762]
    #
    def groups
      @groups
    end

    #
    # Returns an instance of the prefix object
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    #   ip6.prefix
    #     #=> 64
    #
    def prefix
      @prefix
    end

    #
    # Set a new prefix number for the object
    #
    # This is useful if you want to change the prefix
    # to an object created with IPv6::parse_u128 or
    # if the object was created using the default prefix
    # of 128 bits.
    #
    #   ip6 = IPAddress("2001:db8::8:800:200c:417a")
    #
    #   puts ip6.to_string
    #     #=> "2001:db8::8:800:200c:417a/128"
    #
    #   ip6.prefix = 64
    #   puts ip6.to_string
    #     #=> "2001:db8::8:800:200c:417a/64"
    #
    def prefix=(num)
      @prefix = Prefix128.new(num)
    end

    #
    # Unlike its counterpart IPv6#to_string method, IPv6#to_string_uncompressed
    # returns the whole IPv6 address and prefix in an uncompressed form
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    #   ip6.to_string_uncompressed
    #     #=> "2001:0db8:0000:0000:0008:0800:200c:417a/64"
    #
    def to_string_uncompressed
      "#@address/#@prefix"
    end

    #
    # Returns the IPv6 address in a human readable form,
    # using the compressed address.
    #
    #   ip6 = IPAddress "2001:0db8:0000:0000:0008:0800:200c:417a/64"
    #
    #   ip6.to_string
    #     #=> "2001:db8::8:800:200c:417a/64"
    #
    def to_string
      "#@compressed/#@prefix"
    end

    #
    # Returns the IPv6 address in a human readable form,
    # using the compressed address.
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    #   ip6.to_s
    #     #=> "2001:db8::8:800:200c:417a"
    #
    def to_s
      @compressed
    end

    #
    # Returns a decimal format (unsigned 128 bit) of the
    # IPv6 address
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    #   ip6.to_i
    #     #=> 42540766411282592856906245548098208122
    #
    def to_i
      to_hex.hex
    end
    alias_method :to_u128, :to_i

    #
    # True if the IPv6 address is a network
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    #   ip6.network?
    #     #=> false
    #
    #   ip6 = IPAddress "2001:db8:8:800::/64"
    #
    #   ip6.network?
    #     #=> true
    #
    def network?
      to_u128 | @prefix.to_u128 == @prefix.to_u128
    end

    #
    # Returns a new IPv6 object with the
    # first host IP address in the range.
    # 
    # Example: given the 2001:db8:8:800::/64 network, the first
    # host IP address is 2001:db8:8:800::
    #
    #   ip = IPAddress("2001:db8:8:800::/64")
    #
    #   ip.first.to_s
    #     #=> "2001:db8:8:800::"
    #
    # The object IP doesn't need to be a network: the method
    # automatically gets the network number from it
    #
    #   ip = IPAddress("2001:db8:9:800::2/64")
    #
    #   ip.first.to_s
    #     #=> "2001:db8:9:800::"
    #
    def first
      if prefix == 128
        return self
      else
        IPAddress::IPv6::parse_u128(network_u128)
      end
    end

    #
    # 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.254
    #
    #   ip = IPAddress("2001:db8:8:800::/64")
    #
    #   ip.last.to_s
    #     #=> "2001:db8:8:800:ffff:ffff:ffff:ffff"
    #
    # The object IP doesn't need to be a network: the method
    # automatically gets the network number from it
    #
    #   ip = IPAddress("2001:db8:9:800::2/64")
    #
    #   ip.last.to_s
    #     #=> "2001:db8:9:800:ffff:ffff:ffff:ffff"
    #
    def last
      if prefix == 128
        return self
      else
        IPAddress::IPv6::parse_u128(broadcast_u128)
      end
    end

    #
    # Returns the 16-bits value specified by index
    #
    #   ip = IPAddress("2001:db8::8:800:200c:417a/64")
    #
    #   ip[0]
    #     #=> 8193
    #   ip[1]
    #     #=> 3512
    #   ip[2]
    #     #=> 0
    #   ip[3]
    #     #=> 0
    #
    def [](index)
      @groups[index]
    end
    alias_method :group, :[]

    #
    # Updated the octet specified at index
    #
    def []=(index, value)
      @groups[index] = value
      initialize("#{IN6FORMAT % @groups}/#{prefix}")
    end
    alias_method :group=, :[]=

    #
    # Returns a Base16 number representing the IPv6
    # address
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    #   ip6.to_hex
    #     #=> "20010db80000000000080800200c417a"
    #
    def to_hex
      hexs.join("")
    end

    # Returns the address portion of an IPv6 object
    # in a network byte order format.
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    #   ip6.data
    #     #=> " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
    #
    # It is usually used to include an IP address
    # in a data packet to be sent over a socket
    #
    #   a = Socket.open(params) # socket details here
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #   binary_data = ["Address: "].pack("a*") + ip.data
    #
    #   # Send binary data
    #   a.puts binary_data
    #
    def data
      @groups.pack("n8")
    end

    #
    # Returns an array of the 16 bits groups in hexdecimal
    # format:
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    #   ip6.hexs
    #     #=> ["2001", "0db8", "0000", "0000", "0008", "0800", "200c", "417a"]
    #
    # Not to be confused with the similar IPv6#to_hex method.
    #
    def hexs
      @address.split(":")
    end

    #
    # Returns the IPv6 address in a DNS reverse lookup
    # string, as per RFC3172 and RFC2874.
    #
    #   ip6 = IPAddress "3ffe:505:2::f"
    #
    #   ip6.reverse
    #     #=> "f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa"
    #
    def reverse
      to_hex.reverse.gsub(/./){|c| c+"."} + "ip6.arpa"
    end
    alias_method :arpa, :reverse
    
    #
    # Returns a new IPv6 object which is the result 
    # of advancing this IP address by a given value.
    # In other words, this arithmetically adds IP addresses.
    #
    # Will raise an error if the resulting address is in a different subnet,
    # except validating is set to false.
    #
    # Example:
    #
    #   ip = IPAddress::IPv6.new("fc42:1337::/64")
    #   ip.add(5).to_string
    #     #=> "fc42:1337::5/64"
    def add(oth, validating=true)
      oth = oth.to_i if oth.kind_of? IPAddress::IPv6 # oth shall be integer
      
      new_obj = self.class.parse_u128(self.to_i + oth, prefix)
      
      if validating and self.network_u128 != new_obj.network_u128
        raise RuntimeError, "Subnet (/#{@prefix}) is not large enough."
      end
      
      new_obj
    end
    
    #
    # Returns a new IPv6 object which is the result 
    # of decreasing this IP address by a given value.
    # In other words, this arithmetically subtracts IP addresses.
    #
    # Will raise an error if the resulting address is in a different subnet,
    # except validating is set to false.
    #
    # Example:
    #
    #   ip = IPAddress::IPv6.new("fc42:1337::a/64")
    #   ip.subtract(5).to_string
    #     #=> "fc42:1337::5/64"
    def subtract(oth, validating=true)
      oth = oth.to_i if oth.kind_of? IPAddress::IPv6 # oth shall be integer
      add(-oth, validating)
    end

    #
    # Returns the network address of the n-th network succeeding this one.
    #
    # Example:
    #
    #   ip = IPAddress::IPv6.new("fc42:1337:0:0::/64")
    #   ip.advance_network(5).to_string
    #     #=> "fc42:1337:0:5::/64"
    def advance_network(amount)
      IPAddress::IPv6.parse_u128(self.network.to_i + amount*self.size, @prefix)
    end
    
    #
    # Returns the network address of the network succeeding this one.
    #
    # Example:
    #
    #   ip = IPAddress::IPv6.new("fc42:1337:0:0::/64")
    #   ip.next_network.to_string
    #     #=> "fc42:1337:0:1::/64"
    def next_network
      advance_network 1
    end
    
    #
    # Returns the network address of the n-th network preceeding this one.
    #
    # Example:
    #
    #   ip = IPAddress::IPv6.new("fc42:1337:0:5::/64")
    #   ip.regress_network(4).to_string
    #     #=> "fc42:1337:0:1::/64"
    def regress_network(amount)
      advance_network(-amount)
    end
    
    #
    # Returns the network address of the network preceeding this one.
    #
    # Example:
    #
    #   ip = IPAddress::IPv6.new("fc42:1337:0:5::/64")
    #   ip.previous_network.to_string
    #     #=> "fc42:1337:0:4::/64"
    def previous_network
      regress_network 1
    end
    
    #
    # Returns a new IPv6 object containing only the host part of this IP.
    #
    #   ip = IPAddress::IPv6.new("fc42:1337:0:5::7/64")
    #
    #   ip.hostpart.to_s
    #     #=> "::7"
    #
    def hostpart
      self.class.parse_u128(hostpart_u128, 128)
    end
    
    # Splits a network into different subnets
    #
    # NOTE: Will allow you to split past /64 against RFC 5375
    #
    # If the IP Address is a network, it can be divided into
    # multiple networks. If +self+ is not a network, this
    # method will calculate the network from the IP and then
    # subnet it.
    #
    # If +subnets+ is an power of two number, the resulting 
    # networks will be divided evenly from the supernet.
    #
    #   network = IPAddress("2001:db8:8::/48")
    #
    #   network / 4   # implies map{|i| i.to_string}
    #     #=> ["2001:db8:8::/50",
    #     #=>  "2001:db8:8:4000::/50",
    #     #=>  "2001:db8:8:8000::/50",
    #     #=>  "2001:db8:8:c000::/50"]
    #     
    # 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("2001:db8:8::/48")
    #
    #   network / 3   # implies map{|i| i.to_string}
    #   
    #     #=> ["2001:db8:8::/50",
    #     #=>  "2001:db8:8:4000::/50",
    #     #=>  "2001:db8:8:8000::/49"]
    #
    # Returns an array of IPv6 objects
    #
    def split(subnets=2)
      unless (1..(2**@prefix.host_prefix)).include? subnets
        raise ArgumentError, "Value #{subnets} out of range" 
      end
      networks = subnet(newprefix(subnets))
      until networks.size == subnets
        networks = sum_first_found(networks)
      end
      return networks
    end
    alias_method :/, :split

    #
    # Returns the network number in Unsigned 128bits format
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    #   ip6.network_u128
    #     #=> 42540766411282592856903984951653826560
    #
    def network_u128
      to_u128 & @prefix.to_u128
    end
    
    #
    # Returns this address' host part in unsigned 128bits format
    #
    #   ip = IPAddress::IPv6.new("fc42:1337:0:5::7/64")
    #
    #   ip.host_u128
    #     #=> 7
    #
    def hostpart_u128
      to_u128 & ~@prefix.to_u128
    end

    #
    # Returns the broadcast address in Unsigned 128bits format
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    #   ip6.broadcast_u128
    #     #=> 42540766411282592875350729025363378175
    #
    # Please note that there is no Broadcast concept in IPv6
    # addresses as in IPv4 addresses, and this method is just
    # an helper to other functions.
    #
    def broadcast_u128
      network_u128 + size - 1
    end

    #
    # Returns the number of IP addresses included
    # in the network. It also counts the network
    # address and the broadcast address.
    #
    #   ip6 = IPAddress("2001:db8::8:800:200c:417a/64")
    #
    #   ip6.size
    #     #=> 18446744073709551616
    #
    def size
      2 ** @prefix.host_prefix
    end

    #
    # Checks whether a subnet includes the given IP address.
    #
    # Example:
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #   addr = IPAddress "2001:db8::8:800:200c:1/128"
    #
    #   ip6.include? addr
    #     #=> true
    #
    #   ip6.include? IPAddress("2001:db8:1::8:800:200c:417a/76")
    #     #=> false
    #
    #   ip6.include? "2001:db8::8:800:200c:1"
    #     #=> true
    #
    def include?(oth)
      unless oth.is_a? IPAddress::IPv6
        oth = IPv6.new(oth)
      end
      @prefix <= oth.prefix and network_u128 == self.class.new(oth.address+"/#@prefix").network_u128
    end

    #
    # Checks whether a subnet includes all the
    # given IPv4 objects or strings.
    #
    #   ip = IPAddress("2001:db8:8:800::1/64")
    #
    #   addr1 = IPAddress("2001:db8:8:800::2/64")
    #   addr2 = IPAddress("2001:db8:8:800::8/64")
    #
    #   ip.include_all?(addr1,addr2)
    #     #=> true
    #
    #   ip.include_all?("2001:db8:8:800::2/64", "2001:db8:8:800::8/64")
    #     #=> true
    #
    def include_all?(*others)
      others.all? {|oth| include?(oth)}
    end

    #
    # Compressed form of the IPv6 address
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    #   ip6.compressed
    #     #=> "2001:db8::8:800:200c:417a"
    #
    def compressed
      @compressed
    end

    #
    # Returns true if the address is an unspecified address
    #
    # See IPAddress::IPv6::Unspecified for more information
    #
    def unspecified?
      @prefix == 128 and @compressed == "::"
    end

    #
    # Returns true if the address is a loopback address
    #
    # See IPAddress::IPv6::Loopback for more information
    #
    def loopback?
      @prefix == 128 and @compressed == "::1"
    end

    #
    # Checks if an IPv6 address objects belongs
    # to a link-local network RFC4291
    #
    # Example:
    #
    #   ip = IPAddress "fe80::1"
    #   ip.link_local?
    #     #=> true
    #
    def link_local?
      [self.class.new("fe80::/10")].any? {|i| i.include? self}
    end

    #
    # Checks if an IPv6 address objects belongs
    # to a unique-local network RFC4193
    #
    # Example:
    #
    #   ip = IPAddress "fc00::1"
    #   ip.unique_local?
    #     #=> true
    #
    def unique_local?
      [self.class.new("fc00::/7")].any? {|i| i.include? self}
    end

    #
    # Returns true if the address is a mapped address
    #
    # See IPAddress::IPv6::Mapped for more information
    #
    def mapped?
      to_u128 >> 32 == 0xffff
    end

    #
    # Returns a new IPv6 object which is the result
    # of the summarization, if possible, of the two
    # objects
    #
    # Example:
    #
    #   ip1 = IPAddress("172.16.10.1/24")
    #   ip2 = IPAddress("172.16.11.2/24")
    #
    #   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)
      aggregate(*[self,oth].sort.map{|i| i.network})
    end

    #
    # Returns a new IPv4 object from the supernetting
    # of the instance network.
    #
    # Supernetting is similar to subnetting, except
    # that you getting as a result a network with a
    # smaller prefix (bigger host space). For example,
    # given the network
    #
    #   ip = IPAddress("2001:db8:8:800::1/64")
    #
    # you can supernet it with a new /32 prefix
    #
    #   ip.supernet(32).to_string
    #     #=> "2001:db8::/32"
    #
    # However if you supernet it with a /22 prefix, the
    # network address will change:
    #
    #   ip.supernet(22).to_string
    #     #=> "2001:c00::/22"
    #
    # If +new_prefix+ is less than 1, returns 0000:0000:0000:0000:0000:0000:0000:0000/0
    #
    def supernet(new_prefix)
      raise ArgumentError, "New prefix must be smaller than existing prefix" if new_prefix >= @prefix.to_i
      return self.class.new("0000:0000:0000:0000:0000:0000:0000:0000/0") if new_prefix < 1
      return self.class.new(@address+"/#{new_prefix}").network
    end

    #
    # This method implements the subnetting function
    # similar to the one described in RFC3531.
    #
    # By specifying a new prefix, the method calculates
    # the network number for the given IPv4 object
    # and calculates the subnets associated to the new
    # prefix.
    #
    # For example, given the following network:
    #
    #   ip = IPAddress "172.16.10.0/24"
    #
    # we can calculate the subnets with a /26 prefix
    #
    #   ip.subnet(26).map{&:to_string)
    #     #=> ["172.16.10.0/26", "172.16.10.64/26",
    #          "172.16.10.128/26", "172.16.10.192/26"]
    #
    # The resulting number of subnets will of course always be
    # a power of two.
    #
    def subnet(subprefix)
      unless ((@prefix.to_i)..128).include? subprefix
        raise ArgumentError, "New prefix must be between #@prefix and 128"
      end
      Array.new(2**(subprefix-@prefix.to_i)) do |i|
        self.class.parse_u128(network_u128+(i*(2**(128-subprefix))), subprefix)
      end
    end


    #
    # Iterates over all the IP addresses for the given
    # network (or IP address).
    #
    # The object yielded is a new IPv6 object created
    # from the iteration.
    #
    #   ip6 = IPAddress("2001:db8::4/125")
    #
    #   ip6.each do |i|
    #     p i.compressed
    #   end
    #     #=> "2001:db8::"
    #     #=> "2001:db8::1"
    #     #=> "2001:db8::2"
    #     #=> "2001:db8::3"
    #     #=> "2001:db8::4"
    #     #=> "2001:db8::5"
    #     #=> "2001:db8::6"
    #     #=> "2001:db8::7"
    #
    # WARNING: if the host portion is very large, this method
    # can be very slow and possibly hang your system!
    #
    def each
      (network_u128..broadcast_u128).each do |i|
        yield self.class.parse_u128(i, @prefix)
      end
    end

    #
    # Returns the successor to the IP address
    #
    # Example:
    #
    #   ip6 = IPAddress("2001:db8::8:800:200c:417a/64")
    #
    #   ip6.succ.to_string
    #     => "2001:db8::8:800:200c:417b/64"
    #
    def succ
      IPAddress::IPv6.parse_u128(to_u128.succ, prefix)
    end
    alias_method :next, :succ

    #
    # Returns the predecessor to the IP address
    #
    # Example:
    #
    #   ip6 = IPAddress("2001:db8::8:800:200c:417a/64")
    #
    #   ip6.pred.to_string
    #     => "2001:db8::8:800:200c:4179/64"
    #
    def pred
      IPAddress::IPv6.parse_u128(to_u128.pred, prefix)
    end

    #
    # Spaceship operator to compare IPv6 objects
    #
    # Comparing IPv6 addresses is useful to ordinate
    # them into lists that match our intuitive
    # perception of ordered IP addresses.
    #
    # The first comparison criteria is the u128 value.
    # For example, 2001:db8:1::1 will be considered
    # to be less than 2001:db8:2::1, because, in a ordered list,
    # we expect 2001:db8:1::1 to come before 2001:db8:2::1.
    #
    # The second criteria, in case two IPv6 objects
    # have identical addresses, is the prefix. An higher
    # prefix will be considered greater than a lower
    # prefix. This is because we expect to see
    # 2001:db8:1::1/64 come before 2001:db8:1::1/65
    #
    # Example:
    #
    #   ip1 = IPAddress "2001:db8:1::1/64"
    #   ip2 = IPAddress "2001:db8:2::1/64"
    #   ip3 = IPAddress "2001:db8:1::1/65"
    #
    #   ip1 < ip2
    #     #=> true
    #   ip1 < ip3
    #     #=> false
    #
    #   [ip1,ip2,ip3].sort.map{|i| i.to_string}
    #     #=> ["2001:db8:1::1/64","2001:db8:1::1/65","2001:db8:2::1/64"]
    #
    def <=>(oth)
      return nil unless oth.is_a?(self.class)
      return prefix <=> oth.prefix if to_u128 == oth.to_u128
      to_u128 <=> oth.to_u128
    end

    #
    # Returns the address portion of an IP in binary format,
    # as a string containing a sequence of 0 and 1
    #
    #   ip6 = IPAddress("2001:db8::8:800:200c:417a")
    #
    #   ip6.bits
    #     #=> "0010000000000001000011011011100000 [...] "
    #
    def bits
      data.unpack("B*").first
    end

    #
    # Expands an IPv6 address in the canocical form
    #
    #   IPAddress::IPv6.expand "2001:0DB8:0:CD30::"
    #     #=> "2001:0DB8:0000:CD30:0000:0000:0000:0000"
    #
    def self.expand(str)
      self.new(str).address
    end

    #
    # Compress an IPv6 address in its compressed form
    #
    #   IPAddress::IPv6.compress "2001:0DB8:0000:CD30:0000:0000:0000:0000"
    #     #=> "2001:db8:0:cd30::"
    #
    def self.compress(str)
      self.new(str).compressed
    end

    #
    # Literal version of the IPv6 address
    #
    #   ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
    #
    #   ip6.literal
    #     #=> "2001-0db8-0000-0000-0008-0800-200c-417a.ipv6-literal.net"
    #
    def literal
      @address.gsub(":","-") + ".ipv6-literal.net"
    end

    #
    # Returns a new IPv6 object with the network number
    # for the given IP.
    #
    #   ip = IPAddress "2001:db8:1:1:1:1:1:1/32"
    #
    #   ip.network.to_string
    #     #=> "2001:db8::/32"
    #
    def network
      self.class.parse_u128(network_u128, @prefix)
    end

    #
    # Returns the broadcast address for the given IP.
    # As this is IPv6 it is just the last IP
    #
    #   ip = IPAddress("2001:db8:8:800::/64")
    #
    #   ip.broadcast.to_s
    #     #=> "2001:db8:8:800::"
    #
    def broadcast
      if prefix == 128
        return self
      else
        IPAddress::IPv6::parse_u128(broadcast_u128)
      end
    end

    #
    # Extract 16 bits groups from a string
    #
    def self.groups(str)
      l, r = if str =~ /^(.*)::(.*)$/
               [$1,$2].map {|i| i.split ":"}
             else
               [str.split(":"),[]]
             end
      (l + Array.new(8-l.size-r.size, '0') + r).map {|i| i.hex}
    end

    #
    # Creates a new IPv6 object from binary data,
    # like the one you get from a network stream.
    #
    # For example, on a network stream the IP
    #
    #  "2001:db8::8:800:200c:417a"
    #
    # is represented with the binary data
    #
    #   " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
    #
    # With that data you can create a new IPv6 object:
    #
    #   ip6 = IPAddress::IPv6::parse_data " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
    #   ip6.prefix = 64
    #
    #   ip6.to_s
    #     #=> "2001:db8::8:800:200c:417a/64"
    #
    def self.parse_data(str)
      self.new(IN6FORMAT % str.unpack("n8"))
    end

    #
    # Creates a new IPv6 object from an
    # unsigned 128 bits integer.
    #
    #   ip6 = IPAddress::IPv6::parse_u128(42540766411282592856906245548098208122)
    #   ip6.prefix = 64
    #
    #   ip6.to_string
    #     #=> "2001:db8::8:800:200c:417a/64"
    #
    # The +prefix+ parameter is optional:
    #
    #   ip6 = IPAddress::IPv6::parse_u128(42540766411282592856906245548098208122, 64)
    #
    #   ip6.to_string
    #     #=> "2001:db8::8:800:200c:417a/64"
    #
    def self.parse_u128(u128, prefix=128)
      str = IN6FORMAT % (0..7).map{|i| (u128>>(112-16*i))&0xffff}
      self.new(str + "/#{prefix}")
    end

    #
    # Creates a new IPv6 object from a number expressed in
    # hexdecimal format:
    #
    #   ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a")
    #   ip6.prefix = 64
    #
    #   ip6.to_string
    #     #=> "2001:db8::8:800:200c:417a/64"
    #
    # The +prefix+ parameter is optional:
    #
    #   ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a", 64)
    #
    #   ip6.to_string
    #     #=> "2001:db8::8:800:200c:417a/64"
    #
    def self.parse_hex(hex, prefix=128)
      self.parse_u128(hex.hex, prefix)
    end

    #
    # Summarization (or aggregation) is the process when two or more
    # networks are taken together to check if a supernet, including all
    # and only these networks, exists. If it exists then this supernet
    # is called the summarized (or aggregated) network.
    #
    # It is very important to understand that summarization can only
    # occur if there are no holes in the aggregated network, or, in other
    # words, if the given networks fill completely the address space
    # of the supernet. So the two rules are:
    #
    # 1) The aggregate network must contain +all+ the IP addresses of the
    #    original networks;
    # 2) The aggregate network must contain +only+ the IP addresses of the
    #    original networks;
    #
    # A few examples will help clarify the above. Let's consider for
    # instance the following two networks:
    #
    #   ip1 = IPAddress("2001:db8:8:800::1/64")
    #   ip2 = IPAddress("2001:0db8:8:801::2/64")
    #
    # These two networks can be expressed using only one IP address
    # network if we change the prefix. Let Ruby do the work:
    #
    #   IPAddress::IPv6::summarize(ip1,ip2).to_s
    #     #=> "2001:db8:8:800::/63"
    #
    # We note how the network "2001:db8:8:800::/63" includes all the addresses
    # specified in the above networks, and (more important) includes
    # ONLY those addresses.
    #
    # If we summarized +ip1+ and +ip2+ with the following network:
    #
    #   "2001:db8::/32"
    #
    # we would have satisfied rule #1 above, but not rule #2. So "2001:db8::/32"
    # is not an aggregate network for +ip1+ and +ip2+.
    #
    # If it's not possible to compute a single aggregated network for all the
    # original networks, the method returns an array with all the aggregate
    # networks found. For example, the following four networks can be
    # aggregated in a single /22:
    #
    #   ip1 = IPAddress("2001:db8:8:800::1/64")
    #   ip2 = IPAddress("2001:db8:8:801::1/64")
    #   ip3 = IPAddress("2001:db8:8:802::1/64")
    #   ip4 = IPAddress("2001:db8:8:803::1/64")
    #
    #   IPAddress::IPv6::summarize(ip1,ip2,ip3,ip4).to_string
    #     #=> "2001:db8:8:800::/62",
    #
    # But the following networks can't be summarized in a single network:
    #
    #   ip1 = IPAddress("2001:db8:8:801::1/64")
    #   ip2 = IPAddress("2001:db8:8:802::1/64")
    #   ip3 = IPAddress("2001:db8:8:803::1/64")
    #   ip4 = IPAddress("2001:db8:8:804::1/64")
    #
    #   IPAddress::IPv6::summarize(ip1,ip2,ip3,ip4).map{|i| i.to_string}
    #     #=> ["2001:db8:8:801::/64","2001:db8:8:802::/63","2001:db8:8:804::/64"]
    #
    def self.summarize(*args)
      # one network? no need to summarize
      return [args.first.network] if args.size == 1
      args_size = args.size 

      i = 0
      result = args.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.flatten!
      if result.size == args_size
        # nothing more to summarize
        return result
      else
        # keep on summarizing
        return self.summarize(*result)
      end
    end

    #
    # Allocates a new ip from the current subnet. Optional skip parameter
    # can be used to skip addresses.
    #
    # Will raise StopIteration exception when all addresses have been allocated
    #
    # Example:
    #
    #    ip = IPAddress("10.0.0.0/24")
    #    ip.allocate
    #      #=> "10.0.0.1/24"
    #    ip.allocate
    #      #=> "10.0.0.2/24"
    #    ip.allocate(2)
    #      #=> "10.0.0.5/24"
    #
    #
    # Uses an internal @allocator which tracks the state of allocated
    # addresses.
    #
    def allocate(skip=0)
        @allocator += 1 + skip

        next_ip = network_u128+@allocator
        if next_ip > broadcast_u128
            raise StopIteration
        end
        self.class.parse_u128(next_ip, @prefix)
    end

    #
    # Finds the adjacent block to a subnet.
    #
    # Example:
    #
    #    ip = IPAddress("2001:db8::/32")
    #    ip.find_adjacent_subnet
    #      #=> "2001:db9::/32"
    #
    def find_adjacent_subnet
      return false if prefix == 0
      current_subnet = to_string
      self.prefix = @prefix - 1
      adjacent_subnet = (split.map{|i| i.to_string} - [current_subnet])[0]
      self.prefix = @prefix + 1
      return adjacent_subnet
    end

    private

    def newprefix(num)
      return @prefix + (Math::log2(num).ceil )
    end

    def sum_first_found(arr)
      dup = arr.dup.reverse
      dup.each_with_index do |obj,i|
        a = [self.class.summarize(obj,dup[i+1])].flatten
        if a.size == 1
          dup[i..i+1] = a
          return dup.reverse
        end
      end
      return dup.reverse
    end

    def compress_address
      str = @groups.map{|i| i.to_s 16}.join ":"
      loop do
        break if str.sub!(/\A0:0:0:0:0:0:0:0\Z/, '::')
        break if str.sub!(/\b0:0:0:0:0:0:0\b/, ':')
        break if str.sub!(/\b0:0:0:0:0:0\b/, ':')
        break if str.sub!(/\b0:0:0:0:0\b/, ':')
        break if str.sub!(/\b0:0:0:0\b/, ':')
        break if str.sub!(/\b0:0:0\b/, ':')
        break if str.sub!(/\b0:0\b/, ':')
        break
      end
      str.sub(/:{3,}/, '::')
    end

    def aggregate(ip1,ip2)
      return [ip1] if ip1.include? ip2

      snet = ip1.supernet(ip1.prefix-1)
      if snet.include_all?(ip1, ip2) && ((ip1.size + ip2.size) == snet.size)
        return [snet]
      else
        return [ip1, ip2]
      end
    end

  end # class IPv6

  #
  # The address with all zero bits is called the +unspecified+ address
  # (corresponding to 0.0.0.0 in IPv4). It should be something like this:
  #
  #   0000:0000:0000:0000:0000:0000:0000:0000
  #
  # but, with the use of compression, it is usually written as just two
  # colons:
  #
  #   ::
  #
  # or, specifying the netmask:
  #
  #   ::/128
  #
  # With IPAddress, create a new unspecified IPv6 address using its own
  # subclass:
  #
  #   ip = IPAddress::IPv6::Unspecified.new
  #
  #   ip.to_s
  #     #=> => "::/128"
  #
  # You can easily check if an IPv6 object is an unspecified address by
  # using the IPv6#unspecified? method
  #
  #   ip.unspecified?
  #     #=> true
  #
  # An unspecified IPv6 address can also be created with the wrapper
  # method, like we've seen before
  #
  #   ip = IPAddress "::"
  #
  #   ip.unspecified?
  #     #=> true
  #
  # This address must never be assigned to an interface and is to be used
  # only in software before the application has learned its host's source
  # address appropriate for a pending connection. Routers must not forward
  # packets with the unspecified address.
  #
  class IPAddress::IPv6::Unspecified < IPAddress::IPv6
    #
    # Creates a new IPv6 unspecified address
    #
    #   ip = IPAddress::IPv6::Unspecified.new
    #
    #   ip.to_s
    #      #=> => "::/128"
    #
    def initialize
      @address = ("0000:"*8).chop
      @groups = Array.new(8,0)
      @prefix = Prefix128.new(128)
      @compressed = compress_address
    end
  end # class IPv6::Unspecified

  #
  #   The loopback  address is a unicast localhost address. If an
  # application in a host sends packets to this address, the IPv6 stack
  # will loop these packets back on the same virtual interface.
  #
  # Loopback addresses are expressed in the following form:
  #
  #   ::1
  #
  # or, with their appropriate prefix,
  #
  #   ::1/128
  #
  # As for the unspecified addresses, IPv6 loopbacks can be created with
  # IPAddress calling their own class:
  #
  #   ip = IPAddress::IPv6::Loopback.new
  #
  #   ip.to_string
  #     #=> "::1/128"
  #
  # or by using the wrapper:
  #
  #   ip = IPAddress "::1"
  #
  #   ip.to_string
  #     #=> "::1/128"
  #
  # Checking if an address is loopback is easy with the IPv6#loopback?
  # method:
  #
  #   ip.loopback?
  #     #=> true
  #
  # The IPv6 loopback address corresponds to 127.0.0.1 in IPv4.
  #
  class IPAddress::IPv6::Loopback < IPAddress::IPv6
    #
    # Creates a new IPv6 unspecified address
    #
    #   ip = IPAddress::IPv6::Loopback.new
    #
    #   ip.to_string
    #     #=> "::1/128"
    #
    def initialize
      @address = ("0000:"*7)+"0001"
      @groups = Array.new(7,0).push(1)
      @prefix = Prefix128.new(128)
      @compressed = compress_address
    end
  end # class IPv6::Loopback

  #
  # It is usually identified as a IPv4 mapped IPv6 address, a particular
  # IPv6 address which aids the transition from IPv4 to IPv6. The
  # structure of the address is
  #
  #   ::ffff:w.y.x.z
  #
  # where w.x.y.z is a normal IPv4 address. For example, the following is
  # a mapped IPv6 address:
  #
  #   ::ffff:192.168.100.1
  #
  # IPAddress is very powerful in handling mapped IPv6 addresses, as the
  # IPv4 portion is stored internally as a normal IPv4 object. Let's have
  # a look at some examples. To create a new mapped address, just use the
  # class builder itself
  #
  #   ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128"
  #
  # or just use the wrapper method
  #
  #   ip6 = IPAddress "::ffff:172.16.10.1/128"
  #
  # Let's check it's really a mapped address:
  #
  #   ip6.mapped?
  #     #=> true
  #
  #   ip6.to_string
  #     #=> "::FFFF:172.16.10.1/128"
  #
  # Now with the +ipv4+ attribute, we can easily access the IPv4 portion
  # of the mapped IPv6 address:
  #
  #   ip6.ipv4.address
  #     #=> "172.16.10.1"
  #
  # Internally, the IPv4 address is stored as two 16 bits
  # groups. Therefore all the usual methods for an IPv6 address are
  # working perfectly fine:
  #
  #   ip6.to_hex
  #     #=> "00000000000000000000ffffac100a01"
  #
  #   ip6.address
  #     #=> "0000:0000:0000:0000:0000:ffff:ac10:0a01"
  #
  # A mapped IPv6 can also be created just by specify the address in the
  # following format:
  #
  #   ip6 = IPAddress "::172.16.10.1"
  #
  # That is, two colons and the IPv4 address. However, as by RFC, the ffff
  # group will be automatically added at the beginning
  #
  #   ip6.to_string
  #     => "::ffff:172.16.10.1/128"
  #
  # making it a mapped IPv6 compatible address.
  #
  class IPAddress::IPv6::Mapped < IPAddress::IPv6

    # Access the internal IPv4 address
    attr_reader :ipv4

    #
    # Creates a new IPv6 IPv4-mapped address
    #
    #   ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128"
    #
    #   ipv6.ipv4.class
    #     #=> IPAddress::IPv4
    #
    # An IPv6 IPv4-mapped address can also be created using the
    # IPv6 only format of the address:
    #
    #   ip6 = IPAddress::IPv6::Mapped.new "::0d01:4403"
    #
    #   ip6.to_string
    #     #=> "::ffff:13.1.68.3"
    #
    def initialize(str)
      string, netmask = str.split("/")
      if string =~ /\./ # IPv4 in dotted decimal form
        @ipv4 = IPAddress::IPv4.extract(string)
      else # IPv4 in hex form
        groups = IPAddress::IPv6.groups(string)
        @ipv4 = IPAddress::IPv4.parse_u32((groups[-2]<< 16)+groups[-1])
      end
      super("::ffff:#{@ipv4.to_ipv6}/#{netmask}")
    end

    #
    # Similar to IPv6#to_s, but prints out the IPv4 address
    # in dotted decimal format
    #
    #   ip6 = IPAddress "::ffff:172.16.10.1/128"
    #
    #   ip6.to_s
    #     #=> "::ffff:172.16.10.1"
    #
    def to_s
      "::ffff:#{@ipv4.address}"
    end

    #
    # Similar to IPv6#to_string, but prints out the IPv4 address
    # in dotted decimal format
    #
    #
    #   ip6 = IPAddress "::ffff:172.16.10.1/128"
    #
    #   ip6.to_string
    #     #=> "::ffff:172.16.10.1/128"
    #
    def to_string
      "::ffff:#{@ipv4.address}/#@prefix"
    end

    #
    # Checks if the IPv6 address is IPv4 mapped
    #
    #   ip6 = IPAddress "::ffff:172.16.10.1/128"
    #
    #   ip6.mapped?
    #     #=> true
    #
    def mapped?
      true
    end
  end # class IPv6::Mapped

end # module IPAddress