#!/usr/bin/perl -w
#
# Syntax: mr-net-find <IP Addr>
# finds on which network device the the server is
# $Id$
#
# Copyright B. Cornec 2007-2012
# Provided under the GPL v2

# Syntax: see at end

use strict 'vars';
use Socket;
use Data::Dumper;
use Getopt::Long qw(:config auto_abbrev no_ignore_case);
use English;
#use lib qw (lib);
use ProjectBuilder::Version;
use ProjectBuilder::Base;

=pod

=head1 NAME

mr-net-find, finds on which network device a machine is reachable

=head1 DESCRIPTION

mr-net-find, finds on which network device an machine is reachable
It takes the IP or named looked at as single parameter and diagnose on which device it's reachable or gives NONET if it's not.

=head1 SYNOPSIS

mr-net-find [-v] NFS_SERVER_NAME

=head1 OPTIONS

=over 4

=item B<-v|--verbose>

Be more verbose

=back

=head1 WEB SITES

The main Web site of the project is available at L<http://www.mondorescue.org/>. Bug reports should be filled using the trac instance of the project at L<http://trac.mondorescue.org/>.

=head1 USER MAILING LIST


=head1 AUTHORS

The MondoRescue team L<http://www.mondorescue.org/> lead by Bruno Cornec L<mailto:bruno@mondorescue.org>.

=head1 COPYRIGHT

MondoRescue.org is distributed under the GPL v2.0 license
described in the file C<COPYING> included with the distribution.

=cut

# Global variables
my ($mrver,$mrrev) = pb_version_init();
my $appname = "mr-net-find";
my %opts;					# CLI Options

# Initialize the syntax string

pb_syntax_init("$appname Version $mrver-$mrrev\n");

GetOptions("help|?|h" => \$opts{'h'}, 
		"man" => \$opts{'man'},
		"verbose|v+" => \$opts{'v'},
) || pb_syntax(-1,0);

if (defined $opts{'h'}) {
	pb_syntax(0,1);
}
if (defined $opts{'man'}) {
	pb_syntax(0,2);
}
if (defined $opts{'v'}) {
	$pbdebug = $opts{'v'};
}
pb_log_init($pbdebug, $pbLOG);


# Check to which network an IP balongs
sub mr_net_for_ip {

my ($ip, $if) = @_;
my $dev = undef;

# For each device on the system
foreach my $d (keys %$if) {
	# Skip non fully activated interfaces
	next if ((not defined $if->{$d}->{'nm'}) || (not defined $if->{$d}->{'net'}) || (not defined $if->{$d}->{'bcast'}));
	# Get the net & bcast assuming the ip is on that net with that nm
	my ($net,$bcast) = mr_net_nbcast_from_in ($ip,$if->{$d}->{'nm'});
	# Is it true ?
	if (($net eq $if->{$d}->{'net'}) && ($bcast eq $if->{$d}->{'bcast'})) {
		# found it, return that value
		$dev = $d;
		last;
	}
}
return($dev);
}

sub mr_net_find_all {

my %if;
my $if = \%if;

my $curdev = "";
# TODO: Check whther the ip command is available ! 
# If not consider using ifconfig for compatibility or non Linux availability
my $ipcmd = pb_check_req("ip",1);
if (defined $ipcmd) {
	open(IP,"$ipcmd addr |") || die "Unable to read IP config with $ipcmd addr\n";
	while (<IP>) {
		# Remove duplicate spaces
		my $line = $_;
		$line =~ s/\s+/ /g;
		pb_log(2,"Line: $line\n");
		# Check for a new interface
		my $tmp;
		if ($line =~ /^[0-9]+:/) {
			my $dev;
			my $rank;
			($rank,$dev,$tmp) = split(/:/,$line);
			pb_log(3,"$rank,$dev,$tmp\n");
			$dev =~ s/\s*//;
			$if->{$dev}->{'if'} = $dev;
			$curdev = $dev;
		}
		($tmp,$tmp,$if->{$curdev}->{'mac'},$tmp) = split(/ /,$line) if (/^\s*link/);
		($tmp,$tmp,$if->{$curdev}->{'cidr'},$tmp) = split(/ /,$line) if (/^\s*inet/);
	}
	close(IP);
} else { 
	my $ipcmd = pb_check_req("ifconfig",1);
	if (defined $ipcmd) {
		open(IP,"$ipcmd -a |") || die "Unable to read IP config with $ipcmd -a\n";
		while (<IP>) {
			# Remove duplicate spaces
			my $line = $_;
			$line =~ s/\s+/ /g;
			pb_log(2,"Line: $line\n");
			# Check for a new interface
			my $tmp;
			my $tmp2;
			if ($line =~ /^[^ ]+ /) {
				my $dev;
				($dev,$tmp) = split(/ /,$line,2);
				pb_log(3,"DEV: $dev,$tmp\n");
				$dev =~ s/\s*//;
				$if->{$dev}->{'if'} = $dev;
				$curdev = $dev;
				($tmp2,$tmp2,$tmp2,$if->{$curdev}->{'mac'}) = split(/ /,$tmp);
			}
			if (/^\s*inet/) {
				($tmp,$if->{$curdev}->{'ip'},$if->{$curdev}->{'nm'},$tmp) = split(/:/,$line);
				$if->{$curdev}->{'ip'} =~ s/ .*$// if (defined $if->{$curdev}->{'ip'});
				$if->{$curdev}->{'nm'} =~ s/ .*$// if (defined $if->{$curdev}->{'nm'});
				pb_log(3,"IP: $if->{$curdev}->{'ip'},$if->{$curdev}->{'nm'}\n");
				$if->{$curdev}->{'cidr'} = $if->{$curdev}->{'ip'}."/".mr_net_cvt_mask_bits($if->{$curdev}->{'nm'}) if ((defined $if->{$curdev}->{'ip'}) && (defined $if->{$curdev}->{'nm'}));
				}
		}
		close(IP);
	} else {
		die "Neither ip nor ifconfig are available to get IP configuration.\nPlease report upstream how to deal with your platform.\n";
	}
}

# Ideas Taken from http://nixcraft.com/shell-scripting/11398-simple-ipcalc-perl-script.html
foreach my $dev (keys %if) {
	next if (not defined $if->{$dev}->{'cidr'});
	($if->{$dev}->{'ip'},my $cidr) = split(/\//,$if->{$dev}->{'cidr'}) if (not defined $if->{$dev}->{'ip'});

	$if->{$dev}->{'nm'} = mr_net_cvt_bits_mask($cidr) if (not defined $if->{$dev}->{'nm'});;

	($if->{$dev}->{'net'},$if->{$dev}->{'bcast'}) = mr_net_nbcast_from_in($if->{$dev}->{'ip'},$if->{$dev}->{'nm'});
}
pb_log(2,"exit IP: ".Dumper($if)."\n");

return($if);
}


# Improved from http://icmp.ru/man/cisco/cookbook/ciscockbk-CHP-5-SECT-3.htm
sub mr_net_cvt_bits_mask {

my ($bits) = @_;
my $a = 0;
my $b = 0;
my $c = 0;
my $d = 0;

if ($bits <= 8 ) {
	$a = mr_net_bits_to_dec($bits);
} else {
	$a = 255;
	if ($bits <= 16 ) {
		$b = mr_net_bits_to_dec($bits-8);
	} else {
		$b=255;
		if ($bits <= 24 ) {
			$c = mr_net_bits_to_dec($bits-16);
		} else {
			$c=255;
			if ($bits <= 32 ) {
				$d = mr_net_bits_to_dec($bits-24);
			} else {
				die "invalid bit count\n";
			}
		}
	}
}
return ($a.".".$b.".".$c.".".$d);
}

sub mr_net_cvt_mask_bits {

my ($mask) = @_;
my ($a,$b,$c,$d) = split(/\./,$mask);
my $bits =0;

$bits= mr_net_dec_to_bits($a) + mr_net_dec_to_bits($b) + mr_net_dec_to_bits($c) + mr_net_dec_to_bits($d);
return ($bits);
}
  
sub mr_net_bits_to_dec {
my ($bits) = @_;
   
if ($bits == 0 ) { return 0; }
if ($bits == 1 ) { return 128; }
if ($bits == 2 ) { return 192; }
if ($bits == 3 ) { return 224; }
if ($bits == 4 ) { return 240; }
if ($bits == 5 ) { return 248; }
if ($bits == 6 ) { return 252; }
if ($bits == 7 ) { return 254; }
if ($bits == 8 ) { return 255; }
}

sub mr_net_dec_to_bits {
my ($dec) = @_;
   
if ($dec == 0 ) { return 0; }
if ($dec == 128 ) { return 1; }
if ($dec == 192 ) { return 2; }
if ($dec == 224 ) { return 3; }
if ($dec == 240 ) { return 4; }
if ($dec == 248 ) { return 5; }
if ($dec == 252 ) { return 6; }
if ($dec == 254 ) { return 7; }
if ($dec == 255 ) { return 8; }
}

# Get Net and Broadcast from IP and Netmask
sub mr_net_nbcast_from_in  {

my ($ip,$nm) = @_;
my ($ipaddress) = unpack("N",pack( "C4",split(/\./,$ip)));
my ($netmask) = unpack( "N", pack( "C4",split(/\./,$nm)));

# Calculate network address by logical AND operation of addr & netmask
# and convert network address to IP address format
my $net = join(".",unpack("C4",pack("N",($ipaddress & $netmask))));

# Calculate broadcase address by inverting the netmask
# and do a logical or with network address
my $bcast = join(".",unpack("C4",pack("N",($ipaddress & $netmask)+(~ $netmask))));
return($net,$bcast);
}

#Main

my $nfsip = inet_ntoa(scalar gethostbyname($ARGV[0] || 'localhost'));

my $if = mr_net_find_all();
pb_log(1,"IP: ".Dumper($if)."\n");
my $dev = mr_net_for_ip($nfsip,$if);
my $strnet = "NONET";
$strnet = $dev if (defined $dev);
pb_log(1,"NFS IP ($nfsip) is on net $strnet)\n");
pb_log(0,"$strnet\n");
