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