App-Netdisco
view release on metacpan or search on metacpan
lib/App/Netdisco/Transport/SNMP.pm view on Meta::CPAN
not support specifying the device class. The purpose is to test the SNMP
connectivity to the device before a renumber.
Attempts to have no side effect, however there will be a stored SNMP
authentication hint (tag) in the database if the connection is successful.
Returns C<undef> if the connection fails.
=cut
sub test_connection {
my ($class, $ip) = @_;
my $addr = NetAddr::IP::Lite->new($ip) or return undef;
# avoid renumbering to localhost loopbacks
return undef if $addr->addr eq '0.0.0.0'
or acl_matches($addr->addr, 'group:__LOOPBACK_ADDRESSES__');
my $device = schema(vars->{'tenant'})->resultset('Device')
->new_result({ ip => $addr->addr }) or return undef;
my $readers = $class->instance->readers or return undef;
return $readers->{$device->ip} if exists $readers->{$device->ip};
debug sprintf 'snmp reader cache warm: [%s]', $device->ip;
return ($readers->{$device->ip} = _snmp_connect_generic('read', $device));
}
=head1 writer_for( $ip, $useclass? )
Same as C<reader_for> but uses the read-write community strings from the
application configuration file.
Returns C<undef> if the connection fails.
=cut
sub writer_for {
my ($class, $ip, $useclass) = @_;
my $device = get_device($ip) or return undef;
return undef if $device->in_storage and $device->is_pseudo;
my $writers = $class->instance->writers or return undef;
return $writers->{$device->ip} if exists $writers->{$device->ip};
debug sprintf 'snmp writer cache warm: [%s]', $device->ip;
return ($writers->{$device->ip}
= _snmp_connect_generic('write', $device, $useclass));
}
sub _snmp_connect_generic {
my ($mode, $device, $useclass) = @_;
$mode ||= 'read';
my %snmp_args = (
AutoSpecify => 0,
DestHost => $device->ip,
# the defined() allows 0 to be a settable value
Retries => defined(setting('snmpretries')) ? setting('snmpretries') : 2,
Timeout => (setting('snmptimeout') || 1000000),
NonIncreasing => (setting('nonincreasing') || 0),
BulkWalk => ((defined setting('bulkwalk_off') && setting('bulkwalk_off'))
? 0 : 1),
BulkRepeaters => (setting('bulkwalk_repeaters') || 20),
MibDirs => [ get_mibdirs() ],
IgnoreNetSNMPConf => 1,
Debug => ($ENV{INFO_TRACE} || 0),
DebugSNMP => ($ENV{SNMP_TRACE} || 0),
);
# an override for RemotePort
($snmp_args{RemotePort}) =
(pairkeys pairfirst { acl_matches($device, $b) }
%{setting('snmp_remoteport') || {}}) || 161;
# an override for bulkwalk
$snmp_args{BulkWalk} = 0 if acl_matches($device, 'bulkwalk_no');
# further protect against buggy Net-SNMP, and disable bulkwalk
if ($snmp_args{BulkWalk}
and ($SNMP::VERSION eq '5.0203' || $SNMP::VERSION eq '5.0301')) {
warning sprintf
"[%s] turning off BulkWalk due to buggy Net-SNMP - please upgrade!",
$device->ip;
$snmp_args{BulkWalk} = 0;
}
# support for offline cache
my $cache = load_cache_for_device($device);
if (scalar keys %$cache) {
$snmp_args{Cache} = $cache;
$snmp_args{Offline} = 1;
#Â support pseudo/offline device renumber and also pseudo device autovivification
$device->set_column(is_pseudo => \'true') if not $device->is_pseudo;
debug sprintf 'snmp transport running in offline mode for: [%s]', $device->ip;
}
#Â any net-snmp options to add or override
foreach my $k (keys %{ setting('net_snmp_options') }) {
$snmp_args{ $k } = setting('net_snmp_options')->{ $k };
}
if (scalar keys %{ setting('net_snmp_options') }
or not $snmp_args{BulkWalk}) {
foreach my $k (sort keys %snmp_args) {
next if $k eq 'MibDirs';
debug sprintf 'snmp transport conf: %s => %s', $k, $snmp_args{ $k };
}
}
# get the community string(s)
my @communities = $snmp_args{Offline}
? ({read => 1, write => 0, only => $device->ip, community => 'public'})
: get_communities($device, $mode);
# which SNMP versions to try and in what order
my @versions =
( acl_matches($device->ip, 'snmpforce_v3') ? (3)
: acl_matches($device->ip, 'snmpforce_v2') ? (2)
: acl_matches($device->ip, 'snmpforce_v1') ? (1)
: (reverse (1 .. (setting('snmpver') || 3))) );
# use existing or new device class
my @classes = ($useclass || 'SNMP::Info');
if ($device->snmp_class and not $useclass) {
unshift @classes, $device->snmp_class;
}
# try last known-good by tag if it's stored
#Â this gets in the way of SNMP version upgrade (2 to 3)
#Â but can use only/no or snmpforce_v* to get around that
my $tag_name = 'snmp_auth_tag_'. $mode;
my $stored_tag = eval { $device->community->$tag_name };
if ($device->in_storage and $stored_tag) {
debug sprintf '[%s:%s] try_connect with cached tag %s',
$snmp_args{DestHost}, $snmp_args{RemotePort}, $stored_tag;
my $comm = $communities[0];
my $ver = (exists $comm->{community} ? 2 : 3);
my %local_args = (%snmp_args,
Version => $ver, Retries => 0, Timeout => 200000);
my $info = _try_connect($device, $classes[0], $comm, $mode, \%local_args,
($useclass ? 0 : 1) );
# if successful, restore the default/user timeouts and return
if ($info) {
my $class = ($useclass ? $classes[0] : $info->device_type);
return $class->new(
%snmp_args, Version => $ver,
($info->offline ? (Cache => $info->cache) : ()),
_mk_info_commargs($comm),
);
}
}
#Â try the communities in a fast pass using best version
VERSION: foreach my $ver (3, 2) {
my %local_args = (%snmp_args,
Version => $ver, Retries => 0, Timeout => 200000);
COMMUNITY: foreach my $comm (@communities) {
next unless $comm;
next if $ver eq 3 and exists $comm->{community};
next if $ver ne 3 and !exists $comm->{community};
my $info = _try_connect($device, $classes[0], $comm, $mode, \%local_args,
($useclass ? 0 : 1) );
# if successful, restore the default/user timeouts and return
if ($info) {
my $class = ($useclass ? $classes[0] : $info->device_type);
return $class->new(
%snmp_args, Version => $ver,
($info->offline ? (Cache => $info->cache) : ()),
_mk_info_commargs($comm),
);
}
}
}
#Â then revert to conservative settings and repeat with all versions
#Â unless user wants just the fast connections for bulk discovery
#Â or we are on the first discovery attempt of a new device
return if setting('snmp_try_slow_connect') == false;
CLASS: foreach my $class (@classes) {
next unless $class;
VERSION: foreach my $ver (@versions) {
next unless $ver;
my %local_args = (%snmp_args, Version => $ver);
COMMUNITY: foreach my $comm (@communities) {
next unless $comm;
next if $ver eq 3 and exists $comm->{community};
next if $ver ne 3 and !exists $comm->{community};
my $info = _try_connect($device, $class, $comm, $mode, \%local_args,
($useclass ? 0 : 1) );
return $info if $info;
}
}
}
return undef;
}
sub _try_connect {
my ($device, $class, $comm, $mode, $snmp_args, $reclass) = @_;
my %comm_args = _mk_info_commargs($comm);
my $debug_comm = '<hidden>';
if ($ENV{ND2_SHOW_COMMUNITY} || $ENV{SHOW_COMMUNITY}) {
$debug_comm = ($comm->{community} ||
(sprintf 'v3:%s:%s/%s', ($comm->{user},
($comm->{auth}->{proto} || 'noAuth'),
($comm->{priv}->{proto} || 'noPriv'))) );
}
my $info = undef;
try {
$snmp_args->{Offline} || debug
sprintf '[%s:%s] try_connect with v: %s, t: %s, r: %s, class: %s, comm: %s',
$snmp_args->{DestHost}, $snmp_args->{RemotePort},
$snmp_args->{Version}, ($snmp_args->{Timeout} / 1000000), $snmp_args->{Retries},
$class, $debug_comm;
Module::Load::load $class;
$info = $class->new(%$snmp_args, %comm_args) or return;
$info = ($mode eq 'read' ? _try_read($info, $device, $comm)
: _try_write($info, $device, $comm));
# first time a device is discovered, re-instantiate into specific class
if ($reclass and $info and $info->device_type ne $class) {
$class = $info->device_type;
$info->offline || debug
sprintf '[%s:%s] try_connect with v: %s, new class: %s, comm: %s',
$snmp_args->{DestHost}, $snmp_args->{RemotePort},
$snmp_args->{Version}, $class, $debug_comm;
Module::Load::load $class;
$info = $class->new(%$snmp_args, %comm_args);
if ($info->offline) {
add_snmpinfo_aliases($info);
fixup_browser_from_aliases($device, $info);
}
}
else {
add_snmpinfo_aliases($info) if $info and $info->offline;
}
}
catch {
debug sprintf 'caught error in try_connect: %s', $_;
undef $info;
die "exception in SNMP - could be job timeout or crash\n";
#Â use DDP; debug p $_;
};
return $info;
}
sub _try_read {
my ($info, $device, $comm) = @_;
return undef unless (
(not defined $info->error)
and (defined $info->uptime or defined $info->hrSystemUptime or defined $info->sysUpTime)
and ($info->layers or $info->description)
and $info->class
);
return $info if $info->offline;
$device->in_storage
? $device->update({snmp_ver => $info->snmp_ver})
: $device->set_column(snmp_ver => $info->snmp_ver);
if ($comm->{community}) {
my $new_comm = (($info->snmp_ver and ($info->snmp_ver == 3))
? undef : $comm->{community});
$device->in_storage
? $device->update({snmp_comm => $new_comm})
: $device->set_column(snmp_comm => $new_comm);
}
( run in 0.845 second using v1.01-cache-2.11-cpan-39bf76dae61 )