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