623 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
			
		
		
	
	
			623 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
| package App::Netdisco::Util::Snapshot;
 | ||
| 
 | ||
| use Dancer qw/:syntax :script !to_json !from_json/;
 | ||
| use Dancer::Plugin::DBIC 'schema';
 | ||
| 
 | ||
| use App::Netdisco::Util::SNMP 'sortable_oid';
 | ||
| 
 | ||
| use File::Spec::Functions qw/splitdir catdir catfile/;
 | ||
| use MIME::Base64 qw/encode_base64 decode_base64/;
 | ||
| use File::Slurper qw/read_lines read_text/;
 | ||
| use Sub::Util 'subname';
 | ||
| use Storable qw/dclone nfreeze thaw/;
 | ||
| use Scalar::Util 'blessed';
 | ||
| use Module::Load ();
 | ||
| use SNMP::Info;
 | ||
| 
 | ||
| use base 'Exporter';
 | ||
| our @EXPORT = ();
 | ||
| our @EXPORT_OK = qw/
 | ||
|   load_cache_for_device
 | ||
|   snmpwalk_to_cache
 | ||
| 
 | ||
|   gather_every_mib_object
 | ||
|   dump_cache_to_browserdata
 | ||
|   add_snmpinfo_aliases
 | ||
| 
 | ||
|   get_oidmap_from_database
 | ||
|   get_oidmap_from_mibs_files
 | ||
|   get_mibs_for
 | ||
|   get_munges
 | ||
| /;
 | ||
| our %EXPORT_TAGS = (all => \@EXPORT_OK);
 | ||
| 
 | ||
| =head1 NAME
 | ||
| 
 | ||
| App::Netdisco::Util::Snapshot
 | ||
| 
 | ||
| =head1 DESCRIPTION
 | ||
| 
 | ||
| Helper functions for L<SNMP::Info> instances.
 | ||
| 
 | ||
| There are no default exports, however the C<:all> tag will export all
 | ||
| subroutines.
 | ||
| 
 | ||
| =head1 EXPORT_OK
 | ||
| 
 | ||
| =head2 load_cache_for_device( $device )
 | ||
| 
 | ||
| Tries to find a device cache in database or on disk, or build one from
 | ||
| a net-snmp snmpwalk on disk. Returns a cache.
 | ||
| 
 | ||
| =cut
 | ||
| 
 | ||
| sub load_cache_for_device {
 | ||
|   my $device = shift;
 | ||
|   return {} unless ($device->is_pseudo or not $device->in_storage);
 | ||
| 
 | ||
|   # ideally we have a cache in the db
 | ||
|   if ($device->is_pseudo and my $snapshot = $device->snapshot) {
 | ||
|       return thaw( decode_base64( $snapshot->cache ) );
 | ||
|   }
 | ||
| 
 | ||
|   # or we have a file on disk - could be cache or walk
 | ||
|   my $pseudo_cache = catfile( catdir(($ENV{NETDISCO_HOME} || $ENV{HOME}), 'logs', 'snapshots'), $device->ip );
 | ||
|   if (-f $pseudo_cache and not $device->in_storage) {
 | ||
|       my $content = read_text($pseudo_cache);
 | ||
| 
 | ||
|       if ($content =~ m/^\.1/) {
 | ||
|           my %oids = ();
 | ||
| 
 | ||
|           # parse the snmpwalk output which looks like
 | ||
|           # .1.0.8802.1.1.2.1.1.1.0 = INTEGER: 30
 | ||
|           my @lines = split /\n/, $content;
 | ||
|           foreach my $line (@lines) {
 | ||
|               my ($oid, $val) = $line =~ m/^(\S+) = (?:[^:]+: )?(.+)$/;
 | ||
|               next unless $oid and $val;
 | ||
| 
 | ||
|               # empty string makes the capture go wonky
 | ||
|               $val = '' if $val =~ m/^[^:]+: ?$/;
 | ||
| 
 | ||
|               # remove quotes from strings
 | ||
|               $val =~ s/^"//;
 | ||
|               $val =~ s/"$//;
 | ||
| 
 | ||
|               $oids{$oid} = $val;
 | ||
|           }
 | ||
| 
 | ||
|           return snmpwalk_to_cache(%oids);
 | ||
|       }
 | ||
|       else {
 | ||
|           my $cache = thaw( decode_base64( $content ) );
 | ||
|           return add_snmpinfo_aliases( $cache );
 | ||
|       }
 | ||
| 
 | ||
|       # there is a late phase discover worker to generate the oids
 | ||
|       # and also to save the cache into the database, because we want
 | ||
|       # to wait for device-specific SNMP::Info class and all its methods.
 | ||
|   }
 | ||
| 
 | ||
|   return {};
 | ||
| }
 | ||
| 
 | ||
| =head2 snmpwalk_to_cache ( %oids )
 | ||
| 
 | ||
| Take the snmpwalk of the device which is numeric (no MIB translateObj),
 | ||
| resolve to MIB identifiers using netdisco-mibs data, then return as an
 | ||
| SNMP::Info instance cache.
 | ||
| 
 | ||
| =cut
 | ||
| 
 | ||
| sub snmpwalk_to_cache {
 | ||
|   my %oids = @_;
 | ||
|   return () unless scalar keys %oids;
 | ||
| 
 | ||
|   my %oidmap = reverse get_oidmap_from_database();
 | ||
|   my %leaves = ();
 | ||
| 
 | ||
|   OID: foreach my $orig_oid (keys %oids) {
 | ||
|       my $oid = $orig_oid;
 | ||
|       my $idx = '';
 | ||
| 
 | ||
|       while (length($oid) and !exists $oidmap{$oid}) {
 | ||
|           $oid =~ s/\.(\d+)$//;
 | ||
|           $idx = ((defined $idx and length $idx) ? "${1}.${idx}" : $1);
 | ||
|       }
 | ||
| 
 | ||
|       if (exists $oidmap{$oid}) {
 | ||
|           $idx =~ s/^\.//;
 | ||
|           my $qleaf = $oidmap{$oid};
 | ||
|           my $key = $oid .'~~'. $qleaf;
 | ||
| 
 | ||
|           if ($idx eq 0) {
 | ||
|               $leaves{$key} = $oids{$orig_oid};
 | ||
|           }
 | ||
|           else {
 | ||
|               # on rare occasions a vendor returns .0 and .something
 | ||
|               delete $leaves{$key}
 | ||
|                 if defined $leaves{$key} and ref q{} eq $leaves{$key};
 | ||
|               $leaves{$key}->{$idx} = $oids{$orig_oid};
 | ||
|           }
 | ||
| 
 | ||
|           # debug "snapshot $device - cached $oidmap{$oid}($idx) from $orig_oid";
 | ||
|           next OID;
 | ||
|       }
 | ||
| 
 | ||
|       # this is not too surprising
 | ||
|       # debug sprintf "cache builder - error:  missing OID %s in netdisco-mibs", $orig_oid;
 | ||
|   }
 | ||
| 
 | ||
|   my $info = SNMP::Info->new({
 | ||
|     Offline => 1,
 | ||
|     Cache => {},
 | ||
|     AutoSpecify => 0,
 | ||
|     Session => {},
 | ||
|   });
 | ||
| 
 | ||
|   foreach my $attr (keys %leaves) {
 | ||
|       my ($oid, $qleaf) = split m/~~/, $attr;
 | ||
|       my $val = $leaves{$attr};
 | ||
| 
 | ||
|       # resolve the enums if needed
 | ||
|       my $row = schema('netdisco')->resultset('SNMPObject')->find($oid);
 | ||
|       if ($row and $row->enum) {
 | ||
|           my %emap = map { reverse split m/\(/ }
 | ||
|                      map { s/\)//; $_ }
 | ||
|                      @{ $row->enum };
 | ||
| 
 | ||
|           if (ref q{} eq ref $val) {
 | ||
|               $val = $emap{$val} if exists $emap{$val};
 | ||
|           }
 | ||
|           elsif (ref {} eq ref $val) {
 | ||
|               foreach my $k (keys %$val) {
 | ||
|                   $val->{$k} = $emap{ $val->{$k} }
 | ||
|                     if exists $emap{ $val->{$k} };
 | ||
|               }
 | ||
|           }
 | ||
|       }
 | ||
| 
 | ||
|       my $leaf = $qleaf;
 | ||
|       $leaf =~ s/.+:://;
 | ||
| 
 | ||
|       my $snmpqleaf = $qleaf;
 | ||
|       $snmpqleaf =~ s/[-:]/_/g;
 | ||
| 
 | ||
|       # do we need this ?? $info->_cache($oid,  $leaves{$attr});
 | ||
|       $info->_cache($leaf, $leaves{$attr});
 | ||
|       $info->_cache($snmpqleaf, $leaves{$attr});
 | ||
|   }
 | ||
| 
 | ||
|   debug sprintf "snmpwalk_to_cache: cache size: %d", scalar keys %{ $info->cache };
 | ||
| 
 | ||
|   # inject a basic set of SNMP::Info globals and funcs aliases
 | ||
|   # which are needed for initial device discovery
 | ||
|   add_snmpinfo_aliases($info);
 | ||
| 
 | ||
|   return $info->cache;
 | ||
| }
 | ||
| 
 | ||
| =head2 gather_every_mib_object( $device, $snmp, @extramibs? )
 | ||
| 
 | ||
| Gathers evey MIB Object in the MIBs loaded for the device and store
 | ||
| to the database for SNMP browser.
 | ||
| 
 | ||
| Optionally add a list of vendors, MIBs, or SNMP:Info class for extra MIB
 | ||
| Objects from the netdisco-mibs bundle.
 | ||
| 
 | ||
| The passed SNMP::Info instance has its cache update with the data.
 | ||
| 
 | ||
| =cut
 | ||
| 
 | ||
| sub gather_every_mib_object {
 | ||
|   my ($device, $snmp, @extra) = @_;
 | ||
| 
 | ||
|   # get MIBs loaded for device
 | ||
|   my @mibs = keys %{ $snmp->mibs() };
 | ||
|   my @extra_mibs = get_mibs_for(@extra);
 | ||
|   debug sprintf "covering %d MIBs", (scalar @mibs + scalar @extra_mibs);
 | ||
|   SNMP::loadModules($_) for @extra_mibs;
 | ||
| 
 | ||
|   # get qualified leafs for those MIBs from snmp_object
 | ||
|   my %oidmap = get_oidmap_from_database(@mibs, @extra_mibs);
 | ||
|   debug sprintf "gathering %d MIB Objects", scalar keys %oidmap;
 | ||
| 
 | ||
|   foreach my $qleaf (sort {sortable_oid($oidmap{$a}) cmp sortable_oid($oidmap{$b})} keys %oidmap) {
 | ||
|       my $leaf = $qleaf;
 | ||
|       $leaf =~ s/.+:://;
 | ||
| 
 | ||
|       my $snmpqleaf = $qleaf;
 | ||
|       $snmpqleaf =~ s/[-:]/_/g;
 | ||
| 
 | ||
|       # gather the leaf
 | ||
|       $snmp->$snmpqleaf;
 | ||
| 
 | ||
|       # skip any leaf which did not return data from device
 | ||
|       # this works for both funcs and globals as funcs create stub global
 | ||
|       next unless exists $snmp->cache->{'_'. $snmpqleaf};
 | ||
| 
 | ||
|       # store a short name alias which is needed for netdisco actions
 | ||
|       $snmp->_cache($leaf, $snmp->$snmpqleaf);
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| =head2 dump_cache_to_browserdata( $device, $snmp )
 | ||
| 
 | ||
| Dumps any valid MIB leaf from the passed SNMP::Info instance's cache into
 | ||
| the Netdisco database SNMP Browser table.
 | ||
| 
 | ||
| Ideally the leafs are fully qualified, but if not then a best effort will
 | ||
| be made to find their correct MIB.
 | ||
| 
 | ||
| =cut
 | ||
| 
 | ||
| sub dump_cache_to_browserdata {
 | ||
|   my ($device, $snmp) = @_;
 | ||
| 
 | ||
|   my %qoidmap = get_oidmap_from_database();
 | ||
|   my %oidmap  = get_leaf_to_qleaf_map();
 | ||
|   my %munges  = get_munges($snmp);
 | ||
| 
 | ||
|   my $cache = $snmp->cache;
 | ||
|   my %oids = ();
 | ||
| 
 | ||
|   foreach my $key (keys %$cache) {
 | ||
|       next unless $key and $key =~ m/^_/;
 | ||
| 
 | ||
|       my $snmpqleaf = $key;
 | ||
|       $snmpqleaf =~ s/^_//;
 | ||
| 
 | ||
|       my $qleaf = $snmpqleaf;
 | ||
|       $qleaf =~ s/__/::/;
 | ||
|       $qleaf =~ s/_/-/g;
 | ||
| 
 | ||
|       my $leaf = $qleaf;
 | ||
|       $leaf =~ s/.+:://;
 | ||
| 
 | ||
|       next unless exists $qoidmap{$qleaf}
 | ||
|                   or (exists $oidmap{$leaf} and exists $qoidmap{ $oidmap{$leaf} });
 | ||
| 
 | ||
|       my $oid  = exists $qoidmap{$qleaf} ? $qoidmap{$qleaf} : $qoidmap{ $oidmap{$leaf} };
 | ||
|       my $data = exists $cache->{'store'}{$snmpqleaf} ? $cache->{'store'}{$snmpqleaf}
 | ||
|                                                       : $cache->{$key};
 | ||
|       next unless defined $data;
 | ||
| 
 | ||
|       push @{ $oids{$oid} }, {
 | ||
|         oid => $oid,
 | ||
|         oid_parts => [ grep {length} (split m/\./, $oid) ],
 | ||
|         leaf  => $leaf,
 | ||
|         qleaf => $qleaf,
 | ||
|         munge => ($munges{$snmpqleaf} || $munges{$leaf}),
 | ||
|         value => encode_base64( nfreeze( [$data] ) ),
 | ||
|       };
 | ||
|   }
 | ||
| 
 | ||
|   %oids = map { ($_ => [sort {length($b->{qleaf}) <=> length($a->{qleaf})} @{ $oids{$_} }]) }
 | ||
|           keys %oids;
 | ||
| 
 | ||
|   schema('netdisco')->txn_do(sub {
 | ||
|     my $gone = $device->oids->delete;
 | ||
|     debug sprintf 'removed %d oids from db', $gone;
 | ||
|     $device->oids->populate([ sort {sortable_oid($a->{oid}) cmp sortable_oid($b->{oid})}
 | ||
|                               map  { delete $_->{qleaf}; $_ }
 | ||
|                               map  { $oids{$_}->[0] } keys %oids ]);
 | ||
|     debug sprintf 'added %d new oids to db', scalar keys %oids;
 | ||
|   });
 | ||
| }
 | ||
| 
 | ||
| =head2 add_snmpinfo_aliases( $snmp_info_instance | $snmp_info_cache )
 | ||
| 
 | ||
| Add in any GLOBALS and FUNCS aliases from the SNMP::Info device class
 | ||
| or else a set of defaults that allow device discovery. Returns the cache.
 | ||
| 
 | ||
| =cut
 | ||
| 
 | ||
| sub add_snmpinfo_aliases {
 | ||
|   my $info = shift or return {};
 | ||
| 
 | ||
|   if (not blessed $info) {
 | ||
|       $info = SNMP::Info->new({
 | ||
|         Offline => 1,
 | ||
|         Cache => $info,
 | ||
|         AutoSpecify => 0,
 | ||
|         Session => {},
 | ||
|       });
 | ||
|   }
 | ||
| 
 | ||
|   my %globals = %{ $info->globals };
 | ||
|   my %funcs   = %{ $info->funcs };
 | ||
| 
 | ||
|   while (my ($alias, $leaf) = each %globals) {
 | ||
|       next if $leaf =~ m/\.\d+$/;
 | ||
|       $info->_cache($alias, $info->$leaf) if $info->$leaf;
 | ||
|   }
 | ||
| 
 | ||
|   while (my ($alias, $leaf) = each %funcs) {
 | ||
|       $info->_cache($alias, dclone $info->$leaf) if ref q{} ne ref $info->$leaf;
 | ||
|   }
 | ||
| 
 | ||
|   # SNMP::Info::Layer3 has some weird aliases we can fix here
 | ||
|   $info->_cache('serial1',      $info->chassisId->{''})         if ref {} eq ref $info->chassisId;
 | ||
|   $info->_cache('router_ip',    $info->ospfRouterId->{''})      if ref {} eq ref $info->ospfRouterId;
 | ||
|   $info->_cache('bgp_id',       $info->bgpIdentifier->{''})     if ref {} eq ref $info->bgpIdentifier;
 | ||
|   $info->_cache('bgp_local_as', $info->bgpLocalAs->{''})        if ref {} eq ref $info->bgpLocalAs;
 | ||
|   $info->_cache('sysUpTime',    $info->sysUpTimeInstance->{''}) if ref {} eq ref $info->sysUpTimeInstance
 | ||
|                                                                    and not $info->sysUpTime;
 | ||
|   $info->_cache('mac',          $info->ifPhysAddress->{1})      if ref {} eq ref $info->ifPhysAddress;
 | ||
| 
 | ||
|   # now for any other SNMP::Info method in GLOBALS or FUNCS which Netdisco
 | ||
|   # might call, but will not have data, we fake a cache entry to avoid
 | ||
|   # throwing errors
 | ||
| 
 | ||
|   while (my $method = <DATA>) {
 | ||
|     $method =~ s/\s//g;
 | ||
|     next unless length $method and not $info->$method;
 | ||
| 
 | ||
|     $info->_cache($method, '') if exists $globals{$method};
 | ||
|     $info->_cache($method, {}) if exists $funcs{$method};
 | ||
|   }
 | ||
| 
 | ||
|   debug sprintf "add_snmpinfo_aliases: cache size: %d", scalar keys %{ $info->cache };
 | ||
|   return $info->cache;
 | ||
| }
 | ||
| 
 | ||
| =head2 get_leaf_to_qleaf_map( )
 | ||
| 
 | ||
| =cut
 | ||
| 
 | ||
| sub get_leaf_to_qleaf_map {
 | ||
|   debug "loading database leaf to qleaf map";
 | ||
| 
 | ||
|   my %oidmap = map { ( $_->{leaf} => (join '::', $_->{mib}, $_->{leaf}) ) }
 | ||
|                schema('netdisco')->resultset('SNMPObject')
 | ||
|                                  ->search({
 | ||
|                                      num_children => 0,
 | ||
|                                      leaf => { '!~' => 'anonymous#\d+$' },
 | ||
|                                      -or => [
 | ||
|                                        type   => { '<>' => '' },
 | ||
|                                        access => { '~' => '^(read|write)' },
 | ||
|                                        \'oid_parts[array_length(oid_parts,1)] = 0'
 | ||
|                                      ],
 | ||
|                                    },{columns => [qw/mib leaf/], order_by => 'oid_parts'})
 | ||
|                                  ->hri->all;
 | ||
| 
 | ||
|   debug sprintf "loaded %d mapped objects", scalar keys %oidmap;
 | ||
|   return %oidmap;
 | ||
| }
 | ||
| 
 | ||
| =head2 get_oidmap_from_database( @mibs? )
 | ||
| 
 | ||
| =cut
 | ||
| 
 | ||
| sub get_oidmap_from_database {
 | ||
|   my @mibs = @_;
 | ||
|   debug "loading netdisco-mibs object cache (database)";
 | ||
| 
 | ||
|   my %oidmap = map { ((join '::', $_->{mib}, $_->{leaf}) => $_->{oid}) }
 | ||
|                schema('netdisco')->resultset('SNMPObject')
 | ||
|                                  ->search({
 | ||
|                                      (scalar @mibs ? (mib => { -in => \@mibs }) : ()),
 | ||
|                                      num_children => 0,
 | ||
|                                      leaf => { '!~' => 'anonymous#\d+$' },
 | ||
|                                      -or => [
 | ||
|                                        type   => { '<>' => '' },
 | ||
|                                        access => { '~' => '^(read|write)' },
 | ||
|                                        \'oid_parts[array_length(oid_parts,1)] = 0'
 | ||
|                                      ],
 | ||
|                                    },{columns => [qw/mib oid leaf/], order_by => 'oid_parts'})
 | ||
|                                  ->hri->all;
 | ||
| 
 | ||
|   if (not scalar @mibs) {
 | ||
|       debug sprintf "loaded %d MIB objects", scalar keys %oidmap;
 | ||
|   }
 | ||
| 
 | ||
|   return %oidmap;
 | ||
| }
 | ||
| 
 | ||
| =head2 get_oidmap_from_mibs_files( @vendors? )
 | ||
| 
 | ||
| Read in netdisco-mibs translation report and make an OID -> leafname map this
 | ||
| is an older version of get_oidmap which uses disk file test on my laptop shows
 | ||
| this version is four seconds and the database takes two.
 | ||
| 
 | ||
| =cut
 | ||
| 
 | ||
| sub get_oidmap_from_mibs_files {
 | ||
|   debug "loading netdisco-mibs object cache (netdisco-mibs)";
 | ||
| 
 | ||
|   my $home = (setting('mibhome') || catdir(($ENV{NETDISCO_HOME} || $ENV{HOME}), 'netdisco-mibs'));
 | ||
|   my $reports = catdir( $home, 'EXTRAS', 'reports' );
 | ||
|   my @maps = map  { (splitdir($_))[-1] }
 | ||
|              grep { ! m/^(?:EXTRAS)$/ }
 | ||
|              grep { ! m/\./ }
 | ||
|              grep { -f }
 | ||
|              glob (catfile( $reports, '*_oids' ));
 | ||
| 
 | ||
|   my @report = ();
 | ||
|   push @report, read_lines( catfile( $reports, $_ ), 'latin-1' )
 | ||
|     for (qw(rfc_oids net-snmp_oids cisco_oids), @maps);
 | ||
| 
 | ||
|   my %oidmap = ();
 | ||
|   foreach my $line (@report) {
 | ||
|     my ($oid, $qual_leaf, $rest) = split m/,/, $line;
 | ||
|     next unless defined $oid and defined $qual_leaf;
 | ||
|     next if exists $oidmap{$oid};
 | ||
|     my ($mib, $leaf) = split m/::/, $qual_leaf;
 | ||
|     $oidmap{$oid} = $leaf;
 | ||
|   }
 | ||
| 
 | ||
|   debug sprintf "loaded %d MIB objects",
 | ||
|     scalar keys %oidmap;
 | ||
|   return %oidmap;
 | ||
| }
 | ||
| 
 | ||
| =head2 get_mibs_for( @extra )
 | ||
| 
 | ||
| =cut
 | ||
| 
 | ||
| sub get_mibs_for {
 | ||
|   my @extra = @_;
 | ||
|   my @mibs = ();
 | ||
| 
 | ||
|   my $home = (setting('mibhome') || catdir(($ENV{NETDISCO_HOME} || $ENV{HOME}), 'netdisco-mibs'));
 | ||
|   my $cachedir = catdir( $home, 'EXTRAS', 'indexes', 'cache' );
 | ||
| 
 | ||
|   foreach my $item (@extra) {
 | ||
|       next unless $item;
 | ||
|       if ($item =~ m/^[a-z0-9-]+$/) {
 | ||
|           push @mibs, map { (split m/\s+/)[0] }
 | ||
|                       read_lines( catfile( $cachedir, $item ), 'latin-1' );
 | ||
|       }
 | ||
|       elsif ($item =~ m/::/) {
 | ||
|           Module::Load::load $item;
 | ||
|           $item .= '::MIBS';
 | ||
|           {
 | ||
|             no strict 'refs';
 | ||
|             push @mibs, keys %${item};
 | ||
|           }
 | ||
|       }
 | ||
|       else {
 | ||
|           push @mibs, $item;
 | ||
|       }
 | ||
|   }
 | ||
| 
 | ||
|   return @mibs;
 | ||
| }
 | ||
| 
 | ||
| =head2 get_munges( $snmpinfo )
 | ||
| 
 | ||
| =cut
 | ||
| 
 | ||
| sub get_munges {
 | ||
|   my $snmp = shift;
 | ||
|   my %munge_set = ();
 | ||
| 
 | ||
|   my %munge   = %{ $snmp->munge() };
 | ||
|   my %funcs   = %{ $snmp->funcs() };
 | ||
|   my %globals = %{ $snmp->globals() };
 | ||
| 
 | ||
|   while (my ($alias, $leaf) = each %globals) {
 | ||
|     $munge_set{$leaf} = subname($munge{$leaf}) if exists $munge{$leaf};
 | ||
|     $munge_set{$leaf} = subname($munge{$alias}) if exists $munge{$alias};
 | ||
|   }
 | ||
| 
 | ||
|   while (my ($alias, $leaf) = each %funcs) {
 | ||
|     $munge_set{$leaf} = subname($munge{$leaf}) if exists $munge{$leaf};
 | ||
|     $munge_set{$leaf} = subname($munge{$alias}) if exists $munge{$alias};
 | ||
|   }
 | ||
| 
 | ||
|   return %munge_set;
 | ||
| }
 | ||
| 
 | ||
| true;
 | ||
| 
 | ||
| __DATA__
 | ||
| agg_ports
 | ||
| at_paddr
 | ||
| bgp_peer_addr
 | ||
| bp_index
 | ||
| c_cap
 | ||
| c_id
 | ||
| c_if
 | ||
| c_ip
 | ||
| c_platform
 | ||
| c_port
 | ||
| cd11_mac
 | ||
| cd11_port
 | ||
| cd11_rateset
 | ||
| cd11_rxbyte
 | ||
| cd11_rxpkt
 | ||
| cd11_sigqual
 | ||
| cd11_sigstrength
 | ||
| cd11_ssid
 | ||
| cd11_txbyte
 | ||
| cd11_txpkt
 | ||
| cd11_txrate
 | ||
| cd11_uptime
 | ||
| class
 | ||
| contact
 | ||
| docs_if_cmts_cm_status_inet_address
 | ||
| dot11_cur_tx_pwr_mw
 | ||
| e_class
 | ||
| e_descr
 | ||
| e_fru
 | ||
| e_fwver
 | ||
| e_hwver
 | ||
| e_index
 | ||
| e_model
 | ||
| e_name
 | ||
| e_parent
 | ||
| e_pos
 | ||
| e_serial
 | ||
| e_swver
 | ||
| e_type
 | ||
| eigrp_peers
 | ||
| fw_mac
 | ||
| fw_port
 | ||
| has_topo
 | ||
| i_80211channel
 | ||
| i_alias
 | ||
| i_description
 | ||
| i_duplex
 | ||
| i_duplex_admin
 | ||
| i_err_disable_cause
 | ||
| i_faststart_enabled
 | ||
| i_ignore
 | ||
| i_lastchange
 | ||
| i_mac
 | ||
| i_mtu
 | ||
| i_name
 | ||
| i_speed
 | ||
| i_speed_admin
 | ||
| i_speed_raw
 | ||
| i_ssidbcast
 | ||
| i_ssidlist
 | ||
| i_ssidmac
 | ||
| i_stp_state
 | ||
| i_type
 | ||
| i_up
 | ||
| i_up_admin
 | ||
| i_vlan
 | ||
| i_vlan_membership
 | ||
| i_vlan_membership_untagged
 | ||
| i_vlan_type
 | ||
| interfaces
 | ||
| ip_index
 | ||
| ip_netmask
 | ||
| ipv6_addr
 | ||
| ipv6_addr_prefixlength
 | ||
| ipv6_index
 | ||
| ipv6_n2p_mac
 | ||
| ipv6_type
 | ||
| isis_peers
 | ||
| lldp_ipv6
 | ||
| lldp_media_cap
 | ||
| lldp_rem_model
 | ||
| lldp_rem_serial
 | ||
| lldp_rem_sw_rev
 | ||
| lldp_rem_vendor
 | ||
| location
 | ||
| model
 | ||
| name
 | ||
| ospf_peer_id
 | ||
| ospf_peers
 | ||
| peth_port_admin
 | ||
| peth_port_class
 | ||
| peth_port_ifindex
 | ||
| peth_port_power
 | ||
| peth_port_status
 | ||
| peth_power_status
 | ||
| peth_power_watts
 | ||
| ports
 | ||
| qb_fw_vlan
 | ||
| serial
 | ||
| serial1
 | ||
| snmpEngineID
 | ||
| snmpEngineTime
 | ||
| snmp_comm
 | ||
| snmp_ver
 | ||
| v_index
 | ||
| v_name
 | ||
| vrf_name
 | ||
| vtp_d_name
 | ||
| vtp_version
 |