From 64d472e06527123a42832402fa82a465a022cb4a Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sun, 28 Jan 2018 19:19:32 +0100 Subject: [PATCH] show-switch: the first attempt --- show-switch | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100755 show-switch diff --git a/show-switch b/show-switch new file mode 100755 index 0000000..60e166f --- /dev/null +++ b/show-switch @@ -0,0 +1,220 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Net::SNMP (); +use Net::Netmask; +use Data::Dumper; +use Getopt::Long; + +sub usage { + die "Usage: $0 [--debug] [--mac] \n"; +} + +my $debug = 0; +my $mac = 0; +GetOptions( + 'debug' => \$debug, + 'mac' => \$mac, +) or usage; + +@ARGV == 1 or usage; +my ($switch_ip) = @ARGV; +my $community = 'public'; + +my $is_tty = -t STDOUT; +sub attr { + my ($a) = @_; + return $is_tty ? `tput $a` : ""; +} +my $t_red = attr("setaf 1"); +my $t_green = attr("setaf 2"); +my $t_yellow = attr("setaf 3"); +my $t_norm = attr("sgr0"); + +my ($snmp, $err) = Net::SNMP->session( + -hostname => $switch_ip, + -version => '2c', + -community => $community, +); +$snmp or die "Cannot establish session: $err\n"; +$snmp->translate(0); + +sub my_get_table { + my ($cols) = @_; + my $tab = {}; + for my $c (keys %$cols) { + my $depth = split /\./, $cols->{$c}; + my $t = $snmp->get_table(-baseoid => $cols->{$c}) or next; + for my $k (keys %$t) { + my @k = split /\./, $k; + my $kk = join('.', @k[$depth..$#k]); + $tab->{$kk}->{$c} = $t->{$k}; + } + } + return $tab; +} + +sub format_uptime { + my ($t) = @_; + my $d = ""; + $t = int($t/100); + if ($t >= 86400) { + $d = int($t/86400) . " days, "; + $t %= 86400; + } + return $d . sprintf "%02d:%02d:%02d", int($t/3600), int(($t%3600)/60), $t%60; +} + +my $OID_basic = '1.3.6.1.2.1.1'; + +my $basics = my_get_table({ + 'desc' => "$OID_basic.1", + 'uptime' => "$OID_basic.3", + 'contact' => "$OID_basic.4", + 'name' => "$OID_basic.5", + 'location' => "$OID_basic.6", +}); +print Dumper($basics) if $debug; +my $bas = $basics->{0} or die "Cannot find basic info"; + +print "### Basics ###\n\n"; +print "Device: ", $bas->{desc}, "\n"; +print "Uptime: ", format_uptime($bas->{uptime}), "\n"; +print "Contact: ", $bas->{contact}, "\n"; +print "Name: ", $bas->{name}, "\n"; +print "Location: ", $bas->{location}, "\n"; + +my $OID_ifTable = '1.3.6.1.2.1.2.2'; +my $OID_ifTablev2 = '1.3.6.1.2.1.31.1.1'; + +my $if_table = my_get_table({ + 'desc' => "$OID_ifTable.1.2", + 'name' => "$OID_ifTablev2.1.1", + 'alias' => "$OID_ifTablev2.1.18", + 'speed' => "$OID_ifTable.1.5", + 'hispeed' => "$OID_ifTablev2.1.15", + 'admin' => "$OID_ifTable.1.7", + 'oper' => "$OID_ifTable.1.8", +}); +print Dumper($if_table) if $debug; +my @ifaces = sort { $a <=> $b } keys %$if_table; + +my $OID_ipTable = '1.3.6.1.2.1.4.20'; +my $ip_table = my_get_table({ + 'iface' => "$OID_ipTable.1.2", + 'mask' => "$OID_ipTable.1.3", +}); +print Dumper($ip_table) if $debug; + +# XXX: IPv6 not supported yet +for my $ipa (keys %$ip_table) { + my $ip = $ip_table->{$ipa}; + my $if = $if_table->{$ip->{iface}} or die "IP table refers to unknown iface"; + my $nm = Net::Netmask->new2($ipa, $ip->{mask}) or die "Cannot parse IP prefix"; + push @{$if->{ip_addrs}}, $ipa . '/' . $nm->bits; +} +print Dumper($if_table) if $debug; + +if ($mac) { + my $OID_1qTpFdbTable = '1.3.6.1.2.1.17.7.1.2.2'; + my $OID_1qPort = "$OID_1qTpFdbTable.1.2"; + my $OID_1qStatus = "$OID_1qTpFdbTable.1.3"; + my $vlan_fdb_table = my_get_table({ + 'port' => $OID_1qPort, + 'status' => $OID_1qStatus, + }); + print Dumper($vlan_fdb_table) if $debug; + + for my $m (keys %$vlan_fdb_table) { + my $fdb = $vlan_fdb_table->{$m}; + my $port = $if_table->{$fdb->{port}} or die "Forwarding DB refers to unknown iface"; + my @m = split /\./, $m; + my $vlan = shift @m; + my $mac = join(':', map { sprintf('%02x', $_) } @m); + push @{$port->{macs}}, $mac; + } +} + +my $OID_VlanStaticTable = '1.3.6.1.2.1.17.7.1.4.3'; +my $vlan_table = my_get_table({ + 'name' => "$OID_VlanStaticTable.1.1", + 'egress-ports' => "$OID_VlanStaticTable.1.2", + 'untagged-ports' => "$OID_VlanStaticTable.1.4", + 'row-status' => "$OID_VlanStaticTable.1.5", +}); +for my $vlan (values %$vlan_table) { + for my $k ('egress-ports', 'untagged-ports') { + $vlan->{$k} = [ split //, unpack("B*", $vlan->{$k} // "") ]; + } +} +print Dumper($vlan_table) if $debug; +my @vlans = sort { $a <=> $b } grep { $vlan_table->{$_}->{'row-status'} == 1 } keys %$vlan_table; + +print "\n### VLANs ###\n\n"; +if (@vlans) { + for my $vid (@vlans) { + printf "%-4d %s\n", $vid, $vlan_table->{$vid}->{'name'} // '-'; + } +} else { + print "No VLAN support.\n"; +} + +print "\n### Ports ###\n\n"; +# XXX: We assume that 802.1d switch ports IDs are equal to interface IDs +for my $port (@ifaces) { + my $if = $if_table->{$port}; + my $state; + my $scolor = $t_norm; + if ($if->{'admin'} != 1) { + $state = 'OFF'; + } elsif ($if->{'oper'} != 1) { + $state = 'DOWN'; + $scolor = $t_red; + } else { + $state = 'UP'; + $scolor = $t_green; + } + my $speed = $if->{hispeed} || int($if->{speed} / 1000000); + if (!$speed) { + $speed = ""; + } elsif ($speed < 1000) { + $speed = "${speed}M"; + } else { + $speed = int($speed/1000); + $speed = "${speed}G"; + } + printf "%-4d %-15.15s %s%-4s %-5s %s%-20s%s", $port, $if->{name}, $scolor, $state, $speed, $t_yellow, $if->{alias}, $t_norm; + + if ($mac) { + my $macs = $if->{macs}; + my $show_mac = ""; + my $more_macs = " "; + if ($macs && @$macs) { + $show_mac = $macs->[0]; + $more_macs = "${t_yellow}+${t_norm}" if @$macs > 1; + } + printf "%-17s%s ", $show_mac, $more_macs; + } + + for my $vid (@vlans) { + my $vlan = $vlan_table->{$vid}; + if ($vlan->{'egress-ports'}->[$port]) { + if ($vlan->{'untagged-ports'}->[$port]) { + if ($t_green ne "") { + print " ${t_green}${vid}${t_norm}"; + } else { + print " ${vid}U"; + } + } else { + print " $vid"; + } + } + } + + print "\n"; + + if ($if->{ip_addrs}) { + print "${t_yellow} IP: ", join(" ", @{$if->{ip_addrs}}), "${t_norm}\n"; + } +} -- 2.39.2