- <HTML>
- <HEAD>
- <TITLE>A tutorial for NetAddr::IP</TITLE>
- </HEAD><BODY>
-
- <tt>$Id: tutorial.htm,v 1.3 2003/10/09 00:12:22 lem Exp $</tt>
-
- <P><FONT SIZE="small">This tutorial is kept online at <A
- HREF="http://mipagina.cantv.net/lem/perl/iptut.htm">this</A>
- location. Please see the online version if you can, as it is probably
- more up to date...</FONT></P>
-
- <a name="about"><h2>What this tutorial is all about...</h2></a>
-
- <p>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 <a
- href="http://search.cpan.org/search?mode=module&query=NetAddr%3A%3AIP">NetAddr::IP</a>
- 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.
-
- <p>This tutorial discusses many common tasks along with solutions
- using <a
- href="http://search.cpan.org/search?mode=module&query=NetAddr%3A%3AIP">NetAddr::IP</a>.
- 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.
-
- <p>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.
-
- <hr>
-
- <a name="new"><h2>Specifying an IP Address or a subnet</h2></a>
-
- <p>A <a
- href="http://search.cpan.org/search?mode=module&query=NetAddr%3A%3AIP">NetAddr::IP</a>
- object represents a subnet. This involves storing an IP address
- <b>within</b> 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.
-
- <p>You can create a <a
- href="http://search.cpan.org/search?mode=module&query=NetAddr%3A%3AIP">NetAddr::IP</a>
- object with an incantation like the following:
-
- <PRE>
- use NetAddr::IP;
- my $ip = new NetAddr::IP '127.0.0.1';
- </PRE>
-
- <p>which will create an object representing the 'address'
- <tt>127.0.0.1</tt> or the 'subnet' <tt>127.0.0.1/32</tt>.
-
- <p>Creating a subnet is equally easy. Just specify the address and
- netmask in almost any common notation, as in the following examples:
-
- <PRE>
- 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';
- </PRE>
-
- <p>The following is a list of the acceptable arguments to
- <tt>->new()</tt> and their meanings:
-
- <ul>
- <li><tt>->new('broadcast')</tt>
-
- <p>Equivalent to the address <tt>255.255.255.255/32</tt> which is often
- used to denote a broadcast address.
-
- <li><tt>->new('default')</tt>
-
- <p>Synonim to the address <tt>0.0.0.0/0</tt> which is universally used
- to represent a default route. This subnet is guaranteed to
- <tt>->contains()</tt> any other subnet. More on that later.
-
- <p>For the benefit of many Cisco users out there, <tt>any</tt> is
- considered a synonim of <tt>default</tt>.
-
- <li><tt>->new('loopback')</tt>
-
- <p>The same as the address <tt>127.0.0.1/8</tt> which is the standard
- <tt>loopback</tt> address.
-
- <li><tt>->new('10.10.10.10')</tt> or <tt>->new('foo.bar.com')</tt>
-
- <p>This represents a single host. When no netmask is supplied, a netmask
- of <tt>/32</tt> is assumed. When supplying a name, the host name will
- be looked up using <a
- href="http://www.perldoc.com/perl5.6.1/pod/perlfunc.html">gethostbyname()</a>,
- which will in turn use whatever name resolution is configured in your
- system to obtain the IP address associated with the supplied name.
-
- <li><tt>->new('10.10.1')</tt>
-
- <p>An ancient notation that allows the <em>middle zeroes</em> to be
- skipped. The example is equivalent to <tt>->new('10.10.0.1')</tt>.
-
- <li><tt>->new('10.10.1.')</tt>
-
- <p>Note the trailing dot. This format allows the omission of the
- netmask for classful subnets. The example is equivalent to
- <tt>->new('10.10.1.0/24')</tt>.
-
- <li><tt>->new('10.10.10.0 - 10.10.10.255')</tt>
-
- <p>This is also known as <em>range notation</em>. 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.
-
- <li><tt>->new('10.10.10.0-255')</tt>
-
- <p>This notation is a shorthand for the <em>range notation</em>
- 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.
-
- <li><tt>->new(1024)</tt>
-
- <p>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.
-
-
- <li><tt>->new()</tt> with two arguments
-
- <p>Whenever two arguments are specified to <tt>->new()</tt>, 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.
-
- <p>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 <tt>broadcast</tt>, <tt>default</tt> or <tt>host</tt> can be
- used as netmasks.
-
- </ul>
-
- <p>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
- <tt>->new()</tt> 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.
-
- <hr>
-
- <a name="overloading"><h2>Simple operations with subnets</h2></a>
-
- <P>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:
-
- <ul>
- <li>Scalarization
-
- <p>A <a
- href="http://search.cpan.org/search?mode=module&query=NetAddr%3A%3AIP">NetAddr::IP</a>
- object will become its CIDR representation whenever a scalar
- representation for it is required. For instance, you can very well do
- something like <tt>print "My object contains $ip\n";</tt>.
-
- <li>Numerical comparison
-
- <p>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.
-
- <li>Increments and decrements
-
- <p>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:
-
- <PRE>
- use NetAddr::IP;
- my $ip = new NetAddr::IP('10.0.0.0/30');
- while ($ip < $ip->broadcast) {
- print "ip = $ip\n";
- $ip ++;
- }
- </PRE>
-
- <p>which will produce the following output:
-
- <PRE>
- ip = 10.0.0.0/30
- ip = 10.0.0.1/30
- ip = 10.0.0.2/30
- </PRE>
-
- <li>List expansion
-
- <p>When required, a <a
- href="http://search.cpan.org/search?mode=module&query=NetAddr%3A%3AIP">NetAddr::IP</a>
- 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:
-
- <PRE>
- use NetAddr::IP;
- my $ip = new NetAddr::IP('10.0.0.0/30');
- print join(' ', @$ip), "\n";
- </PRE>
-
- <p>And the output would be
-
- <PRE>
- 10.0.0.1/32 10.0.0.2/32
- </PRE>
-
- </ul>
-
- <hr>
-
- <a name="common"><h2>Common (and not so common) tasks</h2></a>
-
- <p>Below I will try to provide an example for each major feature of <a
- href="http://search.cpan.org/search?mode=module&query=NetAddr%3A%3AIP">NetAddr::IP</a>,
- along with a description of what is being done, where appropiate.
-
- <a name="compact"><h3>Optimising the address space</h3></a>
-
- <p>This is one of the reason for writing <a
- href="http://search.cpan.org/search?mode=module&query=NetAddr%3A%3AIP">NetAddr::IP</a>
- in the first place. Let's say you have a few chunks of IP space and
- you want to find the <em>optimum</em> 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:
-
- <PRE>
- 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
- </PRE>
-
- <p>Which will, of course, output <tt>10.0.0.0/17, 10.0.160.0/19,
- 10.0.192.0/18</tt>. Let's see how this is done...
-
- <p>First, the line starting with <tt>push ...</tt> creates a list of
- objects representing all the subnets read in via the
- <tt><DATA></tt> filehandle. There should be no surprises here.
-
- <p>Then, we call <tt>NetAddr::IP::compact</tt> 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.
-
- <p>Using <tt>compact()</tt> 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 <tt>compactref()</tt> 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.
-
- <p>Something that gets asked quite frequently is <em>"why not
- <tt>@EXPORT</tt> or at least, <tt>@EXPORT_OK</tt> methods such as
- <tt>compact()</tt>?"</em>. The answer is that I believe
- <tt>compact()</tt> 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.
-
- <hr>
-
- <a name="split"><h3>Assigning address space</h3></a>
-
- <p>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:
-
- <PRE>
- 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";
- </PRE>
-
- <p>Which will divide your precious address space (the one specified in
- the <tt>NetAddr::IP->new()</tt>) in subnets with a netmask of 24
- bytes. This magic is accomplished by the <tt>->split()</tt> 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.
-
- <p>Again, in situations where the split might return a large number of
- subnets, you might prefer the use of <tt>->splitref()</tt>, which
- returns a reference to an array instead.
-
- <p>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.
-
- <hr>
-
- <a name="wildcard"><h3>Cisco's wildcard notation (and other dialects)</h3></a>
-
- <p>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.
-
- <p>An easy way to convert between traditional notation and Cisco's
- wildcard notation, is to use the eloquently named
- <tt>->wildcard()</tt> method, as this example shows:
-
- <PRE>
- use NetAddr::IP;
-
- print join(' ', NetAddr::IP->new('10.0.0.0/25')->wildcard());
- </PRE>
-
- <p>As you might have guessed, <tt>->wildcard()</tt> 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
- <tt>scalar</tt>, only the netmask will be returned, as this is most
- likely what you want.
-
- <p>In case you wonder, the example outputs <tt>10.0.0.0
- 0.0.0.127</tt>.
-
- <p>Just for the record, below is a number of outputs from different
- methods for the above example:
-
- <ul>
- <li>Range (The <tt>->range()</tt> method)
-
- <p>Outputs <tt>10.0.0.0 - 10.0.0.127</tt>. Note that this range goes
- from the <em>network address</em> to the <em>broadcast
- address</em>.
-
- <li>CIDR notation (The <tt>->cidr()</tt> method)
-
- <p>As expected, it outputs <tt>10.0.0.0/25</tt>.
-
- <li>Prefix notation (The <tt>->prefix()</tt> method)
-
- <p>Similar to <tt>->range()</tt>, this method produces
- <tt>10.0.0.1-127</tt>. However, note that the first address is
- <b>not</b> the network address but the first host address.
-
- <li><em>n</em>-Prefix notation (The <tt>->nprefix()</tt> method)
-
- <p>Produces <tt>10.0.0.1-126</tt>. Note how the broadcast address is
- not within the range.
-
- <li>Numeric (The <tt>->numeric()</tt> method)
-
- <p>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 <tt>(167772160,
- 4294967168)</tt>. This is very useful when serializing the object for
- storage. You can pass those two numbers back to <tt>->new()</tt> and
- get your object back.
-
- <li>Just the IP address (The <tt>->addr()</tt> method)
-
- <li>Just the netmask as a dotted quad (The <tt>->mask()</tt> method)
-
- <li>The length in bits of the netmask (The <tt>->masklen()</tt> method)
-
- </ul>
- <hr>
-
- <a name="contains"><h3>Matching against your address space</h3></a>
-
- <p>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:
-
- <PRE>
- 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
- </PRE>
-
- <p>This code will output only the addresses belonging to your address
- space, represented by <tt>$space</tt>. The only interesting thing here
- is the use of the <tt>->contains()</tt> method. As used in our
- example, it returns a true value if <tt>$ip</tt> is completely contained within
- the <tt>$space</tt> subnet.
-
- <p>Alternatively, the condition could have been written as
- <tt>$ip->within($space)</tt>. Remember that TIMTOWTDI.
-
- <hr>
-
- <a name="iteration"><h3>Walking through the network without leaving
- the office</h3></a>
-
- <p>Some of the nicest features of <a
- href="http://search.cpan.org/search?mode=module&query=NetAddr%3A%3AIP">NetAddr::IP</a>
- can be better put to use when you want to perform actions with your
- address space. Some of them are discussed below.
-
- <p>One of the most efficient ways to walk your address space is
- building a <tt>for</tt> loop, as this example shows:
-
- <PRE>
- 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
- </PRE>
-
- <p>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.
-
- <p>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.
-
- <p>This C-like <tt>for</tt> loop uses the <tt>->first()</tt> function to
- find the first subnet address. The first subnet address is defined as
- that having all of its <em>host bits</em> but the rightmost set to
- zero and the rightmost, set to one.
-
- <p>We then use the numerical comparison discussed earlier to see if
- the value of <tt>$ip</tt> is less than or equal to whatever
- <tt>->last()</tt> returns. <tt>->last()</tt> 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 <tt>$ip</tt> to get the
- next IP address in the subnet.
-
- <p>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?).
-
- <p>This other way is invoked with the <tt>->hostenum()</tt> or the
- <tt>->hostenumref()</tt> methods. They return either an array or a
- reference to an array respectively, containing one object for each
- <em>host</em> address in the subnet. Note that only valid host
- addresses will be returned (as objects) since the network and
- broadcast addresses are seldom useful.
-
- <p>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 <tt>64.0.0.0/8</tt> to the
- <tt>__DATA__</tt> section and see if your machine chews through it
- all).
-
- <PRE>
- 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
- </PRE>
-
- <p>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.
-
- <hr>
-
- <a name="num"><h3>Finding out how big is your network</h3></a>
-
- <p>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 <em>yes</em>, then read on.
-
- <p>There is a method called <tt>->num()</tt> 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 <tt>scalar
- $subnet->hostenum</tt> but this is a really expensive way of doing it.
-
- <p>A more conservative (in resources) approach is depicted below:
-
- <PRE>
- 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
- </PRE>
-
- <p>Sometimes, you will be surprised at how many addresses are lost by
- subnetting, but we'll leave that discussion to other tutorials.
-
- <hr>
- </BODY></HTML>