8 # usage: NAME_MAC=/dev/shm/file-with-name-space-mac sbw-parse.pl [optional-switch-snmpbulkwalk-dump]
10 use Data::Dump qw(dump);
14 my $debug = $ENV{DEBUG} || 0;
16 my @cols = qw( ifName ifHighSpeed ifAdminStatus ifOperStatus ifType dot1dStpPortPathCost ifAlias );
20 foreach my $name_mac ( qw( /dev/shm/sw-name-mac /dev/shm/wap-name-mac ), $ENV{NAME_MAC} ) {
21 next unless -e $name_mac;
22 open(my $f, '<'. $name_mac);
25 #my ( $ip, $name, $mac ) = split(/ /,$_);
26 my ( $name, $mac ) = split(/ /,$_);
28 $mac2name->{$mac} = $name;
33 my ( $mac, $name ) = @_;
37 if ( exists $mac2name->{$mac} ) {
38 my $mac_name = $mac2name->{$mac};
39 warn "ERROR: name different $name != $mac_name" if $name && $name ne $mac_name;
40 return ( $mac, $mac_name );
42 return ( $mac, $name );
45 warn "# mac2name = ",dump($mac2name) if $debug;
53 $mac =~ s/^([0-9a-f]):/0$1:/;
54 while ( $mac =~ s/:([0-9a-f]):/:0$1:/g ) {};
55 $mac =~ s/:([0-9a-f])$/:0$1/;
60 sub sw_name_mac_port {
61 my ( $name, $mac, $port ) = @_;
63 if ( exists $mac2name->{$mac} ) {
64 my $sw_name = $mac2name->{$mac};
65 $sw->{$name}->{port_for_switch}->{$port}->{ $sw_name }++;
66 #print "## $name $port $sw_name\n";
70 my $gv; # collect for graphviz
73 @files = glob('snmpbulkwalk/*') unless @files;
75 foreach my $file ( @files ) {
76 my ( undef, $name ) = split(/\//, $file);
77 print "# $name $file\n" if $debug;
79 if ( -s "/dev/shm/$file" ) {
80 if ( -M $file < -M "/dev/shm/$file" ) {
81 warn "UPDATE $file\n";
82 system "cp -pv /dev/shm/$file $file";
84 $file="/dev/shm/$file";
86 warn "WARNING: using old file $file\n";
89 open(my $f, '<', $file);
92 if ( m/::(sysName|sysLocation|ipDefaultTTL|dot1dStpPriority|dot1dStpTopChanges|dot1dStpDesignatedRoot|dot1dStpRootCost|dot1dStpRootPort|dot1qNumVlans)\./ ) {
93 my ( undef, $v ) = split(/ = \w+: /,$_,2);
94 $sw->{$name}->{$1} = $v;
95 } elsif ( m/::(ifMtu|ifHighSpeed)\[(\d\d?)\] = (?:INTEGER|Gauge32): (\d+)/ ) {
96 $sw->{$name}->{$1}->[$2] = $3;
97 } elsif ( m/::(ifPhysAddress)\[(\d\d?)\] = STRING: ([0-9a-f:]+)/ ) {
98 $sw->{$name}->{$1}->[$2] = fix_mac($3);
99 } elsif ( m/::(ifName|ifAlias)\[(\d\d?)\] = STRING: (.+)/ ) {
100 $sw->{$name}->{$1}->[$2] = $3;
101 if ( $1 eq 'ifName' ) {
102 my ( $if_name, $port ) = ($3,$2);
103 $sw->{$name}->{port_name_to_number}->{$3} = $2;
105 } elsif ( m/::(ifAdminStatus|ifOperStatus|ifType|dot3StatsDuplexStatus)\[(\d\d?)\] = INTEGER: (\w+)\(/ ) {
106 $sw->{$name}->{$1}->[$2] = $3;
107 } elsif ( m/::(dot1dStpPortPathCost)\[(\d\d?)\] = INTEGER: (\d+)/ ) {
108 $sw->{$name}->{$1}->[$2] = $3;
109 } elsif ( m/::(dot1dTpFdbPort)\[STRING: ([0-9a-f:]+)\] = INTEGER: (\d+)/ ) {
110 $sw->{$name}->{$1}->{ fix_mac($2) } = $3;
111 sw_name_mac_port( $name, $2, $3 );
112 } elsif ( m/::(dot1qTpFdbPort)\[(\d+)\]\[STRING: ([0-9a-f:]+)\] = INTEGER: (\d+)/ ) {
113 $sw->{$name}->{$1}->{ fix_mac($3) } = [ $4, $2 ]; # port, vlan
114 sw_name_mac_port( $name, $3, $4 );
117 # dot1qVlanCurrentEgressPorts
118 # dot1qVlanCurrentUntaggedPorts
119 # dot1qVlanStaticName
120 # dot1qVlanStaticEgressPorts
121 # dot1qVlanStaticUntaggedPorts
129 warn "# sw $name = ",dump($sw->{$name}) if $debug;
131 foreach my $port ( 1 .. $#{ $sw->{$name}->{ifName} } ) {
133 foreach my $oid ( @cols ) {
134 if ( $oid eq 'ifAlias' ) {
135 if ( defined( $sw->{$name}->{$oid}->[$port] ) ) {
136 print " [",$sw->{$name}->{$oid}->[$port],"]";
138 } elsif ( defined $sw->{$name}->{$oid}->[$port] ) {
139 print " ", $sw->{$name}->{$oid}->[$port];
142 #warn "MISSING $name $oid $port\n";
146 if ( exists( $sw->{$name}->{port_for_switch}->{ $port } ) ) {
147 my @visible = sort keys %{ $sw->{$name}->{port_for_switch}->{ $port } };
148 print " ",join(',', @visible);
150 if ( scalar @visible == 1 ) {
151 $gv->{$name}->{$port}->{ $visible[0] }->{ 'no_port' } = [$port,0]; # no port
159 # read neighbour visibility from lldp
162 my ($name,$port) = @_;
164 return $port if $port =~ m/^\d+$/;
165 $port =~ s{bridge\d*/}{}; # remove mikrotik port prefix
166 $port =~ s{,bridge\d*$}{}; # remove mikrotik port suffix
167 $port =~ s{,bonding\d*$}{}; # remove mikrotik port suffix
169 if ( exists $sw->{$name}->{port_name_to_number}->{$port} ) {
170 return $sw->{$name}->{port_name_to_number}->{$port};
173 # gigabitethernet1/0/45 or gi1/0/45
174 if ( $port =~ m{1/0/(\d+)$} ) {
179 if ( $port =~ m{eth(\d+)} ) {
183 my @fuzzy = grep { m/^$port/ } keys %{ $sw->{$name}->{port_name_to_number} };
184 if ( scalar @fuzzy == 1 ) {
185 return $sw->{$name}->{port_name_to_number}->{$fuzzy[0]}
188 warn "ERROR [$_] can't find port $port for $name in ",dump( $sw->{$name}->{port_name_to_number} );
193 if ( $name eq 'rack3-lib' ) {
194 $name = 'sw-lib-srv';
200 open(my $n_fh, '<', '/dev/shm/neighbors.tab');
203 my ( $sw1, $port1, undef, $port2, $sw2, undef ) = split(/\t/, $_, 6 );
204 next if $port2 =~ m/:/; # skip mac in port number (wap lldp leek)
205 next unless $sw2 && $port2;
206 $sw1 = fix_sw_name($sw1);
207 my $port1_nr = port2number( $sw1, $port1 );
208 my $port2_nr = port2number( $sw2, $port2 );
210 if ( $sw->{$sw1}->{ifType}->[$port1_nr] ne 'ethernetCsmacd' ) {
211 warn "SKIP $sw1 $port1_nr $sw->{$sw1}->{ifType}->[$port1_nr]\n";
213 } elsif ( $port1 =~ m{bridge} ) { # skip mikrotik bridges
214 # warn "SKIP $sw1 $port1_nr $port1\n";
218 $gv->{$sw1}->{$port1_nr}->{$sw2}->{$port2_nr} = [ $port1, $port2 ];
219 delete $gv->{$sw1}->{$port1_nr}->{$sw2}->{'no_port'} if exists $gv->{$sw1}->{$port1_nr}->{$sw2}->{'no_port'};
222 print "# gv = ",dump( $gv );
224 open(my $dot_fh, '>', '/tmp/network.dot');
227 graph [ rankdir = LR ]
228 node [ shape = record ]
229 edge [ color = "gray" ]
235 foreach my $sw1 ( sort keys %$gv ) {
236 foreach my $p1 ( sort { $a <=> $b } keys %{ $gv->{$sw1} } ) {
237 foreach my $sw2 ( sort keys %{ $gv->{$sw1}->{$p1} } ) {
238 if ( $sw1 eq $sw2 ) {
239 warn "SKIP same switch $sw1 == $sw2\n";
242 push @{ $node->{$sw1} }, [ $p1, $sw2 ];
243 foreach my $p2 ( keys %{ $gv->{$sw1}->{$p1}->{$sw2} } ) {
244 push @edges, [ $sw1, $sw2, $p1, $p2 ];
245 #push @{ $node->{$sw2} }, [ $p2, $sw1 ];
251 foreach my $n ( keys %$node ) {
254 sort { $a->[0] <=> $b->[0] }
256 print $dot_fh qq!"$n" [ label="!.uc($n).'|' . join('|', map { sprintf "<%d>%2d %s", $_->[0], $_->[0], $_->[1] } @port_sw ) . qq!" ];\n!;
259 foreach my $e ( @edges ) {
261 print $dot_fh sprintf qq{ "%s":%d -> "%s":%d\n}, $e->[0], $e->[2], $e->[1], $e->[3];
268 system "dot -Tsvg /tmp/network.dot > /var/www/network.svg";