diff --git a/IP.pm b/IP.pm index 37b5479..f3c7668 100644 --- a/IP.pm +++ b/IP.pm @@ -1,6 +1,6 @@ #!/usr/bin/perl -w -# $Id: IP.pm,v 1.16 2003/11/27 20:03:53 lem Exp $ +# $Id: IP.pm,v 1.22 2004/03/02 20:23:57 lem Exp $ package NetAddr::IP; @@ -48,7 +48,7 @@ our @ISA = qw(Exporter); -our $VERSION = '3.19'; +our $VERSION = '3.20'; ############################################# # These are the overload methods, placed here @@ -748,21 +748,25 @@ $ip = '::1'; } elsif ($ip =~ /:::/ || $ip =~ /::.*::/) { - return undef; + return; } - return undef unless $ip =~ /^[\da-f\:]+$/i; + return unless $ip =~ /^[\da-f\:]+$/i; $colons = ($ip =~ tr/:/:/); - return undef unless $colons >= 2 && $colons <= 7; + return unless $colons >= 2 && $colons <= 7; $expanded = ':0' x (9 - $colons); + $expanded =~ s/0$// if ($ip =~ /^[\da-f]+::[\da-f]+$/); +# warn "# colons = $colons\n"; +# warn "# expanded = $expanded\n"; $ip =~ s/::/$expanded/; $ip = '0' . $ip if $ip =~ /^:/; +# warn "# ip = $ip\n"; # .:.:.:.:.:.:.:. @ip = split(/:/, $ip); grep($_ = expand_v6($_), @ip);; for (0..$#ip) { $addr = _v6_part($addr, $ip[$_], $_); - return undef unless defined $addr; + return unless defined $addr; } return { addr => $addr, mask => $mask, bits => 128 }; @@ -928,23 +932,6 @@ : _to_ipv6 $self->{addr}; } -sub _compact ($) { - my $addr = shift; - - $addr =~ s/(^|:)0{1,3}/${1}/g; - # Not optimized for the biggest :0 sequence to collapse - $addr =~ s/((^|:)0)+/:/; - # unspecified :-( - $addr .= ':' if $addr eq ':'; - - return $addr; -} - -sub compact_addr ($) { - my $self = shift; - $self->{bits} == 32 ? _to_quad $self->{addr} - : _compact _to_ipv6 $self->{addr}; -} =pod @@ -1111,10 +1098,29 @@ sub numeric ($) { my $self = shift; - return - wantarray() ? ( vec($self->{addr}, 0, 32), - vec($self->{mask}, 0, 32) ) : - vec($self->{addr}, 0, 32); + if ($self->version == 4) + { + return + wantarray() ? ( vec($self->{addr}, 0, 32), + vec($self->{mask}, 0, 32) ) : + vec($self->{addr}, 0, 32); + } + else + { + my $n = new Math::BigInt 0; + my $m = new Math::BigInt 0 if wantarray; + for (0 .. 3) + { + $n <<= 32; + $n += vec($self->{addr}, $_, 32); + if (wantarray) + { + $m <<= 32; + $m += vec($self->{mask}, $_, 32); + } + } + return wantarray ? ($n, $m) : $n; + } } =pod @@ -1140,6 +1146,73 @@ =pod +=item C<-Eshort()> + +Returns the address part in a short or compact notation. (ie, +127.0.0.1 becomes 127.1). Works with both, V4 and V6. Note that +C is now deprecated. + +=cut + +sub _compact_v6 ($) { + my $addr = shift; + + my @o = split /:/, $addr; + return $addr unless @o and grep { $_ =~ m/^0+$/ } @o; + + my @candidates = (); + my $start = undef; + + for my $i (0 .. $#o) + { + if (defined $start) + { + if ($o[$i] !~ m/^0+$/) + { + push @candidates, [ $start, $i - $start ]; + $start = undef; + } + } + else + { + $start = $i if $o[$i] =~ m/^0+$/; + } + } + + push @candidates, [$start, 8 - $start] if defined $start; + + my $l = (sort { $b->[1] <=> $a->[1] } @candidates)[0]; + + return $addr unless defined $l; + + $addr = $l->[0] == 0 ? '' : join ':', @o[0 .. $l->[0] - 1]; + $addr .= '::'; + $addr .= join ':', @o[$l->[0] + $l->[1] .. $#o]; + $addr =~ s/(^|:)0{1,3}/$1/g; + + return $addr; +} + +sub short ($) +{ + my $self = shift; + my $addr = $self->addr; + if ($self->{bits} == 32) + { + my @o = split(/\./, $addr, 4); + splice(@o, 1, 2) if $o[1] == 0 and $o[2] == 0; + return join '.', @o; + } + else + { + return _compact_v6 _to_ipv6 $self->{addr}; + } +} + +*{compact_addr} = \&short; + +=pod + =item C<$me-Econtains($other)> Returns true when C<$me> completely contains C<$other>. False is @@ -1163,16 +1236,15 @@ unless $bits == $b->{bits}; # $a must be less specific than $b... - return 0 - unless ($mask = vec($a->{mask}, 0, $bits)) - <= vec($b->{mask}, 0, $bits); + my ($a_addr, $a_mask) = $a->numeric; + my ($b_addr, $b_mask) = $b->numeric; + + return 0 unless $a_mask <= $b_mask; # A default address always contains - return 1 if ($mask == 0x0); + return 1 if ($a_mask == 0x0); - return - ((vec($a->{addr}, 0, $bits) & $mask) - == (vec($b->{addr}, 0, $bits) & $mask)); + return ($a_addr & $a_mask) == ($b_addr & $a_mask); } =pod @@ -1514,7 +1586,7 @@ =head1 HISTORY -$Id: IP.pm,v 1.16 2003/11/27 20:03:53 lem Exp $ +$Id: IP.pm,v 1.22 2004/03/02 20:23:57 lem Exp $ =over @@ -2079,6 +2151,16 @@ Fixed a bug pointed out by Andrew D. Clark, regarding proper parsing of IP ranges with non-contiguous masks. Thanks Andrew! +=item 3.20 + +Suggestion by Reuland Olivier gave birth to C, which provides +for a compact representation of the IP address. Rewrote C<_compact> to +find the longest sequence of zeros to compact. Reuland also pointed +out a flaw in contains() and within(), which was fixed. Thanks +Reuland! + +Fixed rt bug #5478 in t/00-load.t. + =back =head1 AUTHOR diff --git a/MANIFEST b/MANIFEST index cf4448f..4b2290a 100644 --- a/MANIFEST +++ b/MANIFEST @@ -7,6 +7,7 @@ tutorial.htm t/00-load.t t/loops.t +t/short.t t/bitops.t t/v4-new.t t/v4-num.t @@ -30,12 +31,16 @@ t/wildcard.t t/v4-compact.t t/v4-numeric.t +t/v6-numeric.t t/v4-sprefix.t t/v4-xprefix.t t/v4-compplus.t t/v4-contains.t +t/v6-contains.t t/v4-hostenum.t t/v4-split-bulk.t t/v6-split-bulk.t t/v4-split-list.t + + META.yml Module meta-data (added by MakeMaker) diff --git a/META.yml b/META.yml index 66046b6..f98ab68 100644 --- a/META.yml +++ b/META.yml @@ -1,7 +1,7 @@ # http://module-build.sourceforge.net/META-spec.html #XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX# name: NetAddr-IP -version: 3.19 +version: 3.20 version_from: IP.pm installdirs: site requires: diff --git a/t/00-load.t b/t/00-load.t index 4865087..3d8ee01 100644 --- a/t/00-load.t +++ b/t/00-load.t @@ -1,6 +1,6 @@ # Generic load/POD test suite -# $Id: 00-load.t,v 1.3 2003/10/08 06:46:02 lem Exp $ +# $Id: 00-load.t,v 1.4 2004/03/02 20:21:36 lem Exp $ use Test::More; @@ -16,7 +16,8 @@ my $checker = 0; -eval { use Test::Pod; +eval { require Test::Pod; + Test::Pod::import(); $checker = 1; }; for my $m (@modules) diff --git a/t/relops.t b/t/relops.t index ae74a2a..53db1f8 100644 --- a/t/relops.t +++ b/t/relops.t @@ -1,30 +1,37 @@ use NetAddr::IP; -# $Id: relops.t,v 1.4 2003/10/08 06:46:02 lem Exp $ +# $Id: relops.t,v 1.5 2004/02/23 22:20:17 lem Exp $ BEGIN { @gt = ( - [ '255.255.255.255/32', '0.0.0.0/0' ], - [ '10.0.1.0/16', '10.0.0.1/24' ], - [ '10.0.0.1/24', '10.0.0.0/24' ], - ); + [ '255.255.255.255/32', '0.0.0.0/0' ], + [ 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', '::/0' ], + [ '10.0.1.0/16', '10.0.0.1/24' ], + [ '10.0.0.1/24', '10.0.0.0/24' ], + [ 'deaf:beef::1/64', 'dead:beef::/64' ], + ); @ngt = ( - [ '0.0.0.0/0', '255.255.255.255/32' ], - [ '10.0.0.0/24', '10.0.0.0/24' ], - ); + [ '0.0.0.0/0', '255.255.255.255/32' ], + [ '::/0', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' ], + [ '10.0.0.0/24', '10.0.0.0/24' ], + [ 'dead:beef::/60', 'dead:beef::/60' ], + ); @cmp = ( - [ '0.0.0.0/0', '255.255.255.255/32', -1 ], - [ '10.0.0.0/16', '10.0.0.0/8', 1 ], - [ '10.0.0.0/24', '10.0.0.0/8', 1 ], - [ '255.255.255.255/32', '0.0.0.0/0', 1 ], - [ '142.52.5.87', '142.52.2.88', 1 ], - [ '10.0.0.0/24', '10.0.0.0/24', 0 ], - [ 'default', 'default', 0 ], - [ 'broadcast', 'broadcast', 0], - [ 'loopback', 'loopback', 0], - ); + [ '0.0.0.0/0', '255.255.255.255/32', -1 ], + [ '::/0', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', -1 ], + [ '10.0.0.0/16', '10.0.0.0/8', 1 ], + [ 'dead:beef::/60', 'dead:beef::/40', 1 ], + [ '10.0.0.0/24', '10.0.0.0/8', 1 ], + [ '255.255.255.255/32', '0.0.0.0/0', 1 ], + [ 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', '::/0', 1 ], + [ '142.52.5.87', '142.52.2.88', 1 ], + [ '10.0.0.0/24', '10.0.0.0/24', 0 ], + [ 'default', 'default', 0 ], + [ 'broadcast', 'broadcast', 0], + [ 'loopback', 'loopback', 0], + ); }; diff --git a/t/short.t b/t/short.t new file mode 100644 index 0000000..b6cc561 --- /dev/null +++ b/t/short.t @@ -0,0 +1,32 @@ +use Test::More; + +# $Id: short.t,v 1.1 2004/02/22 05:07:51 lem Exp $ + +my %cases = +( + '127.1' => '127.0.0.1', + '127.0.1.1' => '127.0.1.1', + '127.1.0.1' => '127.1.0.1', + 'dead:beef::1' => 'dead:beef::1', + '::1' => '::1', + '::' => '::', + '2001:620:600::1' => '2001:620:600::1', + '2001:620:600:0:1::1' => '2001:620:600:0:1::1', + '2001:620:601:0:1::1' => '2001:620:601::1:0:0:1', + ); + +my $tests = 2 * keys %cases; +plan tests => 1 + $tests; + +SKIP: { + use_ok('NetAddr::IP') or skip "Failed to load NetAddr::IP", $tests; + for my $c (sort keys %cases) + { + my $ip = new NetAddr::IP $cases{$c}; + isa_ok($ip, 'NetAddr::IP', "$cases{$c}"); + unless (is($ip->short, $c, "short() returns $c")) + { + diag "ip=$ip"; + } + } +} diff --git a/t/v6-base.t b/t/v6-base.t index 88ac309..2a15687 100644 --- a/t/v6-base.t +++ b/t/v6-base.t @@ -1,6 +1,6 @@ # This -*- perl -*- code excercises the basic v6 functionality -# $Id: v6-base.t,v 1.9 2003/10/10 18:46:53 lem Exp $ +# $Id: v6-base.t,v 1.13 2004/03/02 20:33:37 lem Exp $ our @addr = ( @@ -8,6 +8,9 @@ ['::1', 3, '0000:0000:0000:0000:0000:0000:0000:0001/128'], ['f34::123/40', 3, '0f34:0000:0000:0000:0000:0000:0000:0003/40'], ['dead:beef::1/40', 3, 'dead:beef:0000:0000:0000:0000:0000:0003/40'], + ['1000::2/40', 1, '1000:0000:0000:0000:0000:0000:0000:0001/40'], + ['1000::2000/40', 1, '1000:0000:0000:0000:0000:0000:0000:0001/40'], + ['dead::cafe/40', 1, 'dead:0000:0000:0000:0000:0000:0000:0001/40'], ['dead:beef::1/40', 4, 'dead:beef:0000:0000:0000:0000:0000:0004/40'], ['dead:beef::1/40', 5, 'dead:beef:0000:0000:0000:0000:0000:0005/40'], ['dead:beef::1/40', 6, 'dead:beef:0000:0000:0000:0000:0000:0006/40'], @@ -19,6 +22,10 @@ ['dead:beef::1/40', 257, 'dead:beef:0000:0000:0000:0000:0000:0101/40'], ['dead:beef::1/40', 65536, 'dead:beef:0000:0000:0000:0000:0001:0000/40'], ['dead:beef::1/40', 65537, 'dead:beef:0000:0000:0000:0000:0001:0001/40'], + ['2001:620:0:4::/64', 1, '2001:0620:0000:0004:0000:0000:0000:0001/64'], + ['3ffe:2000:0:4::/64', 1, '3ffe:2000:0000:0004:0000:0000:0000:0001/64'], + ['2001:620:600::1', 1, '2001:0620:0600:0000:0000:0000:0000:0001/128'], + ['2001:620:600:0:1::1', 1,'2001:0620:0600:0000:0001:0000:0000:0001/128'], ); use NetAddr::IP; @@ -31,17 +38,18 @@ for $a (@addr) { $ip = new NetAddr::IP $a->[0]; $a->[0] =~ s,/\d+,,; - isa_ok($ip, 'NetAddr::IP'); - is($ip->compact_addr, $a->[0]); - is($ip->bits, 128); - is($ip->version, 6); - is($ip->nth($a->[1]), $a->[2]); + isa_ok($ip, 'NetAddr::IP', "$a->[0] "); + is($ip->short, $a->[0], "short returns $a->[0]"); + is($ip->bits, 128, "bits == 128"); + is($ip->version, 6, "version == 6"); + is($ip->nth($a->[1]), $a->[2], "nth $a->[0], $a->[1]"); } $test = new NetAddr::IP 'f34::1'; isa_ok($test, 'NetAddr::IP'); -ok($ip->network->contains($test), "->contains"); +ok($test->network->contains($test), "->contains"); $test = new NetAddr::IP 'f35::1/40'; isa_ok($test, 'NetAddr::IP'); -ok($ip->network->contains($test), "->contains"); +ok($test->network->contains($test), "->contains"); + diff --git a/t/v6-contains.t b/t/v6-contains.t new file mode 100644 index 0000000..c1e1573 --- /dev/null +++ b/t/v6-contains.t @@ -0,0 +1,53 @@ +use NetAddr::IP; +use Test::More; + +# $Id: v6-contains.t,v 1.2 2004/02/23 22:38:28 lem Exp $ + +my @yes_pairs = + ( + [ '::/0', '2001:620:0:4:a00:20ff:fe9c:7e4a' ], + [ '3ffe:2000:0:4::/64', '3ffe:2000:0:4:a00:20ff:fe9c:7e4a' ], + [ '3ffe:2000:0:4::/64', '3ffe:2000:0:4:a00:20ff:fe9c:7e4a/65' ], + [ '2001:620:0:4::/64', '2001:620:0:4:a00:20ff:fe9c:7e4a' ], + [ '2001:620:0:4::/64', '2001:620:0:4:a00:20ff:fe9c:7e4a/65' ], + [ '2001:620:0:4::/64', '2001:620:0:4::1' ], + [ '2001:620:0:4::/64', '2001:620:0:4:0:0:0:1' ], + [ 'deaf:beef::/32', 'deaf:beef::1' ], + [ 'deaf:beef::/32', 'deaf:beef::1:1' ], + [ 'deaf:beef::/32', 'deaf:beef::1:0:1' ], + [ 'deaf:beef::/32', 'deaf:beef::1:0:0:1' ], + [ 'deaf:beef::/32', 'deaf:beef::1:0:0:0:1' ], + ); + +my @no_pairs = + ( + [ '3ffe:2000:0:4::/64', '3ffe:2000:0:4:a00:20ff:fe9c:7e4a/63' ], + [ '2001:620:0:4::/64', '2001:620:0:4:a00:20ff:fe9c:7e4a/63' ], + [ 'deaf:beef::/32', 'dead:cafe::1' ], + [ 'deaf:beef::/32', 'dead:cafe::1:1' ], + [ 'deaf:beef::/32', 'dead:cafe::1:0:1' ], + [ 'deaf:beef::/32', 'dead:cafe::1:0:0:1' ], + [ 'deaf:beef::/32', 'dead:cafe::1:0:0:0:1' ], + ); + +my $tests = 6 * @yes_pairs + 1; +plan tests => $tests; + +ok(NetAddr::IP->new('::')->contains(NetAddr::IP->new('::')), + ":: contains itself"); + +for my $p (@yes_pairs) +{ + my $a = new NetAddr::IP $p->[0]; + my $b = new NetAddr::IP $p->[1]; + + isa_ok($a, 'NetAddr::IP', "$p->[0]"); + isa_ok($b, 'NetAddr::IP', "$p->[1]"); + + SKIP: { + ok($a->contains($b), "->contains $p->[0], $p->[1] is true"); + ok($b->within($a), "->within $p->[1], $p->[0] is true"); + ok(!$b->contains($a), "->contains $p->[1], $p->[0] is false"); + ok(!$a->within($b), "->within $p->[0], $p->[1] is false"); + } +} diff --git a/t/v6-numeric.t b/t/v6-numeric.t new file mode 100644 index 0000000..e7d90f3 --- /dev/null +++ b/t/v6-numeric.t @@ -0,0 +1,93 @@ +use NetAddr::IP; +use Test::More; + +# $Id: v6-numeric.t,v 1.2 2004/03/02 20:33:37 lem Exp $ + +my @pairs = + ( + [ '::/0', '0', '0' ], + [ '::/128', '0', '340282366920938463463374607431768211455' ], + [ 'cafe:cafe::/64', + '269827015721314068804783158349174669312', + '340282366920938463444927863358058659840' ], + [ 'cafe:cafe::1/64', + '269827015721314068804783158349174669313', + '340282366920938463444927863358058659840' ], + [ 'dead:beef::/100', + '295990755014133383690938178081940045824', + '340282366920938463463374607431499776000' ], + [ 'dead:beef::1/100', + '295990755014133383690938178081940045825', + '340282366920938463463374607431499776000' ], + ); + +my @scale = +qw( + 0000:0000:0000:0000:0000:0000:0000:0000 + 0000:0000:0000:0000:0000:0000:0000:0001 + 0000:0000:0000:0000:0000:0000:0000:0010 + 0000:0000:0000:0000:0000:0000:0000:0100 + 0000:0000:0000:0000:0000:0000:0000:1000 + 0000:0000:0000:0000:0000:0000:0001:0000 + 0000:0000:0000:0000:0000:0001:0000:0000 + 0000:0000:0000:0000:0000:0010:0000:0000 + 0000:0000:0000:0000:0000:0100:0000:0000 + 0000:0000:0000:0000:0000:1000:0000:0000 + 0000:0000:0000:0000:0001:0000:0000:0000 + 0000:0000:0000:0001:0000:0000:0000:0000 + 0000:0000:0000:0010:0000:0000:0000:0000 + 0000:0000:0000:0100:0000:0000:0000:0000 + 0000:0000:0000:1000:0000:0000:0000:0000 + 0000:0000:0001:0000:0000:0000:0000:0000 + 0000:0001:0000:0000:0000:0000:0000:0000 + 0000:0010:0000:0000:0000:0000:0000:0000 + 0000:0100:0000:0000:0000:0000:0000:0000 + 0000:1000:0000:0000:0000:0000:0000:0000 + 0001:0000:0000:0000:0000:0000:0000:0000 + 0010:0000:0000:0000:0000:0000:0000:0000 + 0100:0000:0000:0000:0000:0000:0000:0000 + 1000:0000:0000:0000:0000:0000:0000:0000 + ); + +my $tests = 4 * @pairs + @scale ** 2; +plan tests => $tests; + +for my $p (@pairs) +{ + my $a = new NetAddr::IP $p->[0]; + isa_ok($a, 'NetAddr::IP', "$p->[0]"); + is($a->numeric, $p->[1], "$p->[0] Scalar numeric ok"); + is(($a->numeric)[0], $p->[1], "$p->[0] Array numeric ok for network"); + is(($a->numeric)[1], $p->[2], "$p->[0] Array numeric ok for mask"); +} + +@ip_scale = map { new NetAddr::IP $_ } @scale; + +isa_ok($_, 'NetAddr::IP', $_->addr) for @ip_scale; + +for my $i (0 .. $#ip_scale) +{ + for my $l (0 .. $i - 1) + { + next if $l >= $i; + unless (ok($ip_scale[$i]->numeric > $ip_scale[$l]->numeric, + "[$i, $l] $scale[$i] > $scale[$l]")) + { + diag "assertion [$i]: " . $ip_scale[$i]->numeric . + " > " . $ip_scale[$l]->numeric; + } + } + + next if $i == $#ip_scale; + + for my $l ($i + 1 .. $#ip_scale) + { + next if $l <= $i; + unless (ok($ip_scale[$i]->numeric < $ip_scale[$l]->numeric, + "[$i, $l] $scale[$i] < $scale[$l]")) + { + diag "assertion [$i]: " . $ip_scale[$i]->numeric . + " < " . $ip_scale[$l]->numeric; + } + } +}