#!/usr/bin/perl
# ====================================================================
# Copyright (c) 1999, MeepZor Consulting.
# All rights reserved.
#
# The use and distribution of this code or document is
# governed by version 1.0.1 of the MeepZor Consulting Public
# Licence (MCPL), which may be found on the Internet at
# .
#
#
# $Id: ipmath,v 1.11 2001/07/23 21:42:37 coar Exp $
#
#
# A script to perform IPV4 address math. Given a host address and a
# netmask, it derives the network number and broadcast address. This
# script may be invoked either from the command line or as a CGI Web script.
#
# If invoked from the shell, it has two required arguments:
#
# ipmath -a host-address -m network-mask
#
#
# History:
#
# 1.3 Ken Coar
# D'oh fixit for incorrect reporting of CIDR value. Explicate the
# math a bit more.
#
# 1.2 Ken Coar
# Minor tweak to add a usage display and support for '-h'.
#
# 1.1 Ken Coar
# Added code to deal with network numbers in addition to simple
# host numbers. (Inspired by Paul L. Lussier .)
#
use Getopt::Std;
#
# Set up some global cells.
#
$html = ($ENV{'GATEWAY_INTERFACE'} ne "");
%arg = ();
$CIDR = 1;
$zeroes = 0;
#
# Strip off any path information explicit in the script invocation.
#
$script = $0;
$script =~ s:^.*/::;
#
# How we collect our input depends upon the mode in which we were
# invoked.
#
if ($html) {
local ($query) = $ENV{'QUERY_STRING'};
if ($query ne "") {
local (@args) = split(m:\&:, $query);
foreach (@args) {
local ($key, $value) = split(m:=:, $_);
$arg{$key} = $value;
}
}
$ipa_a = $arg{'address'};
$ipm_a = $arg{'netmask'};
#
# Display the form with the last values (if any) already pre-input.
#
&display_form($ipa_a, $ipm_a);
#
# If there weren't any, we're done -- end the page and exit.
#
if (! $query) {
&close_page();
exit 0;
}
}
else {
#
# Do the shell thing to collect our arguments from the command line.
#
getopts("a:hm:", \%arg);
$ipa_a = $arg{'a'};
$ipm_a = $arg{'m'};
}
if ($arg{'h'}) {
exit &usage(0);
}
push(@errors, "Invalid host address specified") if (! &valid_IPv4($ipa_a));
push(@errors, "Invalid network mask specified") if (! &valid_IPv4($ipm_a));
#
# If there's anything wrong, this is our opportunity to say so.
#
if ($#errors > -1) {
#
# As usual, if we're running as a CGI script, our output is much
# more complex to create.
#
if ($html) {
print <
EOHT
foreach (@errors) {
print <$_
EOHT
}
print <
EOHT
&close_page();
exit(1);
}
else {
#
# Invoked from the shell? Error reporting is *easy*...
#
foreach (@errors) {
print STDERR "$script: $_\n";
}
exit &usage(1);
}
}
#
# Everything is in readiness. Turn the strings into 32-bit binary
# quantities so we can do the bitwise math on them.
#
$ipa_n = 0;
@octets = split(m:\.:, $ipa_a);
foreach (@octets) {
$ipa_n = $ipa_n << 8;
$ipa_n += $_;
}
$ipm_n = 0;
@octets = split(m:\.:, $ipm_a);
foreach (@octets) {
$ipm_n = $ipm_n << 8;
$ipm_n += $_;
}
#
# Do the math. (Apologies to Nintendo, or whomever..)
#
$ipn_n = $ipa_n & $ipm_n;
$iph_n = $ipa_n & (~$ipm_n);
$ipb_n = $ipn_n | (~$ipm_n);
#
# If the host number is zero, we're being asked to determine info about
# an entire network. Count the hosts and figure out other particulars.
#
{
my ($maskedit) = ($ipm_n);
my ($i);
#
# The maximum number of hosts in a network is (2**n) - 2, where 'n'
# is the number of zero bits in the mask. In order to deal with
# discontiguous netmasks (rare, but possible), we need to count
# those bits and do the calc. We actually increment the $zeroes
# variable for each clear bit we find embedded in the mask; a positive
# value indicates a discontiguous mask -- not valid for CIDR.
#
for ($i = 0; $i < 32; $i++) {
if ($maskedit == 0) {
$zeroes += 32 - $i;
last;
}
if (($maskedit & 0x80000000) == 0) {
$zeroes++;
}
else {
$CIDR = 0 if ($zeroes);
}
$maskedit = ($maskedit << 1) & 0xffffffff;
}
$numhosts = 2**$zeroes - 2;
}
$is_network = ($iph_n == 0);
$ipa_s = &bin2string($ipa_n);
$ipn_s = &bin2string($ipn_n);
$ipm_s = &bin2string($ipm_n);
$ipb_s = &bin2string($ipb_n);
$plural = ($numhosts == 1) ? "" : "s";
$network_notes = "";
#
# Classful networks are on their way out, but categorise this network
# anyway for historical reasons.
#
if (($ipa_n & 0x80000000) == 0x00000000) {
$network_notes .= "Class A, ";
}
elsif (($ipa_n & 0xc0000000) == 0x80000000) {
$network_notes .= "Class B, ";
}
elsif (($ipa_n & 0xe0000000) == 0xc0000000) {
$network_notes .= "Class C, ";
}
elsif (($ipa_n & 0xf0000000) == 0xe0000000) {
$network_notes .= "Class D, ";
}
elsif (($ipa_n & 0xf8000000) == 0xf0000000) {
$network_notes .= "Class E, ";
}
#
# Now if the network bits are high-aligned and contiguous, the network
# mask can be CIDR-categorised.
#
if ($CIDR) {
$network_notes .= "CIDR/" . (32 - $zeroes) . ", ";
}
else {
$network_notes .= "discontiguous, ";
}
#
# If we were asked about a network, determine the first and last host
# addresses in it. We don't enumerate them because of the pain if the
# mask is discontigous.
#
$first_address = 0;
$last_address = 0;
if ($is_network) {
my ($lowhostbit) = 1;
my ($maskedit) = $ipm_n;
my ($i, $lowbit);
for ($i = 0; $i < 32; $i++) {
if (($maskedit & 1) == 0) {
last;
}
$maskedit >>= 1;
$lowhostbit <<= 1;
}
$first_address = $ipn_n | $lowhostbit;
$last_address = $ipb_n & (~ $lowhostbit);
$ipf_s = &bin2string($first_address);
$ipl_s = &bin2string($last_address);
}
#
# If we were invoked as a CGI script, HTMLise our output. The top
# portion of the page has already been emitted, so we just need to
# display the results and close the page.
#
if ($html) {
print <
Host address:
$ipa_s
EOHT
if ($is_network) {
print <(This is a network, not a host)
EOHT
}
print <
Network:
$ipn_s
($network_notes$numhosts host$plural)
Network mask:
$ipm_s
Broadcast address:
$ipb_s
EOHT
if ($is_network) {
print <
First host address:
$ipf_s
Last host address:
$ipl_s
EOHT
}
print <
EOHT
&close_page();
}
else {
#
# Things are much simpler if we were invoked from the shell..
#
print "Host address = $ipa_s\n";
print "Network number = $ipn_s "
. "($network_notes$numhosts host$plural)\n";
print "Network mask = $ipm_s\n";
print "Broadcast address = $ipb_s\n";
if ($is_network) {
print "First host address = $ipf_s\n";
print "Last host address = $ipl_s\n";
}
}
#
# All done!
#
exit 0;
#
# Display a usage summary.
#
sub usage {
my($estatus) = @_;
print STDERR "Usage:\n $script: [-h] -a ipv4-address -m ipv4-netmask\n";
return $estatus;
}
#
# Turn a 32-bit binary quantity into a dotted-notation string.
#
sub bin2string {
local ($long) = @_;
local (@octets) = ();
local ($i);
for ($i = 1; $i <= 4; $i++) {
push(@octets, ($long & 0xFF));
$long = $long >> 8;
}
@octets = reverse(@octets);
return join(".", @octets);
}
#
# Function to present the Web form. Even a fulfilled query has the
# form at the top.
#
sub display_form {
local (@data) = @_;
print <IP Address Math Calculator
IPV4 Address Math Calculator
EOHT
return 0;
}
#
# Emit the common closing HTML for a Web invocation.
#
sub close_page {
print <
EOHT
return 0;
}
#
# Validate a four-octet quantity syntax.
#
sub valid_IPv4 {
local ($q) = @_;
#
# Verify the syntax..
#
if ($q !~ m,(?:\d+\.){3}\d+,) {
return 0;
}
#
# Now verify valid values..
#
foreach (split(m:\.:, $q)) {
return 0 if (($_ < 0) || ($_ > 255));
}
return 1;
}