Skip to content

Instantly share code, notes, and snippets.

@dreness
Created October 6, 2013 03:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dreness/6849151 to your computer and use it in GitHub Desktop.
Save dreness/6849151 to your computer and use it in GitHub Desktop.
For informational / forensic purposes only. If you still have a working Airport Extreme base station, this might work, but I doubt it, cause even apple's link to the MIB file is dead. Newer Airport base station models don't have SNMP.
#!/usr/bin/perl -w
# Fetch and display wireless stats from an Apple Airport Extreme Base Station.
# Andre LaBranche, dre at mac dot com, 4/27/07
use Switch;
use Getopt::Long qw(:config permute bundling);
# User Tuneables Start
# We need to define base stations and their SNMP community names for querying.
# For slight obscurity purposes, we store a base64 encoded version of the snmp
# community name. Generate such a string at the command line like this:
# echo "the password" | openssl enc -base64
# or just use the --encode option to this script.
# e.g. airport --encode "my password"
# Define base stations as shown here. You'll need the IP address and encoded
# form of the SNMP community name. Specify as many base stations as you like.
$basestations{"10.0.1.1"} = "dGhlIHBhc3N3b3JkCg==";
# Our friends. Store the MAC addresses like this (all caps, with spaces). If
# we find a wireless client in this list, we'll display the specified name
# instead of the MAC address.
$friends{"00 17 F2 02 8E 96"} = "donk";
$friends{"00 17 F2 02 2F 97"} = "dreness";
# snmpwalk binary location
$snmpbin = "/usr/bin/snmpwalk";
# User Tunables End
# some variables
@friends = keys %friends; # a list of friendly MAC addresses
$fieldOrder = ""; # used to hold order of output fields
# be sure you've installed Apple's Airport MIB:
# http://docs.info.apple.com/article.html?artnum=120227
if ( -e "/usr/share/snmp/mibs/airport-extreme.mib" ) {
} #zee goggles!
else {
print "Can't find Airport MIB. Execute the following to install it:\n";
print "sudo curl -L -o /usr/share/snmp/mibs/airport-extreme.mib ";
print "http://download.info.apple.com/Mac_OS_X/061-0652.20030619.5ibjt/";
print "airport-extreme.mib\n";
die("Error: Missing SNMP MIB file.\n");
}
# get and process command line arguments and options. We use Getopt::Long to
# handle the command line help and verbose options. Anything else gets passed
# to the &orderFields subroutine. This is so we can take these in order to
# build the output format.
GetOptions(
'v+' => \$debug,
'h|help' => \$printHelp,
'd' => \$delimited,
'<>' => \&orderFields,
'encode=s' => \$pwhash
);
# process cli arguments / options
&printCliHelp if ( defined $printHelp );
# make sure we have work to do
if ( !defined %basestations ) {
print "Edit this script to configure base stations to monitor.\n";
exit 1;
}
if ( defined $pwhash ) {
$base64 = `echo "$pwhash" | openssl enc -base64`;
print "base64 encoded version of $pwhash is: $base64\n";
exit 0;
}
if ( !defined $debug ) { $debug = 0 }
print "Debug level: $debug\n" if ( $debug gt 0 );
# if user passed in a field specification, $fieldOrder would be populated
if ( defined $fieldOrder ) {
# make an array of individual field spec characters
@fields = split( //, $fieldOrder );
# test each character and load @infos with the corresponding field name
foreach $a (@fields) {
switch ($a) {
case "n" { push @infos, "wirelessPhysAddress" };
case "y" { push @infos, "wirelessType" };
case "s" { push @infos, "wirelessStrength" };
case "r" { push @infos, "wirelessRate" };
case "o" { push @infos, "wirelessNoise" };
case "t" { push @infos, "wirelessNumTX" };
case "e" { push @infos, "wirelessNumRX" };
case "T" { push @infos, "wirelessNumTXErrors" };
case "E" { push @infos, "wirelessNumRXErrors" };
case "a" { push @infos, "wirelessTimeAssociated" };
case "i" { push @infos, "wirelessLastRefreshTime" };
};
}
}
if ( grep ( /wireless/, @infos ) ) {
# user passed in some options, we'll use them
}
else {
# User didn't specify any output fields, so set up default output
@infos = (
'wirelessPhysAddress', 'wirelessType',
'wirelessStrength', 'wirelessRate',
'wirelessNoise', 'wirelessTimeAssociated',
'wirelessLastRefreshTime'
);
}
print "Using data field order: [@infos]\n" if ( $debug gt 0 );
# We need to build a format string appropriate to the output field order we'll
# be using. Pass &buildFormatStrings the list of fields, and it returns a
# matching array of format strings (used for printf later).
@formats = &buildFormatStrings(@infos);
print "Using printf formats: [@formats]\n" if ( $debug gt 1 );
# Query each base station in turn. We'll build a hash in the form
# $host{$key} = $value, where $host is the IP address, and $key is the name of
# an SNMP object, with corresponding $value.
foreach $host ( keys %basestations ) {
chomp( $community = `echo $basestations{$host} | openssl enc -d -base64` );
# Build the SNMP command. Walk the entire airport MIB ...
$snmpcmd = "$snmpbin -m AIRPORT-BASESTATION-3-MIB -Osq -v 2c ";
# ... use the supplied snmp community and host names
$snmpcmd = $snmpcmd . "-c \"$community\" \"$host\" ";
# ... and root the search at this OID
$snmpcmd = $snmpcmd . "SNMPv2-SMI::enterprises.apple.airport";
# Fire off the snmpwalk command. All the output is stored in @output.
print "About to run SNMP command:\n$snmpcmd\n" if ( $debug gt 0 );
@output = `$snmpcmd`;
chomp @output;
# step through each line of output and organize the data
foreach $line (@output) {
# match the interesting bits
$line =~ /^(.*?)\s(.*?)$/;
$key = $1;
$val = $2;
# store the two values as a hash element, if we got them both
$host{$key} = "$val" if ( ( defined $key ) && ( defined $val ) );
undef $key;
undef $val;
}
# make sure we got useful output from the snmpwalk
if ( !defined $host{"sysConfName.0"} ) {
$snmpHelp = <<EOF;
Unable to query $host. Check SNMP configuration.
* Verify that you have defined the correct IP Address or host name for your
base station(s). Do this near the top of this script.
* Verify that SNMP is enabled for this base station. Using the Airport Admin
Utility, check "Base Station Options" under the "Airport" section.
* Have the correct SNMP community name? The SNMP community name is the same
as the base station password used for remote administration.
* Try running in verbose mode (-v) for more diagnostic information.
EOF
print $snmpHelp;
next;
}
# Get the base station ID and other important info
$name = $host{"sysConfName.0"}; # bssid
$wlan = $host{"wirelessNumber.0"}; # number of wlan clients
$dhcp = $host{"dhcpNumber.0"}; # number of dhcp clients
if ( !$delimited ) {
print "$host ($name) WLAN clients: $wlan DHCP clients: $dhcp\n";
}
else {
print "*,$host,$name,$wlan,$dhcp\n";
}
# make a list of data keys we got from the output. One of these for each
# line of snmpwalk output.
@keys = keys %host;
# Look for lines containing MAC address, and make @MACs out of them. Each
# of these lines also includes a wireless device identifier. It is used as
# a key to that device's data across various parts of the SNMP MIB.
@MACs = grep( /wirelessPhysAddress/, @keys );
foreach $macline (@MACs) {
# Extract the unique client ID mentioned above. Populate the @CLIENTS
# arrayy. This array is a list of devices wirelessly associated with
# this base station.
$macline =~ /"".(.*?)$/;
push @CLIENTS, $1;
# A bit of reformatting on the MAC address as returned by snmpwalk...
$mac = $host{$macline};
$mac =~ s/"//g;
$mac =~ s/^\s//;
$mac =~ s/\s$//g;
$host{$macline} = "$mac";
# Look up this device's name in the %friends hash. If it exists, replace
# the MAC address in the %host hash with the friendly name.
if ( grep /$mac/, @friends ) {
$host{$macline} = $friends{$mac};
}
}
# Print the field headings with a bit of surrounding space
if ( !defined $delimited ) {
foreach $item (@infos) {
print " $headings{$item} ";
}
print "\n";
}
# for each device that is associated to the base station, we'll call the
# getClientInfo subroutine, passing it the wireless client ID number and
# the list of fields for which we want information. @results are returned.
$size = @infos; # so we know when to stop printing delimiters...
print "printing $size attributes\n" if ( $debug gt 0 );
foreach $client (@CLIENTS) {
print "going to pass: [$client] and [@infos] to getClientInfo\n"
if ( $debug gt 1 );
@results = getClientInfo( "$client", "@infos" );
print "got results [@results]\n" if ( $debug gt 1 );
# $i is used here to keep track of our position in @CLIENTS
$i = 0;
print " " unless ( defined $delimited );
# Step through each of the results and print, according to a
# pre-determined format string. Some data requires pre-processing...
foreach $item (@results) {
print "Working result: [$infos[$i]]" if ( $debug gt 2 );
# handle special cases (e.g. time)
if ( ( $infos[$i] eq "wirelessTimeAssociated" )
|| ( $infos[$i] eq "wirelessLastRefreshTime" ) )
{
# format the duration in seconds in a more readable fashion
if ( !defined $delimited ) {
printf " %1d %2d:%02d:%02d ",
( gmtime $item )[ 7, 2, 1, 0 ];
}
else {
printf "%d:%d:%d:%d", ( gmtime $item )[ 7, 2, 1, 0 ];
}
}
else {
# print the data $results[$i] using the format string in
# $formats[$i], or as comma delimited
if ( !defined $delimited ) {
printf " $formats[$i] ", "$results[$i]";
}
else {
printf "%s", "$results[$i]";
}
}
print "," if ( ( $i <= ( $size - 2 ) ) && ( defined $delimited ) );
print "End of loop $i...\n" if ( $debug gt 1 );
$i++;
}
$i = 0;
$wlan = $host{"wirelessNumber.0"};
$dhcp = $host{"dhcpNumber.0"};
print "\n";
}
undef @CLIENTS;
undef @MACs;
undef %host;
}
# Receives wireless client ID and array of data object names.
# Returns array of data object values in the same order
# This sub is here because looking up the data we stored from the snmpwalk
# output requires looking for a specific pattern of data key name and wireless
# client id number. This sub prepares that pattern, which is also a key
# name in a %host hash, then retrieves the value of that hash element, storing
# it in @clientData.
sub getClientInfo {
my @clientData;
my $clientid = shift;
my @objects = shift;
@objects = split( /\s/, $objects[0] );
print "client is $client\n" if ( $debug gt 2 );
foreach $obj (@objects) {
print "working obj: $obj\n" if ( $debug gt 2 );
$string = "$obj.\"\".$clientid";
print "string is: $string\n" if ( $debug gt 2 );
push( @clientData, "$host{$string}" );
}
#print "clientData is @clientData\n";
return @clientData;
}
sub printCliHelp {
$helpText = <<EOF;
Program Options:
-h print this help
-d output in comma delimited format
(base station ID lines are designated by '*')
-v verbose mode
(stacks up to 3 times)
--encode Specify plaintext to retrieve the base64 encoding.
This should be used to store the SNMP community name near
the top of this script.
Output Field Specification
Each of these letters represents a piece of available information about a
wireless device associated to the base station. The order in which the tokens
are specified is used as the output field order. Do not prefix these tokens
with "-" or "--".
n wirelessPhysAddress
y wirelessType
s wirelessStrength
r wirelessRate
o wirelessNoise
t wirelessNumTX
e wirelessNumRX
T wirelessNumTXErrors
E wirelessNumRXErrors
a wirelessTimeAssociated
i wirelessLastRefreshTime
Default output is equivalent to "nysroai".
Examples:
Debug mode with default fields:
airport -v
Comma-delimited mode with field specification:
airport -d nsroteTE
EOF
print "$helpText";
exit 0;
}
sub buildFormatStrings {
#print "building format strings for [@infos]\n";
# will use global @infos
# returns array of format strings
@formats = (); # return this
%formats = (
# These are tweaked to align with the column headings. Not used if
# running in $delimited mode.
'wirelessPhysAddress' => "%-17s",
'wirelessType' => "%-4s",
'wirelessStrength' => "%-4s",
'wirelessRate' => "%-4s",
'wirelessNoise' => "%-5s",
'wirelessNumTX' => "%-10s",
'wirelessNumRX' => "%-10s",
'wirelessNumTXErrors' => "%-10s",
'wirelessNumRXErrors' => "%-10s",
'wirelessTimeAssociated' => "%-8s",
'wirelessLastRefreshTime' => "%-8s"
);
%headings = (
'wirelessPhysAddress' => "Name or Address ",
'wirelessType' => "Type",
'wirelessStrength' => "Str ",
'wirelessRate' => "Rate",
'wirelessNoise' => "Noise",
'wirelessNumTX' => "TX Frames ",
'wirelessNumRX' => "RX Frames ",
'wirelessNumTXErrors' => "TX Errors ",
'wirelessNumRXErrors' => "RX Errors ",
'wirelessTimeAssociated' => "Connected ",
'wirelessLastRefreshTime' => "Idle "
);
foreach $a (@infos) {
print "buildFormatStrings: got $a from infos...\n" if ( $debug gt 2 );
print "buildFormatStrings: $a is: $formats{$a}\n" if ( $debug gt 2 );
push( @formats, "$formats{$a}" );
}
return @formats;
}
sub orderFields {
$fieldOrder .= "$_[0]";
print "fieldOrder is: $fieldOrder\n"
if ( ( defined $debug ) && ( $debug gt 1 ) );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment