diff --git a/IP.pm b/IP.pm index f7ef524..8cadd3b 100644 --- a/IP.pm +++ b/IP.pm @@ -8,7 +8,7 @@ use strict; use warnings; -our $VERSION = '3.10'; +our $VERSION = '3.11'; ############################################# # These are the overload methods, placed here @@ -269,10 +269,10 @@ my $bmask = ''; - if ($mask =~ m/^default$/i) { + if ($mask =~ m/^default|any$/i) { vec($bmask, 0, $bits) = 0x0; } - elsif ($mask =~ m/^broadcast$/i) { + elsif ($mask =~ m/^broadcast|host$/i) { vec($bmask, 0, $bits) = _ones $bits; } elsif ($mask =~ m/^loopback$/i) { @@ -330,7 +330,7 @@ my $addr = ''; - if ($ip =~ m!^default$!i) { + if ($ip =~ m!^default|any$!i) { vec($addr, 0, 32) = 0x0; } elsif ($ip =~ m!^broadcast$!i) { @@ -515,7 +515,7 @@ $ip = $1; $mask = $2; } - elsif ($ip =~ m!^(default|broadcast|loopback)$!) { + elsif ($ip =~ m!^(default|any|broadcast|loopback)$!) { $mask = $ip; } } @@ -802,7 +802,7 @@ # this loop will NOT work on IPv6... # $net, $to and $num might very well # be too large for most integer or - # floating pointrepresentations. + # floating point representations. for (my $i = vec($net, 0, $bits); $i <= vec($to, 0, $bits); @@ -872,6 +872,16 @@ return $self->network + 1; } +sub nth ($$) { + my $self = shift; + my $count = shift; + + return undef if ($count < 1 or $count > $self->num ()); + return $self->network + $count; +} + + + sub last ($) { my $self = shift; @@ -1058,6 +1068,12 @@ Returns a new object representing the last useable IP address within the subnet (ie, one less than the broadcast address). +=item C<-Enth($index)> + +Returns a new object representing the I-th useable IP address within +the subnet (ie, the I-th host address). If no address is available +(for example, when the network is too small for C<$index> hosts), +C is returned. =item C<-Enum()> @@ -1509,6 +1525,27 @@ =back +=item 3.11 + +=over + +=item * + +Thanks to David D. Zuhn for contributing the C<-Enth()> method. + +=item * + +tutorial.htm now included in the distribution. I hope this helps some +people to better understand what kind of stuff can be done with this +module. + +=item * + +C<'any'> can be used as a synonim of C<'default'>. Also, C<'host'> is +now a valid (/32) netmask. + +=back + =back =head1 AUTHOR @@ -1517,8 +1554,8 @@ =head1 WARRANTY -This software comes with the same warranty as perl itself (ie, none), so -by using it you accept any and all the liability. +This software comes with the same warranty as perl itself (ie, none), +so by using it you accept any and all the liability. =head1 LICENSE diff --git a/MANIFEST b/MANIFEST index 594627c..44e8ed2 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,7 +1,8 @@ IP.pm +README MANIFEST Makefile.PL -README +tutorial.htm t/00-load.t t/loops.t t/v4-new.t diff --git a/README b/README index fc5f4ef..8fefa86 100644 --- a/README +++ b/README @@ -45,7 +45,7 @@ ...which is quite useful for generating config files and the such. This works even for huge ranges of IP addresses. -This module is entirely written in perl, so you do not need access to +This module is entirely written in Perl, so you do not need access to a compiler to use it. It has been extensively tested in a variety of platforms. An extensive test suite is provided with the module to verify correct results. @@ -82,7 +82,9 @@ $ perldoc NetAddr::IP -to access the documentation. +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. Bug reports are welcome. Please do not forget to tell me what version/platform are you running this code on. Providing a small piece diff --git a/t/v4-first.t b/t/v4-first.t index accf0d6..6f0a7a5 100644 --- a/t/v4-first.t +++ b/t/v4-first.t @@ -1,13 +1,14 @@ use NetAddr::IP; my $nets = { - '10.0.0.16' => [ 24, '10.0.0.1', '10.0.0.254'], - '10.128.0.1' => [ 8, '10.0.0.1', '10.255.255.254'], - '10.0.0.5' => [ 30, '10.0.0.5', '10.0.0.6' ], + '10.0.0.16' => [ 24, '10.0.0.1', '10.0.0.254', '10.0.0.10'], + '10.0.0.5' => [ 30, '10.0.0.5', '10.0.0.6', 'undef' ], + '10.128.0.1' => [ 8, '10.0.0.1', '10.255.255.254', '10.0.0.10'], + '10.128.0.1' => [ 24, '10.128.0.1', '10.128.0.254', '10.128.0.10'], }; $| = 1; -print "1..", (2 * scalar keys %$nets), "\n"; +print "1..", (3 * scalar keys %$nets), "\n"; my $count = 1; @@ -19,6 +20,11 @@ print '', (($ip->last->addr ne $nets->{$a}->[2] ? 'not ' : ''), "ok ", $count++, "\n"); + + my $new = $ip->nth(10); + print '', (((defined $new ? $new->addr : 'undef') ne $nets->{$a}->[3] ? + 'not ' : ''), + "ok ", $count++, "\n"); } diff --git a/t/v4-new.t b/t/v4-new.t index 7e12587..c31066f 100644 --- a/t/v4-new.t +++ b/t/v4-new.t @@ -6,6 +6,7 @@ [ 0x01010101, '1.1.1.1' ], [ 1, '0.0.0.1' ], [ 'default', '0.0.0.0' ], + [ 'any', '0.0.0.0' ], ); my @m = ( @@ -19,6 +20,7 @@ [ 24, '255.255.255.0' ], [ 'default', '0.0.0.0' ], [ 32, '255.255.255.255' ], + [ 'host', '255.255.255.255' ], [ 0xffffff00, '255.255.255.0' ], [ '255.255.255.240', '255.255.255.240' ], [ '255.255.128.0', '255.255.128.0' ], diff --git a/tutorial.htm b/tutorial.htm new file mode 100644 index 0000000..7d3540e --- /dev/null +++ b/tutorial.htm @@ -0,0 +1,537 @@ + +

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. + +