#!/usr/bin/perl -w
#-------------------------------------------------------------------------------
# subnetzcalc.pl                                 (C) 2003 T.Birnthaler OSTC GmbH
# Alle Netze und Subnetze für IP + Netzmaske + Subnetzmaske erechnen.
#-------------------------------------------------------------------------------
# Input:  IP       = 192.168.65.23
#         Maske    = 255.255.255.0     oder   /24    (beides erlaubt)
#         SubMaske = 255.255.255.128   oder   /25    (beides erlaubt)
#-------------------------------------------------------------------------------
# Output: Binärdarstellungen von IP und Maske
#            IP    = 11000000.10101000.01000001.00010111
#            Maske = 11111111.11111111.11111111.00000000
#         Netzwerk in Dot-Notation und binär:
#            Netzwerk = 192.168.65.0
#            Netzwerk = 11000000.10101000.01000001.00000000
#         Minimale + max. Host-Adresse (Dot-Notation + Binär):
#            192.168.65.1    11000000.10101000.01000001.00000001
#            192.168.65.254  11000000.10101000.01000001.11111110
#         Anzahl der Hosts pro Netz:
#            254
#         Netzwerk-Klasse:
#            C
#         Anzahl Subnetze
#            2
#         Für jedes Subnetz die obigen Netzdaten
#            ...
#         Anzahl Hosts in allen Subnetzen zusammen
#            252
#---------------------------------------------------------------------
# Möglichkeiten zur Repräsentation der IP-Adressen/Netzwerk-Masken:
# 1.) Array mit 32 Elementen "0"/"1".
#     (evtl. zerlegt in 4 Arrays a 8 Bit)
# 2.) Für Netzwerk-Masken: Anzahl der gesetzten Bits: /1 .. /31
# 3.) Binär mit 32 Bit (long!) ********* HIER VERWENDET!
# 4.) String mit Dot-Notation
#     (evtl. zerlegt in 4 Elemente 0..255)
# ...
# Abhängig davon werden bestimmte Programmteile einfach, dafür aber
# andere kompliziert. Entwurfsentscheidung, d.h. nicht unbedingt
# eine optimale Lösung erkennbar (Auswahl abhängig vom Kenntnisstand).
#---------------------------------------------------------------------
use strict;

# Für die Eingaben
my ($ip, $netmask, $subnetmask);

# Option "-h" => Usage-Meldung
Usage() if (@ARGV != 0 and $ARGV[0] eq "-h");

# Eingabe beim Aufruf über Argumente oder einlesen?
if ((($ip, $netmask, $subnetmask) = @ARGV) < 2) {
	# Eingabe Einlesen (für Austesten nicht so geschickt, da langsam!)
	print "IP-Adresse (W.X.Y.Z): ";
	chomp($ip = <STDIN>);
	print "Netzwerk-Maske (K.L.M.N oder /BIT oder BIT): ";
	chomp($netmask = <STDIN>);
	print "Sub-Netzwerk-Maske (K.L.M.N oder /BIT oder BIT): ";
	chomp($subnetmask = <STDIN>);
}
else {
	my $netmask_dot    = Bit2Dot($netmask);
	my $netmask_bit    = Dot2Bit($netmask);
	my $subnetmask_dot = Bit2Dot($subnetmask);
	my $subnetmask_bit = Dot2Bit($subnetmask);
	print "NETMASK: $netmask\n";
	print "SUBMASK: $subnetmask\n";
	print "NETMASK_DOT: $netmask_dot\n";
	print "SUBMASK_DOT: $subnetmask_dot\n";
	print "NETMASK_BIT: $netmask_bit\n";
	print "SUBMASK_BIT: $subnetmask_bit\n";
	print "IP-Adresse:         $ip\n"                               if (defined($ip));
	print "Netzwerk-Maske:     $netmask_dot ($netmask_bit)\n"       if (defined($netmask));
	print "Sub-Netzwerk-Maske: $subnetmask_dot ($subnetmask_bit)\n" if (defined($subnetmask));
}

# Hash für die Umwandlung Slash- => Dot-Notation füllen
# (32 relativ komplizierte Zeilen, d.h. Tippfehler-anfällig)
#my %bits_to_dot = {
#	"1" => "128.0.0.0",
#	"2" => "192.0.0.0",
#	"3" => "224.0.0.0",
#	"4" => "240.0.0.0",
#	"5" => "248.0.0.0",
#	"6" => "252.0.0.0",
#	"7" => "254.0.0.0",
#	"8" => "255.0.0.0",
#	"9" => "255.128.0.0",
#    ...
#};

# Besser: Hash für Umwandlung Slash- => Dot-Notation per Schleife berechnen
# ACHTUNG: Mit höchstwertigem Bit wird Aufbau begonnen (links nach rechts)!
my %bits_to_dot;
my $sum = 0;
for (1 .. 32) {
	$sum += 2 ** (32 - $_);
	$bits_to_dot{$_} = Bin2Dot($sum);
}		

# Hash invertieren für Umwandlung Dot- => Slash-Notation
my %dot_to_bits = reverse %bits_to_dot;

foreach my $key (sort keys %dot_to_bits) {
	print "DOT2BITS: $key -> $dot_to_bits{$key}\n";
}
foreach my $key (sort { $a <=> $b } keys %bits_to_dot) {
	print "BITS2DOT: $key -> $bits_to_dot{$key}\n";
}

# Hauptnetz ausgeben
my ($net_bin, $netmask_bin) = &PrintMainNet($ip, $netmask);

# Subnetz-Maske angegeben? => Subnetze ausgeben
if (defined($subnetmask)) {
	&PrintSubNets($net_bin, $netmask_bin, $subnetmask);
}

# Hauptnetz-Daten berechnen und ausgeben
sub Usage
{
	print "usage: subnetzcalc.pl [IP NETMASK [SUBNETMASK]]";
	print "       IP         = W.X.Y.Z\n";
	print "       NETMASK    = K.L.M.N oder /BIT (/1../31) oder BIT (1..31)\n";
	print "       SUBNETMASK = K.L.M.N oder /BIT (/1../31) oder BIT (1..31)\n";
	exit;
}

# Hauptnetz-Daten berechnen und ausgeben
sub PrintMainNet
{
	my ($ip, $netmask) = @_;
	$netmask = Bit2Dot($netmask);

	# Eingaben in (interne) Binär-Form umwandeln und Netz ausrechnen
	my $ip_bin         = &Dot2Bin($ip);
	my $netmask_bin    = &Dot2Bin($netmask);
	my $net_bin        = $ip_bin & $netmask_bin;   # Netz = IP UND-verknüpft Maske

	# Hauptnetz ausgeben
	print "\nHauptnetz\n";
	printf("IP        = %-15s %08b.%08b.%08b.%08b\n", $ip, Bin2Arr($ip_bin));
	PrintNet($net_bin, $netmask_bin);
	return ($net_bin, $netmask_bin);
}

# Subnetz-Daten berechnen und ausgeben
sub PrintSubNets
{
	my ($net_bin, $netmask_bin, $subnetmask) = @_;
	my $subnetmask_bin = &Dot2Bin(Bit2Dot($subnetmask));

	# Anzahl der Subnetze und IPs pro Subnetz
	my $netmask_bits    = $dot_to_bits{&Bin2Dot($netmask_bin)};
	my $subnetmask_bits = $dot_to_bits{&Bin2Dot($subnetmask_bin)};
	my $subnets         = 2 ** ($subnetmask_bits - $netmask_bits);
	my $subnet_ips      = 2 ** (32 - $subnetmask_bits);

	# Alle Subnetze ausrechnen und ausgeben
	my $subnet_hosts = 0;
	for (1 .. $subnets) {
		print "\n$_. Subnetz\n";
		&PrintNet($net_bin, $subnetmask_bin);
		$net_bin      += $subnet_ips;
		$subnet_hosts += $subnet_ips - 2;
	}

	# Subnetz-Statistik berechnen und ausgeben
	my $net_hosts = ~ $netmask_bin - 1;
	print "\n";
	printf("Statistik\n");
	printf("---------\n");
	printf("Hosts im Gesamtnetz: %d\n", $net_hosts);
	printf("Hosts in Subnetzen:  %d\n", $subnet_hosts);
	printf("Anzahl Subnetze:     %d\n", $subnets);
	printf("Host-Verschnitt:     %d (%.2f%%)\n\n",
		$net_hosts - $subnet_hosts,
		100 * ($net_hosts - $subnet_hosts) / $net_hosts);
}

# Netzwerk-Daten berechnen und ausgeben
sub PrintNet
{
	my ($net_bin, $netmask_bin) = @_;

	my $host_min  = $net_bin + 1;
	my $host_max  = $net_bin | ~ $netmask_bin - 1;  # Maske invertiert addieren
	my $net_hosts = $host_max - $host_min + 1;      # 1. Methode
	   $net_hosts = ~ $netmask_bin - 1;             # 2. Methode
	my $host_bits = 32 - $dot_to_bits{&Bin2Dot($netmask_bin)};
	   $net_hosts = 2 ** $host_bits - 2;            # 3. Methode
	my $broadcast = $net_bin | ~ $netmask_bin;

	printf("Maske     = %-15s %08b.%08b.%08b.%08b\n",
		&Bin2Dot($netmask_bin), &Bin2Arr($netmask_bin));
	printf("Netzwerk  = %-15s %08b.%08b.%08b.%08b\n",
		&Bin2Dot($net_bin), &Bin2Arr($net_bin));
	printf("Broadcast = %-15s %08b.%08b.%08b.%08b\n",
		&Bin2Dot($broadcast), &Bin2Arr($broadcast));
	printf("Min. Host = %-15s %08b.%08b.%08b.%08b\n",
		&Bin2Dot($host_min), &Bin2Arr($host_min));
	printf("Max. Host = %-15s %08b.%08b.%08b.%08b\n",
		&Bin2Dot($host_max), &Bin2Arr($host_max));
	printf("Anz.Hosts = %d\n", $net_hosts);
	printf("Klasse    = %s\n", &NetClass($net_bin));
}

# Umwandlungsfunktion: /24 => 255.255.255.0 oder 255.255.255.0 lassen
sub Bit2Dot
{
	my ($mask) = @_;

	# Art der Masken-Angabe erkennen und in Dot-Notation umwandeln
	if ($mask !~ /\./) {
		$mask =~ s/\/0*//;
		$mask = $bits_to_dot{$mask};
	}
	return $mask;
}

# Umwandlungsfunktion: 255.255.255.0 => 24 oder 24 lassen
sub Dot2Bit
{
	my ($mask) = @_;

	# Art der Masken-Angabe erkennen und in Dot-Notation umwandeln
	if ((my @tmp = split(/\./, $mask)) != 4) {  # @tmp nur wg. split-Warnung!
		$mask =~ s/^0+//;
		$mask =~ s/\.0+/./g;
		$mask = $dot_to_bits{$mask};
	}
	else {
		$mask =~ s/\/0*//;
	}
	return $mask;
}

# Umwandlungsfunktion: 192.168.65.23 => 32-Bit-Binär-Zahl
sub Dot2Bin {
	my $ip = shift @_;            # Am Punkt in 4 Teile zerlegen
	my @ip = split(/\./, $ip);    # (Punkt "\" schützen, da Metazeichen)
	$ip = $ip[0] << 24 |          # Um 24 Bit nach links verschieben
	      $ip[1] << 16 |          # "<<" = shift nach links
	      $ip[2] <<  8 |          # (entspricht * 2 hoch 24)
	      $ip[3];                 # "|" = oder (auch "+" wäre okay!)
	return $ip;
}

# Umwandlungsfunktion: 32-Bit-Binär-Zahl => 192.168.65.23 
sub Bin2Dot {
	return join(".", Bin2Arr($_[0])); # 4 Byte mit Punkten zu Dot-
}                                     # Notation zusammensetzen

# Umwandlungsfunktion: 32-Bit-Binär-Zahl => Array
sub Bin2Arr {                     # Die GANZE IP wird verschoben,
	my $ip = shift @_;            # (über Rand hinausschieben geht)
	my @ip = (($ip >> 24) & 255,  # Byte 1 (192) isolieren (& = und)
		      ($ip >> 16) & 255,  # Byte 2 (168) isolieren (">>" =)
		      ($ip >>  8) & 255,  # Byte 3 (65) isolieren (nach rechts)
		       $ip & 255);        # Byte 4 (23) isolieren (schieben)
	return @ip;
}

# Aus binärer Netzwerk-Adresse die Netzwerk-Klasse ermitteln
# (definiert durch die ersten 1-4 Bits)
# Klasse A = 0    #   0 Dezimal
# Klasse B = 10   # 128 Dezimal
# Klasse C = 110  # 192 Dezimal
# Klasse D = 1110 # 224 Dezimal
# Klasse E = 1111 # 240 Dezimal
sub NetClass {
	my $net_bin = shift @_;
	my $netclass;

	$netclass = $net_bin & 0b1000_0000_0000_0000_0000_0000_0000_0000;
	if ($netclass == 0) {
		return "A";
	}
	$netclass = $net_bin & 0b1100_0000_0000_0000_0000_0000_0000_0000;
	if ($netclass == 0b1000_0000_0000_0000_0000_0000_0000_0000) {
		return "B";
	}
	$netclass = $net_bin & 0b1110_0000_0000_0000_0000_0000_0000_0000;
	if ($netclass == 0b1100_0000_0000_0000_0000_0000_0000_0000) {
		return "C";
	}
	$netclass = $net_bin & 0b1111_0000_0000_0000_0000_0000_0000_0000;
	if ($netclass == 0b1110_0000_0000_0000_0000_0000_0000_0000) {
		return "D";
	}
	return "E";
}
