12.06.2008

ipcount: a handy tool for IP address allocation in OS X

If you happen to be in a position where allocating IP address blocks is a way of life, breaking up, say, a /21 into a bunch of /28's is not a difficult task--it just takes someone like me longer than I'd like.

How about an example? I'll use a nice reserved range that won't pick on anyone in particular: Say I have an assigned block, 37.24.48.0/21, and I need to subnet it into bite-size /28 blocks. I know that four bits in the fourth octet will be used for the subnet ( 28 - 24 = 4 ). Since four bits can be flipped in various ways to give me 16 possibilities, I know that, for every /24 in my /21, there will be 16 /28's ( 16 x 16 = 256 ).

In order to find all of the /28's contained within my /21, I just need to keep adding 16 to my fourth octet until I reach the end of that /24, then increment my third octet by one. After that, I just rinse and repeat, eventually incrementing my third octet seven times; this, because there are eight /24's in a /21. Once finished, my list should look like this:
37.24.48.0
/28
37.24.48.16
/28
37.24.48.32
/28
...
37.24.48.240
/28
37.24.49.0
/28
37.24.49.16
/28
37.24.49.32
/28...
This would continue all the way up to 37.24.55.240/28.

As it stands, I have two problems with this process: First, if this reasoning sounds convoluted, that's probably because it is. Sure, the logic is sound and the result is accurate, but my inability to quickly perform arithmetic operations--apparently a result of my ADD compounded by general scatter-brainedness--makes this much too slow for my liking.

In my defense, part of the reason I create so many steps is for verification. By creating lots of little, evidentiary droppings, I'm able to check my work throughout, making me much more confident in the result. Unnecessary? Perhaps, though I would be more inclined to call it "prudent," but I digress.

My second problem with this method is that, whenever generating documentation--and make no mistake: this is documentation, or if it's not, someone else who actually wants to remember how these blocks are going to be assigned next week should take care of it--you should seek to store the critical information in the most desirable medium, as soon as humanly possible. For example, if I plan on keeping the information in a spreadsheet, I should probably enter it directly into the intended document, lest I lose my sticky note or simply forget to transfer my hard work to its proper destination. If some data I'm compiling is meant for a database, I would want to get it in there as soon as possible, so as to quickly make my work as visible and accessible as I can, or as Gerald A. Donahue, author of the excellent O'Reilly book Network Warrior, might say: if you never document your brilliance, how will anyone ever know? For anyone who has had to spend entirely too much time hunting for some much-needed information that a coworker diligently scribbled onto one of a hundred identical pieces of paper, then shoved in a desk before calling in sick the next day, the benefits of this strategy should be immediately obvious.

"So what's the point?" you bellow. I know, I know... I'll wrap it up. In short, what I'm trying to say is this: yeah, you can do it all in your head and write it on a piece of paper, and probably a lot faster than your friends and neighbors, but it requires a lot less time and effort to use a tool. Enter ipcount.

ipcount is actually a PERL script that is included in OS X. It's usage output is fairly straight-forward:
jstorm[0]@absinthe:~$ ipcount

Usage:
ipcount [-r] [-d prefix] address

-r: Print Reverse Ranges
-d prefix: Cut down the original prefix in several prefixes

The address range can be one of:

ipcount IP + size
ipcount IP1 - IP2
ipcount IP/len

Now, before we start playing around with it, we'll have to fix it. "Fix it?" Yes, fix it. The code has a couple of bugs that must first be purged in order for this thing to work. Here is an example of what it did before I unfluked it:
jstorm[0]@absinthe:~$ ipcount -d 24 10.0.0.0/22
Invalid chars in IP 10.0.0.0+255 at /usr/bin/ipcount line 83.

See? Not good.
As it turns out, line 83 of ipcount contains the following:
$new_ip->set($current->last_ip.'+'.$size) or die (Error());
I'm no PERL wizard, but that certainly looks alright to me--assuming, of course, that sticking a plus character and an arbitrary scalar on the end of an IP is syntactically valid. The only possible "invalid char" that stood out to me would have been the '+' being concatenated onto the end of the IP, but for the time being, I decided to move on.
Next, I looked at the definition of $current:
my $current = new Net::IP($ip->ip);
It looks like we have a PERL extension, here. Let's take a look at the definition of $ip:

my $ip = new Net::IP($arg) or die ("Cannot create IP object $arg: ".Error());
And there's our input. It turns out that this script uses an extension called Net::IP to do all of the fancy, binary footwork. According to CPAN.org, it is syntactically correct to throw a '+' and a scalar on the end of an IP. However, it is definitely not permissible to do this without spaces around the '+'. Let's spruce it up a bit:


$new_ip->set($current->last_ip.' + '.$size) or die (Error());
Shall we try it again?

jstorm[0]@absinthe:~$ ipcount -d 24 10.0.0.0/22
10.0.0/24
Invalid chars in IP 10.0.0.255+ 1 at /usr/bin/ipcount line 91.

Grr. Another one. Line 91 contains the following:

$current->set($new_ip->last_ip .'+ 1') or die (Error());
Same exact problem. After adding the space:

$current->set($new_ip->last_ip .' + 1') or die (Error());
One more time...
jstorm[0]@absinthe:~$ ipcount -d 24 10.0.0.0/22

Found 4 /24s in 10.0.0/22

Tada! Yaaay!
Now that you have a working copy, let's see what it can do. The first usage syntax is this:
jstorm[0]@absinthe:~$ ipcount 10.0.0.0 + 255
Cannot create IP object 10.0.0.0+255: Invalid chars in IP 10.0.0.0+255 at /usr/bin/ipcount line 58.

Well, shit. Just when you thought it was over... Here comes line 58:
my $ip = new Net::IP($arg) or die ("Cannot create IP object $arg: ".Error());
Gaah! It's our definition! Let's see what it's doing to our input:
my $arg = join '',@ARGV;
$arg =~ s/\s+//g;

Not much. While it is trying to scrub any existing whitespace, it has little to do with the whitespace around our '+'; the '+' is its own argument.
Well, let's make some whitespace; my addition is the line in bold:
my $arg = join '',@ARGV;
$arg =~ s/\s+//g;
$arg =~ s/\+/ \+ /g;

Good enough for government work, I'd say. Will it work?

jstorm[0]@absinthe:~$ ipcount 10.0.0.0 + 255
10.0.0/24 10.0.0.0 - 10.0.0.255 [256]

Weee! Let's get on with it. If I had a network number and a certain number of hosts which I needed to provide with IP's, this syntax is the ticket. For example, what if my next available subnetwork number is 37.24.48.32 and my next subnet needs to support 60 hosts? Well, we know that 60 can't be represented by a series of consecutive bits, but 63 can. Taking the subnetwork number that we already have and adding 63 more IP's to it will give us 64 IP's. Subtracting the broadcast and network addresses then gives us 62 IP's--right? Let's have a look:

jstorm[0]@absinthe:~$ ipcount 37.24.48.32 + 63
37.24.48.32/27 37.24.48.32 - 37.24.48.63 [32]
37.24.48.64/27 37.24.48.64 - 37.24.48.95 [32]

37.24.48.32/27,/27 37.24.48.32 - 37.24.48.95 [64]

Oh, ho-ho! Not quite that simple, is it? Arbitrary bifurcation by borking binary boundaries does not a subnet make. What's worse, even if we were OK with having two subnets instead of one, this will cut us down to exactly 60 usable IP's rather than 62. If you were planning on putting a router on that network with exactly 60 other devices, you certainly won't be able to hack it with only a couple of /27's and the addition of a secondary router IP. Let's try it again--this time, with the correct subnetwork number:

jstorm[0]@absinthe:~$ ipcount 37.24.48.64 + 63
37.24.48.64/26 37.24.48.64 - 37.24.48.127 [64]

Aaaah... Much better. It's nothing miraculous, but it certainly speeds things up a bit, doesn't it? Let's take a look at the next usage syntax. Suppose I've freed up some space in my big /21 block, from 37.24.48.32 up to 37.24.51.63, and would like to see what subnets I can make with my new gap. Here's what we get:

jstorm[0]@absinthe:~$ ipcount 37.24.48.32 - 37.24.51.63
37.24.48.32/27 37.24.48.32 - 37.24.48.63 [32]
37.24.48.64/26 37.24.48.64 - 37.24.48.127 [64]
37.24.48.128/25 37.24.48.128 - 37.24.48.255 [128]
37.24.49/24 37.24.49.0 - 37.24.49.255 [256]
37.24.50/24 37.24.50.0 - 37.24.50.255 [256]
37.24.51.0/26 37.24.51.0 - 37.24.51.63 [64]

37.24.48.32/27,/26,/25,/24,/24,/26 37.24.48.32 - 37.24.51.63 [800]

Wow! Look at that! It breaks it out into the subnets with the shortest prefixes possible, allowing us to instantly assess our available IP space, then gives us a grand total, just for kicks. The next syntax isn't quite as useful, but it still helps. To figure out what range a new /22 will use up, just enter the following:

jstorm[0]@absinthe:~$ ipcount 37.24.48.0/22
37.24.48.0/22 37.24.48/22 37.24.48.0 - 37.24.51.255 [1024]

The only problem I have with this function is that it doesn't do anything useful when you give it the wrong input:

jstorm[0]@absinthe:~$ ipcount 37.24.48.0/19
Cannot create IP object 37.24.48.0/19: Invalid prefix 00100101000110000011000000000000/19 at /usr/bin/ipcount line 60.

I might adjust this later so that it instead returns the shortest, valid prefix, but for now, just know that the /19 fails here because the third octet's 16's bit is still occupied. Changing the third octet to 32 will allow it to work, but, for some reason, ARIN becomes really obtuse when you ask them to change your /20 to a /19. ;-)
OK. One more syntax to look at. I find this one to be slightly more useful than the last. Want to know just how many /28's are in 37.24.48.0/21? Give this a shot:

jstorm[0]@absinthe:~$ ipcount -d 28 37.24.48.0/21
37.24.48.0/28
37.24.48.16/28
37.24.48.32/28
37.24.48.48/28
37.24.48.64/28
...
37.24.55.176/28
37.24.55.192/28
37.24.55.208/28
37.24.55.224/28
37.24.55.240/28

Found 128 /28s in 37.24.48/21

Wow! Just look at them all! You see? Manually generating all of those really would have been a pain. Now, rather than type it all out--or even worse, write it all out, then type it all out--we can just copy and paste, or redirect stdout to a file, or use another PERL script to parse it out and insert it into a database. Regardless, it's much faster and easier than the alternative.

Well, that's all I've got for now. Enjoy the surprise, built-in IP calculator on OS X. I know I will.

No comments:

Post a Comment