#!/usr/bin/perl
#
# Check aruba wireless
#
# $Header: /opt/home/doke/work/nagios/RCS/check_aruba,v 1.25 2009/09/08 20:48:57 doke Exp $

use strict;
use warnings;
no warnings 'redefine';
use Getopt::Long;
use Net::SNMP;
use Storable;
use Fcntl ':flock';
use Time::HiRes qw( usleep );
#use Data::Dumper;


use vars qw( $host $community $aptarget $force $verbose $help $use_snmpv2c
    $mib2 $enterprises $aruba @crits @warns @unknowns @oks $rc $sep
    %snmp_sessions %name2ip $cache_file $ap_cache $cache_holddown_time
    @perfdata );

$host = 'aruba-master';
$community = 'public';
$aptarget = undef;
$force = 0;
$verbose = 0;
$help = 0;

$use_snmpv2c = 1;

$mib2 = '1.3.6.1.2.1';
$enterprises = '1.3.6.1.4.1';
$aruba = "$enterprises.14823";

$cache_file = "/var/tmp/check_aruba_cache.$<";

# don't reload/rebuild the cache more often than this
$cache_holddown_time = 300;   # seconds

sub usage {
    my( $rc ) = @_;
    print "Usage: $0 [-vh] -H <host> [-C <community>] [-a <ap-name>]
    With an ap-name checks on that ap.
    Without an ap-name checks the controller.
    -H s  controller hostname [aruba-master]
    -C s  snmp community [public]
    -a s  access point name
    -f    force cache file rebuild, despite age
    -v    verbose
    -h    help
";
    exit $rc;
    }

Getopt::Long::Configure ("bundling");
GetOptions(
    'H=s' => \$host,
    'C=s' => \$community,
    'a=s' => \$aptarget,
    'f' => \$force,
    'v+' => \$verbose,
                       'h' => \$help,
    );
&usage( 0 ) if ( $help );
&usage( 0 ) if ( ! $host );
&usage( 0 ) if ( ! $community );

if ( $aptarget ) {
    check_ap( $aptarget );
    }
else {
    check_controller();
    }

$rc = 0;
$sep = '';
if ( $#crits >= 0 ) {
    $rc = 2;
    print "CRITICAL ", join( ", ", @crits );
    $sep = '; ';
    }
if ( $#warns >= 0 ) {
    $rc = 1 if ( $rc == 0 );
    print $sep, "Warning ", join( ", ", @warns );
    $sep = '; ';
    }
if ( $#unknowns >= 0 ) {
    $rc = -1 if ( $rc == 0 );
    print $sep, "Unknown ", join( ", ", @unknowns );
    }
if ( $rc == 0 ) {
    print "Ok ", join( ", ", @oks );
    print( " | ", join( ", ", @perfdata ) ) if ( $#perfdata >= 0 );
    }
print "\n";
exit $rc;


##################


sub check_controller {
    my( $result, @oids, @data, $row, $apname );

    my $sysDescr_oid = "$mib2.1.1.0";
    my $sysUpTime_oid = "$mib2.1.3.0";
    my $sysName_oid = "$mib2.1.5.0";
    @oids = ( $sysDescr_oid, $sysUpTime_oid, $sysName_oid );
    $result = snmp_get( $host, $community, 'system', \@oids );
    my $sysDescr = $result->{ $sysDescr_oid };
    my $sysUpTime = $result->{ $sysUpTime_oid };
    my $sysName = $result->{ $sysName_oid };
    if ( $verbose ) {
        print "sysName $sysName\n";
        print "sysDescr $sysDescr\n";
        print "sysUpTime $sysUpTime\n";
        }
    if ( ! $sysDescr ) {
        push @unknowns, "unable to get sysDescr";
        return;
        }
 elsif ( $sysDescr !~ m/ArubaOS/ ) {
        push @unknowns, "not an Aruba controller: $sysDescr";
        return;
        }
    push @oks, "$sysName";
    push @oks, "up $sysUpTime";


    my $role_oid = "$aruba.2.2.1.1.1.4";
    my $serialnum_oid = "$aruba.2.2.1.1.1.12";
    my $num_aps_oid = "$aruba.2.2.1.1.3.1";
    my $num_assocs_oid = "$aruba.2.2.1.1.3.2";
    my $swip_oid = "$aruba.2.2.1.2.1.1";   # needed to index sysext tables
    my $temp_oid = "$aruba.2.2.1.2.1.10";
    #my $num_users_oid = "$aruba.2.2.1.4.1.1.0";   # not useful, wierd counter

    @oids = ( $role_oid, $serialnum_oid, $num_aps_oid, $num_assocs_oid,
        $swip_oid, $temp_oid );
    $result = snmp_get( $host, $community, 'summary data', \@oids );
    my $role = $result->{ $role_oid };
    my $serialnum = $result->{ $serialnum_oid };
    my $num_aps = $result->{ $num_aps_oid };
    my $num_assocs = $result->{ $num_assocs_oid };
    my $swip = $result->{ $swip_oid };
    my $temp = $result->{ $temp_oid };

    my @roles = ( 'unknown', 'master', 'local', 'backup-master' );
    $verbose && print "role $role $roles[ $role ]\n";
    push @oks, "role " . $roles[ $role ];

    if ( $verbose ) {
        print "serialnum $serialnum\n";
        print "aps $num_aps\n";
        print "associations $num_assocs\n";
        }
    push @oks, "serialnum $serialnum";
    push @oks, "aps $num_aps";
    push @oks, "associations $num_assocs";

    if ( $temp =~ m/normal/i ) {
        push @oks, $temp;
        }
    else {
        push @warns, $temp;
        }

    my $swver_oid = "$aruba.2.2.1.2.1.19.1.4.$swip";
    my $status_oid = "$aruba.2.2.1.2.1.19.1.5.$swip";
    @oids = ( $swver_oid, $status_oid );
    $result = snmp_get( $host, $community, 'version', \@oids );
    my $swver = $result->{ $swver_oid };
    my $status = $result->{ $status_oid };

    push @oks, "version $swver";

    my @statuses = ( 'unknown', 'active', 'inactive' );
    $verbose && print "status $status $statuses[ $status ]\n";
    if ( $status != 1 ) {
        push @warns, "status " . $statuses[ $status ];
        }
    else {
        #push @oks, "status " . $statuses[ $status ];
 }


    # processor load table
    @data = walk_table( $host, $community, 'cpuload', "$aruba.2.2.1.2.1.13.1" );
    foreach $row ( 1 .. $#{@{$data[2]}} ) {
        my $descr = $data[2][$row];
        next if ( ! defined $descr );
        my $load = $data[3][$row];
        $verbose && print "$descr load $load%\n";
        if ( $load > 90 ) {
            push @crits, "$descr load $load%";
            }
        elsif ( $load > 80 ) {
            push @warns, "$descr load $load%";
            }
        else  {
            #push @oks, "$descr load $load%";
            }
        }


    # storage table
    @data = walk_table( $host, $community, 'storage', "$aruba.2.2.1.2.1.14.1" );
    foreach $row ( 1 .. $#{@{$data[2]}} ) {
        my $type = $data[2][$row];
        my $size = $data[3][$row];
        my $used = $data[4][$row];
        my $name = $data[5][$row];
        my $pc = int( $used * 100 / $size );
        $verbose && print "$name $pc% full\n";
        if ( $name eq '/' ) {
            # skip it, it's always 98% full
            }
        elsif ( $pc > 90 ) {
            push @crits, "$name $pc% full";
            }
        elsif ( $pc > 80 ) {
            push @warns, "$name $pc% full";
            }
        else  {
            #push @oks, "$name $pc% full";
            }
        }


    # memory table
    @data = walk_table( $host, $community, 'memory', "$aruba.2.2.1.1.1.11" );
    foreach $row ( 1 .. $#{@{$data[2]}} ) {
        my $size = $data[2][$row];
        my $used = $data[3][$row];
        my $free = $data[4][$row];
        my $pc = int( $used * 100 / $size );
        $verbose && print "memory $pc% full\n";
        if ( $pc > 95 ) {
            push @crits, "memory $pc% full";
            }
        elsif ( $pc > 90 ) {
            push @warns, "memory $pc% full";
            }
        else  {
            #push @oks, "memory $pc% full";
 }
        }



    # auth server table
    if ( 0 ) {
        @data = walk_table2( $host, $community, 'auth-servers', "$aruba.2.2.1.8.1.1.1" );
        #print "dump ", Dumper( @data ), "\n";
        foreach $row ( sort keys %{$data[2]} ) {
            my @a = split( /\./, $row );
            shift @a;  # loose the length byte
            my $name = pack( "C*", @a );
            my $type = $data[2]{$row};
            my $responsetime = $data[13]{$row};
            $verbose && print "auth server $name avg response time $responsetime ms\n";
            if ( $responsetime > 5000 ) {
                push @crits, "auth server $name avg response time $responsetime ms";
                }
            elsif ( $responsetime > 1000 ) {
                push @warns, "auth server $name avg response time $responsetime ms";
                }
            else  {
                #push @oks, "auth server $name avg response time $responsetime ms";
                }
            }
        }

    # fixme, do something with alarm mib

    # todo: check licences?  Our current ones say they never expire.


    }



sub check_ap {
    my( $aptarget ) = @_;
    my( $i, $apref, $rc );

    $verbose && print "check_ap( $aptarget )\n";


    for $i ( 1 .. 3 ) {
        if ( load_ap_cache() ) {
            last;
            }
        if ( rebuild_ap_cache() ) {
            last;
            }
        # maybe someone else is rebuilding it?
        usleep( 100000 );
        }
    for $i ( 1, 2 ) {
        $apref = find_ap( $aptarget );
        if ( $apref && $apref->{ 'controller' } ) {
            $rc = scan_ap( $apref, $aptarget, 1 );
            return if ( $rc == 1 );
            }

        if ( ! rebuild_ap_cache() ) {
  last;
            }
        sleep 1;
        }

    if ( ! $apref || $rc == 0 ) {
        # still can't find it.
        push @unknowns, "can't find controller for ap $aptarget";
        return;
        }
    if ( $rc == 2 ) {
        # found it, but it's down
        $rc = scan_ap( $apref, $aptarget, 0 );
        }
    return $rc;
    }

# scan a specified ap on a specified controller
# return 0 if can't find it on this controller, or otherwise mismatched
# return 1 if found it and it's up
# return 2 if found it and it's down
sub scan_ap {
    my( $apref, $aptarget, $no_down ) = @_;
    my( $controller, $apname, $apip, $apmac, $apmac_oid, $result, @oids,
        @apwlanrow, @apradiorow, $radio, %radios, $val, $oid2, $oid,
        $col, $perf_util, $perf_clients );

    $verbose && print "scan_ap( $apref, $aptarget, $no_down )\n";

    $controller = $apref->{ controller };
    $apname = $apref->{ apname };
    $apip = $apref->{ apip };
    $apmac = $apref->{ apmac };
    $apmac_oid = $apref->{ apmac_oid };
    if ( $verbose ) {
        print "$apname, $apip, $apmac, $apmac_oid, $controller\n";
        }

    my $sysDescr_oid = "$mib2.1.1.0";
    my $sysUpTime_oid = "$mib2.1.3.0";
    my $sysName_oid = "$mib2.1.5.0";
    @oids = ( $sysDescr_oid, $sysUpTime_oid, $sysName_oid );
    $result = snmp_get( $controller, $community, 'system', \@oids );
    my $sysName = $result->{ $sysName_oid };
    my $sysDescr = $result->{ $sysDescr_oid };
    my $sysUpTime = $result->{ $sysUpTime_oid };
    if ( $verbose ) {
        print "sysName $sysName\n";
        print "sysDescr $sysDescr\n";
        print "sysUpTime $sysUpTime\n";
        }
    if ( ! $sysDescr ) {
        #push @unknowns, "unable to get sysDescr";
        return 0;
        }
    elsif ( $sysDescr !~ m/ArubaOS/ ) {
        #push @unknowns, "not an Aruba controller: $sysDescr";
        return 0;
        }

    # pick the stuff for this mac out of the AP table
    undef @oids;
    foreach $col ( 2 .. 31 ) {
        $oid = "$aruba.2.2.1.5.2.1.4.1.$col.$apmac_oid";
        push @oids, $oid;
        }
    $result = snmp_get( $controller, $community, 'ap table', \@oids );
    foreach $oid ( keys %$result ) {
        if ( $oid =~ m/2\.2\.1\.5\.2\.1\.4\.1\.(\d+)\.$apmac_oid/ ) {
            $val = $result->{ $oid };
            $verbose > 1 && print "$oid = $val\n";
            $apwlanrow[ $1 ] = $val;
            }
        }

    $apip = $apwlanrow[ 2 ];
    $apname = $apwlanrow[ 3 ];

    my @statuses = ( 'unknown', 'up', 'down' );

    if ( $verbose ) {
        print "ap name $apname, ip $apip, mac $apmac\n";
        print "ap group ", $apwlanrow[ 4 ], "\n";
        #print "ap model ", $apwlanrow[ 5 ], "\n";   # as an index code
        print "ap serial ", $apwlanrow[ 6 ], "\n";
        print "ap num radios ", $apwlanrow[ 9 ], "\n";
        print "ap ipsec mode ", $apwlanrow[ 11 ], "\n";
        print "ap uptime ", $apwlanrow[ 12 ], "\n";
        print "ap model name ", $apwlanrow[ 13 ], "\n";   # as a string
        print "ap external antenna ", $apwlanrow[ 18 ], "\n";   # 1 not present, 2 enabled
        print "ap status ", $apwlanrow[ 19 ], " ", $statuses[ $apwlanrow[19] ], "\n";   # 1 up
        print "ap bootstraps ", $apwlanrow[ 20 ], "\n";
        print "ap reboots ", $apwlanrow[ 21 ], "\n";
        print "ap unprovisioned ", $apwlanrow[ 22 ], "\n";   # 1 up
        print "ap monitor mode ", $apwlanrow[ 23 ], "\n";   # 2 none
        print "ap mesh role ", $apwlanrow[ 31 ], "\n";   # 0 nonmesh
        }

    if ( $apname ne $aptarget && $apip ne $aptarget ) {
        # We didn't get what we asked for.
        # Maybe it got renamed and the cache is out of date?
        $verbose && print "name mismatch: want $aptarget, got $apname $apip\n";
        return 0;
        }

    if ( $no_down ) {
        if ( ! defined $apwlanrow[19] || $apwlanrow[19] == 0 ) {
            return 0;
            }
        elsif ( $apwlanrow[19] == 2 ) {
            return 2;
            }
        # else it's ok, keep going
        }

    if ( $apwlanrow[19] != 1 ) {
        push @crits, $statuses[ $apwlanrow[19] ];
        }
    else {
        push @oks, $statuses[ $apwlanrow[19] ];
        }

    push @oks, $apmac;
 #push @oks, "ip $apip";
    #push @oks, "name $apname";
    push @oks, "ap$apwlanrow[13]";
    push @oks, "sn $apwlanrow[6]";
    push @oks, "group $apwlanrow[4]";


    # pick the stuff for this mac out of the radio table

    # this doesn't work, it says the tables are empty...
    #undef @oids;
    #foreach $col ( 2 .. 14 ) {
    #   $oid = "$aruba.2.2.1.5.2.1.5.1.$col.$apmac_oid";
    #   print "oid $oid\n";
    #   $result = snmp_walk( $controller, $community, 'radio', $oid );
    #
    #   foreach $oid2 ( keys %$result ) {
    #       if ( $oid2 =~ m/2\.2\.1\.5\.2\.1\.5\.1\.$col\.$apmac_oid\.(\d+)$/ ) {
    #           $val = $result->{ $oid2 };
    #           $verbose && print "$oid2 = $val\n";
    #           $apradiorow[ $col ][ $1 ] = $val;
    #           $radios{ $1 } = 1;
    #           }
    #       }
    #   }

    foreach $radio ( 1 .. $apwlanrow[ 9 ] ) {
        # Get each radio seperately, so an snmp not found for an inactive radio
        # won't hide an active one.
        undef @oids;
        foreach $col ( 2 .. 14 ) {
            $oid = "$aruba.2.2.1.5.2.1.5.1.$col.$apmac_oid.$radio";
            push @oids, $oid;
            }
        $result = snmp_get( $controller, $community, "radio $radio", \@oids );
        foreach $oid ( keys %$result ) {
            if ( $oid =~ m/2\.2\.1\.5\.2\.1\.5\.1\.(\d+)\.$apmac_oid\.$radio$/ ) {
                $col = $1;
                $val = $result->{ $oid };
                $verbose > 1 && print "$oid = $val\n";
                $apradiorow[ $col ][ $radio ] = $val;
                }
            }
        }

    my @radio_types = ( 'unknown', 'dot11a', 'dot11b', 'dot11g', 'dot11ag', 'wired' );

    $perf_util = $perf_clients = 0;
    foreach $radio ( 1 .. $apwlanrow[ 9 ] ) {

        next if ( ! $apradiorow[ 2 ][$radio] || $apradiorow[ 2 ][$radio] eq "noSuchInstance" );

        if ( $verbose ) {
            print "radio $radio type ", $radio_types[ $apradiorow[ 2 ][$radio] ], "\n";
            print "radio $radio channel ", $apradiorow[ 3 ][$radio], "\n";
            printf "radio $radio tx power %0.1f dBm\n", $apradiorow[ 4 ][$radio] / 2;
            print "radio $radio mode ", $apradiorow[ 5 ][$radio], "\n";   # as an index code
            print "radio $radio utilization ", $apradiorow[ 6 ][$radio], "\n";
            print "radio $radio num associated clients ", $apradiorow[ 7 ][$radio], "\n";
            print "radio $radio num monitored clients ", $apradiorow[ 8 ][$radio], "\n";
            print "radio $radio num active bssids ", $apradiorow[ 9 ][$radio], "\n";
            print "radio $radio num monitored bssids ", $apradiorow[ 10 ][$radio], "\n";
 print "radio $radio bearing ", $apradiorow[ 11 ][$radio], "\n";   # as a string
            print "radio $radio tilt angle ", $apradiorow[ 12 ][$radio], "\n";   # as a string
            # 13 ?
            # 14 ?
            # 15 channel?
            }

        push @oks, sprintf( "%s ch%d %2.1fdBm %d%%util %dclients",
            $radio_types[ $apradiorow[ 2 ][$radio] ],
            $apradiorow[ 3 ][$radio],
            $apradiorow[ 4 ][$radio] / 2,
            $apradiorow[ 6 ][$radio],
            $apradiorow[ 7 ][$radio] );

        $perf_util = $apradiorow[ 6 ][$radio] if ( $apradiorow[ 6 ][$radio] > $perf_util );
        $perf_clients += $apradiorow[ 7 ][$radio];
        }

    push @perfdata, sprintf "util=%d", $perf_util;
    push @perfdata, sprintf "clients=%d", $perf_clients;

    # fixme, do something with alarm mib

    push @oks, "controller $sysName";

    return 1;
    }



# search the ap_cache for an ap ip or name
# return a reference to it's entry, or 0 on failure
sub find_ap {
    my( $ap ) = @_;
    my( $apmac_oid );

    $verbose && print "find_ap( $ap )\n";

    if ( $ap =~ m/^[\d\.]{7,15}$/ ) {
        # ip address
        foreach $apmac_oid ( keys %$ap_cache ) {
            if ( exists $ap_cache->{ $apmac_oid }{ apip }
                    && $ap_cache->{ $apmac_oid }{ apip } eq $ap ) {
                $verbose && print "found $ap = $apmac_oid\n";
                return \%{$ap_cache->{ $apmac_oid }};
                }
            }
        }
    else {
        # ap name
        foreach $apmac_oid ( keys %$ap_cache ) {
            if ( exists $ap_cache->{ $apmac_oid }{ apname }
                    && $ap_cache->{ $apmac_oid }{ apname } eq $ap ) {
                $verbose && print "found $ap = $apmac_oid\n";
                return \%{$ap_cache->{ $apmac_oid }};
                }
            }
        }

    return 0;   # can't find it
    }
# load ap cache hash from storable file
# on success return $ap_cache
# on failure return false
sub load_ap_cache {

    $verbose && print "load_ap_cache()\n";

    if ( ( ! -f $cache_file ) || ( -s $cache_file ) < 100 ) {
        $verbose && warn "cache file is invalid, $cache_file";
        return 0;
        }

    #open( lockH, $cache_file ) || return 0;
    #flock( lockH, LOCK_SH ) || return 0;

    $ap_cache = retrieve( $cache_file );

    #flock( lockH, LOCK_UN ) || return 0;
    #close( lockH );

    return $ap_cache;
    }

# rebuild the cache of aps
# return 1 success, 0 failure
sub rebuild_ap_cache {
    my( $sysDescr, $result, $oid, @controllers, $controller, $rc, $apmac_oid,
        $apmac, $age );

    $verbose && print "rebuild_ap_cache()\n";

    if ( -f $cache_file ) {
        # can't use -M in embedded perl nagios, it computes file age
        # relative to when nagios demon started.
        my( $dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size,
            $atime, $mtime, $ctime, $blksize, $blocks ) = stat( _ );
        $age = time() - $mtime;
        if ( $age < $cache_holddown_time && ! $force ) {
            $verbose && print "cache file only $age seconds old, not rebuilding\n";
            return 0;
            }
        }

    if ( ! open( lockH, "+>>$cache_file.new" ) ) {
        $verbose && warn "can't open new cache file to lock it: $!\n";
        push @warns, "can't open new cache file to lock it: $!";
        return 0;
        }
    if ( ! flock( lockH, LOCK_EX | LOCK_NB ) ) {
        $verbose && warn "can't lock new cache file: $!\n";
        push @warns, "can't lock new cache file: $!";
        return 0;
        }

    $sysDescr = snmp_get_one( $host, $community, 'sysDescr', "$mib2.1.1.0" );
    if ( ! $sysDescr ) {
        push @unknowns, "unable to get sysDescr";
        flock( lockH, LOCK_UN );
        close( lockH );
        return;
        }
    $verbose && print "sysDescr $sysDescr\n";
 if ( $sysDescr !~ m/ArubaOS/ ) {
        push @unknowns, "not an Aruba controller: $sysDescr";
        flock( lockH, LOCK_UN );
        close( lockH );
        return;
        }

    $result = snmp_walk( $host, $community, 'switchListSwitchRole', "$aruba.2.2.1.1.1.6.1.2" );
    foreach $oid ( keys %$result ) {
        if ( $oid =~ m/6\.1\.2\.(\d+\.\d+\.\d+\.\d+)$/ ) {
            push @controllers, $1;
            }
        }

    $ap_cache = {};
    foreach $controller ( @controllers ) {
        next if ( ! $controller );
        rebuild_ap_cache_per_controller( $controller );
        }

    foreach $apmac_oid ( sort keys %$ap_cache ) {
        $ap_cache->{ $apmac_oid }{ 'apmac_oid' } = $apmac_oid;
        $apmac = sprintf( "%02x:%02x:%02x:%02x:%02x:%02x",
            split( /\./, $apmac_oid ) );
        $ap_cache->{ $apmac_oid }{ 'apmac' } = $apmac;
        if ( $verbose  ) {
            printf "ap mac %s, name %s, ip %s, controller %s, status %s\n",
                $apmac,
                $ap_cache->{ $apmac_oid }{ 'apname' },
                $ap_cache->{ $apmac_oid }{ 'apip' },
                $ap_cache->{ $apmac_oid }{ 'controller' },
                $ap_cache->{ $apmac_oid }{ 'status' };
            }
        }

    if ( ! store $ap_cache, "$cache_file.new" ) {
        $verbose && warn "can't store to new cache file: $!\n";
        push @warns, "can't store to new cache file: $!";
        flock( lockH, LOCK_UN );
        close( lockH );
        return 0;
        }

    # rename is atomic
    # Won't disturb other instances of this script that already # have it open.
    if ( ! rename( "$cache_file.new", $cache_file ) ) {
        $verbose && warn "unable to rename new cache file: $!\n";
        push @warns, "unable to rename new cache file: $!";
        flock( lockH, LOCK_UN );
        close( lockH );
        return 0;
        }

    flock( lockH, LOCK_UN );
    close( lockH );
    return 1;   # success
    }


sub rebuild_ap_cache_per_controller {
    my( $controller ) = @_;
    my( $result, @oids, $val, $oid, $apmac_oid, $status );

	 $verbose && print "rebuild_ap_cache_per_controller( $controller )\n";

    my $sysDescr_oid = "$mib2.1.1.0";
    my $sysUpTime_oid = "$mib2.1.3.0";
    my $sysName_oid = "$mib2.1.5.0";
    @oids = ( $sysDescr_oid, $sysUpTime_oid, $sysName_oid );
    $result = snmp_get( $controller, $community, 'system', \@oids );
    my $sysName = $result->{ $sysName_oid };
    my $sysDescr = $result->{ $sysDescr_oid };
    my $sysUpTime = $result->{ $sysUpTime_oid };
    if ( $verbose ) {
        print "sysName $sysName\n";
        print "sysDescr $sysDescr\n";
        print "sysUpTime $sysUpTime\n";
        }
    if ( ! $sysDescr ) {
        #push @unknowns, "unable to get sysDescr";
        return 0;
        }
    elsif ( $sysDescr !~ m/ArubaOS/ ) {
        #push @unknowns, "not an Aruba controller: $sysDescr";
        return 0;
        }

    my @statuses = ( 'unknown', 'up', 'down' );


    # wlsxWlanAPTable.wlsxWlanAPEntry.wlanAPIpAddress
    $result = snmp_walk( $controller, $community, 'wlanAPIpAddress',
        "$aruba.2.2.1.5.2.1.4.1.2" );
    foreach $oid ( keys %$result ) {
        $val = $result->{ $oid };
        $verbose > 1 && print "$oid = $val\n";
        if ( $oid =~ m/\.(\d+\.\d+\.\d+\.\d+\.\d+\.\d+)$/ ) {
            $apmac_oid = $1;
            $ap_cache->{ $apmac_oid }{ 'apip' } = $val;
            }
        }

    # wlsxWlanAPTable.wlsxWlanAPEntry.wlanAPName
    $result = snmp_walk( $controller, $community, 'wlanAPName', "$aruba.2.2.1.5.2.1.4.1.3" );
    foreach $oid ( keys %$result ) {
        $val = $result->{ $oid };
        $verbose > 1 && print "$oid = $val\n";
        if ( $oid =~ m/\.(\d+\.\d+\.\d+\.\d+\.\d+\.\d+)$/ ) {
            $apmac_oid = $1;
            $ap_cache->{ $apmac_oid }{ 'apname' } = $val;
            }
        }

    # wlsxWlanAPTable.wlsxWlanAPEntry.wlanAPStatus
    $result = snmp_walk( $controller, $community, 'wlanAPStatus',
        "$aruba.2.2.1.5.2.1.4.1.19" );
    foreach $oid ( keys %$result ) {
        $val = $result->{ $oid };
        $status = $statuses[ $val ];
        $verbose > 1 && print "$oid = $val $status\n";
        if ( $oid =~ m/\.(\d+\.\d+\.\d+\.\d+\.\d+\.\d+)$/ ) {
            $apmac_oid = $1;
 # we want to save the controller that sees the ap as 'up'
            if ( ! exists $ap_cache->{ $apmac_oid }{ 'status' }
                    || $ap_cache->{ $apmac_oid }{ 'status' } ne 'up' ) {
                $ap_cache->{ $apmac_oid }{ 'controller' } = $controller;
                $ap_cache->{ $apmac_oid }{ 'status' } = $status;
                }
            }
        }

    return 1;
    }


# for tables with simple 1 integer indecies, ie most normal ones
# Returns an array of arrays: data[ column ][ row ]
sub walk_table {
    my( $dev, $community, $name, $baseoid ) = @_;
    my( $result, $rows, $oid, $val, $col, $row, @data );

    $verbose && print "walk_table $dev $name\n";

    $result = snmp_walk( $dev, $community, $name, $baseoid ) || return undef;

    $rows = 0;
    foreach $oid ( keys %$result ) {
        $val = $result->{ $oid };
        $verbose > 1 && print "$oid = $val\n";
        next if ( $val eq 'endOfMibView' );
        if ( $oid =~ m/.*\.(\d+)\.(\d+)$/ ) {
            $col = $1; $row = $2;
            $data[$col][$row] = $val;
            $rows = $row if ( $row > $rows );
            }
        }

    if ( ! $rows ) {
        push @unknowns, "no rows in $name table on $dev";
        return undef;
        }

    return @data;
    }

# for tables with complex indecies, like macaddrs or ips
# Expectes the next octet of the oid after the baseoid to be the column number,
# and anything after that to be the index/row.
# So usually have to give this a baseoid with a final 1 for the "entry" level,
# not just the "table" level oid.
# Returns an array of hashes: data[ column ]{ row }
sub walk_table2 {
    my( $dev, $community, $name, $baseoid ) = @_;
    my( $result, %rows, $nrows, $oid, $val, $col, $row, @data );

    $verbose && print "walk_table2 $dev $name\n";

    $result = snmp_walk( $dev, $community, $name, $baseoid ) || return undef;

    foreach $oid ( keys %$result ) {
        $val = $result->{ $oid };
        $verbose > 1 && print "$oid = $val\n";
 next if ( $val eq 'endOfMibView' );
        if ( $oid =~ m/$baseoid\.(\d+)\.([\d\.]+)$/ ) {
            $col = $1; $row = $2;
            $rows{ $row } = 1;
            $data[$col]{$row} = $val;
            }
        }

    $nrows = scalar keys %rows;
    if ( ! $nrows ) {
        push @unknowns, "no rows in $name table on $dev";
        return undef;
        }

    return @data;
    }


sub snmp_walk {
    my( $dev, $community, $name, $baseoid ) = @_;
    my( $session, $result );

    $verbose && print "snmp_walk $dev $name\n";

    $session = snmp_session( $dev, $community );
    $result = $session->get_table( -baseoid => $baseoid );
    #print "session error ", $session->error(), "\n";
    if ( ! defined( $result )
            && $session->error() !~ m/Requested table is empty/ ) {
        #warn sprintf( "error walking $name table on %s: %s",
        #    $session->hostname, $session->error() );
        #push @unknowns, sprintf( "error walking $name table on %s: %s",
        #    $session->hostname, $session->error() );
        return undef;
        }
    return $result;
    }


sub snmp_get_one {
    my( $dev, $community, $name, $oid ) = @_;
    my( $session, @oids, $result, $oid2, $val );

    $verbose && print "snmp_get $dev $name\n";
    $session = snmp_session( $dev, $community );
    @oids = ( $oid );
    $result = $session->get_request( -varbindlist => \@oids );
    if ( ! defined( $result ) ) {
        #warn sprintf "error getting %s from %s: %s\n",
        #    $name, $session->hostname, $session->error();
        return undef;
        }

    if ( ! exists $result->{ $oid } ) {
        #warn "snmp_get error: requested $name oid $oid not in response\n";
        return undef;
        }
    return $result->{ $oid };
    }
sub snmp_get {
    my( $dev, $community, $name, $oids ) = @_;
    my( $session, $result, $oid );

    $verbose && print "snmp_get $dev $name\n";
    $session = snmp_session( $dev, $community );
    $result = $session->get_request( -varbindlist => $oids );
    if ( ! defined( $result ) ) {
        #warn sprintf "error getting %s from %s: %s\n",
        #    $name, $session->hostname, $session->error();
        return undef;
        }

    # clean the noSuchObject errors out of the result
    foreach $oid ( keys %{$result} ) {
        if ( $result->{ $oid } eq 'noSuchObject' ) {
            delete $result->{ $oid };
            }
        }

    return $result;
    }



sub snmp_session {
    my( $dev, $community ) = @_;
    my( $ip, $session, $error );

    if ( $dev =~ m/^\d[\d\.]+$/ ) {
        $ip = $dev;
        }
    else {
        $ip = name2ip( $dev );
        }

    if ( exists $snmp_sessions{ "$ip,$community" } ) {
        return $snmp_sessions{ "$ip,$community" };
        }

    ( $session, $error ) = Net::SNMP->session(
        -version => $use_snmpv2c ? 'snmpv2c' : 'snmpv1',
        -hostname => $ip,
        -community => $community,
        -timeout => 5.0,
        #-debug => 0x02
        );
    if ( ! defined( $session ) ) {
        push @unknowns, "snmp setup error: $error\n";
        return undef;
        }
    $session->translate( [ '-octetstring' => 0 ] );

    $snmp_sessions{ "$ip,$community" } = $session;
    return $session;
    }



sub name2ip {
    my( $name ) = @_;
    my( $ip, $a, $b, $c, $d );
  if ( defined( $name2ip{ $name } ) ) {
        $ip = $name2ip{ $name };
        }
    elsif ( $name =~ m/^\s*(\d+\.\d+\.\d+\.\d+)\s*$/ ) {
        $ip = $1;
        $name2ip{ $name } = $ip;
        $name2ip{ $1 } = $ip;
        }
    else {
        $ip = gethostbyname( $name );
        if ( ! $ip ) {
            return undef;
            }
        ($a,$b,$c,$d) = unpack( 'C4', $ip );
        $ip = "$a.$b.$c.$d";
        $name2ip{ $name } = $ip;
        }
    return $ip;
    }

