diff --git a/IP.pm b/IP.pm index 7b74a85..c136de2 100644 --- a/IP.pm +++ b/IP.pm @@ -1,6 +1,6 @@ #!/usr/bin/perl -w -# $Id: IP.pm,v 1.24 2004/10/11 15:40:29 lem Exp $ +# $Id: IP.pm,v 1.30 2005/03/24 21:14:46 lem Exp $ package NetAddr::IP; @@ -44,11 +44,11 @@ use warnings; require Exporter; -our @EXPORT_OK = qw(Compact); +our @EXPORT_OK = qw(Compact Coalesce); our @ISA = qw(Exporter); -our $VERSION = '3.21'; +our $VERSION = '3.24'; ############################################# # These are the overload methods, placed here @@ -229,16 +229,13 @@ $num |= vec($hp, $_, 8); } -# warn "# add - before badd($const): $num\n"; $num->badd($const); -# warn "# add - after badd($const): $num\n"; for (reverse 0 .. 15) { my $x = new Math::BigInt $num; vec($hp, $_, 8) = $x & 0xFF; $num >>= 8; -# warn "# add - octet $_ == $num / ", vec($hp, $_, 8), "\n"; } } else # v4 @@ -278,13 +275,22 @@ my $a = $ip->{addr}; my $m = $ip->{mask}; + my $b = $ip->{bits}; - my $hp = "$a" & ~"$m"; - my $np = "$a" & "$m"; + if ($b == 128) + { + my $nip = NetAddr::IP->new($ip) + 1; + $ip->{$_} = $nip->{$_} for keys %$nip; + } + else + { + my $hp = "$a" & ~"$m"; + my $np = "$a" & "$m"; - vec($hp, 0, 32) ++; + vec($hp, 0, 32) ++; + $ip->{addr} = "$np" | ("$hp" & ~"$m"); + } - $ip->{addr} = "$np" | ("$hp" & ~"$m"); return $ip; } @@ -300,13 +306,22 @@ my $a = $ip->{addr}; my $m = $ip->{mask}; + my $b = $ip->{bits}; - my $hp = "$a" & ~"$m"; - my $np = "$a" & "$m"; + if ($b == 128) + { + my $nip = NetAddr::IP->new($ip) - 1; + $ip->{$_} = $nip->{$_} for keys %$nip; + } + else + { + my $hp = "$a" & ~"$m"; + my $np = "$a" & "$m"; - vec($hp, 0, 32) --; + vec($hp, 0, 32) --; - $ip->{addr} = "$np" | ("$hp" & ~"$m"); + $ip->{addr} = "$np" | ("$hp" & ~"$m"); + } return $ip; } @@ -753,7 +768,7 @@ $colons = ($ip =~ tr/:/:/); return unless $colons >= 2 && $colons <= 7; $expanded = ':0' x (9 - $colons); - $expanded =~ s/0$// if ($ip =~ /^[\da-f]+::[\da-f]+$/); + $expanded =~ s/0$// if ($ip =~ /[\da-f]+::[\da-f]+/); # warn "# colons = $colons\n"; # warn "# expanded = $expanded\n"; $ip =~ s/::/$expanded/; @@ -1477,6 +1492,64 @@ =pod +=item C<$me-Ecoalesce($masklen, $number, @list_of_subnets)> + +Will return a reference to list of C subnets of +C<$masklen> mask length, when C<$number> or more addresses from +C<@list_of_subnets> are found to be contained in said subnet. + +Subnets from C<@list_of_subnets> with a mask shorter than C<$masklen> +are passed "as is" to the return list. + +Subnets from C<@list_of_subnets> with a mask longer than C<$masklen> +will be counted (actually, the number of IP addresses is counted) +towards C<$number>. + +=cut + +sub coalesce +{ + my $masklen = shift; + my $number = shift; + + # Addresses are at @_ + my %ret = (); + + for my $ip (@_) + { + my $n = NetAddr::IP->new($ip->addr . '/' . $masklen)->network; + if ($ip->masklen > $masklen) + { + $ret{$n} += $ip->num + 1; + } + } + + my @ret = (); + + # Add to @ret any arguments with netmasks longer than our argument + for my $c (sort { $a->masklen <=> $b->masklen } + grep { $_->masklen <= $masklen } @_) + { + next if grep { $_->contains($c) } @ret; + push @ret, $c->network; + } + + # Now add to @ret all the subnets with more than $number hits + for my $c (map { new NetAddr::IP $_ } + grep { $ret{$_} >= $number } + keys %ret) + { + next if grep { $_->contains($c) } @ret; + push @ret, $c; + } + + return \@ret; +} + +*Coalesce = \&coalesce; + +=pod + =item C<$me-Ecompactref(\@list)> As usual, a faster version of =item C<-Ecompact()> that returns a @@ -1611,7 +1684,72 @@ return ~vec($self->{mask}, 0, $self->{bits}) & 0xFFFFFFFF; } - # Output a vec() as a dotted-quad +=pod + +=item C<-Ere()> + +Returns a Perl regular expression that will match an IP address within +the given subnet. This is currently only implemented for IPv4 +addresses. + +=cut + +sub re ($) +{ + my $self = shift->network; # Insure a "zero" host part + return unless $self->bits == 32; + my ($addr, $mlen) = ($self->addr, $self->masklen); + my @o = split('\.', $addr, 4); + + my $octet= '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'; + my @r = @o; + my $d; + +# for my $i (0 .. $#o) +# { +# warn "# $self: $r[$i] == $o[$i]\n"; +# } + + if ($mlen != 32) + { + if ($mlen > 24) + { + $d = 2 ** (32 - $mlen) - 1; + $r[3] = '(?:' . join('|', ($o[3]..$o[3] + $d)) . ')'; + } + else + { + $r[3] = $octet; + if ($mlen > 16) + { + $d = 2 ** (24 - $mlen) - 1; + $r[2] = '(?:' . join('|', ($o[2]..$o[2] + $d)) . ')'; + } + else + { + $r[2] = $octet; + if ($mlen > 8) + { + $d = 2 ** (16 - $mlen) - 1; + $r[1] = '(?:' . join('|', ($o[1]..$o[1] + $d)) . ')'; + } + else + { + $r[1] = $octet; + if ($mlen > 0) + { + $d = 2 ** (8 - $mlen) - 1; + $r[0] = '(?:' . join('|', ($o[0] .. $o[0] + $d)) . ')'; + } + else { $r[0] = $octet; } + } + } + } + } + + ### no digit before nor after (look-behind, look-ahead) + return "(?:(?re() as +contributed by Laurent Facq (Thanks Laurent!). Added Coalesce() as +suggested by Perullo. + +=item 3.24 + +Version bump. Transfer of 3.23 to CPAN ended up in a truncated file +being uploaded. + =back =head1 AUTHOR diff --git a/MANIFEST b/MANIFEST index 58f8ab1..5b20dcd 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,48 +1,50 @@ IP.pm -TODO -README -MANIFEST -MANIFEST.SKIP Makefile.PL -tutorial.htm +MANIFEST This list of files +MANIFEST.SKIP +META.yml Module meta-data (added by MakeMaker) +README +SIGNATURE t/00-load.t -t/loops.t -t/short.t +t/00-Sign.t t/bitops.t -t/v4-new.t -t/v4-num.t -t/relops.t t/imhoff.t -t/v4-aton.t -t/v4-snew.t -t/v4-cnew.t -t/v4-last.t -t/v4-wnew.t -t/over-qq.t +t/loops.t t/masklen.t -t/v4-base.t -t/v4-cidr.t -t/v6-base.t -t/over-arr.t -t/v4-range.t -t/v4-badnm.t -t/v4-basem.t -t/v4-first.t -t/wildcard.t -t/old-store.t t/new-store.t +t/old-store.t +t/over-arr.t +t/over-qq.t +t/relops.t +t/short.t +t/v4-aton.t +t/v4-badnm.t +t/v4-base.t +t/v4-basem.t +t/v4-cidr.t +t/v4-cnew.t +t/v4-coalesce.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-first.t t/v4-hostenum.t +t/v4-last.t +t/v4-new.t +t/v4-num.t +t/v4-numeric.t +t/v4-range.t +t/v4-re.t +t/v4-snew.t t/v4-split-bulk.t -t/v6-split-bulk.t t/v4-split-list.t - - -META.yml Module meta-data (added by MakeMaker) +t/v4-sprefix.t +t/v4-wnew.t +t/v4-xprefix.t +t/v6-base.t +t/v6-contains.t +t/v6-inc.t +t/v6-numeric.t +t/v6-split-bulk.t +t/wildcard.t +TODO diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index e6aa309..d9961d4 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -1,8 +1,12 @@ +^Build$ +^_build/ ^blib/ +^blibdirs ^Makefile$ ^Makefile\.[a-z]+$ -^pm_to_blib$ +^pm_to_blib CVS/.* +\.cvs ,v$ ^tmp/ \.old$ diff --git a/META.yml b/META.yml index 90c6e65..8db21c1 100644 --- a/META.yml +++ b/META.yml @@ -1,12 +1,11 @@ -# 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.21 +version: 3.24 version_from: IP.pm installdirs: site requires: - Math::BigInt: 0 Test::More: 0 + Math::BigInt: 0 distribution_type: module -generated_by: ExtUtils::MakeMaker version 6.17 +generated_by: ExtUtils::MakeMaker version 6.12 diff --git a/Makefile.PL b/Makefile.PL index bd47210..b67741d 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -2,7 +2,7 @@ # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. -# $Id: Makefile.PL,v 1.9 2004/10/11 15:40:29 lem Exp $ +# $Id: Makefile.PL,v 1.10 2005/03/24 18:55:10 lem Exp $ my $checker = 0; @@ -47,13 +47,14 @@ ; WriteMakefile( - 'NAME' => 'NetAddr::IP', - 'VERSION_FROM' => 'IP.pm', # finds $VERSION - 'PREREQ_PM' => { - Test::More => 0, - Math::BigInt => 0, - }, # e.g., Module::Name => 1.1 + (MM->can('signature_target') ? (SIGN => 1) : ()), + 'NAME' => 'NetAddr::IP', + 'VERSION_FROM' => 'IP.pm', # finds $VERSION + 'PREREQ_PM' => { + Test::More => 0, + Math::BigInt => 0, + }, # e.g., Module::Name => 1.1 ($] >= 5.005 ? ## Add these new keywords supported since 5.005 (ABSTRACT_FROM => 'IP.pm', AUTHOR => 'Luis E. Mu�oz ') : ()), -); + ); diff --git a/README b/README index 62df416..6e5f31d 100644 --- a/README +++ b/README @@ -1,6 +1,9 @@ -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 + + + NetAddr::IP - Manages IP addresses and subnets * * * * THIS MODULE REQUIRES PERL 5.6.0 OR NEWER. * * * * @@ -102,9 +105,14 @@ $ perldoc NetAddr::IP -to access the documentation. There is also a tutorial in HTML. Look -for the file tutorial.htm within the package bundle, which is a -tutorial I submitted to perlmonks.org recently. +to access the documentation. There is also a tutorial in HTML at the +following URIs + + http://mipagina.cantv.net/lem/perl/iptut.htm + http://mipagina.cantv.net/lem/perl/ipperf.htm + +If you want to thank me for this module, please go look at those +tutorials and if you see banners there, click on a few of them :) Bug reports are welcome. Please do not forget to tell me what version/platform are you running this code on. Providing a small piece @@ -137,6 +145,11 @@ This information includes the correct keys, fingerprints, etc.Note that this README file should also be signed. +Additionally, I am also using Module::Signature to ease with the +signature verification. Module::Signature can automatically retrieve +the PGP keys from public keyservers, as well as verifying each +individual file. + LICENSE AND WARRANTY This software is (c) Luis E. Mu�oz. It can be used under the terms of @@ -147,10 +160,9 @@ No warranty of any kind is expressed or implied. This code might make your computer go up in a puff of black smoke. -----BEGIN PGP SIGNATURE----- -Version: GnuPG v1.0.6 (Darwin) -Comment: For info see http://www.gnupg.org +Version: GnuPG v1.2.1 (Darwin) -iD8DBQE99iAmQyDWGRI/hhARAsPlAJ9VFVFGSUHjxDUvXm2x9x20YfhuYQCeJu5A -F5c3pam/zZQiVf7ZaUVs3fc= -=VNNB +iD8DBQFCQwzJQyDWGRI/hhARAqd9AJ0XLAPEZ/r0laqAs4XhZ26xf00OAQCgzYxk +IjdkSW+uHhcUXN6nJlo7yqA= +=9Ztj -----END PGP SIGNATURE----- diff --git a/SIGNATURE b/SIGNATURE new file mode 100644 index 0000000..7e09cfc --- /dev/null +++ b/SIGNATURE @@ -0,0 +1,72 @@ +This file contains message digests of all files listed in MANIFEST, +signed via the Module::Signature module, version 0.44. + +To verify the content in this distribution, first make sure you have +Module::Signature installed, then type: + + % cpansign -v + +It will check each file's integrity, as well as the signature's +validity. If "==> Signature verified OK! <==" is not displayed, +the distribution may already have been compromised, and you should +not run its Makefile.PL or Build.PL. + +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA1 + +SHA1 8200f5d7dc8f45ffefc9ab0da2c885359830383f IP.pm +SHA1 6433ee417d2773d717542beda307a4b769f23445 MANIFEST +SHA1 abef850c470fc33a86dd3d6658d9b7365ceb63b5 MANIFEST.SKIP +SHA1 aeb53c5d74c070190b2e543ea6a3d8b3c4d90f29 META.yml +SHA1 08862d39eaaa83329195724aae0817bcd2485727 Makefile.PL +SHA1 e0a622d5dfe578b0344b824b27f9c0a46f029250 README +SHA1 a3c3e184556159978520c47b7837c26efa453b29 TODO +SHA1 2bf2f62cb765b20126756819844b45cbe5f89979 t/00-Sign.t +SHA1 ab5209c930fd972d45b9e740fb00ada18810c13b t/00-load.t +SHA1 647ad43b533838cfcacfd85af3c2176d9de9dc20 t/bitops.t +SHA1 408e29a1b1ded7a1f42501c3e0843a1b07ac01c1 t/imhoff.t +SHA1 ad8be2dc7d91acd0ab1e637f3032ff0175b879b1 t/loops.t +SHA1 458350bd045c9428456ff3d61b1a2facbad1225f t/masklen.t +SHA1 5516882545f1326eecca22a298c91775e8515897 t/new-store.t +SHA1 3833f81312dcd4ff7a8387a8ef937d6d9f906726 t/old-store.t +SHA1 1849b8a630d689a2c81a8eb33d1fb7697e8f0840 t/over-arr.t +SHA1 b577c363d7cad97f82750799ee2391bb2b625569 t/over-qq.t +SHA1 a7b84175a7c961dfcc786d423a104c8e3e4c447b t/relops.t +SHA1 1e520807bd379a9b1fa856f854cdfd1a41f81234 t/short.t +SHA1 2811d25f196f16858f8842118619b40199f155bd t/v4-aton.t +SHA1 aff5265fae459859785d1fa31a0d5b7da999032b t/v4-badnm.t +SHA1 44e867fa16b31c47303407d2cf36c9e5281be695 t/v4-base.t +SHA1 87c2bb554a3411ae076615b34ee89f8de1254621 t/v4-basem.t +SHA1 215bc6deba22404eee3aa398dc66e5ddc070747a t/v4-cidr.t +SHA1 2831dc3b7559505aa7b7fc464aa0f61dde07fb48 t/v4-cnew.t +SHA1 fd66ae275d094028efcfc9581d52e58a49591116 t/v4-coalesce.t +SHA1 ab3bbe6342d9f130c97025d2461592a5b6fdd74c t/v4-compact.t +SHA1 4560b06c36e56a5546d4033ba0bb0eb96b6f7389 t/v4-compplus.t +SHA1 7f4448ba664ed6c5eb1febe4178730421025fe9e t/v4-contains.t +SHA1 66de0299983348642bd517bd6a2503c90014ba50 t/v4-first.t +SHA1 a75533c20083e12ba26596c9b14a6e4dde0ff7c0 t/v4-hostenum.t +SHA1 c22f8aa965b42dbe318114bc1461c69446ab4d80 t/v4-last.t +SHA1 96656288c0cfa335327f827350538f33914ec2f0 t/v4-new.t +SHA1 f6791d9f35ea14ac2d09c81a78e9b2ca69fc6e6a t/v4-num.t +SHA1 0a75321f7fef3f5acd1e1555dd6924241ea824cc t/v4-numeric.t +SHA1 d8c49cfe039381704b5c457129aa188323762e4c t/v4-range.t +SHA1 11e6cb5998a67010a7f9cfad077634aa47314605 t/v4-re.t +SHA1 2e921e360c3e84be4a2d0963f367e35aeb1e2c8b t/v4-snew.t +SHA1 797a980512fd6a15accb51a0557a4bf5023e0503 t/v4-split-bulk.t +SHA1 df0c1e48533f361a447f2af6f6c8f891666bf851 t/v4-split-list.t +SHA1 56336d7cc3bfb20e221b12b51e5a3542493ab19e t/v4-sprefix.t +SHA1 52daa44312542825a5a5b4237008d8d043689012 t/v4-wnew.t +SHA1 1439861493b6c9c16b79ada07aeee752ce968ba6 t/v4-xprefix.t +SHA1 bc1583d9adeb4fc684a24c058635bd5b5c435e6d t/v6-base.t +SHA1 08bc66b8418c0166e2e5d0e80f738685a0217d97 t/v6-contains.t +SHA1 d44fbb3ba0df4cc93bbec19c3a22b0936a4f2073 t/v6-inc.t +SHA1 f2b96606d38e1ca44509271c5f13c4b071678d03 t/v6-numeric.t +SHA1 840842286ef5909296ae7a4dfcaff9ae5c25d460 t/v6-split-bulk.t +SHA1 083a3ddd6a2f9083acbf294f79be757f2db516d6 t/wildcard.t +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.2.1 (Darwin) + +iD8DBQFCQy80QyDWGRI/hhARAnTvAJ9aC7i9pgxI3kBJG7PJe4536GX50ACfSNdG +5v1M8ea98NiVxftZS/aQJpg= +=HR+z +-----END PGP SIGNATURE----- diff --git a/TODO b/TODO index ca7bf32..4249d7c 100644 --- a/TODO +++ b/TODO @@ -1,8 +1,12 @@ -$Id: TODO,v 1.3 2002/12/10 17:14:02 lem Exp $ +$Id: TODO,v 1.6 2005/03/24 20:41:51 lem Exp $ o More tests for IPv6 functionality. This should be thoroughly tested. o Extend the formats accepted for v6 addresses. o Add support for other notations (when found). + +o Add a way to force the recognition of a v6 IP address when the input + is presented numerically. (Suggested by Carlos Vicente) + diff --git a/t/00-Sign.t b/t/00-Sign.t new file mode 100644 index 0000000..09db4b4 --- /dev/null +++ b/t/00-Sign.t @@ -0,0 +1,24 @@ +#!/usr/bin/perl +use strict; +$|++; + +print "1..1\n"; + +if (!-s 'SIGNATURE') { + print "ok 1 # skip No signature file found\n"; + } +elsif (!eval { require Module::Signature; 1 }) { + print "ok 1 # skip ", + "Next time around, consider install Module::Signature, ", + "so you can verify the integrity of this distribution.\n"; +} +elsif (!eval { require Socket; Socket::inet_aton('pgp.mit.edu') }) { + print "ok 1 # skip Cannot connect to the keyserver\n"; + } +else { + (Module::Signature::verify() == Module::Signature::SIGNATURE_OK()) + or print "not "; + print "ok 1 # Valid signature\n"; + } + +__END__ diff --git a/t/v4-coalesce.t b/t/v4-coalesce.t new file mode 100644 index 0000000..7a3dd44 --- /dev/null +++ b/t/v4-coalesce.t @@ -0,0 +1,43 @@ +use Test::More; + +# $Id: v4-coalesce.t,v 1.2 2005/03/24 20:47:40 lem Exp $ + +plan tests => 11; + +die "# Cannot continue without NetAddr::IP\n" + unless use_ok('NetAddr::IP', 'Coalesce'); + +# Test a rather large set... + +my @ips = (); + +for my $o (0 .. 255) +{ + push @ips, new NetAddr::IP "10.0.$o.1"; + push @ips, new NetAddr::IP "10.0.$o.10"; + push @ips, new NetAddr::IP "10.0.$o.100"; +} + +# This should return the empty list... +my $r = Coalesce(24, 4, @ips); +diag "Coalesce returned $r" + unless isa_ok($r, 'ARRAY', 'Return type from Coalesce'); +is(@$r, 0, "Empty array returned as expected"); + +# This should produce a list with all the /24s +$r = Coalesce(24, 2, @ips); +diag "Coalesce returned $r" + unless isa_ok($r, 'ARRAY', 'Return type from Coalesce'); +is(@$r, 256, "Whole result set as expected"); +my @c = NetAddr::IP::Compact(@$r); +is(@c, 1, "Results are compactable"); +ok($c[0] eq '10.0.0.0/16', "Correct results"); + +# This should produce the same result as before, with an added /23 +$r = Coalesce(24, 2, @ips, NetAddr::IP->new('10.0.0.125/23')); +diag "Coalesce returned $r" + unless isa_ok($r, 'ARRAY', 'Return type from Coalesce'); +ok((grep { $_ eq '10.0.0.0/23' } @$r), "/23 went through"); +@c = NetAddr::IP::Compact(@$r); +is(@c, 1, "Results are compactable"); +ok($c[0] eq '10.0.0.0/16', "Correct results"); diff --git a/t/v4-re.t b/t/v4-re.t new file mode 100644 index 0000000..6c4f90d --- /dev/null +++ b/t/v4-re.t @@ -0,0 +1,38 @@ +use Test::More; + +# $Id: v4-re.t,v 1.1 2005/03/24 20:41:51 lem Exp $ + +my @ips = qw! + 10.11.12.13 + 10.11.12/24 + 10.11.0/27 + !; + +plan tests => 299; + +die "# Cannot continue without NetAddr::IP\n" + unless use_ok('NetAddr::IP'); + +my @addrs = map { new NetAddr::IP $_ } @ips; + +for my $a (@addrs) +{ + isa_ok($a, 'NetAddr::IP'); + my $re = $a->re; + my $rx; + + eval { $rx = qr/$re/ }; + diag "Compilation of the resulting regular expression failed: $@" + unless ok(!$@, "Compilation of the resulting regular expression"); + + for (my $ip = $a->network; + $ip < $a->broadcast && $a->masklen != 32; + $ip ++) + { + ok($a->addr =~ m/$rx/, "Match of $ip in $a"); + } + + ok($a->broadcast->addr =~ m/$rx/, "Match of broadcast of $a"); + ok(NetAddr::IP->new('default') !~ m/$rx/, "0/0 does not match"); +} + diff --git a/t/v6-inc.t b/t/v6-inc.t new file mode 100644 index 0000000..a716b23 --- /dev/null +++ b/t/v6-inc.t @@ -0,0 +1,38 @@ +use Test::More; +use NetAddr::IP; + +# Test ++ in IPv6 addresses (Bug rt.cpan.org #7070 by a guest) + +@ip = (NetAddr::IP->new('2001:468:ff:fffe::2/64'), + NetAddr::IP->new('2001:468:ff:fffe::2/64'), + NetAddr::IP->new('2001:468:ff:fffe::2/64')); + +$ip[1] ++; +$ip[2] ++; $ip[2] ++; + +plan tests => 11; + +# Test correct v6 creation +isa_ok($_, 'NetAddr::IP') for @ip; + +# Test that we did actually do something +diag "$ip[0] -- $ip[1]" + unless ok($ip[0] != $ip[1], "Auto incremented once differ"); +diag "$ip[0] -- $ip[2]" + unless ok($ip[0] != $ip[2], "Auto incremented twice differ"); +diag "$ip[1] -- $ip[2]" + unless ok($ip[1] != $ip[2], "Auto incremented two times differ"); + +# Test that what we did is correct +is($ip[1], $ip[0] + 1, "Test of first auto-increment"); +is($ip[2], $ip[0] + 2, "Test of second auto-increment"); + +# Now test auto-decrement + +$ip[1] --; +$ip[2] --; $ip[2] --; + +is($ip[0], $ip[1], "Decrement of decrement once is ok"); +is($ip[0], $ip[2], "Decrement of decrement twice is ok"); +is($ip[1], $ip[2], "Third case"); + diff --git a/tutorial.htm b/tutorial.htm deleted file mode 100644 index 6e297fa..0000000 --- a/tutorial.htm +++ /dev/null @@ -1,549 +0,0 @@ - - -A tutorial for NetAddr::IP - - -$Id: tutorial.htm,v 1.3 2003/10/09 00:12:22 lem Exp $ - -

This tutorial is kept online at this -location. Please see the online version if you can, as it is probably -more up to date...

- -

What this tutorial is all about...

- -

Some of us, monks who love Perl, also have to deal with the -complexities of IP addresses, subnets and such. A while ago, I wrote NetAddr::IP -to help me work out tedious tasks such as finding out which addresses -fell within a certain subnet or allocating IP space to network -devices. - -

This tutorial discusses many common tasks along with solutions -using NetAddr::IP. -Since Perl lacks a native type to represent either an IP address or an -IP subnet, I feel this module has been quite helpful for fellow monks -who like me, need to work in this area. - -

Note however that neither the module itself nor this tutorials are -intended as replacements to your knowledge about how to work with -chunks of IP space. The module was written as a tool to help with the -boring tasks (after all, we're plentiful with good laziness, aren't -we?) and this tutorial, was written to help answer the most common -questions I get. Both the module and this tutorial expect you to be -fluent in basic networking and somewhat fluent in Perl. You should not -writing Perl code to manage your subnetting otherwise. - -


- -

Specifying an IP Address or a subnet

- -

A NetAddr::IP -object represents a subnet. This involves storing an IP address -within the subnet along with the subnet's netmask. Of course, -using a host netmask (/32 or in decimal notation, 255.255.255.255) -allows for the specification of a single IP address. - -

You can create a NetAddr::IP -object with an incantation like the following: - -

-use NetAddr::IP;
-my $ip = new NetAddr::IP '127.0.0.1';
-
- -

which will create an object representing the 'address' -127.0.0.1 or the 'subnet' 127.0.0.1/32. - -

Creating a subnet is equally easy. Just specify the address and -netmask in almost any common notation, as in the following examples: - -

-use NetAddr::IP;
-my $loopback = new NetAddr::IP '127.0.0.1', '255.0.0.0';
-my $rfc1918 = new NetAddr::IP '10.0.0.0/8';
-my $another = new NetAddr::IP '1.2.0.0/255.255.0.0';
-my $loopback2 = new NetAddr::IP 'loopback';
-
- -

The following is a list of the acceptable arguments to -->new() and their meanings: - -

    -
  • ->new('broadcast') - -

    Equivalent to the address 255.255.255.255/32 which is often -used to denote a broadcast address. - -

  • ->new('default') - -

    Synonim to the address 0.0.0.0/0 which is universally used -to represent a default route. This subnet is guaranteed to -->contains() any other subnet. More on that later. - -

    For the benefit of many Cisco users out there, any is -considered a synonim of default. - -

  • ->new('loopback') - -

    The same as the address 127.0.0.1/8 which is the standard -loopback address. - -

  • ->new('10.10.10.10') or ->new('foo.bar.com') - -

    This represents a single host. When no netmask is supplied, a netmask -of /32 is assumed. When supplying a name, the host name will -be looked up using gethostbyname(), -which will in turn use whatever name resolution is configured in your -system to obtain the IP address associated with the supplied name. - -

  • ->new('10.10.1') - -

    An ancient notation that allows the middle zeroes to be -skipped. The example is equivalent to ->new('10.10.0.1'). - -

  • ->new('10.10.1.') - -

    Note the trailing dot. This format allows the omission of the -netmask for classful subnets. The example is equivalent to -->new('10.10.1.0/24'). - -

  • ->new('10.10.10.0 - 10.10.10.255') - -

    This is also known as range notation. Both ends of an -address range are specified. Note that this notation is only supported -if the specified subnet can be represented in valid CIDR notation. - -

  • ->new('10.10.10.0-255') - -

    This notation is a shorthand for the range notation -discussed above. It provides for the specification of an address range -where both of its ends share the first octets. This notation is only -supported when the specified range of hosts defined a proper CIDR -subnet. - -

  • ->new(1024) - -

    Whenever the address is specified as a numeric value greater than -255, it is assumed to contain an IP address encoded as an unsigned int. - - -

  • ->new() with two arguments - -

    Whenever two arguments are specified to ->new(), the first -is always going to be interpreted as the IP address and the second -will always be the netmask, in any of the formats discussed so far. - -

    Netmasks can be specified in dotted-quad notation, as the number of -one-bits or as the equivalent unsigned int. Also, special keywords -such as broadcast, default or host can be -used as netmasks. - -

- -

The semantics and notations depicted above, are supposed to comply -strictly with the DWIM approach which is so popular with Perl. The -general idea is that you should be able to stick almost anything -resembling an IP address or a subnet specification into the -->new() method to get an equivalent object. However, if you -can find another notation that is not included in the above list, -please by all means let me know. - -


- -

Simple operations with subnets

- -

There is a number of operations that have been simplified along the -different versions of the module. The current version, as of this -writing, provides support for the following operations: - -

    -
  • Scalarization - -

    A NetAddr::IP -object will become its CIDR representation whenever a scalar -representation for it is required. For instance, you can very well do -something like print "My object contains $ip\n";. - -

  • Numerical comparison - -

    Two objects can be compared using any of the numerical comparison -operators. Only the address part of the subnet is compared. The -netmask is ignored in the comparison. - -

  • Increments and decrements - -

    Adding or substracting a scalar from an object will change the -address in the subnet, but always keeping it within the subnet. This -is very useful to write loops, like the following: - -

    -use NetAddr::IP;
    -my $ip = new NetAddr::IP('10.0.0.0/30');
    -while ($ip < $ip->broadcast) {
    -  print "ip = $ip\n";
    -  $ip ++;
    -}
    -
    - -

    which will produce the following output: - -

    -ip = 10.0.0.0/30
    -ip = 10.0.0.1/30
    -ip = 10.0.0.2/30
    -
    - -
  • List expansion - -

    When required, a NetAddr::IP -will expand automatically to a list containing all the addresses -within a subnet, conveniently leaving the subnet and the broadcast -addresses out. The following code shows this: - -

    -use NetAddr::IP;
    -my $ip = new NetAddr::IP('10.0.0.0/30');
    -print join(' ', @$ip), "\n";
    -
    - -

    And the output would be - -

    -10.0.0.1/32 10.0.0.2/32
    -
    - -
- -
- -

Common (and not so common) tasks

- -

Below I will try to provide an example for each major feature of NetAddr::IP, -along with a description of what is being done, where appropiate. - -

Optimising the address space

- -

This is one of the reason for writing NetAddr::IP -in the first place. Let's say you have a few chunks of IP space and -you want to find the optimum CIDR representation for -them. By optimum, I mean the least amount of CIDR subnets that exactly -represent the given IP address space. The code below is an example of -this: - -

-use NetAddr::IP;
-
-push @addresses, NetAddr::IP->new($_) for <DATA>;
-print join(", ", NetAddr::IP::compact(@addresses)), "\n";
-__DATA__
-10.0.0.0/18
-10.0.64.0/18
-10.0.192.0/18
-10.0.160.0/19
-
- -

Which will, of course, output 10.0.0.0/17, 10.0.160.0/19, -10.0.192.0/18. Let's see how this is done... - -

First, the line starting with push ... creates a list of -objects representing all the subnets read in via the -<DATA> filehandle. There should be no surprises here. - -

Then, we call NetAddr::IP::compact with the list of -subnets build earlier. This function accepts a list of subnets as its -input (actually, an array of objects). It processes them internally -and outputs another array of objects, as summarized as possible. - -

Using compact() as in the example is fine when you're -dealing with a few subnets or are writing a throw-away one-liner. If -you think your script will be handling more than a few tens of -subnets, you might find compactref() useful. It works exactly -as shown before, but takes (and returns) references to arrays. I've -seen 10x speed improvements when working with huge lists of subnets. - -

Something that gets asked quite frequently is "why not -@EXPORT or at least, @EXPORT_OK methods such as -compact()?". The answer is that I believe -compact() to be a very generic name, for an operation that -is not always used. I think fully qualifying it, adds to the mnemonics -of what's being done while not polluting the namespace innecesarilly. - -


- -

Assigning address space

- -

This problem can be tought as the complement to the prior -one. Let's say a couple of network segments need to be connected to -your network. You can carve slices out of your address space easily, -such as in the following code: - -

-use NetAddr::IP;
-
-print "My address space contains the following /24s:\n", 
-	join("\n", NetAddr::IP->new('10.0.0.0/22')->split(24)), "\n";
-
- -

Which will divide your precious address space (the one specified in -the NetAddr::IP->new()) in subnets with a netmask of 24 -bytes. This magic is accomplished by the ->split() method, -which takes the number of bits in the mask as its only parameter. It -returns a list of subnets contained in the original object. - -

Again, in situations where the split might return a large number of -subnets, you might prefer the use of ->splitref(), which -returns a reference to an array instead. - -

Returning to our example, you might assign a /24 to each new -subnet. Ok, perhaps assigning a /24 is not that good an example, as -this falls on an octet boundary but trust me, when you have to split a -/16 in /20s, to be allocated in chunks of /22s in a network spanning -the whole country, it's nice to know your subnetting is well done. - -


- -

Cisco's wildcard notation (and other dialects)

- -

Those of you who have had to write an ACL in a Cisco router, know -about the joys of this peculiar format in which the netmask works the -opposite of what custom says. - -

An easy way to convert between traditional notation and Cisco's -wildcard notation, is to use the eloquently named -->wildcard() method, as this example shows: - -

-use NetAddr::IP;
-
-print join(' ', NetAddr::IP->new('10.0.0.0/25')->wildcard());
-
- -

As you might have guessed, ->wildcard() returns an array -whose first element is the address and its second element is the -netmask, in wildcard notation. If scalar context is forced using -scalar, only the netmask will be returned, as this is most -likely what you want. - -

In case you wonder, the example outputs 10.0.0.0 -0.0.0.127. - -

Just for the record, below is a number of outputs from different -methods for the above example: - -

    -
  • Range (The ->range() method) - -

    Outputs 10.0.0.0 - 10.0.0.127. Note that this range goes -from the network address to the broadcast -address. - -

  • CIDR notation (The ->cidr() method) - -

    As expected, it outputs 10.0.0.0/25. - -

  • Prefix notation (The ->prefix() method) - -

    Similar to ->range(), this method produces -10.0.0.1-127. However, note that the first address is -not the network address but the first host address. - -

  • n-Prefix notation (The ->nprefix() method) - -

    Produces 10.0.0.1-126. Note how the broadcast address is -not within the range. - -

  • Numeric (The ->numeric() method) - -

    In scalar context, produces and unsigned int that represents the -address in the subnet. In array context, both the address and netmask -are returned. For the example, the array output is (167772160, -4294967168). This is very useful when serializing the object for -storage. You can pass those two numbers back to ->new() and -get your object back. - -

  • Just the IP address (The ->addr() method) - -
  • Just the netmask as a dotted quad (The ->mask() method) - -
  • The length in bits of the netmask (The ->masklen() method) - -
-
- -

Matching against your address space

- -

Let's say you have a log full of IP addresses and you want to know -which ones belong to your IP space. A simple way to achieve this is -shown below: - -

-use NetAddr::IP;
-
-my $space = new NetAddr::IP->new('10.128.0.0/17');
-
-for my $ip (map { new NetAddr::IP->new($_) } <DATA>)
-{
-    print $ip, "\n"
-        if $space->contains($ip);
-}
-
-__DATA__
-172.16.1.1
-172.16.1.5
-172.16.1.11
-172.16.1.10
-172.16.1.9
-172.16.1.3
-172.16.1.2
-172.16.1.7
-172.16.1.4
-172.16.1.1
-10.128.0.1
-10.128.0.12
-10.128.0.13
-10.128.0.41
-10.128.0.17
-10.128.0.19
-
- -

This code will output only the addresses belonging to your address -space, represented by $space. The only interesting thing here -is the use of the ->contains() method. As used in our -example, it returns a true value if $ip is completely contained within -the $space subnet. - -

Alternatively, the condition could have been written as -$ip->within($space). Remember that TIMTOWTDI. - -


- -

Walking through the network without leaving -the office

- -

Some of the nicest features of NetAddr::IP -can be better put to use when you want to perform actions with your -address space. Some of them are discussed below. - -

One of the most efficient ways to walk your address space is -building a for loop, as this example shows: - -

-use NetAddr::IP;
-
-push @space, new NetAddr::IP->new($_) for <DATA>;
-
-for my $netblock (NetAddr::IP::compact @space) 
-{
-    for (my $ip = $netblock->first;
-         $ip <= $netblock->last;
-         $ip++)
-    {
-         # Do something with $ip
-    }
-}
-__DATA__
-10.0.0.0/16
-172.16.0.0/24
-
- -

The nicest thing about this way of walking your IP space, is that -even if you are lucky enough to have lots of it, you won't eat all -your memory by generating a huge list of objects. In this example, -only one object is created in every iteration of the loop. - -

Everything up to the inner loop should be pretty clear by now, so -we just ignore it. Since a couple of new friends were introduced in -the inner loop of our example, an explanation is in order. - -

This C-like for loop uses the ->first() function to -find the first subnet address. The first subnet address is defined as -that having all of its host bits but the rightmost set to -zero and the rightmost, set to one. - -

We then use the numerical comparison discussed earlier to see if -the value of $ip is less than or equal to whatever -->last() returns. ->last() returns an address with -all of its host bits set to one but the rightmost. If this condition -holds, we execute the loop and post-increment $ip to get the -next IP address in the subnet. - -

I started the discussion on this topic with the approach that -insures less wasted resources. However, in the purest Perl tradition, -this is not the only way to do it. There's another way, reserved for -the true lazy (or those with memory to burn, but we all know you never -have enough memory, right?). - -

This other way is invoked with the ->hostenum() or the -->hostenumref() methods. They return either an array or a -reference to an array respectively, containing one object for each -host address in the subnet. Note that only valid host -addresses will be returned (as objects) since the network and -broadcast addresses are seldom useful. - -

With no further preamble, I introduce an example that kids -shouldn't attempt at home, or at least in production code. (If you -find this warning superfluous, try adding 64.0.0.0/8 to the -__DATA__ section and see if your machine chews through it -all). - -

-use NetAddr::IP;
-
-push @space, new NetAddr::IP->new($_) for <DATA>;
-
-for my $ip (map { $_->hostenum } NetAddr::IP::compact @space) 
-{
-    # Do something with $ip
-}
-__DATA__
-10.0.0.0/16
-172.16.0.0/24
-
- -

If you really have enough memory, you'll see that each host address -in your IP space is generated into a huge array. This is much more -costly (read, slow) than the approach presented earlier, but provides -for more compact one-liners or quickies. - -


- -

Finding out how big is your network

- -

Have you wondered just how many IP addresses can you use in your -current subnet plan? If the answer to this (or to a similar question) -is yes, then read on. - -

There is a method called ->num() that will tell you -exactly how many addresses can you use in a given subnet. For the -quick observers out there, you can also use something like scalar -$subnet->hostenum but this is a really expensive way of doing it. - -

A more conservative (in resources) approach is depicted below: - -

-use NetAddr::IP;
-
-my $hosts = 0;
-push @space, new NetAddr::IP->new($_) for <DATA>;
-$hosts += $_->num for @space;
-print "You have $hosts\n";
-__DATA__
-10.0.0.0/16
-172.16.0.0/24
-
- -

Sometimes, you will be surprised at how many addresses are lost by -subnetting, but we'll leave that discussion to other tutorials. - -


- \ No newline at end of file