5fd1fb2e52e23b35a3097ffa8e5f2fba5405fbb8
[misc_tools.git] / cisco-stats / generate_cisco_port_configuration_overview
1 #!/usr/bin/perl -w
2 #
3 # Maximilian Wilhelm <max@rfc2324.org>
4 #  --  Mon 24 Dec 2007 12:45:04 AM CET
5 #
6 # Copyright (C) 2007 Maximilian Wilhelm <max@rfc2324.org>
7 #
8 #   generate_cisco_port_configuration_overview is free software; you can
9 #   redistribute it and/or modify it under the terms of the GNU General
10 #   Public License v3 as published by the Free Software Foundation.
11 #
12 #   On Debian GNU/Linux systems you can find a copy of the GPL in
13 #   /usr/share/common-licenses/GPL-3
14 #
15
16 use strict;
17
18 # Default configuration
19 my $opt = {
20         ignore_shutdown_interfaces => undef,
21 };
22
23
24 # Try to get filename
25 my $filename = shift @ARGV;
26
27 my @include_ports_list = @ARGV;
28 foreach my $arg (@ARGV) {
29         if ($arg =~ m/^--(.*)$/) {
30                 my $arg_name = $1;
31
32                 # Simple trick to simplfy argument handling
33                 if (exists $opt->{$arg_name}) {
34                         $opt->{$arg_name} = 42;
35                 }
36
37         } else {
38                 push @include_ports_list, $arg;
39         }
40 }
41
42
43 # The device/hostname
44 my $device_name = undef;
45
46 # Hash with ports to be included
47 #  By default all ports shall be included, therefore no list -> undef
48 my $include_ports = undef;
49
50 # Hash with interfaces
51 #  Top level keys: Interface type (FastEthernet, GigabitEthernet, ...)
52 #  2nd level keys: Module or interface number
53 #  optional 3rd level keys: interface number in case of modules.
54 my $interfaces = {};
55
56 # Hash to mark all found vlans
57 #  keys: vlan ID
58 #  values: something...
59 my $vlans = {};
60
61 if (! defined $filename) {
62         print "Usage: $0 filename [--ignore_shutdown_interfaces] [port1 [port2 [...]]]\n";
63         exit 1;
64 }
65
66 #
67 # Read interface configuration
68 my $current_int = undef;
69 my $line_num = 0;
70
71 open (RUNNING_CONFIG, "< $filename")
72         or die "Could not open file \"$filename\": $!\n";
73
74 if (@include_ports_list) {
75         $include_ports = {};
76
77         # Just put all given ports into the hash
78         # No input validation as garbage won't harm us here.
79         # If there is an invalid string in the hash, it just won't match anything
80         foreach my $port (@include_ports_list) {
81                 # Expand short forms
82                 $port =~ s/^Fa([0-9]+\/)/FastEthernet$1/;
83                 $port =~ s/^Gi([0-9]+\/)/GigabitEthernet$1/;
84
85                 $include_ports->{$port} = 42;
86         }
87 }
88
89
90 while (my $line = <RUNNING_CONFIG>) { # Read configuration {{{
91         chomp $line;
92         $line_num++;
93
94         # Try to get device name
95         if ($line =~ m/^hostname (.+)$/) { # {{{
96                 if (defined $device_name) {
97                         die "Error: Trying to reset hostname at line $line_num\n";
98                 }
99
100                 $device_name = $1;
101         } # }}}
102
103         #
104         # Interface detection
105         #
106         if ($line =~ /^interface (.+)$/) { # {{{
107                 my $port_name = $1;
108
109                 if (defined $include_ports && ! defined $include_ports->{$port_name}) {
110                         next;
111                 }
112
113                 # Simple IOS interface on unmodular switch
114                 if ($port_name =~ /^([a-zA-Z]+)([0-9])\/([0-9]+)$/) { # {{{
115                         my ($type, $module, $port) = ($1, $2, $3);
116
117                         if (exists $interfaces->{$type}->{$module}->{$port}) {
118                                 die "Error: Redefined interface $port_name at line $line_num\n";
119                         }
120
121
122                         $interfaces->{$type}->{$module}->{$port} = {
123                                 type => $type,
124                                 module => $module,
125                                 port => $port,
126
127                                 name => $port_name,
128                                 access_vlan => undef,
129                                 trunk_allowed_vlans => undef,
130                                 mode => undef,
131                                 desc => undef,
132                                 disabled => undef,
133                         };
134
135                         # Act on this interface util finding next
136                         $current_int = $interfaces->{$type}->{$module}->{$port};
137                         next;
138                 } # }}}
139
140                 # Warn user on unknown/unhandled interface types
141                 elsif ($line =~ /^interface (.*)$/) { # {{{
142                         print STDERR "Unknown interface \"$1\" at line $line_num, skipping it\n";
143                         next;
144                 } # }}}
145         } # }}}
146
147         # Don't read further if $current_int isn't set (maybe unknown interface type
148         elsif (! defined $current_int) { # {{{
149                 next;
150         } # }}}
151
152
153         #
154         # Read interface configuration options
155         #
156
157         # descriptio
158         elsif ($line =~ m/^ description (.*)$/) { # {{{
159                 if (! exists $current_int->{desc}) {
160                         die "Error: description found without interface declaraion at line $line_num...\n";
161                 }
162
163                 if (defined $current_int->{desc}) {
164                         die "Error: Trying to reset description for interface $current_int->{name} at line $line_num\n";
165                 }
166
167                 $current_int->{desc} = $1;
168         } # }}}
169
170         # Access vlan
171         elsif ($line =~ m/^ switchport access vlan ([0-9]{1,4})$/) { # {{{
172                 if (defined $current_int->{access_vlan}) {
173                         die "Error: Trying to reset access vlan for interface $current_int->{name} at line $line_num\n";
174                 }
175
176                 $current_int->{access_vlan} = $1;
177                 $vlans->{$1} = 42;
178         } # }}}
179
180         # Access or trunk port (ignore dynamic, private-vlan etc)
181         elsif ($line =~ m/^ switchport mode (access|trunk)$/) { # {{{
182                 if (defined $current_int->{mode}) {
183                         die "Error: Trying to reset mode for interface $current_int->{name} at line $line_num\n";
184                 }
185
186                 $current_int->{mode} = $1;
187         } # }}}
188
189         # Allowed vlans on a trunk
190         elsif ($line =~ m/^ switchport trunk allowed vlan (.+)$/) { # {{{
191                 if (defined $current_int->{trunk_allowed_vlans}) {
192                         die "Error: Trying to reset allowed vlans for trunk on interface $current_int->{name} at line $line_num\n";
193                 }
194
195                 $current_int->{trunk_allowed_vlans} = [];
196
197                 foreach my $vlan (split (',', $1)) {
198                         if ($vlan =~ m/^[0-9]{1,4}$/) {
199                                 # Save vlan in list
200                                 push @{$current_int->{trunk_allowed_vlans}}, $vlan;
201
202                                 # Mark vlan as existing
203                                 $vlans->{$vlan} = 42;
204                         }
205
206                         # If there is a range in the list, expand it
207                         elsif ($vlan =~ m/^([0-9]{1,4})-([0-9]{1,4})$/) {
208                                 for (my $n = $1; $n <= $2; $n++) {
209                                         # Save vlan in list
210                                         push @{$current_int->{trunk_allowed_vlans}}, $n;
211
212                                         # Mark vlan as existing
213                                         $vlans->{$n} = 42;
214                                 }
215                         }
216
217                         else {
218                                 die "Error: Invalid string found in allowed vlan list at line $line_num: \"$vlan\"\n";
219                         }
220                 }
221         } # }}}
222
223         # Is interface shutdown?
224         elsif ($line =~ /^ shutdown/) { # {{{
225                 if (defined $current_int->{disabled}) {
226                         die "Error: Read 'shutdown' command for interface $current_int->{name} for the second time at line $line_num\n";
227                 }
228
229                 if ($opt->{ignore_shutdown_interfaces}) {
230                         delete $interfaces->{$current_int->{type}}->{$current_int->{module}}->{$current_int->{port}};
231                         $current_int = undef;
232                         next;
233                 }
234
235                 $current_int->{disabled} = 'shutdown';
236         } # }}}
237
238         # End interface stanza
239         elsif ($line =~ m/^!$/) { # {{{
240                 $current_int = undef;
241         } # }}}
242
243 } # }}}
244
245 close (RUNNING_CONFIG);
246
247 # Got a device/hostname?
248 if (! defined $device_name) {
249         $device_name = "unknown device";
250 }
251
252 #
253 # Sort vlans ID numerically to be able to check them in the same order for every interface
254 my @vlan_list = sort { $a <=> $b } keys %{$vlans};
255 my $vlan_count = scalar (@vlan_list);
256
257
258 #
259 # Output generation
260 #
261
262 # HTML header + styles # {{{
263 print '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/strict.dtd">
264         <html>
265          <head>
266          <meta name="content-language" content="EN">
267          <meta name="Generator" content="Cisco port configuration overview generator by Max">
268          <meta name="Copyright" content="Cisco port configuration overview generator, &copy; 2007 by Maximilian Wilhelm">
269
270          <!-- Generate beatyful layout //-->
271          <style type="text/css" media="screen">
272           h1 {
273                 text-align: center;
274                 color: #000080;
275           }
276
277           img {
278                 border: 0;
279           }
280
281           table {
282                 border: 2px solid black;
283                 empty-cells: show;
284                 border-collapse: collapse;
285           }
286
287           td {
288                 border: 1px solid black;
289           }
290
291           th {
292                 border-top: 2px solid black;
293                 border-bottom: 2px solid black;
294
295                 border-left: 1px solid black;
296                 border-right: 1px solid black;
297           }
298
299           .border_right_2px_solid {
300                 border-right: 2px solid black;
301           }
302
303           .trunk_port {
304                 border-right: 2px solid black;
305
306                 color: #000080;
307                 font-weight: bold;
308           }
309
310           .unlimited_trunk {
311                 border-right: 2px solid black;
312
313                 color: #FF0000;
314                 font-weight: bold;
315           }
316
317           .access_port {
318                 border-right: 2px solid black;
319
320                 color: #000000;
321           }
322
323           .x {
324                 text-align: center;
325                 vertical-align: middle;
326           }
327          </style>
328
329
330          <title>Cisco port configuration overview for ' . $device_name . ' </title>
331         </head>
332
333         <body>
334          <h1>Cisco port configuration overview for ' . $device_name . ' </h1>
335
336 '; # }}}
337
338 # Generate table row with vlan IDs
339 my $vlan_row = '          <tr>
340            <th class="border_right_2px_solid">Interface \ Vlan</th>
341 ';
342
343 # Generate a column for each used vlan
344 foreach my $vlan_id (@vlan_list) {
345         $vlan_row .= "\t   <th>$vlan_id</th>\n";
346 }
347 $vlan_row .= "\t  </tr>\n";
348
349 # Loop over each found interface type (lexiclly sorted)
350 foreach my $int_type (sort keys %{$interfaces}) {
351         print "\t <h3>$int_type interfaces</h3>\n";
352
353         print "\t <table cellpadding=\"3\" cellspacing=\"2\">\n";
354         print $vlan_row;
355         print "\n";
356
357         # Loop over each module found with the current interface type
358         foreach my $module (sort { $a <=> $b } keys %{$interfaces->{$int_type}}) {
359
360                 # Loop over each interface found on the current module
361                 foreach my $int (sort { $a <=> $b } keys %{$interfaces->{$int_type}->{$module}}) { # {{{
362                         my $int_ref = $interfaces->{$int_type}->{$module}->{$int};
363
364                         print "\t   <tr>\n";
365                         my $int_class="";
366
367                         # IntName [(IntDesc)] \[IntMode [UNLIMITED]\]
368                         my $int_string = "$int_ref->{name} ";
369                         if (defined $int_ref->{desc}) {
370                                 $int_string .= "($int_ref->{desc}) ";
371                         }
372
373                         # Port mode (access or trunk)
374                         $int_class = "$int_ref->{mode}" . "_port";
375
376                         $int_string .= "[";
377                         if ($int_ref->{mode} eq 'trunk' && ! defined $int_ref->{trunk_allowed_vlans}) {
378                                 $int_string .= "UNLIMITED ";
379                                 $int_class = "unlimited_trunk";
380                         }
381                         $int_string .= "$int_ref->{mode}]";
382
383                         # Is the port shutdown?
384                         if ($int_ref->{disabled}) {
385                                 $int_string = "<strike>$int_string</strike>";
386                         }
387
388                         # Print it
389                         print "\t    <td class=\"$int_class\">\n";
390                         print "\t     $int_string\n";
391                         print "\t    </td>  ";
392
393                         # Print X'es
394                         foreach my $vlan_id (@vlan_list) {
395                                 print " <td class=\"x\">";
396
397                                 print "<strike>" if ($int_ref->{disabled});
398
399                                 if (($int_ref->{mode} eq 'access' && $int_ref->{access_vlan} == $vlan_id) ||
400                                     ($int_ref->{mode} eq 'trunk' && (! defined $int_ref->{trunk_allowed_vlans} || grep {/^$vlan_id$/} @{$int_ref->{trunk_allowed_vlans}})) )  {
401                                         print "<b>X</b>";
402                                 } else {
403                                         print " &nbsp; ";
404                                 }
405
406                                 print "</strike>" if ($int_ref->{disabled});
407
408                                 print "</td> ";
409                         }
410
411                         print "\t   </tr>\n";
412                 } # }}}
413                 print "\n";
414         }
415
416         print "  </table>\n";
417         print "\n";
418 }
419
420 print '
421          <p>
422           <a href="http://validator.w3.org/check?uri=referer">
423            <img src="http://www.w3.org/Icons/valid-html401" alt="Valid HTML 4.01 Strict" height="31" width="88">
424           </a>
425           <a href="http://jigsaw.w3.org/css-validator/">
426            <img style="border:0;width:88px;height:31px" src="http://jigsaw.w3.org/css-validator/images/vcss" alt="Valid CSS!">
427           </a>
428          </p>
429         </body>
430 </html>';
431
432 # vim:foldmathod=marker