# Copyright (c) 1998,2001					RIPE NCC
#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
# provided that the above copyright notice appear in all copies and that
# both that copyright notice and this permission notice appear in
# supporting documentation, and that the name of the author not be
# used in advertising or publicity pertaining to distribution of the
# software without specific, written prior permission.
#
# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
# AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

#------------------------------------------------------------------------------
# Module Header
# Filename          : ipv4pack.pm
# Purpose           : provide translation functions for the different address
#			space notations, and other ip functionalities
# Author            : Monica Cortes
# Date              : August 1998
# Description       :
# Language Version  : Perl (Tested with v5.00401 and v5.00404)
# OSs Tested        : BSD/OS 3.1
# Command Line      : -
# Input Files       : -
# Output Files      : -
# External Programs : -
# Problems          :
# To Do             : 
# Comments          : most of these routines where taken form 
#			/ncc/db/dbase/src/net2net.pl coded originally by D.
#			Kessens. I have added comments! and made some changes
#			to adapt it to be a module and be able to work with
#			use strict, added more checks where relvant. (19980626)
# Comments          : the exported variables are used by the routines and
#			taken from /ncc/db/dbase/src/defines.pl (19980626)
# Comments	    : there are some non exported routines
# Comments	    : NOT all routines make all the checking! To manipulate
#			IPs the best is to use integers, so use
#				1- normalizerange
#				2- quad2int
#			To go back to a prefix or quad notation after
#			manipulation use
#				1- range2prefixes
#			  or	2- int2quad
#------------------------------------------------------------------------------
#
# Changes	: 19981130 Monica 
#			new exported routine normalizerange121 to comply 
#			with RIPE 121 document (outdated but still 
#			valid), new control value to indicate that
#			a range has been treated classfull
#
# Changes       : 19990527 Monica
#			changes to normalizerange121
#                       there are inetnums with only one IP address
#                       the routine treated this as "systax error" 
#                       will now be treated as a /32 with a proper warning code
#
# Changes	: 20001115 BaT
#			add a function to translate error code into string			
# Changes	: 20010410 BaT
#			add handling of encompassing object as valid one
#			fixed bunch of typos

require 5.004;

package ipv4pack;

use strict;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);

require Exporter;

@ISA = qw(Exporter);

use vars qw($O_OK $O_NOK $O_PRIVATERANGE $O_INVALIDIP $O_INVALIDPREFIX 
	$O_INVALIDRANGE $O_SYNTAXERROR $O_NOVERLAP $O_OVERLAP $O_BINAOVERLAP 
	$O_AINBOVERLAP $O_RESERVEDCLASS $O_CLASSFULL $O_IPADDRONLY);

@EXPORT=qw($O_OK $O_NOK $O_PRIVATERANGE $O_INVALIDIP $O_INVALIDPREFIX 
	$O_INVALIDRANGE $O_SYNTAXERROR $O_NOVERLAP $O_OVERLAP $O_BINAOVERLAP 
	$O_AINBOVERLAP $O_RESERVEDCLASS $O_CLASSFULL  $O_IPADDRONLY 
	&quad2int &int2quad &normalizerange &range2prefixes &testoverlap 
	&aggregate &normalizerange121 &error2str);

@EXPORT_OK=qw($O_OK $O_NOK $O_PRIVATERANGE $O_INVALIDIP $O_INVALIDPREFIX 
	$O_INVALIDRANGE $O_SYNTAXERROR $O_NOVERLAP $O_OVERLAP $O_BINAOVERLAP 
	$O_AINBOVERLAP $O_RESERVEDCLASS $O_CLASSFULL  $O_IPADDRONLY 
	&quad2int &int2quad &normalizerange &range2prefixes &testoverlap 
	&aggregate &normalizerange121 &error2str);


$VERSION = '1.04';

#
# these are error codes for checking the results of the
# different net-routines
#
$O_OK		=	1;		# no problem found
$O_NOK		=	0;		# an unknown problem was found

$O_IPADDRONLY	=	20;		# range treated as an /32 only
$O_CLASSFULL	=	21;		# ip treated as of classfull ranges
$O_RESERVEDCLASS=	22;		# ip from reserved range was given
$O_PRIVATERANGE	=	23;		# ip from private range was given
$O_INVALIDIP	=	24;		# wrong ip was given
$O_INVALIDPREFIX=	25;		# invalid index was given
$O_INVALIDRANGE	=	26;		# invalid range was given
$O_SYNTAXERROR	=	27;		# syntax error was found

$O_NOVERLAP	=	-1;		# no overlap between two ranges
$O_OVERLAP	=	100;		# overlap between two ranges
$O_AINBOVERLAP  =	101;		# range A is part of range B
$O_BINAOVERLAP	=	110;		# range B is part of range A

# Correspondence between error codes and their meaning
my %errorcode = (
	$O_OK		=> "No problems were found",
	$O_NOK		=> "An unknown problem was found",
	
	$O_IPADDRONLY	=> "Range treated as an /32 only",
	$O_CLASSFULL	=> "IP treated as of classfull ranges",
	$O_RESERVEDCLASS=> "IP from reserved range was given",
	$O_PRIVATERANGE	=> "IP from private range was given",
	$O_INVALIDIP	=> "Wrong IP was given",
	$O_INVALIDPREFIX=> "Invalid index was given",
	$O_INVALIDRANGE	=> "Invalid range was given",
	$O_SYNTAXERROR	=> "Syntax error was found",
	
	$O_NOVERLAP	=> "No overlap between two ranges",
	$O_OVERLAP	=> "Overlap between two ranges",
	$O_AINBOVERLAP	=> "Range A is part of the range B",
	$O_BINAOVERLAP	=> "Range B is part of the range A"
	);

#
# these variables are just constants for obtaining prefix lengths
#
# TINYLOGVALUE is important if the range for which one wants to optain a
#	prefix is very big. The algorithm for obtaining the prefix relies
#	on the integer part of a logarithm (log(x)). If x is small, there
#	are no problems, but if x is big, the precision of the log can be
#	insufficient to give the correct int value (think of (2**32-1)).
#	TINYLOGVALUE is used to force precision on this cases and is
#	sufficiently small to let the other cases un-affected.
#
my($ONEDIVLOG2)=1/log(2);				# log constant
my($TINYLOGVALUE)=(32-(log(2**32-1)*$ONEDIVLOG2))/100;	# another log constant
my @masks= (0x0,
	0x80000000, 0xc0000000, 0xe0000000, 0xf0000000,
	0xf8000000, 0xfc000000, 0xfe000000, 0xff000000,
	0xff800000, 0xffc00000, 0xffe00000, 0xfff00000,
	0xfff80000, 0xfffc0000, 0xfffe0000, 0xffff0000,
	0xffff8000, 0xffffc000, 0xffffe000, 0xfffff000,
	0xfffff800, 0xfffffc00, 0xfffffe00, 0xffffff00,
	0xffffff80, 0xffffffc0, 0xffffffe0, 0xfffffff0,
	0xfffffff8, 0xfffffffc, 0xfffffffe, 0xffffffff ); # network masks


##
##
## exported functions
##
##

##
#
#
#------------------------------------------------------------------------------
# Subroutine Header
# Purpose           : translate an quad written IP number into a long int
# Side Effects      :
# Comments          :
# Exported Routine  : yes
# Routine Arguments :	scalar value of an IP in quad notation
# Return values     : 	-1 		if wrong quad number
#			long int	translation of quad to int
#------------------------------------------------------------------------------
#
sub quad2int ($) {
  my($quad) = @_;

  # split quad into each byte
  my(@bytes) = split(/\./, $quad);

  # check the value of each byte [0,255] and pack the info 
  return unpack("N",pack("C4",@bytes)) if
    (@bytes == 4 && scalar(grep {$_>=0 && $_<=255} @bytes) == 4);

  return(-1);
}

##
#
#------------------------------------------------------------------------------
# Subroutine Header
# Purpose           : translate an IP written as long int into a quad
# Side Effects      :
# Comments          : different perl versions handle variables differently
#			in the sense that the default is on some "string" in
#			others "int". To avoid problems a very ugly hack is
#			put in place to make sure perl handles it as a number
# Exported Routine  : yes
# Routine Arguments :	scalar value of an IP in long int notation
# Return values     :   scalar quad     translation of int to quad
#			-1		if wrong int
#------------------------------------------------------------------------------
#
sub int2quad ($) {
   my($integer)=@_;
   my($MaxValidInt)=4294967295;		# int corresponding to 255.255.255.255 

   # here are some checks of the int value, it has to be bigger than 0
   # but smaller than the highest ip possible: 255.255.255.255
   return -1 if ($integer !~ /^\d+$/ || $integer < 0 || 
					$integer > $MaxValidInt);

   # ugly hack
   $integer += 0;

   return join(".", unpack("C4",pack("N",$integer)));
}



##
#
#------------------------------------------------------------------------------
# Subroutine Header
# Purpose           : gets a range as argument and tries to identify the 
#			range notation and translate it into quad - quad format
# Side Effects      :
# Comments          : it understands following notations:
#			quad - quad
#			quad/prefix
#			quad	(class-full ip)
#
#	      it checks for the use of private ips and reserved classes:
#
#		quad					int value
#	0.0.0.0	    - 0.255.255.255
#	10.0.0.0    - 10.255.255.255		
#	127.0.0.0   - 127.255.255.255
#	172.16.0.0  - 172.31.255.255 		2886729728 - 2887778303
#	192.168.0.0 - 192.168.255.255		3232235520 - 3232301055
#
#	Classes D,F and reserved
#	223.0.0.0   - 255.255.255.255
#
# Exported Routine  : yes
# Routine Arguments : scalar value with the range information in one
#			of the above notations
# Return values     : an array with a first field: 	quad - quad
#			and a second field:		errorcode
#------------------------------------------------------------------------------
#
sub normalizerange ($) {
    my($range)=@_;
    my($ip1);

    # test the syntax of $range 
    if ($range=~ /^(\d+)(\.[\.\d]*)?\s*([\/\-])\s*([\d\.]+)*$/) {
       # range is in format quad/prefix or quad - quad

       # check for a valid range: no CLASS-D and upper (223-255), no 127/8 
       # and no private networks allowed (10/8)
       return ("", $O_PRIVATERANGE) if ($1 =~ /^10|127$/);
       return ("", $O_RESERVEDCLASS) if ($1 > 223);

       if ($3 eq '/') {	 # range is in format quad/prefix
          my($len)=$4;

	  # complete the trailing quads if missing
          ($range,$ip1)=&completeip(defined($2)?$1.$2:$1,0);
          return ("", $O_INVALIDIP) if ($ip1 < 0);

	  # check valid prefix and its mask
          if (($len=~ /^0*([12]?[\d]|3[012])$/) &&
              (!($ip1 & (~$masks[$len])))) {

	    my($ip2)=$ip1+(2**(32-$len)-1);

	    # BaT 20010410: We should reject 0/8, but accept 0/0 as legal
	    if($ip1 == 0) { 
		if($ip2 == 4294967295) {
		    return ($range." - ".int2quad($ip2), $O_OK);
		}
		elsif($ip2 <= 16777215) {
		    return ("", $O_RESERVEDCLASS);
		}
	     }

	     # check that it does not overlap a private network (172.16/12 &
	     # 192.168/16)
	     return ("",$O_PRIVATERANGE) unless 
		(&testoverlap("$ip1:$ip2","2886729728:2887778303") == -1);
	     return ("",$O_PRIVATERANGE) unless 
		(&testoverlap("$ip1:$ip2","3232235520:3232301055") == -1);

             return ($range." - ".int2quad($ip2), $O_OK);

          }

          return ("", $O_INVALIDPREFIX);

       } elsif ($3 eq '-') {	 # range is in format quad - quad
          my($torange)=$4;

          ($range,$ip1)=&completeip(defined($2)?$1.$2:$1,0);

          my($ip2);
          ($torange,$ip2)=&completeip($torange,1);
	  
	  # check that the quads where valid and the second is greater
          return ("", $O_INVALIDIP) if (($ip1 < 0) || ($ip2 < 0));
          return ("", $O_INVALIDRANGE) if ($ip2 < $ip1);
	  
	  # BaT 20010410: We should reject 0/8, but accept 0/0 as legal
	  if($ip1 == 0) { 
	    if($ip2 == 4294967295) {
    	      return ($range." - ".$torange, $O_OK);
	    }
	    elsif($ip2 <= 16777215) {
	      return ("", $O_RESERVEDCLASS);
	    }
	  }
	  
	  # check that it does not overlap a private network (172.16/12 &
	  # 192.168/16 )
	  return ("",$O_PRIVATERANGE) unless 
		(&testoverlap("$ip1:$ip2","2886729728:2887778303") == -1);
	  return ("",$O_PRIVATERANGE) unless 
		(&testoverlap("$ip1:$ip2","3232235520:3232301055") == -1);


          return ($range." - ".$torange, $O_OK);

       }

    } elsif ($range=~ /^(\d+)(\.[\.\d]*)?$/) {
       # range is in format quad it will be treated CLASSFULL so
       # the errorcodes will be $O_CLASSFULL

       return ("", $O_PRIVATERANGE) if($1=~ /^10|127$/);
       
       return ("", $O_RESERVEDCLASS) if($1 > 223 || $1 == 0);
       

       ($range,$ip1)=&completeip($range,0);
       
       return ("", $O_INVALIDIP) if ($ip1 < 0);

       # note that the order in the comparisons is important
       # *and* the order of the if's is important !!!

       if (($range=~ /^(\d+)(\.\d+\.\d+)\.0$/) &&
           ($1>=192)) {					# CLASS C

	  # check that it is not a private network 192.168/16
	  return ("",$O_PRIVATERANGE) if ( $1 == 192 && $2 =~ /^\.168/ );

          return ($1.$2.".0 - ".$1.$2.".255", $O_CLASSFULL);

       } elsif (($range=~ /^(\d+)(\.(\d+))\.0\.0$/) &&
              ($1>=128)) {				# CLASS B

	  # check that it is not a private network 172.16/12
	  return ("",$O_PRIVATERANGE) if ( $1 == 172 && 
		($3 >= 16 && $3 <= 31) );

          return ($1.$2.".0.0 - ".$1.$2.".255.255", $O_CLASSFULL);

       } elsif ($range=~ /^(\d+)\.0\.0\.0$/) {		# CLASS A

          return ($1.".0.0.0 - ".$1.".255.255.255", $O_CLASSFULL);
       }
    }
    return ("", $O_SYNTAXERROR);	 # nothing useful found
}



##
#
#------------------------------------------------------------------------------
# Subroutine Header
# Purpose           : gets a range as argument and tries to identify the
#                       range notation and translate it into quad - quad format
#			this routine is compliant with RIPE 121 document
# Side Effects      :
# Comments          : it understands following notations:
#                       quad - quad
#                       quad/prefix
#                       quad    (class-full ip)
#
#             it checks for the use of private ips and reserved classes:
#         
#               quad                                    int value
#       0.0.0.0     - 0.255.255.255
#       10.0.0.0    - 10.255.255.255
#       127.0.0.0   - 127.255.255.255
#       172.16.0.0  - 172.31.255.255            2886729728 - 2887778303
#       192.168.0.0 - 192.168.255.255           3232235520 - 3232301055
#         
#       Classes D,F and reserved
#       223.0.0.0   - 255.255.255.255
#
# Exported Routine  : yes
# Routine Arguments : scalar value with the range information in one
#                       of the above notations
# Return values     : an array with a first field:      quad - quad
#                       and a second field:             errorcode
#
# Changes	: 19981130 Monica
#			this routine was included to be compliant with 
#			RIPE 121 document which should be updated and then
#			this routine can be removed again
#
# Changes	: 19990527 Monica
#			there are inetnums with only one IP address
#			the routine treated this as "systax error" 
#			will now be treated as a /32 with a proper warning code
#------------------------------------------------------------------------------
#   
sub normalizerange121 ($) {
    my($range)=@_;
    my($ip1);
       
    # test the syntax of $range
    if ($range=~ /^(\d+)(\.[\.\d]*)?\s*([\/\-])\s*([\d\.]+)*$/) {
       # range is in format quad/prefix or quad - quad

       # check for a valid range: no CLASS-D and upper (223-255), no 127/8
       # and no private networks allowed (10/8)
       return ("", $O_PRIVATERANGE) if ($1 =~ /^10|127$/);
       return ("", $O_RESERVEDCLASS) if ($1 > 223);
          
       if ($3 eq '/') {         # range is in format quad/prefix
          my($len)=$4;

          # complete the trailing quads if missing
          ($range,$ip1)=&completeip(defined($2)?$1.$2:$1,0);
          return ("", $O_INVALIDIP) if ($ip1 < 0);
          
          # check valid prefix and its mask
          if (($len=~ /^0*([12]?[\d]|3[012])$/) &&
              (!($ip1 & (~$masks[$len])))) {
          
       	    my($ip2)=$ip1+(2**(32-$len)-1);

	    # BaT 20010410: We should reject 0/8, but accept 0/0 as legal
	    if($ip1 == 0) { 
		if($ip2 == 4294967295) {
		    return ($range." - ".int2quad($ip2), $O_OK);
		}
		elsif($ip2 <= 16777215) {
		    return ("", $O_RESERVEDCLASS);
		}
	     }

             # check that it does not overlap a private network (172.16/12 &
             # 192.168/16)
             return ("",$O_PRIVATERANGE) unless
                (&testoverlap("$ip1:$ip2","2886729728:2887778303") == -1);
             return ("",$O_PRIVATERANGE) unless
                (&testoverlap("$ip1:$ip2","3232235520:3232301055") == -1);
      
             return ($range." - ".&int2quad($ip2), $O_OK);
          }

          return ("", $O_INVALIDPREFIX);


       } elsif ($3 eq '-') {    # range is in format quad - quad
          my($torange)=$4;
	  my($errorcode)=$O_OK;

          ($range,$ip1)=&completeip(defined($2)?$1.$2:$1,0);

          my($ip2);
          ($torange,$ip2)=&completeip($torange,1);


          # check that the quads where valid and the second is greater
          return ("", $O_INVALIDIP) if (($ip1 < 0) || ($ip2 < 0));
          return ("", $O_INVALIDRANGE) if ($ip2 < $ip1);

	  # BaT 20010410: We should reject 0/8, but accept 0/0 as legal
	  if($ip1 == 0) { 
	    if($ip2 == 4294967295) {
    	      return ($range." - ".$torange, $O_OK);
	    }
	    elsif($ip2 <= 16777215) {
	      return ("", $O_RESERVEDCLASS);
	    }
	  }

          # check that it does not overlap a private network (172.16/12 &
          # 192.168/16)
          return ("",$O_PRIVATERANGE) unless
                (&testoverlap("$ip1:$ip2","2886729728:2887778303") == -1);
          return ("",$O_PRIVATERANGE) unless
                (&testoverlap("$ip1:$ip2","3232235520:3232301055") == -1);

	  # 19981130 Monica
	  # changes to routine normalizerange
	  my(@quads_ip1)=split(/\./,$range);
	  my(@quads_ip2)=split(/\./,$torange);
	  if ( $quads_ip1[-1] == 0 && $quads_ip2[-1] == 0 ) {
		# old classfull notation, check if it is an old CLASS C
		if ($quads_ip1[0] >= 192) {
			# replace ip2 last quad by 255
			$quads_ip2[-1] = 255;
	  		$torange=join('.',@quads_ip2);
			$errorcode=$O_CLASSFULL;
		} else {
			# NOT a CLASS C: do give an error, invalid range
			return ("", $O_INVALIDRANGE);
		}
	  }

          return ($range." - ".$torange, $errorcode);

       }

    } elsif ($range=~ /^(\d+)(\.[\.\d]*)?$/) {
       # range is in format quad it will be treated CLASSFULL

       return ("", $O_PRIVATERANGE) if($1=~ /^10|127$/);
       
       return ("", $O_RESERVEDCLASS) if($1 > 223 || $1 == 0);

       ($range,$ip1)=&completeip($range,0);
       return ("", $O_INVALIDIP) if ($ip1 < 0);

       # note that the order in the comparisons is important
       # *and* the order of the if's is important !!!

       if ($range !~ /\.0$/) {

	  # the last quad contains a non null argument
	  # it will be treated as on IP number => /32 with a 
	  # warning that it was treated as such
	  return ( $range." - ".$range, $O_IPADDRONLY);

       } elsif (($range=~ /^(\d+)(\.\d+\.\d+)\.0$/) &&
           ($1>=192)) {                                 # CLASS C

          # check that it is not a private network 192.168/16
          return ("",$O_PRIVATERANGE) if ( $1 == 192 && $2 =~ /^\.168/ );

          return ($1.$2.".0 - ".$1.$2.".255", $O_CLASSFULL);

       } elsif (($range=~ /^(\d+)(\.(\d+))\.0\.0$/) &&
              ($1>=128)) {                              # CLASS B

          # check that it is not a private network 172.16/12
          return ("",$O_PRIVATERANGE) if ( $1 == 172 &&
                ($3 >= 16 && $3 <= 31) );

          return ($1.$2.".0.0 - ".$1.$2.".255.255", $O_CLASSFULL);

       } elsif ($range=~ /^(\d+)\.0\.0\.0$/) {          # CLASS A

          return ($1.".0.0.0 - ".$1.".255.255.255", $O_CLASSFULL);
       }
    }
    return ("", $O_SYNTAXERROR);         # nothing useful found
}


##
#
#------------------------------------------------------------------------------
# Subroutine Header
# Purpose           : gets a range in the format "int - int" or "quad - quad"
#			and splits it into the necessary prefixes finding 
#			always the smallest prefix possible
# Side Effects      : 
# Comments          : No checks are performed on the validity of the ips
#			for checks use normalizerange and quad2int first
# Comments	    : To get the prefix decomposition of a range one
#			needs to do two things:
#			- get the smallest posible prefix, which depends
#				on the size of the range
#			- the mask for that prefix has to match the start
#				address of the range
# Exported Routine  : yes
# Routine Arguments : a scalar value with the range in "int - int" 
#			or "quad - quad" format
# Return values     : an array of ip-ranges in the format quad/prefix
#			or -1 	if a problem ocurred
#------------------------------------------------------------------------------
#
sub range2prefixes ($) {
    my(@prefixes)=();
    my($bits);
    my($ip1,$ip2)=split(/ \- /, $_[0], 2);

    $ip1=&quad2int($ip1) if ($ip1 =~ /\./); 	# quad notation
    $ip2=&quad2int($ip2) if ($ip2 =~ /\./); 	# quad notation
    
    # check ip1 and ip2
    return (-1) if ( $ip1 == -1 || $ip2 == -1 );


    do {

      # get smallest useful prefix length based on size of range.
      # $TINYLOGVALUE is used to account for the pecision lost by 
      # the log calculation on large range sizes (see definition of
      # TINYLOGVALUE at the begining of the module).
      $bits = 32-int(log($ip2-$ip1+1)*$ONEDIVLOG2+$TINYLOGVALUE);

      {
        use integer;       # for the following bitwise &

        # find smallest prefix that masks all bits of starting address of the
	# range
        until ($ip1 == ($ip1 & $masks[$bits])) {
          $bits++;
        }
      }
  
      push(@prefixes, &int2quad($ip1)."/".$bits);

      # add on block we've just found
      $ip1 += 2**(32-$bits);

      # stop when we've reached the top end of range
    } until (($ip1-1) == $ip2);


    return @prefixes;
}


##
#
#------------------------------------------------------------------------------
# Subroutine Header
# Purpose           : gets two ip ranges in the format "int [-:] int" or
#			"quad [-:] quad" and test if they overlap and what 
#                       type of overlap
# Side Effects      :
# Comments          :
# Exported Routine  : yes
# Routine Arguments : two scalar variables,
#                       1- range A in format "int [-:] int" or "quad [-:] quad"
#                       2- range B in format "int [-:] int" or "quad [-:] quad"
# Return values     : a scalar value indicating the type of overlap
#------------------------------------------------------------------------------
#
sub testoverlap {
    my($rangeA,$rangeB)=@_;
    my($Aip1,$Aip2,$Bip1,$Bip2);

    if ($rangeA =~ /^\s*(\S*)\s*[\-:]\s*(\S*)\s*$/ ) {
        $Aip1=$1;
        $Aip2=$2;
        $Aip1=&quad2int($Aip1) if ( $Aip1 =~ /\./ );
        $Aip2=&quad2int($Aip2) if ( $Aip2 =~ /\./ );
        return $O_INVALIDRANGE if ($Aip1 == -1 || $Aip2 == -1);
    } else {
        return $O_SYNTAXERROR;  #wrong syntax
    }
    return $O_INVALIDRANGE if ($Aip1 > $Aip2);  #wrong syntax


    if ($rangeB =~ /^\s*(\S*)\s*[\-:]\s*(\S*)\s*$/ ) {
        $Bip1=$1;
        $Bip2=$2;
        $Bip1=&quad2int($Bip1) if( $Bip1 =~ /\./ );
        $Bip2=&quad2int($Bip2) if( $Bip2 =~ /\./ );
        return $O_INVALIDRANGE if ($Bip1 == -1 || $Bip2 == -1);
    } else {
        return $O_SYNTAXERROR;  #wrong syntax
    }
    return $O_INVALIDRANGE if ($Bip1 > $Bip2);  #wrong syntax


    # test if both ranges overlap
    if ( $Bip1 <= $Aip2 && $Bip2 >=$Aip1 ) {
	# range A is part of range B
        return $O_AINBOVERLAP if ( $Bip1 <= $Aip1 && $Bip2 >=$Aip2 );
	# range B is part of range A
        return $O_BINAOVERLAP if ( $Bip1 >= $Aip1 && $Bip2 <=$Aip2 );
	# partial overlap only
        return $O_OVERLAP;
    }


    return $O_NOVERLAP;         # no overlap

}


##
#
#------------------------------------------------------------------------------
# Subroutine Header
# Purpose           : gets two ip ranges in the format "int [-:] int" or
#			"quad [-:] quad" and checks if they are aggregatable
# Side Effects      :
# Comments          : only checks if the second range is contiguos with the
#			first range
# Comments          : To get the prefix of a range one needs to do:
#                       - get the smallest posible prefix, which depends
#                               on the size of the range
#			- check that the size of the prefix found accounts
#				for the complete range size
#                       - the mask for that prefix has to match the start
#                               address of the range
# Exported Routine  : yes
# Routine Arguments : two scalar variables,
#                       1- range A in format "int [-:] int" or "quad [-:] quad"
#                       2- range B in format "int [-:] int" or "quad [-:] quad"
# Return values     : a scalar value indicating 
#				the aggregate in quad/prefix
#			or	-1 if not aggregatable
#------------------------------------------------------------------------------
#
sub aggregate {
    my($rangeA,$rangeB)=@_;
    my($Aip1,$Aip2,$Bip1,$Bip2);

    if ($rangeA =~ /^\s*(\S*)\s*[\-:]\s*(\S*)\s*$/ ) {
        $Aip1=$1;
        $Aip2=$2;
        $Aip1=&quad2int($Aip1) if ( $Aip1 =~ /\./ );
        $Aip2=&quad2int($Aip2) if ( $Aip2 =~ /\./ );
        return $O_INVALIDRANGE if ($Aip1 == -1 || $Aip2 == -1);
    } else {
        return $O_SYNTAXERROR;  #wrong syntax
    }
    return $O_INVALIDRANGE if ($Aip1 > $Aip2);   #wrong syntax


    if ($rangeB =~ /^\s*(\S*)\s*[-:]\s*(\S*)\s*$/ ) {
        $Bip1=$1;
        $Bip2=$2;
        $Bip1=&quad2int($Bip1) if( $Bip1 =~ /\./ );
        $Bip2=&quad2int($Bip2) if( $Bip2 =~ /\./ );
        return $O_INVALIDRANGE if ($Bip1 == -1 || $Bip2 == -1);
    } else {
        return $O_SYNTAXERROR;  #wrong syntax
    }
    return $O_INVALIDRANGE if ($Bip1 > $Bip2);   #wrong syntax


    # test contiguity
    return -1 unless ( ($Aip2+1) == $Bip1 );


    # get smallest useful prefix length based on size of range.
    # $TINYLOGVALUE is used to account for the pecision lost by
    # the log calculation on large range sizes (see definition of
    # TINYLOGVALUE at the begining of the module).
    my($bits)=32-int(log($Bip2-$Aip1+1)*$ONEDIVLOG2+$TINYLOGVALUE);


    # check that the prefix found accounts for the size of the range
    return -1 unless ( ($Bip2-$Aip1+1) == (2**(32-$bits)));

    
    use integer; 		# for the following bitwise &
    if ($Aip1 == ($Aip1 & $masks[$bits])) {
	return &int2quad($Aip1).'/'.$bits;
    }

    # ranges are not aggregatable
    return -1;
}



##
## non exported subroutines
##
##

##
#
#------------------------------------------------------------------------------
# Subroutine Header
# Purpose           : gets an ip in quad format of any length and tries
#                       to complete it with as many trailing ".0" as necessary
# Side Effects      :
# Comments          : 
# Exported Routine  : no (called by normalizerange)
# Routine Arguments : two scalar variables, 
#			1- ip quad to complete
#			2- flag to know if it completes the ip with 0 o 255
#				0		completes with .0
#				>0		completes with .255
# Return values     : an array of two field:
#			first field:		completed quad
#			second field:		integer value of the quad
#							or
#						-1 if there was a problem
#------------------------------------------------------------------------------
#   
sub completeip {
    my($range,$type)=@_;
    my($int,$CompleteQuad);

    # what do we complete with?
    if ($type) {
	$CompleteQuad='.255';
    } else {
	$CompleteQuad='.0';
    }

    # clean up the range of trailing dots
    $range=~ s/\.*$//;

    # get number of valid dots
    my($nrofdots)= $range=~ s/\./\./g;

    # complete the quad with trailing .0
    $range=join("", $range, ("$CompleteQuad") x (3 - $nrofdots)) 
			if ($nrofdots<3);


    # get the int value of the completed quad
    if (($int=&quad2int($range))>=0) {

       return (&int2quad($int),$int);

    }

    # nothing valid found

    return ("",-1);

}


##
#
#------------------------------------------------------------------------------
# Subroutine Header
# Purpose           : conver error code into the string
# Side Effects      : 
# Comments          : BaT 20001115
# Exported Routine  : yes
# Routine Arguments : error code if any
# Return values     : an error string
#------------------------------------------------------------------------------
#

sub error2str {
    my($code) = @_;
    
    my $str = $errorcode{$code};
    # Return the string if exists
    return $str if($str);
    # Nothing to return
    return "";
}

1;
