Dave Cross: Still Munging Data With Perl: Online event - Mar 27 Learn more

#!/usr/bin/env perl
use strict;
our $home;
BEGIN {
use FindBin;
FindBin::again();
$home = ($ENV{NETDISCO_HOME} || $ENV{HOME});
# try to find a localenv if one isn't already in place.
if (!exists $ENV{PERL_LOCAL_LIB_ROOT}) {
my $localenv = File::Spec->catfile($FindBin::RealBin, 'localenv');
exec($localenv, $0, @ARGV) if -f $localenv;
$localenv = File::Spec->catfile($home, 'perl5', 'bin', 'localenv');
exec($localenv, $0, @ARGV) if -f $localenv;
die "Sorry, can't find libs required for App::Netdisco.\n"
if !exists $ENV{PERLBREW_PERL};
}
}
BEGIN {
# stuff useful locations into @INC and $PATH
unshift @INC,
dir($FindBin::RealBin)->parent->subdir('lib')->stringify,
dir($FindBin::RealBin, 'lib')->stringify;
unshift @INC,
split m/:/, ($ENV{NETDISCO_INC} || '');
use Config;
$ENV{PATH} = $FindBin::RealBin . $Config{path_sep} . $ENV{PATH};
}
use App::Netdisco::Util::Node qw/check_mac store_arp/;
use App::Netdisco::Util::FastResolver 'hostnames_resolve_async';
use Dancer ':script';
use Module::Load ();
use MCE::Loop Sereal => 1;
use Pod::Usage 'pod2usage';
Getopt::Long::Configure ("bundling");
my ($debug, $sqltrace) = (undef, 0);
my $result = GetOptions(
'debug|D' => \$debug,
'sqltrace|Q+' => \$sqltrace,
) or pod2usage(
-msg => 'error: bad options',
-verbose => 0,
-exitval => 1,
);
my $CONFIG = config();
$CONFIG->{logger} = 'console';
$CONFIG->{log} = ($debug ? 'debug' : 'info');
$ENV{DBIC_TRACE} ||= $sqltrace;
# reconfigure logging to force console output
Dancer::Logger->init('console', $CONFIG);
#this may be helpful with SSH issues:
#$Net::OpenSSH::debug = ~0;
MCE::Loop::init { chunk_size => 1 };
my %stats;
$stats{entry} = 0;
exit main();
sub main {
my @input = @{ setting('sshcollector') };
my @mce_result = mce_loop {
my ($mce, $chunk_ref, $chunk_id) = @_;
my $host = $chunk_ref->[0];
my $hostlabel = (!defined $host->{hostname} or $host->{hostname} eq "-")
? $host->{ip} : $host->{hostname};
if ($hostlabel) {
my $ssh = Net::OpenSSH->new(
$hostlabel,
user => $host->{user},
password => $host->{password},
timeout => 30,
async => 0,
default_stderr_file => '/dev/null',
master_opts => [
-o => "StrictHostKeyChecking=no",
-o => "BatchMode=no"
],
);
MCE->gather( process($hostlabel, $ssh, $host) );
}
} \@input;
return 0 unless scalar @mce_result;
foreach my $host (@mce_result) {
$stats{host}++;
info sprintf ' [%s] arpnip - retrieved %s entries',
$host->[0], scalar @{$host->[1]};
store_arpentries($host->[1]);
}
info sprintf 'arpnip - processed %s ARP Cache entries from %s devices',
$stats{entry}, $stats{host};
return 0;
}
sub process {
my ($hostlabel, $ssh, $args) = @_;
my $class = "App::Netdisco::SSHCollector::Platform::".$args->{platform};
Module::Load::load $class;
my $device = $class->new();
my $arpentries = [ $device->arpnip($hostlabel, $ssh, $args) ];
# debug p $arpentries;
if (not scalar @$arpentries) {
warning "WARNING: no entries received from <$hostlabel>";
}
hostnames_resolve_async($arpentries);
return [$hostlabel, $arpentries];
}
sub store_arpentries {
my ($arpentries) = @_;
foreach my $arpentry ( @$arpentries ) {
# skip broadcast/vrrp/hsrp and other wierdos
next unless check_mac( $arpentry->{mac} );
debug sprintf ' arpnip - stored entry: %s / %s',
$arpentry->{mac}, $arpentry->{ip};
store_arp({
node => $arpentry->{mac},
ip => $arpentry->{ip},
dns => $arpentry->{dns},
});
$stats{entry}++;
}
}
=head1 NAME
netdisco-sshcollector - Collect ARP data for Netdisco from devices without
full SNMP support
=head1 SYNOPSIS
# install dependencies:
~netdisco/bin/localenv cpanm --notest Net::OpenSSH Expect
# run manually, or add to cron:
~/bin/netdisco-sshcollector [-DQ]
=head1 DESCRIPTION
Collects ARP data for Netdisco from devices without full SNMP support.
Currently, ARP tables can be retrieved from the following device classes:
=over 4
=item * L<App::Netdisco::SSHCollector::Platform::GAIAEmbedded> - Check Point GAIA Embedded
=item * L<App::Netdisco::SSHCollector::Platform::CPVSX> - Check Point VSX
=item * L<App::Netdisco::SSHCollector::Platform::ACE> - Cisco ACE
=item * L<App::Netdisco::SSHCollector::Platform::ASA> - Cisco ASA
=item * L<App::Netdisco::SSHCollector::Platform::IOS> - Cisco IOS
=item * L<App::Netdisco::SSHCollector::Platform::NXOS> - Cisco NXOS
=item * L<App::Netdisco::SSHCollector::Platform::IOSXR> - Cisco IOS XR
=item * L<App::Netdisco::SSHCollector::Platform::BigIP> - F5 Networks BigIP
=item * L<App::Netdisco::SSHCollector::Platform::FreeBSD> - FreeBSD
=item * L<App::Netdisco::SSHCollector::Platform::Linux> - Linux
=item * L<App::Netdisco::SSHCollector::Platform::PaloAlto> - Palo Alto
=back
The collected arp entries are then directly stored in the netdisco database.
=head1 CONFIGURATION
The following should go into your Netdisco 2 configuration file, "C<<
~/environments/deployment.yml >>"
=over 4
=item C<sshcollector>
Data is collected from the machines specified in this setting. The format is a
list of dictionaries. The keys C<ip>, C<user>, C<password>, and C<platform>
are required. Optionally the C<hostname> key can be used instead of the
C<ip>. For example:
sshcollector:
- ip: '192.0.2.1'
user: oliver
password: letmein
platform: IOS
- hostname: 'core-router.example.com'
user: oliver
password: letmein
platform: IOS
Platform is the final part of the classname to be instantiated to query the
host, e.g. platform B<ACE> will be queried using
C<App::Netdisco::SSHCollector::Platform::ACE>.
If the password is "-", public key authentication will be attempted.
=back
=head1 ADDING DEVICES
Additional device classes can be easily integrated just by adding and
additonal class to the C<App::Netdisco::SSHCollector::Platform> namespace.
This class must implement an C<arpnip($hostname, $ssh)> method which returns
an array of hashrefs in the format
@result = ({ ip => IPADDR, mac => MACADDR }, ...)
The parameter C<$ssh> is an active C<Net::OpenSSH> connection to the host.
Depending on the target system, it can be queried using simple methods like
my @data = $ssh->capture("show whatever")
or automated via Expect - this is mostly useful for non-Linux appliances which
don't support command execution via ssh:
my ($pty, $pid) = $ssh->open2pty or die "unable to run remote command";
my $expect = Expect->init($pty);
my $prompt = qr/#/;
my ($pos, $error, $match, $before, $after) = $expect->expect(10, -re, $prompt);
$expect->send("terminal length 0\n");
# etc...
The returned IP and MAC addresses should be in a format that the respective
B<inetaddr> and B<macaddr> datatypes in PostgreSQL can handle.
=head1 DEBUG LEVELS
The flags "C<-DQ>" can be specified, multiple times, and enable the following
items in order:
=over 4
=item C<-D>
Netdisco debug log level
=item C<-Q>
L<DBIx::Class> trace enabled
=back
=head1 DEPENDENCIES
=over 4
=item L<App::Netdisco>
=item L<Net::OpenSSH>
=item L<Expect>
=back
=cut