· last year · May 28, 2024, 09:25 PM
1use strict;
2use Net::SNMP;
3use CGI qw/:standard/;
4use CGI::Carp qw(fatalsToBrowser);
5use Net::Telnet::Cisco;
6use DBI;
7use Win32::TieRegistry( Delimiter=>"/", "KEY_READ" );
8use Data::Dumper;
9
10print "Content-type: text/html\n\n";
11
12my $userid = remote_user();
13$userid = '' unless defined $userid;
14$userid =~ s/..*\\//;
15$userid = lc($userid);
16my $password = $ENV{AUTH_PASSWORD};
17
18if ( param('switch') and param('text') ){
19 step3();
20} elsif (param('switch') ) {
21 step2();
22} else {
23 step1();
24}
25
26exit 0;
27
28###############################################################################
29#
30# Draw the initial form
31#
32###############################################################################
33
34sub step1 {
35
36
37 my %netinv;
38 LoadNetInventory(\%netinv);
39
40 my @switches = grep /^[A-Z]+[1-9][A-Z]S[0-9][0-9]$/i, keys %netinv;
41
42 print start_form;
43 print "<p>Pick the switch: \n";
44
45 print "<select name=switch>\n";
46 foreach my $switch (sort @switches) {
47 $switch = uc $switch;
48 print "<option value=\"$switch\">$switch\n";
49 }
50 print "</select>\n";
51 print '<p><input type=submit value="Next" name=button>';
52 print end_form;
53}
54
55###############################################################################
56#
57#
58###############################################################################
59
60sub step2 {
61
62 my $switch = uc param('switch');
63
64 my $obj = new switch($switch);
65
66 die "Could not create switch object for $switch\n" unless $obj;
67
68 print "<h3>Switch: $switch</h3><p>\n";
69
70 my @ports = $obj->LoadPorts();
71
72 my %portchannel;
73
74 my @lines;
75
76 foreach my $ifIndex ( sort @ports ) {
77 my $port = $obj->GetPort($ifIndex);
78
79 next if $port->name() =~ /^vl/i; # skip vlans
80
81 my $old_desc = $port->desc();
82 my $tmp = $old_desc;
83 my $new_desc = $old_desc;
84
85 my $jack = "";
86 if ( $tmp =~ /^([1-9][A-Z][0-9][0-9])/) { # Core switches have jack numbers - don't lose it
87 $jack = $1;
88 }
89
90 $new_desc = '' if $new_desc =~ /uplink.+e[0-9][0-9]/i; # If old description looks like "Uplink to E54SJ01", it is no longer valid
91
92 my $cdpn = $port->cdpn();
93 $cdpn =~ s/\..+//g; # Get rid of domain name
94 $cdpn =~ s/\(..*\)//;
95
96
97 if ($cdpn) {
98 $new_desc = '';
99 $new_desc .= ' ' if $new_desc;
100 $new_desc .= "$jack $cdpn";
101 if ( $port->pagp() ) {
102 my $name = $obj->GetPort( $port->pagp() )->name();
103 $portchannel{$port->pagp()} = $new_desc if $port->pagp();
104 $new_desc .= " (via $name)";
105 } else {
106 #$new_desc .= " " . $port->cdpnport();
107 }
108
109 $new_desc .= " " . $port->cdpnport() if $cdpn =~ /^vh/i; # Add the port name if ESX server
110
111 }
112
113 $new_desc =~ s/^ //;
114
115 if ($new_desc ne $old_desc) {
116 push @lines, "int " . $port->name();
117 push @lines, "! old description: $old_desc";
118 if ($new_desc) {
119 push @lines, " description $new_desc";
120 } else {
121 push @lines, " no description";
122 }
123 push @lines, "!";
124 }
125 }
126
127 foreach my $ifIndex ( keys %portchannel ) {
128 next unless $portchannel{$ifIndex};
129 push @lines, "interface " . $obj->GetPort($ifIndex)->name();
130 push @lines, " description $portchannel{$ifIndex}";
131 push @lines, "!";
132 }
133
134 my $lc = scalar @lines;
135
136 print start_form;
137
138 print qq(<p><textarea autofocus="autofocus" name="text" wrap="hard" rows="$lc" cols="80">\n);
139 print join "\n", @lines;
140 print qq(</textarea>\n);
141 print qq(<input type=hidden name="switch" value="$switch">\n);
142
143 print '<p><input type=submit value="Apply Changes" name=button>';
144 print end_form;
145
146}
147
148
149
150###############################################################################
151#
152# Use telnet to send the IOS commands to implelemnt the desired changes
153#
154###############################################################################
155
156sub step3 {
157
158 my $switch = param('switch');
159 my $text = param('text');
160 my $logfile = "c:/temp/cdpn_desc-$switch.log";
161 my $session = Net::Telnet::Cisco->new(Host => $switch, errmode => "return", Input_log=>$logfile);
162
163 my $res = $session->login($userid, $password);
164 if( ! $res ) {
165 print "<font size=+2 color=red>ERROR: Logon failed for switch</font>\n";
166 DumpLog($logfile);
167 return;
168 }
169
170 $res = $session->enable(Nunya(2));
171 if( ! $res ) {
172 print "<font size=+2 color=red>ERROR: enable failed for switch</font>\n";
173 $session->Close();
174 DumpLog($logfile);
175 return;
176 }
177
178 # send commands
179
180 my @lines = split /[\n\r]+/, $text;
181
182 $session->cmd('config t');
183
184 foreach my $line (@lines) {
185 $line =~ s/^ +//;
186 if( $line =~ /^int/ or $line =~ /^description/ or $line =~ /^no description/) {
187 $session->cmd($line);
188 }
189 }
190
191 $session->cmd("exit"); # exit "interface ..."
192 $session->cmd("exit"); # exit "config t"
193
194 $session->cmd("copy run start\n");
195
196 # Show the user a transcript of the communication with the router.
197 DumpLog($logfile);
198}
199
200###############################################################################
201# Dump the log file to the user
202###############################################################################
203
204sub DumpLog {
205 my $file = shift;
206
207 open FILE, $file or die "$file: $!\n";
208
209 print "<b>Transcript of session with switch</b><p><pre>";
210
211 while(<FILE>){
212 chomp;
213 next if /^username/i;
214 print "$_\n";
215 }
216
217 print "</pre>\n";
218 close FILE;
219}
220
221
222
223sub LoadNetInventory {
224
225}
226
227
228
229################################################################################
230#
231# Switch object
232#
233################################################################################
234
235package switch;
236
237sub new {
238 my $class = shift;
239 my $name = shift;
240 my $self = {};
241
242 bless ($self, $class);
243
244 $self->{name} = $name;
245 $self->{vlans} = {}; # hash of VLAN objects indexed by VLAN number
246 $self->{ports} = {}; # hash of port obects indexed by ifindex number
247 $self->{arp} = {}; # arp cache
248
249
250 # Open SNMP connection. Don't translate octet strings - Net::SNMP doesn't always get it right - we'll do it ourself
251
252 my $community = "public";
253 my $timeout = 60;
254
255 ($self->{snmp}, my $err) = Net::SNMP->session( -hostname => $name, -version => '2c', -community => $community, -timeout => $timeout, -port => "161", -translate=>[-octetstring => 0]);
256
257 if ( !defined($self->{snmp}) ) {
258 print STDERR "$name: SNMP open error: $err\n";
259 return undef;
260 }
261
262 return $self;
263}
264
265sub LoadPorts { # Grab ports, create port objects, populate, and index by ifindex
266 my $self = shift;
267
268 # get the port names
269
270 my $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.31.1.1.1.1", 1); # ifName
271
272 foreach my $ifindex ( keys %{$res} ) { # entries will look like '234' => 'Gi6/39'
273 my $ifname = $res->{$ifindex};
274 $self->{ports}{$ifindex} = port->new($ifname);
275 }
276
277 # port descriptions
278
279 $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.31.1.1.1.18", 1); # ifAlias
280
281 foreach my $ifindex ( keys %{$res} ) { # entries will look like '236' => 'Uplink to E54APG06'
282 my $desc = $res->{$ifindex};
283 $self->{ports}->{$ifindex}->desc($desc);
284 }
285
286 # port mode (trunk or access)
287
288 $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.46.1.6.1.1.14", 1); # vlanTrunkPortDynamicStatus
289
290 foreach my $ifindex ( keys %{$res} ) {
291 my $mode = $res->{$ifindex};
292 $self->{ports}->{$ifindex}->mode('Access') if $mode == 2;
293 $self->{ports}->{$ifindex}->mode('Trunk') if $mode == 1;
294 }
295
296 # get port status
297 # ( 1=>'up', 2=>'down', 3=>'testing', 4=>'unknown', 5=>'dormant', 6=>'notPresent', 7=>'lowerLayerDown');
298
299
300 $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.2.2.1.8", 1);
301
302 foreach my $ifindex ( keys %{$res} ) {
303 my $mode = $res->{$ifindex};
304 $self->{ports}->{$ifindex}->status('up') if $mode == 1;
305 $self->{ports}->{$ifindex}->status('down') if $mode == 2;
306 $self->{ports}->{$ifindex}->status('testing') if $mode == 3;
307 $self->{ports}->{$ifindex}->status('unknown') if $mode == 4;
308 $self->{ports}->{$ifindex}->status('dormant') if $mode == 5;
309 $self->{ports}->{$ifindex}->status('notPresent') if $mode == 6;
310 $self->{ports}->{$ifindex}->status('lowerLayerDown') if $mode == 7;
311 }
312
313 # CDP neighbor
314
315 $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.23.1.2.1.1.6", 2);
316
317 foreach my $x ( keys %{$res} ) {
318 my ($ifIndex, $junk) = split /\./, $x;
319 $self->{ports}->{$ifIndex}->cdpn($res->{$x});
320 }
321
322 # CDP neighbor port
323
324 $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.23.1.2.1.1.7", 2);
325
326 foreach my $x ( keys %{$res} ) {
327 my ($ifIndex, $junk) = split /\./, $x;
328 $self->{ports}->{$ifIndex}->cdpnport($res->{$x});
329 }
330
331 # Get corresponding VLANs
332
333 $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.68.1.2.2.1.2", 1);
334
335 foreach my $ifindex ( keys %{$res} ) {
336 my $vlan = $res->{$ifindex};
337 $self->{ports}->{$ifindex}->vlan($vlan);
338 }
339
340 # Get any PAGP ports that this port is a member of
341
342 $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.98.1.1.1.1.8", 1);
343
344 foreach my $ifindex ( keys %{$res} ) {
345 my $po = $res->{$ifindex};
346 $self->{ports}->{$ifindex}->pagp($po) unless $po == $ifindex; # the ifindex of the port channel this interface is a member of
347 }
348
349
350 return keys %{$self->{ports}};
351}
352
353
354sub LoadArpCache {
355 my $self = shift;
356
357 my $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.3.1.1.2", 4);
358
359 foreach my $ip ( keys %{$res} ) { # entries will look like '1.2.3.4' => '00112233445566'
360 my $mac = $res->{$ip};
361 $self->{arp}{$ip} = $mac;
362 }
363
364 foreach my $i (keys %{$self->{arp}}) {
365 $self->{arp}->{$i} = unpack "H*", $self->{arp}->{$i};
366 }
367
368 return keys %{$self->{arp}};
369}
370
371
372sub ArpEntry {
373 my $self = shift;
374 my $ip = shift;
375
376 return $self->{arp}->{$ip} if exists $self->{arp}->{$ip};
377 return undef;
378
379}
380
381
382sub GetPort { # Given an ifindex value, return port object
383 my $self = shift;
384 my $p = shift;
385
386 if( ! $self->{ports}->{$p} ) { # No port exists for this ifindex?
387 $self->{ports}->{$p} = port->new($p); # create one - use the ifindex as the name
388 }
389
390 return $self->{ports}->{$p};
391}
392
393sub GetVlan { # Given a VLAN number, return VLAN object
394 my $self = shift;
395 my $p = shift;
396
397 return $self->{vlans}->{$p};
398}
399
400sub LoadVlans { # Get a list of VLANs, create VLAN objects, populate, index by VLAN number
401 my $self = shift;
402
403 # Get a list of the VLANs present on this switch and create a VLAN object
404
405 my $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.46.1.3.1.1.2", 1); # vtpVlanState
406 foreach my $vlan ( keys %{$res} ) {
407 next unless $res->{$vlan}; # VLAN is not operational
408 $self->{vlans}->{$vlan} = vlan->new(($self->{name}, $vlan)); # Create a VLAN object
409 }
410
411 # get VLAN descriptions
412
413 $res = util::tablehash($self->{snmp}, "1.3.6.1.4.1.9.9.46.1.3.1.1.4.1", 1); # vtpVlanName
414 foreach my $vlan ( keys %{$res} ) { # entries will look like '3' => '85175_wire_room_E'
415 $self->{vlans}->{$vlan}->desc($res->{$vlan});
416 }
417
418 return keys %{$self->{vlans}};
419}
420
421package vlan;
422
423sub new {
424 my $class = shift;
425 my $switch = shift;
426 my $vlan = shift;
427
428 my $self = {};
429
430 bless ($self, $class);
431
432 $self->{vlan} = $vlan;
433 $self->{switch} = $switch;
434
435 $self->{bpindex} = undef; # ifindex indexed by bridge port number
436 $self->{macindex} = undef; # MAC addresses indexed by mac index
437 $self->{desc} = ''; # description
438 $self->{cam} = {}; # CAM table - ifIndexes indexed by MAC address
439
440 # Open SNMP connection. Don't translate octet strings - Net::SNMP doesn't always get it right - we'll do it ourself
441
442 my $community = "public";
443 my $timeout = 5;
444
445 ($self->{snmp}, my $err) = Net::SNMP->session( -hostname => $switch, -version => '2c', -community => $community . '@' . $vlan, -timeout => $timeout, -port => "161", -translate=>[-octetstring => 0]);
446
447 if (!defined($self->{snmp})) {
448 print STDERR "swtich $switch VLAN $vlan SNMP open error: $err\n";
449 return undef;
450 }
451
452 return $self;
453}
454
455sub LoadCam { # Grab the CAM table, translate bridge ports to ifindex, and translate MAC indexes to MAC addresses
456 my $self = shift;
457
458 $self->{bpindex} = util::tablehash($self->{snmp}, "1.3.6.1.2.1.17.1.4.1.2", 1); # dot1dBasePortIfIndex bridgeport to ifindex xref
459
460 $self->{macindex} = util::tablehash($self->{snmp}, "1.3.6.1.2.1.17.4.3.1.1", 6); # dot1dTpFdbAddress MAC index to MAC
461
462 # MAC addresses in this table are in binary because we told Net::SNMP not to translate. Translate them now
463
464 foreach my $i (keys %{$self->{macindex}}) {
465 $self->{macindex}->{$i} = unpack "H*", $self->{macindex}->{$i};
466 }
467
468 # Grab the CAM table for this VLAN
469
470 my $res = util::tablehash($self->{snmp}, "1.3.6.1.2.1.17.4.3.1.2", 6); # dot1dTpFdbPort
471 foreach my $macindex ( keys %{$res} ) {
472 if( ! $self->{macindex}->{$macindex} ) { # No entry for this MAC index
473 my @tmp = split /\./, $macindex; # Build a MAC address
474 $self->{macindex}->{$macindex} = join "", @tmp; # save it
475 }
476
477 my $mac = $self->{macindex}->{$macindex};
478 my $bp = $res->{$macindex};
479
480 if( ! $self->{bpindex}->{$bp} ) { # No ifindex exists for this bridgeport - this happens when a MAC address is learned via the ARP table, or some other way
481 $self->{bpindex}->{$bp} = '(other)'; # build it
482 }
483
484 my $ifindex = $self->{bpindex}->{$bp};
485 $self->{cam}->{$mac} = $ifindex;
486 }
487
488 return keys %{$self->{cam}};
489}
490
491sub GetCam { # Given a MAC address, return the ifindex for the port it was learned on
492 my $self = shift;
493 my $p = shift;
494 return $self->{cam}->{$p};
495}
496
497sub desc { # for a given VLAN number, return the description
498 my $self = shift;
499 my $p = shift;
500
501 $self->{desc} = $p if defined $p;
502 return $self->{desc};
503}
504
505package port;
506
507sub new {
508 my $class = shift;
509 my $name = shift;
510
511 my $self = {};
512
513 bless ($self, $class);
514
515 $self->{name} = $name;
516 $self->{desc} = '';
517 $self->{mode} = '';
518 $self->{cdpn} = '';
519 $self->{status} = '';
520 $self->{vlan} = '';
521 $self->{pagp} = '';
522
523 return $self;
524}
525
526sub pagp {
527 my $self = shift;
528 my $p = shift;
529
530 $self->{pagp} = $p if defined $p;
531 return $self->{pagp};
532}
533
534sub status {
535 my $self = shift;
536 my $p = shift;
537
538 $self->{status} = $p if defined $p;
539 return $self->{status};
540}
541
542
543sub cdpn {
544 my $self = shift;
545 my $p = shift;
546
547 $self->{cdpn} = $p if defined $p;
548 return $self->{cdpn};
549}
550
551sub cdpnport {
552 my $self = shift;
553 my $p = shift;
554
555 $self->{cdpnport} = $p if defined $p;
556 return $self->{cdpnport};
557}
558
559sub vlan {
560 my $self = shift;
561 my $p = shift;
562
563 $self->{vlan} = $p if defined $p;
564 return $self->{vlan};
565}
566
567sub mode {
568 my $self = shift;
569 my $p = shift;
570
571 $self->{mode} = $p if defined $p;
572 return $self->{mode};
573}
574
575
576sub desc {
577 my $self = shift;
578 my $p = shift;
579
580 $self->{desc} = $p if defined $p;
581 return $self->{desc};
582}
583
584sub name {
585 my $self = shift;
586 my $p = shift;
587
588 $self->{name} = $p if defined $p;
589 return $self->{name};
590}
591
592
593package util;
594
595################################################################################
596#
597# Build a hash from an SNMP table. The key is created by taking the last $n
598# elements of the returned OID value.
599#
600################################################################################
601
602sub tablehash {
603 my $snmp = shift;
604 my $oid = shift;
605 my $n = shift;
606
607 my $ret = ();
608
609 my $res = $snmp->get_table(-baseoid => $oid, -maxrepetitions => 30);
610
611 foreach my $x ( keys %{$res} ) {
612 my @tmp = split /\./, $x;
613 my $key = join ".", splice(@tmp, -1 * $n);
614 $ret->{$key} = $res->{$x};
615 }
616
617 return $ret;
618}