#!/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

Host/network address:
Network mask:

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; }