Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 858 lines (689 sloc) 19.4 KB
#!/usr/bin/perl -w
###############################################################################
# IP-Kalkylatorn (ipkalk) #
# #
# Based on ipcalc (0.41) created by Krischan Jodies <krischan@jodies.de>. #
# Forked and modified by Göran Gustafsson <gustafsson.g@gmail.com>. #
###############################################################################
# Copyright (C) 2004 Krischan Jodies
# Copyright (C) 2012 Göran Gustafsson
#
# 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., 59 Temple
# Place, Suite 330, Boston, MA 02111-1307 USA
###############################################################################
# Web: https://github.com/ggustafsson/IP-Kalkylatorn #
# Git: https://github.com/ggustafsson/IP-Kalkylatorn.git #
###############################################################################
use strict;
my $version = "0.1";
my $filename = "ipkalk";
my @class = qw (0 8 16 24 4 5 5);
my $normal_color = "\033[0m"; # Default
my $quads_color = "\033[1;36m"; # Bold cyan
my $binary_color = "\033[0m\033[33m"; # Yellow
my $mask_color = "\033[0m\033[31m"; # Red
my $class_color = "\033[0m\033[36m"; # Cyan
my $subnet_color = "\033[0m\033[32m"; # Green
my $error_color = "\033[1;31m"; # Bold red
my $color_old = "";
my $color_actual = "";
my $opt_text = 1;
my $opt_color = 0;
my $opt_print_bits = 1;
my $opt_print_only_class = 0;
my $opt_split = 0;
my $opt_deaggregate = 0;
my $opt_version = 0;
my $opt_help = 0;
my @opt_split_sizes;
my @arguments;
my $error = "";
my $thirtytwobits = 4294967295; # for masking bitwise not on 64 bit arch
main();
exit;
sub main {
my $address = -1;
my $address2 = -1;
my $network = -1;
my $mask1 = -1;
my $mask2 = -1;
if (! defined ($ARGV[0])) {
help();
exit();
}
@ARGV = getopts();
if ($opt_help) {
help();
exit;
}
if ($opt_version) {
version();
exit;
}
if (! $opt_color) {
$normal_color = "";
$quads_color = "";
$binary_color = "";
$mask_color = "";
$class_color = "";
$subnet_color = "";
}
# get base address
if (defined $ARGV[0]) {
$address = argton($ARGV[0], 0);
}
if ($address == -1) {
$error .= "INVALID ADDRESS: $ARGV[0]\n";
$address = argton("192.168.1.1");
}
if ($opt_print_only_class) {
print getclass($address, 1);
exit;
}
# if deaggregate get last address
if ($opt_deaggregate) {
if (defined $ARGV[1]) {
$address2 = argton($ARGV[1], 0);
}
if ($address2 == -1) {
$error .= "INVALID ADDRESS2: $ARGV[1]\n";
$address2 = argton("192.168.1.1");
}
}
if ($opt_deaggregate) {
if ($error) {
print "$error\n";
}
print "Deaggregate " . ntoa($address) . " - " . ntoa($address2) . "\n";
deaggregate($address, $address2);
exit;
}
# get netmasks
if (defined $ARGV[1]) {
$mask1 = argton($ARGV[1], 1);
} else {
#get natural mask ***
$mask1 = argton(24);
}
if ($mask1 == -1) {
$error .= "INVALID MASK1: $ARGV[1]\n";
$mask1 = argton(24);
}
if (defined $ARGV[2]) {
$mask2 = argton($ARGV[2], 1);
} else {
$mask2 = $mask1;
}
if ($mask2 == -1) {
$error .= "INVALID MASK2: $ARGV[2]\n";
$mask2 = argton(24);
}
if ($error) {
if ($opt_color) {
print set_color($error_color);
}
print "$error\n";
}
printline ("Address", $address, $mask1, $mask1, 1);
printline ("Netmask", $mask1, $mask1, $mask1);
printline ("Wildcard", ~$mask1, $mask1, $mask1);
print "\n";
$network = $address & $mask1;
printnet($network, $mask1, $mask1);
if ($opt_deaggregate) {
deaggregate();
}
if ($opt_split) {
split_network($network, $mask1, $mask2, @opt_split_sizes);
exit;
}
if ($mask1 < $mask2) {
print "\nSubnets after transition from /" . ntobitcountmask($mask1);
print " to /" . ntobitcountmask($mask2) . "\n\n";
subnets($network, $mask1, $mask2);
}
if ($mask1 > $mask2) {
print "Supernet\n\n";
supernet($network, $mask1, $mask2);
}
exit;
}
sub supernet {
my ($network, $mask1, $mask2) = @_;
$network = $network & $mask2;
printline ("Netmask", $mask2, $mask2, $mask1, 1);
printline ("Wildcard", ~$mask2, $mask2, $mask1);
print "\n";
printnet($network, $mask2, $mask1);
}
sub subnets {
my ($network, $mask1, $mask2) = @_;
my $subnet = 0;
my $bitcountmask1 = ntobitcountmask($mask1);
my $bitcountmask2 = ntobitcountmask($mask2);
printline ("Netmask", $mask2, $mask2, $mask1, 1);
printline ("Wildcard", ~$mask2, $mask2, $mask1);
print "\n" . set_color($normal_color);
for ($subnet = 0; $subnet < (1 << ($bitcountmask2 - $bitcountmask1)); $subnet++) {
my $net = $network | ($subnet << (32 - $bitcountmask2));
print "- Subnet " . ($subnet + 1) . "\n";
printnet($net, $mask2, $mask1);
print "\n";
if ($subnet >= 1000) {
print "\n... Stopped at 1000 subnets ...\n";
last;
}
}
$subnet = (1 << ($bitcountmask2 - $bitcountmask1));
my $hostn = ($network | ((~$mask2) & $thirtytwobits)) - $network - 1;
if ($hostn > -1) {
print "Subnets: $quads_color$subnet";
print "$normal_color\n";
}
if ($hostn < 1 ) {
$hostn = 1;
}
print "Hosts: $quads_color" . ($hostn * $subnet);
print "$normal_color\n";
}
sub getclass {
my $network = shift;
my $numeric = shift;
my $class = 1;
while (($network & (1 << (32 - $class))) == (1 << (32 - $class))) {
$class++;
if ($class > 5) {
return "invalid\n";
}
}
if ($numeric) {
return $class[$class] . "\n";
} else {
return chr($class + 64);
}
}
sub printnet {
my ($network, $mask1, $mask2) = @_;
my $hmin;
my $hmax;
my $hostn;
my $mask;
my $broadcast = $network | ((~$mask1) & $thirtytwobits);
$hmin = $network + 1;
$hmax = $broadcast - 1;
$hostn = $hmax - $hmin + 1;
$mask = ntobitcountmask($mask1);
if ($mask == 31) {
$hmax = $broadcast;
$hmin = $network;
$hostn = 2;
} elsif ($mask == 32) {
$hostn = 1;
}
if ($mask == 32) {
printline ("Hostroute", $network, $mask1, $mask2, 1);
} else {
printline ("Network", $network, $mask1, $mask2, 1);
printline ("Host Min", $hmin, $mask1, $mask2);
printline ("Host Max", $hmax, $mask1, $mask2);
printline ("Broadcast", $broadcast, $mask1, $mask2) if $mask < 31;
}
print set_color($normal_color);
print "Hosts/Net: " ;
print set_color($quads_color);
printf "%-21s", $hostn;
if ($opt_print_bits) {
print get_description($network, $mask1);
}
print set_color($normal_color);
text("\n");
return $hostn;
}
sub get_description {
my $network = shift;
my $mask = shift;
my @description;
my $field;
# class
if ($opt_color) {
push @description, set_color($normal_color) . "= " . set_color($class_color) . "Class " . getclass($network) . set_color($normal_color);
} else {
push @description, "= Class " . getclass($network);
}
# netblock
my ($netblock_txt, $netblock_url) = split ",", netblock($network, $mask);
if (defined $netblock_txt) {
push @description, $netblock_txt;
}
# /31
if (ntobitcountmask($mask) == 31) {
push @description, "PtP Link RFC 3021";
}
return join ", ", @description;
}
sub printline {
my ($label, $address, $mask1, $mask2) = @_;
$mask1 = ntobitcountmask($mask1);
$mask2 = ntobitcountmask($mask2);
my $line = "";
my $bit;
my $newbitcolor_on = 0;
my $toggle_newbitcolor = 0;
my $bit_color;
my $additional_info = "";
my $classbitcolor_on;
my $second_field;
if ($label eq "Netmask") {
$additional_info = " = $mask1";
}
if ($label eq "Network") {
$classbitcolor_on = 1;
$additional_info = "/$mask1";
}
if ($label eq "Hostroute" && $mask1 == 32) {
$classbitcolor_on = 1;
}
#label
print set_color($normal_color);
printf "%-11s", "$label:";
#address
print set_color($quads_color);
$second_field = ntoa($address) . $additional_info;
printf "%-21s", (ntoa($address) . $additional_info);
if ($opt_print_bits) {
$bit_color = set_color($binary_color);
if ($label eq "Netmask") {
$bit_color = set_color($mask_color);
}
if ($classbitcolor_on) {
$line .= set_color($class_color);
} else {
$line .= set_color($bit_color);
}
for (my $i = 1; $i < 33; $i++) {
$bit = 0;
if (($address & (1 << 32 - $i)) == (1 << 32 - $i)) {
$bit = 1;
}
$line .= $bit;
if ($classbitcolor_on && $bit == 0) {
$classbitcolor_on = 0;
if ($newbitcolor_on) {
$line .= set_color($subnet_color);
} else {
$line .= set_color($bit_color);
}
}
if ($i % 8 == 0 && $i < 32) {
$line .= set_color($normal_color) . ".";
$line .= set_color("oldcolor");
}
if ($i == $mask1) {
$line .= " ";
}
if (($i == $mask1 || $i == $mask2) && $mask1 != $mask2) {
if ($newbitcolor_on) {
$newbitcolor_on = 0;
$line .= set_color($bit_color) if ! $classbitcolor_on;
} else {
$newbitcolor_on = 1;
$line .= set_color($subnet_color) if ! $classbitcolor_on;
}
}
}
$line .= set_color($normal_color);
print "$line";
}
text("\n");
}
sub text {
my $str = shift;
if ($opt_text) {
print "$str";
}
}
sub set_color {
my $new_color = shift;
my $return;
if ($new_color eq $color_old) {
$return = "";
}
if ($new_color eq "oldcolor") {
$new_color = $color_old;
}
$color_old = $color_actual;
$color_actual = $new_color;
$return .= $new_color;
return $return;
}
sub split_network {
my $network = shift;
my $mask1 = shift;
my $mask2 = shift;
my @sizes = reverse sort { $a <=> $b } @_;
my $first_address = $network;
my $broadcast = $network | ((~$mask1) & $thirtytwobits);
my @network;
my $i = 0;
my @net;
my @mask;
my $needed_addresses = 0;
my $needed_size;
foreach (@sizes) {
$needed_size = round2powerof2($_ + 2);
push @network, $needed_size . ":" . $i++;
$needed_addresses += $needed_size;
}
@network = sort { ($b =~ /(.+):/)[0] <=> ($a =~ /(.+):/)[0] } @network;
foreach (@network) {
my ($size, $nr) = split ":", $_;
$net[$nr] = $network;
$mask[$nr] = (32 - log($size) / log(2));
$network += $size;
}
$i = -1;
while ($i++ < $#sizes) {
printf "\n- Subnet %d. Requested size: %d host(s)\n", $i + 1, $sizes[$i];
printline("Netmask", bitcountmaskton($mask[$i]), bitcountmaskton($mask[$i]), $mask2);
printnet($net[$i], bitcountmaskton($mask[$i]), $mask2);
}
my $used_mask = 32 - log(round2powerof2($needed_addresses)) / log(2);
if ($used_mask < ntobitcountmask($mask1)) {
print set_color($error_color) . "\nNetwork is too small!\n" . set_color($normal_color);
}
print "\nNeeded size: " . $needed_addresses . " addresses.\n";
print "Used network: " . ntoa($first_address) . "/$used_mask\n";
print "Unused:\n";
deaggregate($network, $broadcast);
}
sub round2powerof2 {
my $i = 0;
while ($_[0] > (1 << $i)) {
$i++;
}
return 1 << $i;
}
sub deaggregate {
# deaggregate address range
# expects: range: (dotted quads)start (dotted quads)end
my $start = shift;
my $end = shift;
my $base = $start;
my $step;
while ($base <= $end) {
$step = 0;
while (($base | (1 << $step)) != $base) {
if (($base | (((~0) & $thirtytwobits) >> (31 - $step))) > $end) {
last;
}
$step++;
}
print "- " . ntoa($base) . "/" . (32 - $step);
print "\n";
$base += 1 << $step;
}
}
sub getopts {
# expects nothing
# returns @ARGV without options
# sets global opt variables
my @arguments;
my $arg;
my $prefix;
my $nr_opts = 0;
my @tmp;
# opt_color defaults to 1 when connected to a terminal
if (-t STDOUT) {
$opt_color = 1;
}
while (has_opts()) {
$arg = shift @ARGV;
if ($arg =~ /^--(.+)/) {
$nr_opts += read_opt("--", $1);
} elsif ($arg =~ /^-(.+)/) {
$nr_opts += read_opt("-", split //, $1);
} else {
push @tmp, $arg;
}
}
foreach (@ARGV) {
push @tmp, $_;
}
# extract base address and netmasks and ranges
foreach (@tmp) {
if (/^(.+?)\/(.+)$/) {
push @arguments, $1;
push @arguments, $2;
} elsif (/^(.+)\/$/) {
push @arguments, $1;
} elsif (/^(.+)\-(.+)$/) {
push @arguments, $1;
push @arguments, $2;
$opt_deaggregate = 1;
} elsif (/^\-$/) {
$opt_deaggregate = 1;
} else {
push @arguments, $_;
}
}
if ($#arguments == 2 && $arguments[1] eq "-") {
@arguments = ($arguments[0], $arguments[2]);
$opt_deaggregate = 1;
}
if ($error) {
print "$error";
exit;
}
return @arguments;
sub read_opt {
my $prefix = shift;
my $opts_read = 0;
foreach my $opt (@_) {
++$opts_read;
if ($opt eq "h" || $opt eq "help") {
$opt_help = 1;
} elsif ($opt eq "n" || $opt eq "nocolor") {
$opt_color = 0;
} elsif ($opt eq "v" || $opt eq "version") {
$opt_version = 1;
} elsif ($opt eq "b" || $opt eq "nobinary") {
$opt_print_bits = 0;
} elsif ($opt eq "c" || $opt eq "class") {
$opt_print_only_class = 1;
} elsif ($opt eq "r" || $opt eq "range") {
$opt_deaggregate = 1;
} elsif ($opt eq "s" || $opt eq "split") {
$opt_split = 1;
while (defined $ARGV[0] && $ARGV[0] =~ /^\d+$/) {
push @opt_split_sizes, shift @ARGV;
}
if ($#opt_split_sizes < 0) {
$error .= "Argument for " . $prefix . $opt . " is missing or invalid.\n";
}
} else {
$error .= "Unknown option: " . $prefix . $opt . "\n";
--$opts_read;
}
}
return $opts_read;
}
sub has_opts {
foreach (@ARGV) {
return 1 if /^-/;
}
return 0;
}
}
sub netblock {
# gets network address as dq
# returns string description,string url
my ($mynetwork_start, $mymask) = @_;
my $mynetwork_end = $mynetwork_start | ((~$mymask) & $thirtytwobits);
my %netblocks = ( "192.168.0.0/16" => "Private Internet,http://www.ietf.org/rfc/rfc1918.txt",
"172.16.0.0/12" => "Private Internet,http://www.ietf.org/rfc/rfc1918.txt",
"10.0.0.0/8" => "Private Internet,http://www.ietf.org/rfc/rfc1918.txt",
"169.254.0.0/16" => "APIPA,http://www.ietf.org/rfc/rfc3330.txt",
"127.0.0.0/8" => "Loopback,http://www.ietf.org/rfc/rfc1700.txt",
"224.0.0.0/4" => "Multicast,http://www.ietf.org/rfc/rfc3171.txt"
);
my $match = 0;
foreach (keys %netblocks) {
my ($network, $mask) = split "/", $_;
my $start = argton($network);
my $end = $start + (1 << (32 - $mask)) -1;
# mynetwork starts within block
if ($mynetwork_start >= $start && $mynetwork_start <= $end) {
$match++;
}
# mynetwork ends within block
if ($mynetwork_end >= $start && $mynetwork_end <= $end) {
$match++;
}
# block is part of mynetwork
if ($start > $mynetwork_start && $end < $mynetwork_end) {
$match = 1;
}
if ($match == 1) {
return "In Part " . $netblocks{$_};
}
if ($match == 2) {
return $netblocks{$_};
}
}
return "";
}
sub bitcountmaskton {
my $bitcountmask = shift;
my $n;
for (my $i = 0; $i < $bitcountmask; $i++) {
$n |= 1 << (31 - $i);
}
return $n;
}
sub argton {
# expects 1. an address as dotted decimals, bit-count-mask, or hex
# 2. netmask flag. if set -> check netmask and negate wildcard masks
# returns integer or -1 if invalid
my $arg = shift;
my $netmask_flag = shift;
my $i = 24;
my $n = 0;
# dotted decimals
if ($arg =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) {
my @decimals = ($1, $2, $3, $4);
foreach (@decimals) {
if ($_ > 255 || $_ < 0) {
return -1;
}
$n += $_ << $i;
$i -= 8;
}
if ($netmask_flag) {
return validate_netmask($n);
}
return $n;
}
# bit-count-mask (24 or /24)
$arg =~ s/^\/(\d+)$/$1/;
if ($arg =~ /^\d{1,2}$/) {
if ($arg < 1 || $arg > 32) {
return -1;
}
for ($i = 0; $i < $arg; $i++) {
$n |= 1 << (31 - $i);
}
return $n;
}
# hex
if ($arg =~ /^[0-9A-Fa-f]{8}$/ || $arg =~ /^0x[0-9A-Fa-f]{8}$/) {
if ($netmask_flag) {
return validate_netmask(hex($arg));
}
return hex($arg);
}
# invalid
return -1;
sub validate_netmask {
my $mask = shift;
my $saw_zero = 0;
# negate wildcard
if (($mask & (1 << 31)) == 0) {
print "WILDCARD\n";
$mask = ~$mask;
}
# find ones following zeros
for (my $i = 0; $i < 32; $i++) {
if (($mask & (1 << (31 - $i))) == 0) {
$saw_zero = 1;
} else {
if ($saw_zero) {
print "INVALID NETMASK\n";
return -1;
}
}
}
return $mask;
}
}
sub ntoa {
return join ".", unpack("CCCC", pack("N", shift));
}
sub ntobitcountmask {
# expects integer
# returns bitcountmask
my $mask = shift;
my $bitcountmask = 0;
# find first zero
while (($mask & (1 << (31 - $bitcountmask))) != 0) {
if ($bitcountmask > 31) {
last;
}
$bitcountmask++;
}
return $bitcountmask;
}
sub help {
print << "EOF";
Usage: $filename [OPTION(S)]... [ADDRESS[/BITS]]... [NETMASK]...
This program takes an IP address and netmask and calculates the resulting
network address, wildcard mask, broadcast address and host range. By specifying
a second netmask you can design subnets and supernetworks.
-s, --split <size1> <size2> ...
Split address into subnets of specified sizes.
-c, --class Prints out the number of network bits of given address.
-r, --range Deaggregate specified address range.
-n, --nocolor Don't display terminal colors.
-b, --nobinary Don't display the binary output.
-h, --help Display this help and exit.
-v, --version Output version information and exit.
Examples:
$filename 192.168.0.1/24
$filename 192.168.0.1/255.255.128.0
$filename 192.168.0.1 255.255.128.0 255.255.192.0
$filename 192.168.0.1 0.0.63.255
$filename 192.168.0.1 - 192.168.4.1 # Deaggregate address range.
$filename 192.168.0.1/24 --split <size1> <size2> <size3>
# Split network to subnets where size1, size2 and size3 fits in.
EOF
}
sub version {
print << "EOF";
$filename $version
Web: https://github.com/ggustafsson/IP-Kalkylatorn
Git: https://github.com/ggustafsson/IP-Kalkylatorn.git
Based on ipcalc created by Krischan Jodies <krischan\@jodies.de>.
Forked and modified by Göran Gustafsson <gustafsson.g\@gmail.com>.
Released under the GNU General Public License, version 2 or later.
EOF
}