]> mj.ucw.cz Git - jablonka.git/commitdiff
show-switch: the first attempt
authorMartin Mares <mj@ucw.cz>
Sun, 28 Jan 2018 18:19:32 +0000 (19:19 +0100)
committerMartin Mares <mj@ucw.cz>
Sun, 28 Jan 2018 18:19:32 +0000 (19:19 +0100)
show-switch [new file with mode: 0755]

diff --git a/show-switch b/show-switch
new file mode 100755 (executable)
index 0000000..60e166f
--- /dev/null
@@ -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] <ip-addr>\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";
+       }
+}