generate graphviz dot
[dell-switch] / sbw-parse.pl
1 #!/usr/bin/perl
2 use warnings;
3 use strict;
4 use autodie;
5
6 # ./sw-name-mac.sh
7
8 # usage: NAME_MAC=/dev/shm/file-with-name-space-mac sbw-parse.pl [optional-switch-snmpbulkwalk-dump]
9
10 use Data::Dump qw(dump);
11
12 my $debug = $ENV{DEBUG} || 0;
13
14 my @cols = qw( ifName ifHighSpeed ifAdminStatus ifOperStatus ifType dot1dStpPortPathCost ifAlias );
15
16 my $mac2name;
17
18 foreach my $name_mac ( qw( /dev/shm/sw-name-mac /dev/shm/wap-name-mac ), $ENV{NAME_MAC} ) {
19         next unless -e $name_mac;
20         open(my $f, '<'. $name_mac);
21         while(<$f>) {
22                 chomp;
23                 #my ( $ip, $name, $mac ) = split(/ /,$_);
24                 my ( $name, $mac ) = split(/ /,$_);
25                 $mac = lc($mac);
26                 $mac2name->{$mac} = $name;
27         }
28 }
29
30 sub mac2name {
31         my ( $mac, $name ) = @_;
32
33         $mac = lc($mac);
34
35         if ( exists $mac2name->{$mac} ) {
36                 my $mac_name = $mac2name->{$mac};
37                 warn "ERROR: name different $name != $mac_name" if $name && $name ne $mac_name;
38                 return ( $mac, $mac_name );
39         }
40         return ( $mac, $name );
41 }
42
43 warn "# mac2name = ",dump($mac2name) if $debug;
44
45
46 my $sw;
47
48 sub fix_mac {
49         my $mac = shift;
50         $mac = lc($mac);
51         $mac =~ s/^([0-9a-f]):/0$1:/;
52         while ( $mac =~ s/:([0-9a-f]):/:0$1:/g ) {};
53         $mac =~ s/:([0-9a-f])$/:0$1/;
54 #       warn "# $mac\n";
55         return $mac;
56 }
57
58 sub sw_name_mac_port {
59         my ( $name, $mac, $port ) = @_;
60         $mac = fix_mac($mac);
61         if ( exists $mac2name->{$mac} ) {
62                 my $sw_name = $mac2name->{$mac};
63                 $sw->{$name}->{port_for_switch}->{$port}->{ $sw_name }++;
64                 #print "## $name $port $sw_name\n";
65         }
66 }
67
68 my $gv; # collect for graphviz
69
70 my @files = @ARGV;
71 @files = glob('snmpbulkwalk/*') unless @files;
72
73 foreach my $file ( @files ) {
74         my ( undef, $name ) = split(/\//, $file);
75         print "# $name $file\n" if $debug;
76
77         if ( -s "/dev/shm/$file" ) {
78                 if ( -M $file < -M "/dev/shm/$file" ) {
79                         warn "UPDATE $file\n";
80                         system "cp -pv /dev/shm/$file $file";
81                 }
82                 $file="/dev/shm/$file";
83         } else {
84                 warn "WARNING: using old file $file\n";
85         }
86
87         open(my $f, '<', $file);
88         while(<$f>) {
89                 chomp;
90                 if ( m/::(sysName|sysLocation|ipDefaultTTL|dot1dStpPriority|dot1dStpTopChanges|dot1dStpDesignatedRoot|dot1dStpRootCost|dot1dStpRootPort|dot1qNumVlans)\./ ) {
91                         my ( undef, $v ) = split(/ = \w+: /,$_,2);
92                         $sw->{$name}->{$1} = $v;
93                 } elsif ( m/::(ifMtu|ifHighSpeed)\[(\d\d?)\] = (?:INTEGER|Gauge32): (\d+)/ ) {
94                         $sw->{$name}->{$1}->[$2] = $3;
95                 } elsif ( m/::(ifPhysAddress)\[(\d\d?)\] = STRING: ([0-9a-f:]+)/ ) {
96                         $sw->{$name}->{$1}->[$2] = fix_mac($3);
97                 } elsif ( m/::(ifName|ifAlias)\[(\d\d?)\] = STRING: (.+)/ ) {
98                         $sw->{$name}->{$1}->[$2] = $3;
99                         if ( $1 eq 'ifName' ) {
100                                 my ( $if_name, $port ) = ($3,$2);
101                                 $sw->{$name}->{port_name_to_number}->{$3} = $2;
102                         }
103                 } elsif ( m/::(ifAdminStatus|ifOperStatus|ifType|dot3StatsDuplexStatus)\[(\d\d?)\] = INTEGER: (\w+)\(/ ) {
104                         $sw->{$name}->{$1}->[$2] = $3;
105                 } elsif ( m/::(dot1dStpPortPathCost)\[(\d\d?)\] = INTEGER: (\d+)/ ) {
106                         $sw->{$name}->{$1}->[$2] = $3;
107                 } elsif ( m/::(dot1dTpFdbPort)\[STRING: ([0-9a-f:]+)\] = INTEGER: (\d+)/ ) {
108                         $sw->{$name}->{$1}->{ fix_mac($2) } = $3;
109                         sw_name_mac_port( $name, $2, $3 );
110                 } elsif ( m/::(dot1qTpFdbPort)\[(\d+)\]\[STRING: ([0-9a-f:]+)\] = INTEGER: (\d+)/ ) {
111                         $sw->{$name}->{$1}->{ fix_mac($3) } = [ $4, $2 ]; # port, vlan
112                         sw_name_mac_port( $name, $3, $4 );
113                 }
114
115                 # dot1qVlanCurrentEgressPorts
116                 # dot1qVlanCurrentUntaggedPorts
117                 # dot1qVlanStaticName
118                 # dot1qVlanStaticEgressPorts
119                 # dot1qVlanStaticUntaggedPorts
120                 # dot1qPvid
121
122                 # entPhysicalDescr
123                 # entPhysicalClass
124
125                 #print "## $_<--\n";
126         }
127         warn "# sw $name = ",dump($sw->{$name}) if $debug;
128
129         foreach my $port ( 1 .. $#{ $sw->{$name}->{ifName} } ) {
130                 print "$name $port";
131                 foreach my $oid ( @cols ) {
132                         if ( $oid eq 'ifAlias' ) {
133                                 if ( defined( $sw->{$name}->{$oid}->[$port] ) ) {
134                                         print " [",$sw->{$name}->{$oid}->[$port],"]";
135                                 }
136                         } elsif ( defined $sw->{$name}->{$oid}->[$port] ) {
137                                 print " ", $sw->{$name}->{$oid}->[$port];
138                         } else {
139                                 print " ?";
140                                 #warn "MISSING $name $oid $port\n";
141                         }
142
143                 }
144                 if ( exists( $sw->{$name}->{port_for_switch}->{ $port } ) ) {
145                         my @visible = sort keys %{ $sw->{$name}->{port_for_switch}->{ $port } };
146                         print " ",join(',', @visible);
147
148                         if ( scalar @visible == 1 ) {
149                                 $gv->{$name}->{$port}->{ $visible[0] }->{ 'no_port' } = [$port,0]; # no port
150                         }
151                 }
152                 print "\n";
153         }}
154
155 # fix ifPhysAddress
156
157 # read neighbour visibility from lldp
158
159 sub port2number {
160         my ($name,$port) = @_;
161
162         return $port if $port =~ m/^\d+$/;
163         $port =~ s{bridge\d*/}{};  # remove mikrotik port prefix
164         $port =~ s{,bridge\d*$}{}; # remove mikrotik port suffix
165         $port =~ s{,bonding\d*$}{}; # remove mikrotik port suffix
166
167         if ( exists $sw->{$name}->{port_name_to_number}->{$port} ) {
168                 return $sw->{$name}->{port_name_to_number}->{$port};
169         }
170
171         # gigabitethernet1/0/45 or gi1/0/45
172         if ( $port =~ m{1/0/(\d+)$} ) {
173                 return $1;
174         }
175
176         # linux
177         if ( $port =~ m{eth(\d+)} ) {
178                 return $1;
179         }
180
181         my @fuzzy = grep { m/^$port/ } keys %{ $sw->{$name}->{port_name_to_number} };
182         if ( scalar @fuzzy == 1 ) {
183                 return $sw->{$name}->{port_name_to_number}->{$fuzzy[0]}
184         }
185
186         warn "ERROR [$_] can't find port $port for $name in ",dump( $sw->{$name}->{port_name_to_number} );
187 }
188
189 sub fix_sw_name {
190         my $name = shift;
191         if ( $name eq 'rack3-lib' ) {
192                 $name = 'sw-lib-srv';
193         }
194         return $name;
195 }
196
197
198 open(my $n_fh, '<', '/dev/shm/neighbors.tab');
199 while(<$n_fh>) {
200         chomp;
201         my ( $sw1, $port1, undef, $port2, $sw2, undef ) = split(/\t/, $_, 6 );
202         next if $port2 =~ m/:/; # skip mac in port number (wap lldp leek)
203         next unless $sw2 && $port2;
204         $sw1 = fix_sw_name($sw1);
205         my $port1_nr = port2number( $sw1, $port1 );
206         my $port2_nr = port2number( $sw2, $port2 );
207         next unless $sw->{$sw1}->{ifType}->[$port1_nr] eq 'ethernetCsmacd';
208         $gv->{$sw1}->{$port1_nr}->{$sw2}->{$port2_nr} = [ $port1, $port2 ];
209         delete $gv->{$sw1}->{$port1_nr}->{$sw2}->{'no_port'} if exists $gv->{$sw1}->{$port1_nr}->{$sw2}->{'no_port'};
210 }
211
212 print "# gv = ",dump( $gv );
213
214 open(my $dot_fh, '>', '/tmp/network.dot');
215 print $dot_fh qq|
216 digraph topology {
217 graph [ rankdir = LR ]
218 node [ shape = record ]
219 edge [ color = "gray" ]
220 |;
221
222 my @edges;
223 my $node;
224
225 foreach my $sw1 ( sort keys %$gv ) {
226         foreach my $p1 ( sort { $a <=> $b } keys %{ $gv->{$sw1} } ) {
227                 foreach my $sw2 ( sort keys %{ $gv->{$sw1}->{$p1} } ) {
228                         push @{ $node->{$sw1} }, [ $p1, $sw2 ];
229                         foreach my $p2 ( keys %{ $gv->{$sw1}->{$p1}->{$sw2} } ) {
230                                 push @edges, [ $sw1, $sw2, $p1, $p2 ];
231                                 #push @{ $node->{$sw2} }, [ $p2, $sw1 ];
232                         }
233                 }
234         }
235 }
236
237 foreach my $n ( keys %$node ) {
238         no warnings;
239         my @port_sw =
240                 sort { $a->[0] <=> $b->[0] }
241                 @{ $node->{$n} };
242         print $dot_fh qq!"$n" [ label="!.uc($n).'|' . join('|', map { sprintf "<%d>%2d %s", $_->[0], $_->[0], $_->[1] } @port_sw ) . qq!" ];\n!;
243 }
244
245 foreach my $e ( @edges ) {
246         no warnings;
247         print $dot_fh sprintf qq{ "%s":%d -> "%s":%d\n}, $e->[0], $e->[2], $e->[1], $e->[3];
248 }
249
250 print $dot_fh qq|
251 }
252 |;
253
254 system "dot -Tsvg /tmp/network.dot > /var/www/network.svg";