13 Usage: $0 [<options>] <ip-addr>
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)
30 'allmac' => \$all_mac,
31 'force-v1' => \$force_v1,
35 my ($switch_ip) = @ARGV;
36 my $community = 'public';
39 my $is_tty = -t STDOUT;
42 return $is_tty ? `tput $a` : "";
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");
50 my ($snmp, $err) = Net::SNMP->session(
51 -hostname => $switch_ip,
52 -version => ($force_v1 ? '1' : '2c'),
53 -community => $community,
55 $snmp or die "Cannot establish session: $err\n";
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};
78 $d = int($t/86400) . " days, ";
81 return $d . sprintf "%02d:%02d:%02d", int($t/3600), int(($t%3600)/60), $t%60;
84 my $OID_basic = '1.3.6.1.2.1.1';
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",
93 print Dumper($basics) if $debug;
94 my $bas = $basics->{0} or die "Cannot find basic info";
100 print "### Basics ###\n\n";
101 print "Device: ", $bas->{desc}, "\n";
102 print "Uptime: ", format_uptime($bas->{uptime}), "\n";
103 print "Contact: ", $bas->{contact}, "\n";
104 print "Name: ", $bas->{name}, "\n";
105 print "Location: ", $bas->{location}, "\n";
107 my $OID_ifTable = '1.3.6.1.2.1.2.2';
108 my $OID_ifTablev2 = '1.3.6.1.2.1.31.1.1';
110 my $if_table = my_get_table({
111 'desc' => "$OID_ifTable.1.2",
112 'speed' => "$OID_ifTable.1.5",
113 'mac' => "$OID_ifTable.1.6",
114 'admin' => "$OID_ifTable.1.7",
115 'oper' => "$OID_ifTable.1.8",
116 'name' => "$OID_ifTablev2.1.1",
117 'hispeed' => "$OID_ifTablev2.1.15",
118 'alias' => "$OID_ifTablev2.1.18",
121 for my $if (values %$if_table) {
122 $if->{mac} = join(':', map { sprintf "%02x", ord $_ } split(//, $if->{mac}));
123 $if_macs{$if->{mac}} = 1;
125 print Dumper($if_table) if $debug;
126 my @ifaces = sort { $a <=> $b } keys %$if_table;
127 print "MAC addr: ", join(" ", sort keys %if_macs), "\n";
129 my $OID_ipTable = '1.3.6.1.2.1.4.20';
130 my $ip_table = my_get_table({
131 'iface' => "$OID_ipTable.1.2",
132 'mask' => "$OID_ipTable.1.3",
134 print Dumper($ip_table) if $debug;
136 # XXX: IPv6 not supported yet
137 for my $ipa (keys %$ip_table) {
138 my $ip = $ip_table->{$ipa};
139 my $if = $if_table->{$ip->{iface}} or die "IP table refers to unknown iface";
140 my $nm = Net::Netmask->new2($ipa, $ip->{mask}) or die "Cannot parse IP prefix";
141 push @{$if->{ip_addrs}}, $ipa . '/' . $nm->bits;
143 print Dumper($if_table) if $debug;
146 my $OID_1qTpFdbTable = '1.3.6.1.2.1.17.7.1.2.2';
147 my $vlan_fdb_table = my_get_table({
148 'port' => "$OID_1qTpFdbTable.1.2",
149 'status' => "$OID_1qTpFdbTable.1.3",
151 print Dumper($vlan_fdb_table) if $debug;
153 if (%$vlan_fdb_table) {
154 for my $m (keys %$vlan_fdb_table) {
155 my $fdb = $vlan_fdb_table->{$m};
156 $fdb->{status} == 3 or next; # Only learned MACs
157 my $port = $if_table->{$fdb->{port}} or die "Forwarding DB refers to unknown iface";
158 my @m = split /\./, $m;
160 my $mac = join(':', map { sprintf('%02x', $_) } @m);
161 push @{$port->{macs}}, $mac;
164 print "# Trying fall-fack to .1d FDB\n" if $debug;
165 my $OID_1dTpFdbTable = '1.3.6.1.2.1.17.4.3';
166 my $fdb_table = my_get_table({
167 'port' => "$OID_1dTpFdbTable.1.2",
168 'status' => "$OID_1dTpFdbTable.1.3",
170 print Dumper($fdb_table) if $debug;
171 for my $m (keys %$fdb_table) {
172 my $fdb = $fdb_table->{$m};
173 $fdb->{status} == 3 or next; # Only learned MACs
174 my $port = $if_table->{$fdb->{port}} or die "Forwarding DB refers to unknown iface";
175 my @m = split /\./, $m;
176 my $mac = join(':', map { sprintf('%02x', $_) } @m);
177 push @{$port->{macs}}, $mac;
181 for my $if (values %$if_table) {
183 $if->{macs} = [ sort(List::Util::uniq(@{$if->{macs}})) ];
187 my $OID_VlanStaticTable = '1.3.6.1.2.1.17.7.1.4.3';
188 my $vlan_table = my_get_table({
189 'name' => "$OID_VlanStaticTable.1.1",
190 'egress-ports' => "$OID_VlanStaticTable.1.2",
191 'untagged-ports' => "$OID_VlanStaticTable.1.4",
192 'row-status' => "$OID_VlanStaticTable.1.5",
194 for my $vlan (values %$vlan_table) {
195 for my $k ('egress-ports', 'untagged-ports') {
196 $vlan->{$k} = [ split //, unpack("B*", $vlan->{$k} // "") ];
199 print Dumper($vlan_table) if $debug;
200 my @vlans = sort { $a <=> $b } grep { $vlan_table->{$_}->{'row-status'} == 1 } keys %$vlan_table;
202 print "\n### VLANs ###\n\n";
204 for my $vid (@vlans) {
205 printf "%-4d %s\n", $vid, $vlan_table->{$vid}->{'name'} // '-';
208 print "No VLAN support.\n";
211 print "\n### Ports ###\n\n";
212 # XXX: We assume that 802.1d switch ports IDs are equal to interface IDs
213 for my $port (@ifaces) {
214 my $if = $if_table->{$port};
216 my $scolor = $t_norm;
217 if ($if->{'admin'} != 1) {
219 } elsif ($if->{'oper'} != 1) {
226 my $speed = $if->{hispeed} || int($if->{speed} / 1000000);
229 } elsif ($speed < 1000) {
230 $speed = "${speed}M";
232 $speed = int($speed/1000);
233 $speed = "${speed}G";
235 printf "%-4d %-15.15s %s%-4s %-5s %s%-25.25s%s", $port, $if->{name}, $scolor, $state, $speed, $t_yellow, $if->{alias}, $t_norm;
240 my @macs = @{$if->{macs} // []};
242 $show_mac = $macs[0];
243 $more_macs = "${t_yellow}+${t_norm}" if @macs > 1;
245 printf " %-17s%s ", $show_mac, $more_macs;
248 for my $vid (@vlans) {
249 my $vlan = $vlan_table->{$vid};
250 if ($vlan->{'egress-ports'}->[$port]) {
251 if ($vlan->{'untagged-ports'}->[$port]) {
252 if ($t_green ne "") {
253 print " ${t_green}${vid}${t_norm}";
265 if ($if->{ip_addrs}) {
266 print "${t_yellow} IP: ", join(" ", @{$if->{ip_addrs}}), "${t_norm}\n";
270 print "${t_yellow} Local MAC: ", $if->{mac}, "${t_norm}\n";
273 for my $m (@{$if->{macs}}) {
274 print "${t_magenta} MAC: $m${t_norm}\n";