#!/usr/local/bin/perl
#
# dump bridge tables for a list of switches
#
# $Id: btdump,v 1.7 1999/02/10 15:28:55 trockij Exp $
#
# Copyright (C) 1998 Jim Trocki
#
#    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
#
use A3Com::BridgeTable;
use A3Com::ARP;
use A3Com::EtherDetail;
use Getopt::Std;
use Socket;

sub usage {
    print STDERR "$0: @_\n" if @_;
    die <<EOF;
usage: $0 [-a addr] [-c] [-f] [-n] [-m] [-s] switch [switch...]
    -f	do not use the cache (slow)
    -c	force the cache to not expire
    -d	show port detail
    -n	print nodes as IP addresses, not hostnames
    -m	print nodes as MAC addresses
    -s	do not cache port detail (autoneg, port state, etc.)
    -a	find an addr

EOF
}

sub lookup;

getopts ("a:cdfnms", \%opt) || usage ("argument error");

@ARGV || usage ("no list of switches supplied");

%HOSTHASH = ();

if ($opt{"a"}) {
    if ($opt{"a"} =~ /:/) {
    	$TO_FIND = $opt{"a"};
    } elsif ($opt{"a"} =~ /^\d+\.\d+\.\d+\.\d+$/) {
    	$TO_FIND = $opt{"a"};
    } elsif ($opt{"a"} =~ /^[a-z][a-z0-9-]*$/) {
    	die "could not resolve host $opt{a}\n"
	    if (!defined ($TO_FIND = gethostbyname ($opt{"a"})));
	$TO_FIND = inet_ntoa ($TO_FIND);
    } else {
    	die "unidentified address type\n";
    }
}

#
# get ARP stuff
#
$helium = new A3Com::ARP;
$helium->switch ("helium");
$helium->cache (0) if ($opt{"f"});
$helium->cachetime(time) if ($opt{"c"});

$hydrogen = new A3Com::ARP;
$hydrogen->switch ("hydrogen");
$hydrogen->cache (0) if ($opt{"f"});
$hydrogen->cachetime(time) if ($opt{"c"});

if ($opt{"a"} && $TO_FIND =~ /\d+\./) {
    if (defined ($n = $hydrogen->mac ($TO_FIND))) {
    	$TO_FIND = $n;
    } elsif (defined ($n = $helium->mac ($TO_FIND))) {
    	$TO_FIND = $n;
    } else {
    	die "IP address not found in ARP tables\n";
    }
}

if ($opt{"d"}) {
    print "#\n# switch port portState actualPortMode autoNegMode addrs\n#\n";
} else {
    print "#\n# switch port addrs\n#\n";
}

#
# walk through switches and display bridge tables (with IP addresses)
#
foreach $switch (@ARGV) {
    $sw = new A3Com::BridgeTable;
    $sw->switch ($switch);
    $sw->cache (0) if ($opt{"f"});
    $sw->cachetime (time) if ($opt{"c"});

    if ($opt{"d"}) {
	$swdetail = new A3Com::EtherDetail;
	$swdetail->switch ($switch);
	$swdetail->cache (0) if ($opt{"s"});
	$swdetail->cachetime (time) if ($opt{"c"});
    }

    @ports = $sw->ports;
    die "error for $switch: " . $sw->error . "\n" if (@ports == 0);

    foreach $port (sort { $a <=> $b} @ports) {
	my $detail;
	if ($opt{"d"}) {
	    $detail = $swdetail->port_val ($port, "portState") .
		    " " . $swdetail->port_val ($port, "actualPortMode") .
		    " " . $swdetail->port_val ($port, "autoNegMode");
	}

	@macs = $sw->port ($port);
	if (@macs == ()) {
	    if ($opt{"d"}) {
		print "$switch $port $detail\n";
	    } else {
		print "$switch $port\n";
	    }
	    next;
	}

	if ($opt{"m"} && ! $opt{"a"}) {
	    if ($opt{"d"}) {
		print "$switch $port $detail @macs\n";
	    } else {
		print "$switch $port @macs\n";
	    }
	    next;
	}

	@addrs = ();
	foreach $mac (@macs) {
	    next if ($opt{"a"} && $mac ne $TO_FIND);

	    if ($opt{"m"}) {
	    	push @addrs, $mac;
		next;
	    }

	    @ips = $helium->ip ($mac);

	    if (!defined $ips[0]) {
		@ips = $hydrogen->ip ($mac);
		if (!defined $ips[0]) {
		    @ips = "$mac";
		}
	    }
	    push @addrs, @ips;
	}

	next if ($opt{"a"} && @addrs == ());

	if (!$opt{"n"}) {
	    for (@addrs) {
		$_ = lookup ($_) unless (/:/);
	    }
	}

	if ($opt{"d"}) {
	    print "$switch $port $detail @addrs\n";
	} else {
	    print "$switch $port @addrs\n";
	}

    }
}


sub lookup {
    my $ip = shift;

    return $HOSTHASH{$ip} if (defined $HOSTHASH{$ip});
    my $IP = gethostbyaddr (inet_aton ($ip), AF_INET);
    if (!defined $IP) {
    	$HOSTHASH{$ip} = $ip;
	return $ip;
    }
    $IP =~ s/^([^.]+)\..*$/$1/;
    $HOSTHASH{$ip} = $IP;
}
