|
#!/usr/bin/perl
#
# File: vlsm.pl
# Author: Darin Davis, Copyright 1997
# Date: 6 June 1997
# Update: 8 July 1998
# Desc: Command line VLSM address computer. See Usage().
# Most useful (in DOS/Win95) when defined as a DOSKEY macro,
# such as:
#
# doskey vlsm \path\perl \path\vlsm.p $*
#
# Change History:
# 7 July 1997 - corrected one-off error in print_subnets()
# 26 July 1997 - changed recommendation in Desc from DOS
# batch file to DOSKEY macro (to support
# redirection)
# 8 July 1998 - added hex_ip() subroutine
# 10 Sept 2007 - fixed bug in compute_mask() by adding $masks{8}
#
########################
### GLOBAL CONSTANTS ###
########################
# table to convert number of bits
# in octet mask to decimal equivalent
$masks{0} = 0;
$masks{1} = 128;
$masks{2} = 192;
$masks{3} = 224;
$masks{4} = 240;
$masks{5} = 248;
$masks{6} = 252;
$masks{7} = 254;
$masks{8} = 255;
$true = 1 == 1;
$false = ! $true;
########################
### GLOBAL VARIABLES ###
########################
# HOUSEKEEPING VARIABLES (w/defaults)
$arg; # zeroth cmd line argument
$mask_set = $false; # did the user specify a mask? No
$subnets_set = $false; # did the user request all subnets? No
# INPUT VARIABLES (with defaults)
$mask_length = 24; # length of mask in bits
$ipaddr = "192.168.10.10"; # IP address
$mask = "255.255.255.0"; # network mask
# COMPUTED VARIABLES
$subnets; # number of subnets
$hosts; # number of hosts
$addrclass; # address class
$netaddr; # network address (bit-wise AND
# of ipaddr and mask)
###################
### SUBROUTINES ###
###################
####################################
# prints program usage
####################################
sub Usage {
print "\nUsage:\t$0 [ ] ...\n";
print < 0) { # compute each bit, MSB to LSB
$power = length($bin_num) - 1; # MSB * 2 ^ power = dec equiv of MSB
#print "power = $power\n";
$bin_num =~ s/^.//; # discard MSB; decr length($bin_num)
$multiplier = $&; # note discarded MSB
#print "multiplier = $multiplier\n";
# keep a running sum
$dec_num += $multiplier * (2 ** $power);
}
return $dec_num;
}
####################################
# converts a decimal number to any
# base between 2 and 9 (inclusive);
# returns the converted number
# filled to num_fill leading zeros
####################################
sub convert_dec_to_base {
local($base10num, $target_base, $num_fill) = @_;
local($quotient, $remainder, $divisor, $dividend, $new_num);
# validate target base
if (2 <= $target_base && $target_base <= 9) { }
else {
print("VLSM: convert_dec_to_base: '$target_base' is not between 2 and 9");
exit;
}
$dividend = $base10num;
$divisor = $target_base;
$quotient = 1; # just has to satisfy while loop test
$new_num = ""; # no digits initially
# convert bases
while ($quotient > 0) {
#print "q='$quotient', dd='$dividend', ds='$divisor'\n";
$remainder = $dividend % $divisor;
$quotient = int($dividend / $divisor);
$dividend = $quotient;
$new_num = $remainder . $new_num;
}
while (length($new_num) < $num_fill) { # pad with leading zeros
$new_num = "0" . $new_num;
}
return $new_num;
}
####################################
# computes mask length (in bits)
# of a network mask
####################################
sub compute_length {
local($binary_mask, @octets);
@octets = split(/\./, $mask); # decompose mask octets
# convert mask to binary
$binary_mask = &convert_dec_to_base($octets[0], 2, 8) .
&convert_dec_to_base($octets[1], 2, 8) .
&convert_dec_to_base($octets[2], 2, 8) .
&convert_dec_to_base($octets[3], 2, 8);
$binary_mask =~ s/0+$//; # truncate trailing zeros
if ($binary_mask =~ /^1+$/) { # if the mask is all ones
$mask_length = length($binary_mask);
}
else {
print("VLSM: Non-contiguous mask '$mask'! Don't do this!!!");
exit;
}
}
####################################
# compute network address (bitwise
# AND of ipaddr and mask)
####################################
sub compute_netaddr {
local(@a_octets, @m_octets, # ipaddr and mask octets
@n_octets);
@a_octets = split(/\./, $ipaddr); # decompose address octets
@m_octets = split(/\./, $mask); # decompose mask octets
#print join(":",@a_octets); print "\n"; # make sure we've got addr & mask
#print join(":",@m_octets); print "\n";
# AND example from Camel book:
# doesn't work
#print "123.45 AND 234.56 = ", "123.45" & "234.56", "\n";
# doesn't work
#print "123 AND 234 = ", "123" & "234", "\n";
# works! So, operands *have* to
# be numeric; won't convert from
# strings (contrary to Camel book
# example).
#print "123 AND 234 = ", 123 & 234, "\n";
# force a numeric scalar context
$a_octets[0] = hex(sprintf("%x", $a_octets[0]));
$m_octets[0] = hex(sprintf("%x", $m_octets[0]));
$a_octets[1] = hex(sprintf("%x", $a_octets[1]));
$m_octets[1] = hex(sprintf("%x", $m_octets[1]));
$a_octets[2] = hex(sprintf("%x", $a_octets[2]));
$m_octets[2] = hex(sprintf("%x", $m_octets[2]));
$a_octets[3] = hex(sprintf("%x", $a_octets[3]));
$m_octets[3] = hex(sprintf("%x", $m_octets[3]));
# bitwise AND of addr and mask
#print "a0 = $a_octets[0], m0 = $m_octets[0]\n";
$n_octets[0] = $a_octets[0] & $m_octets[0];
$n_octets[1] = $a_octets[1] & $m_octets[1];
$n_octets[2] = $a_octets[2] & $m_octets[2];
$n_octets[3] = $a_octets[3] & $m_octets[3];
$netaddr = join(".", @n_octets);
#print "$netaddr\n";
#exit;
}
####################################
# Returns the subnet class of an
# IP address
####################################
sub compute_class {
local($ipaddr) = @_;
local(@octets);
@octets = split(/\./, $ipaddr);
if (0 <= $octets[0] && $octets[0] < 128) {
return "A";
}
elsif (128 <= $octets[0] && $octets[0] < 191) {
return "B";
}
elsif (191 <= $octets[0] && $octets[0] < 223) {
return "C";
}
else {
return "M"; # Multicast/experimental address
}
}
####################################
# computes the number of subnets
# and hosts given the address class
# GLOBALS: $subnets, $hosts
####################################
sub compute_subnets_hosts {
local($addrclass) = @_;
local($bits);
#print "compute_subnets_hosts: addrclass = $addrclass\n";
if ($addrclass eq "A") { # class A address?
#print "compute_subnets_hosts: class A\n";
if ($mask_length < 8) {
print("VLSM: '$mask' is an invalid class A mask.");
exit;
}
else { # yes, so compute subnets/hosts
$bits = $mask_length - 8;
$subnets = 2 ** $bits;
$hosts = 2 ** (32 - $mask_length);
#print "compute_subnets_hosts: class A, subnets=$subnets, hosts=$hosts\n";
}
}
elsif ($addrclass eq "B") { # class B address?
#print "compute_subnets_hosts: class B\n";
if ($mask_length < 16) {
print("VLSM: '$mask' is an invalid class B mask.");
exit;
}
else { # yes, so compute subnets/hosts
$bits = $mask_length - 16;
$subnets = 2 ** $bits;
$hosts = 2 ** (32 - $mask_length);
}
}
elsif ($addrclass eq "C") { # class C address?
#print "compute_subnets_hosts: class C\n";
if ($mask_length < 24) {
print ("VLSM: '$mask' is an invalid class C mask.");
exit;
}
else { # yes, so compute subnets/hosts
$bits = $mask_length - 24;
$subnets = 2 ** $bits;
$hosts = 2 ** (32 - $mask_length);
}
}
else { # must be a multicast/experimental
# address
$subnets = "Multicast/Experimental address";
$hosts = "Multicast/Experimental address";
#print "compute_subnets_hosts: multicast/experimental\n";
}
}
####################################
# returns TRUE or FALSE depending
# if $host is a valid IP address
####################################
sub is_ip {
local($host) = @_;
local($first_octet, $other_octet);
$first_octet = "[1-9]|[1-9][0-9]|[1][0-9][0-9]|2[0-4][0-9]|25[0-5]";
$other_octet = "[0-9]|[1-9][0-9]|[1][0-9][0-9]|2[0-4][0-9]|25[0-5]";
return ($host =~ /^($first_octet)\.($other_octet)\.($other_octet)\.($other_octet)$/);
}
####################################
# computes and returns a subnet
# number
####################################
sub compute_subnet_number {
local($subnet) = @_;
local($bin_ipaddr, @octets);
@octets = split(/\./, $ipaddr); # decompose address into octets
# convert first 3 octets to binary
$octets[0] = &convert_dec_to_base($octets[0], 2, 8);
$octets[1] = &convert_dec_to_base($octets[1], 2, 8);
$octets[2] = &convert_dec_to_base($octets[2], 2, 8);
# build network prefix of address
if ($addrclass eq "A") {
$bin_ipaddr = $octets[0];
}
elsif ($addrclass eq "B") {
$bin_ipaddr = $octets[0] . $octets[1];
}
elsif ($addrclass eq "C") {
$bin_ipaddr = $octets[0] . $octets[1] . $octets[2];
}
else {
print "\tN/A - Multicast/Experimental Network Number\n";
exit;
}
#print "bin_ipaddr = $bin_ipaddr\n";
# tack on the subnet bits...
$bin_ipaddr .= $subnet;
#print "bin_ipaddr = $bin_ipaddr\n";
# ...and the host bits
while (length($bin_ipaddr) < 32) {
$bin_ipaddr .= "0";
}
#print "bin_ipaddr = $bin_ipaddr\n";
# convert the octets back to decimal
$bin_ipaddr =~ s/^........//; # behead first 8 bits...
$octets[0] = $&; # ...save them & convert to decimal
$octets[0] = &convert_bin_to_dec($octets[0]);
$bin_ipaddr =~ s/^........//; # behead second 8 bits...
$octets[1] = $&; # ...save them & convert to decimal
$octets[1] = &convert_bin_to_dec($octets[1]);
$bin_ipaddr =~ s/^........//; # behead third 8 bits...
$octets[2] = $&; # ...save them & convert to decimal
$octets[2] = &convert_bin_to_dec($octets[2]);
# fourth octet remains, so
# convert to decimal
$octets[3] = &convert_bin_to_dec($bin_ipaddr);
return join(".", @octets); # return the subnet number
}
####################################
# prints all subnet numbers
####################################
sub print_subnets {
local($nbits, # number of bits that compose the
# subnet portion of the mask
$i,
$subnet); # the decimal equiv of the nbits bits
print "\nSubnets:\n";
# compute number of subnet bits
$nbits = length(&convert_dec_to_base($subnets, 2, 0));
# compute and print each subnet num
for ($i=0; $i < $subnets; $i++) {
$subnet = &convert_dec_to_base($i, 2, $nbits - 1);
print compute_subnet_number($subnet), "\n";
}
}
####################################
# returns the dotted hex equiv
# of a dotted decimal IP address
####################################
sub hex_ip {
local($dec_ip) = @_;
local(@octets);
@octets = split(/\./, $dec_ip); # decompose into octets
# convert dec to hex
$octets[0] = sprintf("%02x", $octets[0]);
$octets[1] = sprintf("%02x", $octets[1]);
$octets[2] = sprintf("%02x", $octets[2]);
$octets[3] = sprintf("%02x", $octets[3]);
return(join(".", @octets));
}
#################
### MAIN CODE ###
#################
# process cmd line args into vars
while ($#ARGV >= 0) {
$arg = shift @ARGV; # behead zeroth argument
if ($arg =~ /^-a$/) { # user specified an IP address?
$ipaddr = shift @ARGV;
if (! &is_ip($ipaddr)) {
print ("VLSM: '$ipaddr' is not a valid IP address!");
exit;
}
next;
}
if ($arg =~ /^-h$/) { # user wants help?
&Usage;
}
if ($arg =~ /^-l$/) { # user specified a mask length?
$mask_length = shift @ARGV;
if ($mask_length < 1) {
print "VLSM: Mask length must be greater than zero.";
exit;
}
if ($mask_length > 32) {
print "VLSM: Mask length must be <= 32.";
exit;
}
next;
}
if ($arg =~ /^-m$/) { # user specified a mask?
$mask = shift @ARGV;
if (! &is_ip($mask)) {
print("VLSM: '$mask' is not a valid network mask!");
exit;
}
$mask_set = $true;
next;
}
if ($arg =~ /^-s$/) { # user requested all subnets?
$subnets_set = $true;
next;
}
print "\nVLSM: '$arg' is an unknown flag.\n\n";
&Usage;
}
# compute results
if ($mask_set) { # grant mask precedence over length
&compute_length;
}
else {
$mask = compute_mask($mask_length);
#print "DEBUG: compute_mask($mask_length) = $mask\n";
}
&compute_netaddr;
$addrclass = compute_class($ipaddr);
compute_subnets_hosts($addrclass);
# display results
print "\nInput:\n";
printf(" %-17s%s (%s)\n", "IP Address:", $ipaddr, &hex_ip($ipaddr));
printf(" %-17s%s (%s)\n", "Network Mask:", $mask, &hex_ip($mask));
printf(" %-17s%s\n", "Mask Length:", $mask_length);
print "\nOutput:\n";
printf(" %-17s%s (%s)\n", "Network Address:", $netaddr, &hex_ip($netaddr));
printf(" %-17s%s\n", "Class $addrclass Subnets:", $subnets);
printf(" %-17s%s\n", "Class $addrclass Hosts:", $hosts);
if ($subnets_set) {
if ($subnets < 33000) {
print_subnets()
}
else {
print "\n$subnets subnets is too resource intensive to print. Please ",
"run ",
"vlsm.pl on your own local machine.\n";
}
}
|
|