import torrus 1.0.9
[freeside.git] / torrus / perllib / Torrus / DevDiscover / CiscoIOS.pm
1 #  Copyright (C) 2002  Stanislav Sinyagin
2 #
3 #  This program is free software; you can redistribute it and/or modify
4 #  it under the terms of the GNU General Public License as published by
5 #  the Free Software Foundation; either version 2 of the License, or
6 #  (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software
15 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
16
17 # $Id: CiscoIOS.pm,v 1.1 2010-12-27 00:03:47 ivan Exp $
18 # Stanislav Sinyagin <ssinyagin@yahoo.com>
19
20 # Cisco IOS devices discovery
21 # To do:
22 #   SA Agent MIB
23 #   DiffServ MIB
24
25 package Torrus::DevDiscover::CiscoIOS;
26
27 use strict;
28 use Torrus::Log;
29
30
31 $Torrus::DevDiscover::registry{'CiscoIOS'} = {
32     'sequence'     => 500,
33     'checkdevtype' => \&checkdevtype,
34     'discover'     => \&discover,
35     'buildConfig'  => \&buildConfig
36     };
37
38
39 our %oiddef =
40     (
41      # CISCO-SMI
42      'ciscoProducts'                     => '1.3.6.1.4.1.9.1',
43      # CISCO-PRODUCTS-MIB
44      'ciscoLS1010'                       => '1.3.6.1.4.1.9.1.107',
45      # CISCO-IMAGE-MIB
46      'ciscoImageTable'                   => '1.3.6.1.4.1.9.9.25.1.1',
47      # CISCO-ENHANCED-IMAGE-MIB
48      'ceImageTable'                      => '1.3.6.1.4.1.9.9.249.1.1.1',
49      # OLD-CISCO-MEMORY-MIB
50      'bufferElFree'                      => '1.3.6.1.4.1.9.2.1.9.0',
51      # CISCO-IPSEC-FLOW-MONITOR-MIB
52      'cipSecGlobalHcInOctets'            => '1.3.6.1.4.1.9.9.171.1.3.1.4.0',
53      # CISCO-BGP4-MIB
54      'cbgpPeerAddrFamilyName'            => '1.3.6.1.4.1.9.9.187.1.2.3.1.3',
55      'cbgpPeerAcceptedPrefixes'          => '1.3.6.1.4.1.9.9.187.1.2.4.1.1',
56      'cbgpPeerPrefixAdminLimit'          => '1.3.6.1.4.1.9.9.187.1.2.4.1.3',
57      # CISCO-CAR-MIB
58      'ccarConfigTable'                   => '1.3.6.1.4.1.9.9.113.1.1.1',
59      'ccarConfigType'                    => '1.3.6.1.4.1.9.9.113.1.1.1.1.3',
60      'ccarConfigAccIdx'                  => '1.3.6.1.4.1.9.9.113.1.1.1.1.4',
61      'ccarConfigRate'                    => '1.3.6.1.4.1.9.9.113.1.1.1.1.5',
62      'ccarConfigLimit'                   => '1.3.6.1.4.1.9.9.113.1.1.1.1.6',
63      'ccarConfigExtLimit'                => '1.3.6.1.4.1.9.9.113.1.1.1.1.7',
64      'ccarConfigConformAction'           => '1.3.6.1.4.1.9.9.113.1.1.1.1.8',
65      'ccarConfigExceedAction'            => '1.3.6.1.4.1.9.9.113.1.1.1.1.9',
66      # CISCO-VPDN-MGMT-MIB
67      'cvpdnSystemTunnelTotal'            => '1.3.6.1.4.1.9.10.24.1.1.4.1.2'
68      );
69
70
71 # Not all interfaces are normally needed to monitor.
72 # You may override the interface filtering in devdiscover-siteconfig.pl:
73 # redefine $Torrus::DevDiscover::CiscoIOS::interfaceFilter
74 # or define $Torrus::DevDiscover::CiscoIOS::interfaceFilterOverlay
75
76 our $interfaceFilter;
77 our $interfaceFilterOverlay;
78 my %ciscoInterfaceFilter;
79
80 if( not defined( $interfaceFilter ) )
81 {
82     $interfaceFilter = \%ciscoInterfaceFilter;
83 }
84
85
86 # Key is some unique symbolic name, does not mean anything
87 # ifType is the number to match the interface type
88 # ifDescr is the regexp to match the interface description
89 %ciscoInterfaceFilter =
90     (
91      'Null0' => {
92          'ifType'  => 1,                      # other
93          'ifDescr' => '^Null'
94          },
95
96      'E1 N/N/N' => {
97          'ifType'  => 18,                     # ds1
98          'ifDescr' => '^E1'
99          },
100
101      'Virtual-AccessN' => {
102          'ifType'  => 23,                     # ppp
103          'ifDescr' => '^Virtual-Access'
104          },
105      
106      'DialerN' => {
107          'ifType'  => 23,                     # ppp
108          'ifDescr' => '^Dialer'
109          },
110
111      'LoopbackN'  => {
112          'ifType'  => 24,                     # softwareLoopback
113          'ifDescr' => '^Loopback'
114          },
115
116      'SerialN:N-Bearer Channel' => {
117          'ifType'  => 81,                     #  ds0, Digital Signal Level 0
118          'ifDescr' => '^Serial.*Bearer\s+Channel'
119          },
120
121      'Voice Encapsulation (POTS) Peer: N' => {
122          'ifType'  => 103                     # voiceEncap
123          },
124
125      'Voice Over IP Peer: N' => {
126          'ifType'  => 104                     # voiceOverIp
127          },
128
129      'ATMN/N/N.N-atm subif' => {
130          'ifType'  => 134,                    # atmSubInterface
131          'ifDescr' => '^ATM[0-9\/]+\.[0-9]+\s+subif'
132          },
133      
134      'BundleN' => {
135          'ifType'  => 127,                    # docsCableMaclayer
136          'ifDescr' => '^Bundle'
137          },
138
139      'EOBCN/N' => {
140          'ifType'  => 53,                     # propVirtual
141          'ifDescr' => '^EOBC'
142          },
143
144      'FIFON/N' => {
145          'ifType'  => 53,                     # propVirtual
146          'ifDescr' => '^FIFO'
147          },
148      );
149
150 our %tunnelType =
151     (
152      # CISCO-VPDN-MGMT-MIB Tunnel Types
153      '1' => 'L2F',
154      '2' => 'L2TP',
155      '3' => 'PPTP'
156     );
157
158
159 sub checkdevtype
160 {
161     my $dd = shift;
162     my $devdetails = shift;
163
164     if( not $dd->oidBaseMatch
165         ( 'ciscoProducts',
166           $devdetails->snmpVar( $dd->oiddef('sysObjectID') ) ) )
167     {
168         return 0;
169     }
170
171     my $session = $dd->session();
172     if( not $dd->checkSnmpTable('ciscoImageTable') )
173     {
174         if( $dd->checkSnmpTable('ceImageTable') )
175         {
176             # IOS XR has a new MIB for software image management
177             $devdetails->setCap('CiscoIOSXR');            
178         }
179         else
180         {
181             return 0;
182         }
183     }
184
185     # On some Layer3 switching devices, VlanXXX interfaces give some
186     # useful stats, while on others the stats are not relevant at all
187     
188     if( $devdetails->param('CiscoIOS::enable-vlan-interfaces') ne 'yes' )
189     {
190         $interfaceFilter->{'VlanN'} = {
191             'ifType'  => 53,                     # propVirtual
192             'ifDescr' => '^Vlan\d+'
193             };
194     }
195
196     # same thing with unrouted VLAN interfaces
197     if( $devdetails->param('CiscoIOS::enable-unrouted-vlan-interfaces')
198         ne 'yes' )
199     {
200         $interfaceFilter->{'unrouted VLAN N'} => {
201             'ifType'  => 53,                     # propVirtual
202             'ifDescr' => '^unrouted\s+VLAN\s+\d+'
203             };
204     }
205     
206     &Torrus::DevDiscover::RFC2863_IF_MIB::addInterfaceFilter
207         ($devdetails, $interfaceFilter);
208
209     if( defined( $interfaceFilterOverlay ) )
210     {
211         &Torrus::DevDiscover::RFC2863_IF_MIB::addInterfaceFilter
212             ($devdetails, $interfaceFilterOverlay);
213     }
214
215     $devdetails->setCap('interfaceIndexingManaged');
216
217     return 1;
218 }
219
220
221 my %ccarConfigType =
222     ( 1 => 'all',
223       2 => 'quickAcc',
224       3 => 'standardAcc' );
225
226 my %ccarAction =
227     ( 1 => 'drop',
228       2 => 'xmit',
229       3 => 'continue',
230       4 => 'precedXmit',
231       5 => 'precedCont' );
232                       
233
234
235 sub discover
236 {
237     my $dd = shift;
238     my $devdetails = shift;
239
240     my $session = $dd->session();
241     my $data = $devdetails->data();
242
243     # Old mkroutercfg used cisco-interface-counters
244     if( $Torrus::DevDiscover::CiscoIOS::useCiscoInterfaceCounters )
245     {
246         foreach my $interface ( values %{$data->{'interfaces'}} )
247         {
248             $interface->{'hasHCOctets'} = 0;
249             $interface->{'hasOctets'} = 0;
250             push( @{$interface->{'templates'}},
251                   'CiscoIOS::cisco-interface-counters' );
252         }
253     }
254     else
255     {
256         # This is a well-known bug in IOS: HC counters are implemented,
257         # but always zero. We can catch this only for active interfaces.
258
259         foreach my $ifIndex ( sort {$a<=>$b} keys %{$data->{'interfaces'}} )
260         {
261             my $interface = $data->{'interfaces'}{$ifIndex};
262
263             if( $interface->{'hasHCOctets'} and
264                 ( (
265                    $devdetails->snmpVar( $dd->oiddef('ifHCInOctets')
266                                          . '.' . $ifIndex ) == 0 and
267                    $devdetails->snmpVar( $dd->oiddef('ifInOctets')
268                                          . '.' . $ifIndex ) > 0
269                    )
270                   or
271                   (
272                    $devdetails->snmpVar( $dd->oiddef('ifHCOutOctets')
273                                          . '.' . $ifIndex ) == 0 and
274                    $devdetails->snmpVar( $dd->oiddef('ifOutOctets')
275                                          . '.' . $ifIndex ) > 0
276                    ) ) )
277             {
278                 Debug('Disabling HC octets for ' . $ifIndex . ': ' .
279                       $interface->{'ifDescr'});
280
281                 $interface->{'hasHCOctets'} = 0;
282                 $interface->{'hasHCUcastPkts'} = 0;
283             }
284         }
285     }
286
287     if( $devdetails->param('CiscoIOS::enable-membuf-stats') eq 'yes' )
288     {
289         # Old Memory Buffers, if we have bufferElFree we assume
290         # the rest as they are "required"
291
292         if( $dd->checkSnmpOID('bufferElFree') )
293         {
294             $devdetails->setCap('old-ciscoMemoryBuffers');
295             push( @{$data->{'templates'}},
296                   'CiscoIOS::old-cisco-memory-buffers' );
297         }
298     }
299
300     if( $devdetails->param('CiscoIOS::disable-ipsec-stats') ne 'yes' )
301     {
302         if( $dd->checkSnmpOID('cipSecGlobalHcInOctets') )
303         {
304             $devdetails->setCap('ciscoIPSecGlobalStats');
305             push( @{$data->{'templates'}},
306                   'CiscoIOS::cisco-ipsec-flow-globals' );
307         }
308         
309         if( $dd->oidBaseMatch
310             ( 'ciscoLS1010',
311               $devdetails->snmpVar( $dd->oiddef('sysObjectID') ) ) )
312         {
313             $data->{'param'}{'snmp-oids-per-pdu'} = 10;
314         }
315     }
316
317     if( $devdetails->param('CiscoIOS::disable-bgp-stats') ne 'yes' )
318     {
319         my $peerTable =
320             $session->get_table( -baseoid =>
321                                  $dd->oiddef('cbgpPeerAcceptedPrefixes') );
322         if( defined( $peerTable ) and scalar( %{$peerTable} ) > 0 )
323         {
324             $devdetails->storeSnmpVars( $peerTable );
325             $devdetails->setCap('CiscoBGP');
326
327             my $limitsTable =
328                 $session->get_table( -baseoid =>
329                                      $dd->oiddef('cbgpPeerPrefixAdminLimit') );
330             $limitsTable = {} if not defined( $limitsTable );
331             
332             $data->{'cbgpPeers'} = {};
333             
334             # retrieve AS numbers for neighbor peers
335             Torrus::DevDiscover::RFC1657_BGP4_MIB::discover($dd, $devdetails);
336             
337             # list of indices for peers that are not IPv4 Unicast
338             my @nonV4Unicast;
339
340             # Number of peers for each AS
341             my %asNumbers;    
342
343             foreach my $INDEX
344                 ( $devdetails->
345                   getSnmpIndices( $dd->oiddef('cbgpPeerAcceptedPrefixes') ) )
346             {
347                 my ($a1, $a2, $a3, $a4, $afi, $safi) = split(/\./, $INDEX);
348                 my $peerIP = join('.', $a1, $a2, $a3, $a4);
349
350                 my $peer = {
351                     'peerIP' => $peerIP,
352                     'addrFamily' => 'IPv4 Unicast'
353                     };
354                 
355                 if( $afi != 1 and $safi != 1 )
356                 {
357                     push( @nonV4Unicast, $INDEX );
358                 }
359
360                 my $desc =
361                     $devdetails->param('peer-ipaddr-description-' .
362                                        join('_', split('\.', $peerIP)));
363                 if( length( $desc ) > 0 )
364                 {
365                     $peer->{'description'} = $desc;
366                 }        
367                 
368                 my $peerAS = $data->{'bgpPeerAS'}{$peerIP};
369                 if( defined( $peerAS ) )
370                 {
371                     $peer->{'peerAS'} = $data->{'bgpPeerAS'}{$peerIP};
372                     $asNumbers{$peer->{'peerAS'}}++;
373
374                     my $desc =
375                         $devdetails->param('bgp-as-description-' . $peerAS);
376                     if( length( $desc ) > 0 )
377                     {
378                         if( defined( $peer->{'description'} ) )
379                         {
380                             Warn('Conflicting descriptions for peer ' .
381                                  $peerIP);
382                         }
383                         $peer->{'description'} = $desc;
384                     }
385                 }
386                 else
387                 {
388                     Error('Cannot find AS number for BGP peer ' . $peerIP);
389                     next;
390                 }
391
392                 if( defined( $peer->{'description'} ) )
393                 {
394                     $peer->{'description'} .= ' ';
395                 }
396                 $peer->{'description'} .= '[' . $peerIP . ']';
397
398                 $peer->{'prefixLimit'} =
399                     $limitsTable->{$dd->oiddef('cbgpPeerPrefixAdminLimit') .
400                                        '.' . $INDEX};
401                     
402                 $data->{'cbgpPeers'}{$INDEX} = $peer;                
403             }
404
405             if( scalar( @nonV4Unicast ) > 0 )
406             {
407                 my $addrFamTable =
408                     $session->get_table
409                     ( -baseoid => $dd->oiddef('cbgpPeerAddrFamilyName') );
410                 
411                 foreach my $INDEX ( @nonV4Unicast )
412                 {
413                     my $peer = $data->{'cbgpPeers'}{$INDEX};
414
415                     my $fam = $addrFamTable->{
416                         $dd->oiddef('cbgpPeerAddrFamilyName') .
417                             '.' . $INDEX};
418
419                     $peer->{'addrFamily'} = $fam;
420                     $peer->{'otherAddrFamily'} = 1;
421                     $peer->{'description'} .= ' ' . $fam;
422                 }
423             }
424
425             # Construct the subtree names from AS, peer IP, and address
426             # family
427             foreach my $INDEX ( keys %{$data->{'cbgpPeers'}} )
428             {
429                 my $peer = $data->{'cbgpPeers'}{$INDEX};
430                 
431                 my $subtreeName = 'AS' . $peer->{'peerAS'};
432                 if( $asNumbers{$peer->{'peerAS'}} > 1 )
433                 {
434                     $subtreeName .= '_' . $peer->{'peerIP'};
435                 }
436                 
437                 if( $peer->{'otherAddrFamily'} )
438                 {
439                     my $fam = $data->{'cbgpPeers'}{$INDEX}{'addrFamily'};
440                     $fam =~ s/\W/_/g;
441                     $subtreeName .= '_' . $fam;
442                 }
443                 
444                 $peer->{'subtreeName'} = $subtreeName;
445             }
446         }
447     }
448
449     
450     if( $devdetails->param('CiscoIOS::disable-car-stats') ne 'yes' )
451     {
452         my $carTable =
453             $session->get_table( -baseoid =>
454                                  $dd->oiddef('ccarConfigTable') );
455         if( defined( $carTable ) and scalar( %{$carTable} ) > 0 )
456         {
457             $devdetails->storeSnmpVars( $carTable );
458             $devdetails->setCap('CiscoCAR');
459
460             $data->{'ccar'} = {};
461
462             foreach my $INDEX
463                 ( $devdetails->
464                   getSnmpIndices( $dd->oiddef('ccarConfigType') ) )
465             {
466                 my ($ifIndex, $dir, $carIndex) = split(/\./, $INDEX);
467                 my $interface = $data->{'interfaces'}{$ifIndex};
468
469                 my $car = {
470                     'ifIndex'   => $ifIndex,
471                     'direction' => $dir,
472                     'carIndex'  => $carIndex };
473
474                 $car->{'configType'} =
475                     $ccarConfigType{ $carTable->{$dd->oiddef
476                                                      ('ccarConfigType') .
477                                                      '.' . $INDEX} };
478
479                 $car->{'accIdx'} = $carTable->{$dd->oiddef
480                                                    ('ccarConfigAccIdx') .
481                                                    '.' . $INDEX};
482                 
483                 $car->{'rate'} = $carTable->{$dd->oiddef
484                                                  ('ccarConfigRate') .
485                                                  '.' . $INDEX};
486
487                 
488                 $car->{'limit'} = $carTable->{$dd->oiddef
489                                                   ('ccarConfigLimit') .
490                                                   '.' . $INDEX};
491                 
492                 $car->{'extLimit'} = $carTable->{$dd->oiddef
493                                                      ('ccarConfigExtLimit') .
494                                                      '.' . $INDEX};
495                 $car->{'conformAction'} =
496                     $ccarAction{ $carTable->{$dd->oiddef
497                                                  ('ccarConfigConformAction') .
498                                                  '.' . $INDEX} };
499                 
500                 $car->{'exceedAction'} =
501                     $ccarAction{ $carTable->{$dd->oiddef
502                                                  ('ccarConfigExceedAction') .
503                                                  '.' . $INDEX} };
504
505                 $data->{'ccar'}{$INDEX} = $car;
506             }
507         }
508     }
509
510
511     if( $devdetails->param('CiscoIOS::disable-vpdn-stats') ne 'yes' )
512     {
513         if( $dd->checkSnmpTable( 'cvpdnSystemTunnelTotal' ) )
514         {
515             # Find the Tunnel type
516             my $tableTun = $session->get_table(
517                             -baseoid => $dd->oiddef('cvpdnSystemTunnelTotal') );
518
519             if( $tableTun )
520             {
521                 $devdetails->setCap('ciscoVPDN');
522
523                 $devdetails->storeSnmpVars( $tableTun );
524
525                 # VPDN indexing: 1: l2f, 2: l2tp, 3: pptp
526                 foreach my $typeIndex (
527                             $devdetails->getSnmpIndices(
528                               $dd->oiddef('cvpdnSystemTunnelTotal') ) )
529                 {
530                     Debug("CISCO-VPDN-MGMT-MIB: found Tunnel type " .
531                            $tunnelType{$typeIndex} );
532
533                     $data->{'ciscoVPDN'}{$typeIndex} = $tunnelType{$typeIndex};
534                 }
535             }
536         }
537     }
538
539     if( $devdetails->param('CiscoIOS::short-device-comment') eq 'yes' )
540     {
541         # Remove serials from device comment
542         # 1841 chassis, Hw Serial#: 3625140487, Hw Revision: 6.0
543         
544         $data->{'param'}{'comment'} =~ s/, Hw.*//o;        
545     }
546
547     return 1;
548 }
549
550
551 sub buildConfig
552 {
553     my $devdetails = shift;
554     my $cb = shift;
555     my $devNode = shift;
556
557     my $data = $devdetails->data();
558
559     if( $devdetails->hasCap('CiscoBGP') )
560     {
561         my $countersNode =
562             $cb->addSubtree( $devNode, 'BGP_Prefixes',
563                              {
564                                  'node-display-name' => 'BGP Prefixes',
565                                  'comment' => 'Accepted prefixes',
566                              } );
567
568         foreach my $INDEX ( sort
569                             { $data->{'cbgpPeers'}{$a}{'subtreeName'} <=>
570                                   $data->{'cbgpPeers'}{$b}{'subtreeName'} }
571                             keys %{$data->{'cbgpPeers'}} )
572         {
573             my $peer = $data->{'cbgpPeers'}{$INDEX};
574
575             my $param = {
576                 'peer-index'           => $INDEX,
577                 'peer-ipaddr'          => $peer->{'peerIP'},
578                 'comment'              => $peer->{'description'},
579                 'descriptive-nickname' => $peer->{'subtreeName'},
580                 'precedence'           => 65000 - $peer->{'peerAS'}
581             };
582
583             if( defined( $peer->{'prefixLimit'} ) and
584                 $peer->{'prefixLimit'} > 0 )
585             {
586                 $param->{'upper-limit'} = $peer->{'prefixLimit'};
587                 $param->{'graph-upper-limit'} = $peer->{'prefixLimit'} * 1.03;
588             }
589             
590             $cb->addLeaf
591                 ( $countersNode, $peer->{'subtreeName'}, $param,
592                   ['CiscoIOS::cisco-bgp'] );
593         }
594     }
595
596     
597     if( $devdetails->hasCap('CiscoCAR') )
598     {
599         my $countersNode =
600             $cb->addSubtree( $devNode, 'CAR_Stats', {
601                 'comment' => 'Committed Access Rate statistics',
602                 'node-display-name' => 'CAR', },
603                              ['CiscoIOS::cisco-car-subtree']);
604         
605         foreach my $INDEX ( sort keys %{$data->{'ccar'}} )
606         {
607             my $car = $data->{'ccar'}{$INDEX};
608             my $interface = $data->{'interfaces'}{$car->{'ifIndex'}};
609             
610             my $subtreeName =
611                 $interface->{$data->{'nameref'}{'ifSubtreeName'}};
612
613             $subtreeName .= ($car->{'direction'} == 1) ? '_IN':'_OUT';
614             if( $car->{'carIndex'} > 1 )
615             {
616                 $subtreeName .= '_' . $car->{'carIndex'};
617             }
618                
619             my $param = {
620                 'searchable' => 'yes',
621                 'car-direction' => $car->{'direction'},
622                 'car-index' => $car->{'carIndex'} };
623                 
624             $param->{'interface-name'} =
625                 $interface->{'param'}{'interface-name'};            
626             $param->{'interface-nick'} =
627                 $interface->{'param'}{'interface-nick'};            
628             $param->{'comment'} =
629                 $interface->{'param'}{'comment'};
630
631             my $legend = sprintf("Type: %s;", $car->{'configType'});
632             if( $car->{'accIdx'} > 0 )
633             {
634                 $legend .= sprintf("Access list: %d;", $car->{'accIdx'});
635             }
636             
637             $legend .=
638                 sprintf("Rate: %d bps; Limit: %d bytes; Ext limit: %d bytes;" .
639                         "Conform action: %s; Exceed action: %s",
640                         $car->{'rate'},
641                         $car->{'limit'},
642                         $car->{'extLimit'},
643                         $car->{'conformAction'},
644                         $car->{'exceedAction'});
645
646             $param->{'legend'} = $legend;
647
648             $cb->addSubtree
649                 ( $countersNode,
650                   $subtreeName,
651                   $param, 
652                   ['CiscoIOS::cisco-car']);
653         }
654     }
655
656
657     if( $devdetails->hasCap('ciscoVPDN') )
658     { 
659         my $tunnelNode = $cb->addSubtree
660             ( $devNode, 'VPDN_Statistics',
661               {'node-display-name' => 'VPDN Statistics'},
662               [ 'CiscoIOS::cisco-vpdn-subtree' ] );
663
664         foreach my $INDEX ( sort keys %{$data->{'ciscoVPDN'}} )
665         {
666             my $tunnelProtocol = $data->{'ciscoVPDN'}{$INDEX};
667
668             $cb->addSubtree( $tunnelNode, $tunnelProtocol,
669                 { 'comment'  => $tunnelProtocol . ' information',
670                   'tunIndex' => $INDEX,
671                   'tunFile'  => lc($tunnelProtocol) },
672                 [ 'CiscoIOS::cisco-vpdn-leaf' ] );
673         }
674     }
675 }
676
677
678
679
680 1;
681
682
683 # Local Variables:
684 # mode: perl
685 # indent-tabs-mode: nil
686 # perl-indent-level: 4
687 # End: