]> mj.ucw.cz Git - jablonka.git/blob - show-switch
show-switch: work-around for broken hispeed
[jablonka.git] / show-switch
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5 use Net::SNMP ();
6 use Net::Netmask;
7 use Data::Dumper;
8 use Getopt::Long;
9 use List::Util;
10
11 no warnings 'uninitialized';
12
13 sub usage {
14         die <<AMEN ;
15 Usage: $0 [<options>] <switch>
16
17 Options:
18 --debug         Show debugging outputs
19 --mac           Show one MAC address learned per port
20 --allmac        Show all MAC addresses learned per port
21 --force-v1      Use SNMPv1 (work-around for buggy switches)
22 AMEN
23 }
24
25 my $debug = 0;
26 my $mac = 0;
27 my $all_mac = 0;
28 my $force_v1 = 0;
29 GetOptions(
30         'debug' => \$debug,
31         'mac' => \$mac,
32         'allmac' => \$all_mac,
33         'force-v1' => \$force_v1,
34 ) or usage;
35
36 @ARGV == 1 or usage;
37 my ($switch_ip) = @ARGV;
38 my $community = 'public';
39 $mac ||= $all_mac;
40
41 my $is_tty = -t STDOUT;
42 sub attr {
43         my ($a) = @_;
44         return $is_tty ? `tput $a` : "";
45 }
46 my $t_red = attr("setaf 1");
47 my $t_green = attr("setaf 2");
48 my $t_yellow = attr("setaf 3");
49 my $t_magenta = attr("setaf 5");
50 my $t_norm = attr("sgr0");
51
52 my ($snmp, $err) = Net::SNMP->session(
53         -hostname => $switch_ip,
54         -version => ($force_v1 ? '1' : '2c'),
55         -community => $community,
56 );
57 $snmp or die "Cannot establish session: $err\n";
58 $snmp->translate(0);
59
60 sub my_get_table {
61         my ($cols) = @_;
62         my $tab = {};
63         for my $c (keys %$cols) {
64                 my $depth = split /\./, $cols->{$c};
65                 my $t = $snmp->get_table(-baseoid => $cols->{$c}) or next;
66                 for my $k (keys %$t) {
67                         my @k = split /\./, $k;
68                         my $kk = join('.', @k[$depth..$#k]);
69                         $tab->{$kk}->{$c} = $t->{$k};
70                 }
71         }
72         return $tab;
73 }
74
75 sub format_uptime {
76         my ($t) = @_;
77         my $d = "";
78         $t = int($t/100);
79         if ($t >= 86400) {
80                 $d = int($t/86400) . " days, ";
81                 $t %= 86400;
82         }
83         return $d . sprintf "%02d:%02d:%02d", int($t/3600), int(($t%3600)/60), $t%60;
84 }
85
86 my $OID_basic = '1.3.6.1.2.1.1';
87
88 my $basics = my_get_table({
89         'desc' => "$OID_basic.1",
90         'uptime' => "$OID_basic.3",
91         'contact' => "$OID_basic.4",
92         'name' => "$OID_basic.5",
93         'location' => "$OID_basic.6",
94 });
95 print "# Basics:\n", Dumper($basics) if $debug;
96 my $bas = $basics->{0} or die "Cannot find basic info";
97 for (values %$bas) {
98         s{\r}{}gs;
99         s{\n}{ | }gs;
100 }
101
102 print "### Basics ###\n\n";
103 print "Device:   ", $bas->{desc}, "\n";
104 print "Uptime:   ", format_uptime($bas->{uptime}), "\n";
105 print "Contact:  ", $bas->{contact}, "\n";
106 print "Name:     ", $bas->{name}, "\n";
107 print "Location: ", $bas->{location}, "\n";
108
109 my $OID_ifTable = '1.3.6.1.2.1.2.2';
110 my $OID_ifTablev2 = '1.3.6.1.2.1.31.1.1';
111
112 my $if_table = my_get_table({
113         'desc' => "$OID_ifTable.1.2",
114         'speed' => "$OID_ifTable.1.5",
115         'mac' => "$OID_ifTable.1.6",
116         'admin' => "$OID_ifTable.1.7",
117         'oper' => "$OID_ifTable.1.8",
118         'name' => "$OID_ifTablev2.1.1",
119         'hispeed' => "$OID_ifTablev2.1.15",
120         'alias' => "$OID_ifTablev2.1.18",
121 });
122 my %if_macs = ();
123 for my $if (values %$if_table) {
124         $if->{mac} = join(':', map { sprintf "%02x", ord $_ } split(//, $if->{mac}));
125         $if_macs{$if->{mac}} = 1;
126         if ($if->{hispeed} == $if->{speed}) {
127                 # Some buggy switches put hispeed == speed
128                 delete $if->{hispeed};
129         }
130 }
131 print "# Interface table:\n", Dumper($if_table) if $debug;
132 my @ifaces = sort { $a <=> $b } keys %$if_table;
133 print "MAC addr: ", join(" ", sort keys %if_macs), "\n";
134
135 my $OID_ipTable = '1.3.6.1.2.1.4.20';
136 my $ip_table = my_get_table({
137         'iface' => "$OID_ipTable.1.2",
138         'mask' => "$OID_ipTable.1.3",
139 });
140 print "# IP table:\n", Dumper($ip_table) if $debug;
141
142 # XXX: IPv6 not supported yet
143 for my $ipa (keys %$ip_table) {
144         my $ip = $ip_table->{$ipa};
145         my $if = $if_table->{$ip->{iface}} or die "IP table refers to unknown iface";
146         my $nm = Net::Netmask->new2($ipa, $ip->{mask}) or die "Cannot parse IP prefix";
147         push @{$if->{ip_addrs}}, $ipa . '/' . $nm->bits;
148 }
149
150 # Switch ports
151 my $OID_1dBasePortTable = '1.3.6.1.2.1.17.1.4';
152 my $port_table = my_get_table({
153         'ifindex' => "$OID_1dBasePortTable.1.2",
154 });
155 print "# Switch ports:\n", Dumper($port_table) if $debug;
156
157 for my $port (keys %$port_table) {
158         my $if = $if_table->{$port_table->{$port}->{ifindex}} or die "Port table refers to unknown iface";
159         $if->{switch_port} = $port;
160 }
161
162 print "# Extended interface table:\n", Dumper($if_table) if $debug;
163
164 if ($mac) {
165         my $OID_1qTpFdbTable = '1.3.6.1.2.1.17.7.1.2.2';
166         my $vlan_fdb_table = my_get_table({
167                 'port' => "$OID_1qTpFdbTable.1.2",
168                 'status' => "$OID_1qTpFdbTable.1.3",
169         });
170         print "# VLAN FDB:\n", Dumper($vlan_fdb_table) if $debug;
171
172         if (%$vlan_fdb_table) {
173                 for my $m (keys %$vlan_fdb_table) {
174                         my $fdb = $vlan_fdb_table->{$m};
175                         $fdb->{status} == 3 or next;    # Only learned MACs
176                         my $port = $port_table->{$fdb->{port}} or die "Forwarding DB refers to unknown port";
177                         my $if = $if_table->{$port->{ifindex}} or die "Forwarding DB refers to unknown iface";
178                         my @m = split /\./, $m;
179                         my $vlan = shift @m;
180                         my $mac = join(':', map { sprintf('%02x', $_) } @m);
181                         push @{$if->{macs}}, $mac;
182                 }
183         } else {
184                 print "# Trying fall-fack to .1d FDB\n" if $debug;
185                 my $OID_1dTpFdbTable = '1.3.6.1.2.1.17.4.3';
186                 my $fdb_table = my_get_table({
187                         'port' => "$OID_1dTpFdbTable.1.2",
188                         'status' => "$OID_1dTpFdbTable.1.3",
189                 });
190                 print "# Non-VLAN FDB:\n", Dumper($fdb_table) if $debug;
191                 for my $m (keys %$fdb_table) {
192                         my $fdb = $fdb_table->{$m};
193                         $fdb->{status} == 3 or next;    # Only learned MACs
194                         my $port = $port_table->{$fdb->{port}} or die "Forwarding DB refers to unknown port";
195                         my $if = $if_table->{$port->{ifindex}} or die "Forwarding DB refers to unknown iface";
196                         my @m = split /\./, $m;
197                         my $mac = join(':', map { sprintf('%02x', $_) } @m);
198                         push @{$if->{macs}}, $mac;
199                 }
200         }
201
202         for my $if (values %$if_table) {
203                 $if->{macs} or next;
204                 $if->{macs} = [ sort(List::Util::uniq(@{$if->{macs}})) ];
205         }
206 }
207
208 my $OID_VlanStaticTable = '1.3.6.1.2.1.17.7.1.4.3';
209 my $vlan_table = my_get_table({
210         'name' => "$OID_VlanStaticTable.1.1",
211         'egress-ports' => "$OID_VlanStaticTable.1.2",
212         'untagged-ports' => "$OID_VlanStaticTable.1.4",
213         'row-status' => "$OID_VlanStaticTable.1.5",
214 });
215 for my $vlan (values %$vlan_table) {
216         for my $k ('egress-ports', 'untagged-ports') {
217                 $vlan->{$k} = [ split //, unpack("B*", $vlan->{$k} // "") ];
218         }
219 }
220 print "# VLAN table\n", Dumper($vlan_table) if $debug;
221 my @vlans = sort { $a <=> $b } grep { $vlan_table->{$_}->{'row-status'} == 1 } keys %$vlan_table;
222
223 print "\n### VLANs ###\n\n";
224 if (@vlans) {
225         for my $vid (@vlans) {
226                 printf "%-4d  %s\n", $vid, $vlan_table->{$vid}->{'name'} // '-';
227         }
228 } else {
229         print "No VLAN support.\n";
230 }
231
232 print "\n### Ports ###\n\n";
233 for my $ifindex (@ifaces) {
234         my $if = $if_table->{$ifindex};
235         my $port = $if->{switch_port};
236         my $state;
237         my $scolor = $t_norm;
238         if ($if->{'admin'} != 1) {
239                 $state = 'OFF';
240         } elsif ($if->{'oper'} != 1) {
241                 $state = 'DOWN';
242                 $scolor = $t_red;
243         } else {
244                 $state = 'UP';
245                 $scolor = $t_green;
246         }
247         my $speed = $if->{hispeed} || int($if->{speed} / 1000000);
248         if (!$speed) {
249                 $speed = "";
250         } elsif ($speed < 1000) {
251                 $speed = "${speed}M";
252         } else {
253                 $speed = int($speed/1000);
254                 $speed = "${speed}G";
255         }
256         printf "%-4d %-15.15s  %s%-4s %-5s %s%-25.25s%s", $ifindex, $if->{name}, $scolor, $state, $speed, $t_yellow, $if->{alias}, $t_norm;
257
258         if ($mac) {
259                 my $show_mac = "";
260                 my $more_macs = " ";
261                 my @macs = @{$if->{macs} // []};
262                 if (@macs) {
263                         $show_mac = $macs[0];
264                         $more_macs = "${t_yellow}+${t_norm}" if @macs > 1;
265                 }
266                 printf " %-17s%s ", $show_mac, $more_macs;
267         }
268
269         for my $vid (@vlans) {
270                 my $vlan = $vlan_table->{$vid};
271                 if ($vlan->{'egress-ports'}->[$port]) {
272                         if ($vlan->{'untagged-ports'}->[$port]) {
273                                 if ($t_green ne "") {
274                                         print " ${t_green}${vid}${t_norm}";
275                                 } else {
276                                         print " ${vid}U";
277                                 }
278                         } else {
279                                 print " $vid";
280                         }
281                 }
282         }
283
284         print "\n";
285
286         if ($if->{ip_addrs}) {
287                 print "${t_yellow}     IP: ", join(" ", @{$if->{ip_addrs}}), "${t_norm}\n";
288         }
289         if ($all_mac) {
290                 if ($if->{mac}) {
291                         print "${t_yellow}     Local MAC: ", $if->{mac}, "${t_norm}\n";
292                 }
293                 if ($if->{macs}) {
294                         for my $m (@{$if->{macs}}) {
295                                 print "${t_magenta}     MAC: $m${t_norm}\n";
296                         }
297                 }
298         }
299 }