File: //usr/local/lib64/perl5/NetAddr/IP/UtilPP.pm
#!/usr/bin/perl
package NetAddr::IP::UtilPP;
use strict;
#use diagnostics;
#use lib qw(blib lib);
use AutoLoader qw(AUTOLOAD);
use vars qw($VERSION @EXPORT_OK @ISA %EXPORT_TAGS);
require Exporter;
@ISA = qw(Exporter);
$VERSION = do { my @r = (q$Revision: 1.7 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r };
@EXPORT_OK = qw(
hasbits
isIPv4
shiftleft
addconst
add128
sub128
notcontiguous
ipv4to6
mask4to6
ipanyto6
maskanyto6
ipv6to4
bin2bcd
bcd2bin
comp128
bin2bcdn
bcdn2txt
bcdn2bin
simple_pack
);
%EXPORT_TAGS = (
all => [@EXPORT_OK],
);
sub DESTROY {};
1;
__END__
=head1 NAME
NetAddr::IP::UtilPP -- pure Perl functions for NetAddr::IP::Util
=head1 SYNOPSIS
use NetAddr::IP::UtilPP qw(
hasbits
isIPv4
shiftleft
addconst
add128
sub128
notcontiguous
ipv4to6
mask4to6
ipanyto6
maskanyto6
ipv6to4
bin2bcd
bcd2bin
);
use NetAddr::IP::UtilPP qw(:all)
$rv = hasbits($bits128);
$rv = isIPv4($bits128);
$bitsX2 = shiftleft($bits128,$n);
$carry = addconst($ipv6naddr,$signed_32con);
($carry,$ipv6naddr)=addconst($ipv6naddr,$signed_32con);
$carry = add128($ipv6naddr1,$ipv6naddr2);
($carry,$ipv6naddr)=add128($ipv6naddr1,$ipv6naddr2);
$carry = sub128($ipv6naddr1,$ipv6naddr2);
($spurious,$cidr) = notcontiguous($mask128);
($carry,$ipv6naddr)=sub128($ipv6naddr1,$ipv6naddr2);
$ipv6naddr = ipv4to6($netaddr);
$ipv6naddr = mask4to6($netaddr);
$ipv6naddr = ipanyto6($netaddr);
$ipv6naddr = maskanyto6($netaddr);
$netaddr = ipv6to4($pv6naddr);
$bcdtext = bin2bcd($bits128);
$bits128 = bcd2bin($bcdtxt);
=head1 DESCRIPTION
B<NetAddr::IP::UtilPP> provides pure Perl functions for B<NetAddr::IP::Util>
=over 4
=item * $rv = hasbits($bits128);
This function returns true if there are one's present in the 128 bit string
and false if all the bits are zero.
i.e. if (hasbits($bits128)) {
&do_something;
}
or if (hasbits($bits128 & $mask128) {
&do_something;
}
This allows the implementation of logical functions of the form of:
if ($bits128 & $mask128) {
...
input: 128 bit IPv6 string
returns: true if any bits are present
=cut
sub _deadlen {
my($len,$should) = @_;
$len *= 8;
$should = 128 unless $should;
my $sub = (caller(1))[3];
die "Bad argument length for ".__PACKAGE__.":$sub, is $len, should be $should";
}
sub hasbits {
_deadlen(length($_[0]))
if length($_[0]) != 16;
return 1 if vec($_[0],3,32);
return (isIPv4($_[0])) ? 0 : 1;
}
=item * $rv = isIPv4($bits128);
This function returns true if there are no on bits present in the IPv6
portion of the 128 bit string and false otherwise.
=cut
sub isIPv4 {
_deadlen(length($_[0]))
if length($_[0]) != 16;
return 0 if vec($_[0],0,32);
return 0 if vec($_[0],1,32);
return 0 if vec($_[0],2,32);
return 1;
}
=item * $bitsXn = shiftleft($bits128,$n);
input: 128 bit string variable,
number of shifts [optional]
returns: bits X n shifts
NOTE: input bits are returned
if $n is not specified
=cut
# multiply x 2
#
sub _128x2 {
my $inp = shift;
$$inp[0] = ($$inp[0] << 1 & 0xffffffff) + (($$inp[1] & 0x80000000) ? 1:0);
$$inp[1] = ($$inp[1] << 1 & 0xffffffff) + (($$inp[2] & 0x80000000) ? 1:0);
$$inp[2] = ($$inp[2] << 1 & 0xffffffff) + (($$inp[3] & 0x80000000) ? 1:0);
$$inp[3] = $$inp[3] << 1 & 0xffffffff;
}
# multiply x 10
#
sub _128x10 {
my($a128p) = @_;
_128x2($a128p); # x2
my @x2 = @$a128p; # save the x2 value
_128x2($a128p);
_128x2($a128p); # x8
_sa128($a128p,\@x2,0); # add for x10
}
sub shiftleft {
_deadlen(length($_[0]))
if length($_[0]) != 16;
my($bits,$shifts) = @_;
return $bits unless $shifts;
die "Bad arg value for ".__PACKAGE__.":shiftleft, length should be 0 thru 128"
if $shifts < 0 || $shifts > 128;
my @uint32t = unpack('N4',$bits);
do {
$bits = _128x2(\@uint32t);
$shifts--
} while $shifts > 0;
pack('N4',@uint32t);
}
sub slowadd128 {
my @ua = unpack('N4',$_[0]);
my @ub = unpack('N4',$_[1]);
my $carry = _sa128(\@ua,\@ub,$_[2]);
return ($carry,pack('N4',@ua))
if wantarray;
return $carry;
}
sub _sa128 {
my($uap,$ubp,$carry) = @_;
if (($$uap[3] += $$ubp[3] + $carry) > 0xffffffff) {
$$uap[3] -= 4294967296; # 0x1_00000000
$carry = 1;
} else {
$carry = 0;
}
if (($$uap[2] += $$ubp[2] + $carry) > 0xffffffff) {
$$uap[2] -= 4294967296;
$carry = 1;
} else {
$carry = 0;
}
if (($$uap[1] += $$ubp[1] + $carry) > 0xffffffff) {
$$uap[1] -= 4294967296;
$carry = 1;
} else {
$carry = 0;
}
if (($$uap[0] += $$ubp[0] + $carry) > 0xffffffff) {
$$uap[0] -= 4294967296;
$carry = 1;
} else {
$carry = 0;
}
$carry;
}
=item * addconst($ipv6naddr,$signed_32con);
Add a signed constant to a 128 bit string variable.
input: 128 bit IPv6 string,
signed 32 bit integer
returns: scalar carry
array (carry, result)
=cut
sub addconst {
my($a128,$const) = @_;
_deadlen(length($a128))
if length($a128) != 16;
unless ($const) {
return (wantarray) ? ($const,$a128) : $const;
}
my $sign = ($const < 0) ? 0xffffffff : 0;
my $b128 = pack('N4',$sign,$sign,$sign,$const);
@_ = ($a128,$b128,0);
# perl 5.8.4 fails with this operation. see perl bug [ 23429]
# goto &slowadd128;
slowadd128(@_);
}
=item * add128($ipv6naddr1,$ipv6naddr2);
Add two 128 bit string variables.
input: 128 bit string var1,
128 bit string var2
returns: scalar carry
array (carry, result)
=cut
sub add128 {
my($a128,$b128) = @_;
_deadlen(length($a128))
if length($a128) != 16;
_deadlen(length($b128))
if length($b128) != 16;
@_ = ($a128,$b128,0);
# perl 5.8.4 fails with this operation. see perl bug [ 23429]
# goto &slowadd128;
slowadd128(@_);
}
=item * sub128($ipv6naddr1,$ipv6naddr2);
Subtract two 128 bit string variables.
input: 128 bit string var1,
128 bit string var2
returns: scalar carry
array (carry, result)
Note: The carry from this operation is the result of adding the one's
complement of ARG2 +1 to the ARG1. It is logically
B<NOT borrow>.
i.e. if ARG1 >= ARG2 then carry = 1
or if ARG1 < ARG2 then carry = 0
=cut
sub sub128 {
_deadlen(length($_[0]))
if length($_[0]) != 16;
_deadlen(length($_[1]))
if length($_[1]) != 16;
my $a128 = $_[0];
my $b128 = ~$_[1];
@_ = ($a128,$b128,1);
# perl 5.8.4 fails with this operation. see perl bug [ 23429]
# goto &slowadd128;
slowadd128(@_);
}
=item * ($spurious,$cidr) = notcontiguous($mask128);
This function counts the bit positions remaining in the mask when the
rightmost '0's are removed.
input: 128 bit netmask
returns true if there are spurious
zero bits remaining in the
mask, false if the mask is
contiguous one's,
128 bit cidr
=cut
sub notcontiguous {
_deadlen(length($_[0]))
if length($_[0]) != 16;
my @ua = unpack('N4', ~$_[0]);
my $count;
for ($count = 128;$count > 0; $count--) {
last unless $ua[3] & 1;
$ua[3] >>= 1;
$ua[3] |= 0x80000000 if $ua[2] & 1;
$ua[2] >>= 1;
$ua[2] |= 0x80000000 if $ua[1] & 1;
$ua[1] >>= 1;
$ua[1] |= 0x80000000 if $ua[0] & 1;
$ua[0] >>= 1;
}
my $spurious = $ua[0] | $ua[1] | $ua[2] | $ua[3];
return $spurious unless wantarray;
return ($spurious,$count);
}
=item * $ipv6naddr = ipv4to6($netaddr);
Convert an ipv4 network address into an ipv6 network address.
input: 32 bit network address
returns: 128 bit network address
=cut
sub ipv4to6 {
_deadlen(length($_[0]),32)
if length($_[0]) != 4;
# return pack('L3H8',0,0,0,unpack('H8',$_[0]));
return pack('L3a4',0,0,0,$_[0]);
}
=item * $ipv6naddr = mask4to6($netaddr);
Convert an ipv4 netowrk address into an ipv6 network mask.
input: 32 bit network/mask address
returns: 128 bit network/mask address
NOTE: returns the high 96 bits as one's
=cut
sub mask4to6 {
_deadlen(length($_[0]),32)
if length($_[0]) != 4;
# return pack('L3H8',0xffffffff,0xffffffff,0xffffffff,unpack('H8',$_[0]));
return pack('L3a4',0xffffffff,0xffffffff,0xffffffff,$_[0]);
}
=item * $ipv6naddr = ipanyto6($netaddr);
Similar to ipv4to6 except that this function takes either an IPv4 or IPv6
input and always returns a 128 bit IPv6 network address.
input: 32 or 128 bit network address
returns: 128 bit network address
=cut
sub ipanyto6 {
my $naddr = shift;
my $len = length($naddr);
return $naddr if $len == 16;
# return pack('L3H8',0,0,0,unpack('H8',$naddr))
return pack('L3a4',0,0,0,$naddr)
if $len == 4;
_deadlen($len,'32 or 128');
}
=item * $ipv6naddr = maskanyto6($netaddr);
Similar to mask4to6 except that this function takes either an IPv4 or IPv6
netmask and always returns a 128 bit IPv6 netmask.
input: 32 or 128 bit network mask
returns: 128 bit network mask
=cut
sub maskanyto6 {
my $naddr = shift;
my $len = length($naddr);
return $naddr if $len == 16;
# return pack('L3H8',0xffffffff,0xffffffff,0xffffffff,unpack('H8',$naddr))
return pack('L3a4',0xffffffff,0xffffffff,0xffffffff,$naddr)
if $len == 4;
_deadlen($len,'32 or 128');
}
=item * $netaddr = ipv6to4($pv6naddr);
Truncate the upper 96 bits of a 128 bit address and return the lower
32 bits. Returns an IPv4 address as returned by inet_aton.
input: 128 bit network address
returns: 32 bit inet_aton network address
=cut
sub ipv6to4 {
my $naddr = shift;
_deadlen(length($naddr))
if length($naddr) != 16;
@_ = unpack('L3H8',$naddr);
return pack('H8',@{_}[3..10]);
}
=item * $bcdtext = bin2bcd($bits128);
Convert a 128 bit binary string into binary coded decimal text digits.
input: 128 bit string variable
returns: string of bcd text digits
=cut
sub bin2bcd {
_deadlen(length($_[0]))
if length($_[0]) != 16;
unpack("H40",&_bin2bcdn) =~ /^0*(.+)/;
$1;
}
=item * $bits128 = bcd2bin($bcdtxt);
Convert a bcd text string to 128 bit string variable
input: string of bcd text digits
returns: 128 bit string variable
=cut
sub bcd2bin {
&_bcdcheck;
# perl 5.8.4 fails with this operation. see perl bug [ 23429]
# goto &_bcd2bin;
&_bcd2bin;
}
=pod
=back
=cut
#=item * $onescomp = comp128($ipv6addr);
#
#This function is for testing, it is more efficient to use perl " ~ "
#on the bit string directly. This interface to the B<C> routine is published for
#module testing purposes because it is used internally in the B<sub128> routine. The
#function is very fast, but calling if from perl directly is very slow. It is almost
#33% faster to use B<sub128> than to do a 1's comp with perl and then call
#B<add128>. In the PurePerl version, it is a call to
#
# sub {return ~ $_[0]};
#
#=cut
sub comp128 {
_deadlen(length($_[0]))
if length($_[0]) != 16;
return ~ $_[0];
}
#=item * $bcdpacked = bin2bcdn($bits128);
#
#Convert a 128 bit binary string into binary coded decimal digits.
#This function is for testing only.
#
# input: 128 bit string variable
# returns: string of packed decimal digits
#
# i.e. text = unpack("H*", $bcd);
#
#=cut
sub bin2bcdn {
_deadlen(length($_[0]))
if length($_[0]) != 16;
# perl 5.8.4 fails with this operation. see perl bug [ 23429]
# goto &_bin2bcdn;
&_bin2bcdn;
}
sub _bin2bcdn {
my($b128) = @_;
my @binary = unpack('N4',$b128);
my @nbcd = (0,0,0,0,0); # 5 - 32 bit registers
my ($add3, $msk8, $bcd8, $carry, $tmp);
my $j = 0;
my $k = -1;
my $binmsk = 0;
foreach(0..127) {
unless ($binmsk) {
$binmsk = 0x80000000;
$k++;
}
$carry = $binary[$k] & $binmsk;
$binmsk >>= 1;
next unless $carry || $j; # skip leading zeros
foreach(4,3,2,1,0) {
$bcd8 = $nbcd[$_];
$add3 = 3;
$msk8 = 8;
$j = 0;
while ($j < 8) {
$tmp = $bcd8 + $add3;
if ($tmp & $msk8) {
$bcd8 = $tmp;
}
$add3 <<= 4;
$msk8 <<= 4;
$j++;
}
$tmp = $bcd8 & 0x80000000; # propagate carry
$bcd8 <<= 1; # x2
if ($carry) {
$bcd8 += 1;
}
$nbcd[$_] = $bcd8;
$carry = $tmp;
}
}
pack('N5',@nbcd);
}
#=item * $bcdtext = bcdn2txt($bcdpacked);
#
#Convert a packed bcd string into text digits, suppress the leading zeros.
#This function is for testing only.
#
# input: string of packed decimal digits
# consisting of exactly 40 digits
# returns: hexdecimal digits
#
#Similar to unpack("H*", $bcd);
#
#=cut
sub bcdn2txt {
die "Bad argument length for ".__PACKAGE__.":bcdn2txt, is ".(2 * length($_[0])).", should be exactly 40 digits"
if length($_[0]) != 20;
(unpack('H40',$_[0])) =~ /^0*(.+)/;
$1;
}
#=item * $bits128 = bcdn2bin($bcdpacked,$ndigits);
#
# Convert a packed bcd string into a 128 bit string variable
#
# input: packed bcd string
# number of digits in string
# returns: 128 bit string variable
#
sub bcdn2bin {
my($bcd,$dc) = @_;
$dc = 0 unless $dc;
die "Bad argument length for ".__PACKAGE__.":bcdn2txt, is ".(2 * length($bcd)).", should be 1 to 40 digits"
if length($bcd) > 20;
die "Bad digit count for ".__PACKAGE__.":bcdn2bin, is $dc, should be 1 to 40 digits"
if $dc < 1 || $dc > 40;
return _bcd2bin(unpack("H$dc",$bcd));
}
sub _bcd2bin {
my @bcd = split('',$_[0]);
my @hbits = (0,0,0,0);
my @digit = (0,0,0,0);
my $found = 0;
foreach(@bcd) {
my $bcd = $_ & 0xf; # just the nibble
unless ($found) {
next unless $bcd; # skip leading zeros
$found = 1;
$hbits[3] = $bcd; # set the first digit, no x10 necessary
next;
}
_128x10(\@hbits);
$digit[3] = $bcd;
_sa128(\@hbits,\@digit,0);
}
return pack('N4',@hbits);
}
#=item * $bcdpacked = simple_pack($bcdtext);
#
#Convert a numeric string into a packed bcd string, left fill with zeros
#This function is for testing only.
#
# input: string of decimal digits
# returns: string of packed decimal digits
#
#Similar to pack("H*", $bcdtext);
#
sub _bcdcheck {
my($bcd) = @_;;
my $sub = (caller(1))[3];
my $len = length($bcd);
die "Bad bcd number length $_ ".__PACKAGE__.":simple_pack, should be 1 to 40 digits"
if $len > 40 || $len < 1;
die "Bad character in decimal input string '$1' for ".__PACKAGE__.":simple_pack"
if $bcd =~ /(\D)/;
}
sub simple_pack {
&_bcdcheck;
my($bcd) = @_;
while (length($bcd) < 40) {
$bcd = '0'. $bcd;
}
return pack('H40',$bcd);
}
=head1 EXPORT_OK
hasbits
isIPv4
shiftleft
addconst
add128
sub128
notcontiguous
ipv4to6
mask4to6
ipanyto6
maskanyto6
ipv6to4
bin2bcd
bcd2bin
comp128
bin2bcdn
bcdn2txt
bcdn2bin
simple_pack
threads
=head1 AUTHOR
Michael Robinton E<lt>michael@bizsystems.comE<gt>
=head1 COPYRIGHT
Copyright 2006 - 2008, Michael Robinton <michael@bizsystems.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License (except as noted
otherwise in individuals sub modules) published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
=head1 AUTHOR
Michael Robinton <michael@bizsystems.com>
=cut
1;