#!/usr/bin/perl -w
#
#       ipcalc.pl.
#       Given an IPv4 address and the number of bits in the netmask or
#       the netmask itself, calculate assorted network information.
#       Sat Aug 29 16:43:01 EST 1998
#
#       Takes one positional parameter, the IPv4 address as dotted
#       quad, optionally followed by /mask, mask can be the number of
#       bits in the netmask or a netmask as a dotted quad.  If the
#       mask is omitted, it defaults to the normal class A, B or C
#       mask, depending on the first byte of the address.
#
#       In the bit representation of the address, the network bits are
#       displayed in reverse video, using tput strings.  Environment
#       variable TPUT defines the name of your tput binary, if not set
#       it defaults to "tput".  If TPUT is the empty string then no
#       highlighting is done.  For example, under bash,
#       "TPUT= ipcalc.pl ..." will suppress the use of tput.  If you
#       are paranoid about finding the correct version of tput, do
#       TPUT=/full/path/name/tput ipcalc.pl ...
#
#       All dotted quad addresses are printed twice.  Once for a fixed
#       table layout, again without spaces ready for cut and paste.
#
#       Examples.
#         ipcalc.pl 230.143.254.1          Defaults to class C, 24 bits
#         ipcalc.pl 230.143.254.1/25       25 bit netmask
#         ipcalc.pl 230.143.254.1/255.255.255.128  25 bit netmask as dotted quad
#
#       For demonstration purposes or if you really must have a GUI,
#       see ipcalc.tcl.
#
#       Copyright Keith Owens <kaos@ocs.com.au>.
#
#       This program is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#       the Free Software Foundation; either version 2 of the License, or
#       (at your option) any later version.
#
#       This program is distributed in the hope that it will be useful,
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#       GNU General Public License for more details.
#
#       You should have received a copy of the GNU General Public License
#       along with this program; if not, write to the Free Software
#       Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#

require 5;
use integer;
use strict;

my $tput = $ENV{'TPUT'};
$tput = 'tput' if (!defined($tput));

my $ipaddr = shift(@ARGV);	# user supplied, required
my $mask;			## user supplied, optional

my @ia;		# individual bytes of ipaddr
my @nm;		# individual bytes of netmask
my $netmask;	# netmask as 32 bit value
my $maskbits;	# number of bits in netmask
my $i;		# work
my @j;		# work

if (!defined($ipaddr)) {
	usage();
}

# gotta love those Perl regular expressions :)
if ($ipaddr =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)(?:\/([\d.]+))?$/) {
	(@ia) = ($1, $2, $3, $4);
	$mask = $5;
}
else {
	usage("Parameter $ipaddr not recognised");
}

for ($i = 0; $i < 4; ++$i) {
	if ($ia[$i] > 255) {
		usage("Invalid byte $ia[$i] in IPv4 address, must be 0-255");
	}
}

if ($ia[0] < 1 || $ia[0] > 223) {
	usage("First byte must be 1-223, I do not handle multicast or experimental");
}

if (!defined($mask)) {
	# default to class A, B or C
	if ($ia[0] < 128) {
		$mask = 8;
	}
	elsif ($ia[0] < 192) {
		$mask = 16;
	}
	else {
		$mask = 24;
	}
}

if ($mask =~ /^\d+$/) {
	# user supplied number of bits or we defaulted to number of bits in netmask
	$maskbits = $mask;
	$mask = undef();	# don't use user supplied value any more
	if ($maskbits <= 32) {
		# use integer implies signed shift, I want unsigned
		no integer;
		# only assumption is at least 32 bit arithmetic, should handle > 32 bits
		$i = 0xffffffff << (32 - $maskbits) & 0xffffffff;
		$i = 0 if ($maskbits == 0);	# shift left 32 may do nothing
		(@nm) = ($i >> 24, $i >> 16 & 0xff, $i >> 8 & 0xff, $i & 0xff);
		use integer;
	}
	else {
		usage("Netmask bits must be 0-32, $maskbits is invalid");
	}
}
elsif ($mask =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
	# user supplied dotted quad netmask
	(@nm) = ($1, $2, $3, $4);
	$mask = undef();	# don't use user supplied value any more
	for ($i = 0; $i < 4; ++$i) {
		if ($nm[$i] > 255) {
			usage("Invalid byte $nm[$i] in netmask, must be 0-255");
		}
	}
}
else {
	usage("Netmask must be 0-32 or dotted quad, $mask is invalid");
}

no integer;	# do unsigned shift
$ipaddr  = ($ia[0] << 24) + ($ia[1] << 16) + ($ia[2] << 8) + $ia[3];
$netmask = ($nm[0] << 24) + ($nm[1] << 16) + ($nm[2] << 8) + $nm[3];
for ($maskbits = 0, $i = $netmask; $i != 0; ++$maskbits) {
	if (($i & 0xc0000000) == 0x40000000) {
		usage("Netmask must have contiguous leftmost '1' bits,  " . sprintf("%08X", $netmask) . " is invalid");
	}
	$i <<= 1;
}
use integer;

# We now have $maskbits, $netmask and @nm bytes in sync, no matter
# which format the user supplied.  It's downhill from here.

printf("IP address         %3d   .  %3d   .  %3d   .  %3d    / %2d  %d.%d.%d.%d/%d\n",
	@ia, $maskbits, @ia, $maskbits);
printf("Netmask bits     %s %s %s %s\n",
	unpack("B8", chr($nm[0])),
	unpack("B8", chr($nm[1])),
	unpack("B8", chr($nm[2])),
	unpack("B8", chr($nm[3])));
printf("Netmask bytes      %3d   .  %3d   .  %3d   .  %3d          %d.%d.%d.%d\n",
	@nm, @nm);
my $addressbits = unpack("B8", chr($ia[0])) . " " .
                  unpack("B8", chr($ia[1])) . " " .
                  unpack("B8", chr($ia[2])) . " " .
                  unpack("B8", chr($ia[3]));
if ($tput ne "") {
	# try to use reverse video for the network bits
	my $rev = `$tput rev`;
	my $sgr0 = `$tput sgr0`;
	if (defined($rev) && $rev ne "" && defined($sgr0) && $sgr0 ne "") {
		$i = $maskbits + int(($maskbits-1)/8);		# space every 8 bits
		my $hostbits = substr($addressbits, $i);	# save host bits
		$addressbits = substr($addressbits, 0, $i);	# extract network bits
		$addressbits =~ s/([01]+)/$rev$1$sgr0/g;	# reverse video for network bits
		$addressbits .= $hostbits;			# put it back together
	}
}
printf("Address bits     %s\n",
	$addressbits);
if ($maskbits < 32) {
	no integer;	# do unsigned arithmetic
	$i = $ipaddr & $netmask;
	@j = ($i >> 24, $i >> 16 & 0xff, $i >> 8 & 0xff, $i & 0xff);
	use integer;
	printf("Network            %3d   .  %3d   .  %3d   .  %3d          %d.%d.%d.%d\n",
		@j, @j);
}
else {
	printf("Network          None, mask == 32\n");
}
if ($maskbits < 32) {
	no integer;	# do unsigned arithmetic
	$i |= 0xffffffff >> $maskbits;
	@j = ($i >> 24, $i >> 16 & 0xff, $i >> 8 & 0xff, $i & 0xff);
	use integer;
	printf("Broadcast          %3d   .  %3d   .  %3d   .  %3d          %d.%d.%d.%d\n",
		@j, @j);
}
else {
	printf("Broadcast        None, mask == 32\n");
}
if ($maskbits < 31) {
	no integer;	# do unsigned arithmetic
	$i = $ipaddr & $netmask + 1;
	@j = ($i >> 24, $i >> 16 & 0xff, $i >> 8 & 0xff, $i & 0xff);
	use integer;
	printf("First Host         %3d   .  %3d   .  %3d   .  %3d          %d.%d.%d.%d\n",
		@j, @j);
}
else {
	printf("First Host       None, mask >= 31\n");
}
if ($maskbits < 31) {
	no integer;	# do unsigned arithmetic
	$i |= 0xffffffff >> $maskbits;
	--$i;
	@j = ($i >> 24, $i >> 16 & 0xff, $i >> 8 & 0xff, $i & 0xff);
	use integer;
	printf("Last Host          %3d   .  %3d   .  %3d   .  %3d          %d.%d.%d.%d\n",
		@j, @j);
}
else {
	printf("Last Host        None, mask >= 31\n");
}
if ($maskbits != 0 && $maskbits <= 30) {
	no integer;	# do unsigned arithmetic
	$i = (1 << (32-$maskbits)) - 2;
	use integer;
	printf("Total Hosts      %d\n", $i);
}
elsif ($maskbits == 32) {
	printf("Total Hosts      1\n");
}
elsif ($maskbits == 0) {
	printf("Total Hosts      None, default route\n");
}
else {
	printf("Total Hosts      None, mask >= 31\n");
}
printf("PTR              %d.%d.%d.%d.in-addr.arpa\n", $ia[3], $ia[2], $ia[1], $ia[0]);
printf("IP Address (hex) %08X\n", $ipaddr);

sub usage {
	if (defined($_[0])) {
		print(STDERR "$0: error: $_[0]\n");
	}
	printf(STDERR "usage: $0 IPv4_address[/mask]\n");
	printf(STDERR "\tmask can be number of bits (0-32) or dotted quad\n");
	exit(1);
}
