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