Squashed commit of the following: commit3fe8f383a7Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Mar 11 17:07:42 2019 +0000 add debug lines and tested commit3249739e42Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Mar 11 16:54:11 2019 +0000 change config name to get_credentials commite78558397aAuthor: Oliver Gorwits <oliver@cpan.org> Date: Mon Mar 11 16:51:11 2019 +0000 separate out generic device auth to DeviceAuth module commit249f05165fAuthor: Oliver Gorwits <oliver@cpan.org> Date: Wed Mar 6 18:43:31 2019 +0000 release 2.040007 commite3af64df77Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Mar 6 18:42:47 2019 +0000 #521-redux fix wifi date search commit48857ae300Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Mar 4 12:03:31 2019 +0000 release 2.040006 commite09dab5362Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Mar 4 11:39:12 2019 +0000 #527 update List::MoreUtils version requirement commit6e7de3fff3Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Mar 4 09:59:41 2019 +0000 release 2.040005 commit0c98318a45Author: Oliver Gorwits <oliver@spike.local> Date: Mon Mar 4 09:57:18 2019 +0000 #526 fix discover syntax bug commite9efc45182Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Mar 3 14:56:48 2019 +0000 release 2.040004 commit6cdfd80d10Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Mar 3 14:34:00 2019 +0000 allow undiscovered neighbors report to use discover_{waps,phones} setting commitac381e0802Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Mar 3 14:13:20 2019 +0000 #506 was a red herring commitb83e614c85Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Mar 3 13:00:36 2019 +0000 make discover_{phones,waps} work with LLDP capabilities as well commit189d234b55Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Mar 3 12:47:38 2019 +0000 check discover_no_type and friends earlier on in neighbors list build commit9c956466f3Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Mar 3 12:32:07 2019 +0000 also update default config for new discover_phones and discover_waps settings commit09d29954d2Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Mar 3 12:26:50 2019 +0000 #512 fix regression in phone/wap discovery exclusion commit2bae91f1b6Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Mar 3 12:01:34 2019 +0000 rename match_devicetype() to match_to_setting() commit57cb6ddb70Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Mar 3 09:19:39 2019 +0000 fix for over-eager fix to #506 commitef560fb59aAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Mar 2 22:41:40 2019 +0000 #506 relax device renumber so it works for an alias commit7a8bcb094eAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Mar 2 22:23:39 2019 +0000 #521 Search Node Date Range not working commita643820a62Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Mar 2 21:54:27 2019 +0000 #428 Port-Channels not showing in netmap commit5ba5bcd295Merge:e7aacddba1f95028Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Mar 2 20:04:11 2019 +0000 Merge branch 'master' of github.com:netdisco/netdisco commite7aacddbc6Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Mar 2 20:01:05 2019 +0000 #498 Map with VLAN filter omits unconnected devices commita1f95028caAuthor: nick n <39005454+inphobia@users.noreply.github.com> Date: Sat Mar 2 19:54:22 2019 +0100 catch up with changes noticed that rc-sshcollector-core received updates to changes, add them here as well. didn't mention #499 & #522 commitce1b847ceaAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Mar 2 18:47:44 2019 +0000 fix bug showing no nodes when only one matches in netmap commit78e30a7926Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Mar 2 16:28:15 2019 +0000 #500 filtering in device/ports on native vlan duplicates entries commit9952f0c6c7Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Mar 2 15:02:12 2019 +0000 #499 netdisco-do renumber reports wrong ip (inphobia) commitca3fd8f466Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Mar 2 15:00:18 2019 +0000 #505 device renumber should update device port properties and device skips commit1265bc8470Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Mar 2 14:52:21 2019 +0000 #520 catch slave ports defined without a master commitd4c7579c10Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Mar 2 14:47:49 2019 +0000 #522 TypeAhead.pm can reference empty data (inphobia) commit77decc23b7Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Mar 2 14:45:37 2019 +0000 #514 inconsistent results in ip inventory (inphobia) commit3f211650b8Author: nick n <39005454+inphobia@users.noreply.github.com> Date: Fri Mar 1 12:34:42 2019 +0100 last pieces for db schema upgrade last piece of #510
360 lines
12 KiB
Perl
360 lines
12 KiB
Perl
package App::Netdisco::Worker::Plugin::Discover::Neighbors;
|
||
|
||
use Dancer ':syntax';
|
||
use App::Netdisco::Worker::Plugin;
|
||
use aliased 'App::Netdisco::Worker::Status';
|
||
|
||
use App::Netdisco::Transport::SNMP ();
|
||
use App::Netdisco::Util::Device qw/get_device is_discoverable/;
|
||
use App::Netdisco::Util::Permission 'check_acl_no';
|
||
use App::Netdisco::JobQueue 'jq_insert';
|
||
use Dancer::Plugin::DBIC 'schema';
|
||
use List::MoreUtils ();
|
||
use NetAddr::IP::Lite ();
|
||
use NetAddr::MAC;
|
||
use Encode;
|
||
use Try::Tiny;
|
||
|
||
=head2 discover_new_neighbors( )
|
||
|
||
Given a Device database object, and a working SNMP connection, discover and
|
||
store the device's port neighbors information.
|
||
|
||
Entries in the Topology database table will override any discovered device
|
||
port relationships.
|
||
|
||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||
not yet stored to the database.
|
||
|
||
Any discovered neighbor unknown to Netdisco will have a C<discover> job
|
||
immediately queued (subject to the filtering by the C<discover_*> settings).
|
||
|
||
=cut
|
||
|
||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||
my ($job, $workerconf) = @_;
|
||
|
||
my $device = $job->device;
|
||
return unless $device->in_storage;
|
||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||
or return Status->defer("discover failed: could not SNMP connect to $device");
|
||
|
||
my @to_discover = store_neighbors($device);
|
||
my (%seen_id, %seen_ip) = ((), ());
|
||
|
||
# only enqueue if device is not already discovered,
|
||
# discover_* config permits the discovery
|
||
foreach my $neighbor (@to_discover) {
|
||
my ($ip, $remote_id) = @$neighbor;
|
||
if ($seen_ip{ $ip }++) {
|
||
debug sprintf
|
||
' queue - skip: IP %s is already queued from %s',
|
||
$ip, $device->ip;
|
||
next;
|
||
}
|
||
|
||
if ($remote_id and $seen_id{ $remote_id }++) {
|
||
debug sprintf
|
||
' queue - skip: %s with ID [%s] already queued from %s',
|
||
$ip, $remote_id, $device->ip;
|
||
next;
|
||
}
|
||
|
||
my $newdev = get_device($ip);
|
||
next if $newdev->in_storage;
|
||
|
||
# risk of things going wrong...?
|
||
# https://quickview.cloudapps.cisco.com/quickview/bug/CSCur12254
|
||
|
||
jq_insert({
|
||
device => $ip,
|
||
action => 'discover',
|
||
subaction => 'with-nodes',
|
||
($remote_id ? (device_key => $remote_id) : ()),
|
||
});
|
||
|
||
vars->{'queued'}->{$ip} = true;
|
||
debug sprintf ' [%s] queue - queued %s for discovery (ID: [%s])',
|
||
$device, $ip, ($remote_id || '');
|
||
}
|
||
|
||
return Status->info(sprintf ' [%s] neigh - processed %s neighbors',
|
||
$device->ip, scalar @to_discover);
|
||
});
|
||
|
||
=head2 store_neighbors( $device )
|
||
|
||
returns: C<@to_discover>
|
||
|
||
Given a Device database object, and a working SNMP connection, discover and
|
||
store the device's port neighbors information.
|
||
|
||
Entries in the Topology database table will override any discovered device
|
||
port relationships.
|
||
|
||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||
not yet stored to the database.
|
||
|
||
A list of discovererd neighbors will be returned as [C<$ip>, C<$type>] tuples.
|
||
|
||
=cut
|
||
|
||
sub store_neighbors {
|
||
my $device = shift;
|
||
my @to_discover = ();
|
||
|
||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||
or return (); # already checked!
|
||
|
||
# first allow any manually configured topology to be set
|
||
# and do this before we cache the rows in vars->{'device_ports'}
|
||
set_manual_topology($device);
|
||
|
||
if (!defined $snmp->has_topo) {
|
||
debug sprintf ' [%s] neigh - neighbor protocols are not enabled', $device->ip;
|
||
return @to_discover;
|
||
}
|
||
|
||
my $interfaces = $snmp->interfaces;
|
||
my $c_if = $snmp->c_if;
|
||
my $c_port = $snmp->c_port;
|
||
my $c_id = $snmp->c_id;
|
||
my $c_platform = $snmp->c_platform;
|
||
my $c_cap = $snmp->c_cap;
|
||
|
||
# cache the device ports to save hitting the database for many single rows
|
||
vars->{'device_ports'} =
|
||
{ map {($_->port => $_)} $device->ports->reset->all };
|
||
my $device_ports = vars->{'device_ports'};
|
||
|
||
# v4 and v6 neighbor tables
|
||
my $c_ip = ($snmp->c_ip || {});
|
||
my %c_ipv6 = %{ ($snmp->can('hasLLDP') and $snmp->hasLLDP)
|
||
? ($snmp->lldp_ipv6 || {}) : {} };
|
||
|
||
# remove keys with undef values, as c_ip does
|
||
delete @c_ipv6{ grep { not defined $c_ipv6{$_} } keys %c_ipv6 };
|
||
# now combine them, v6 wins
|
||
$c_ip = { %$c_ip, %c_ipv6 };
|
||
|
||
foreach my $entry (sort (List::MoreUtils::uniq( keys %$c_ip ))) {
|
||
if (!defined $c_if->{$entry} or !defined $interfaces->{ $c_if->{$entry} }) {
|
||
debug sprintf ' [%s] neigh - port for IID:%s not resolved, skipping',
|
||
$device->ip, $entry;
|
||
next;
|
||
}
|
||
|
||
# WRT #475 this is SAFE because we check against known ports below
|
||
my $port = $interfaces->{ $c_if->{$entry} } or next;
|
||
my $portrow = $device_ports->{$port};
|
||
|
||
if (!defined $portrow) {
|
||
debug sprintf ' [%s] neigh - local port %s already skipped, ignoring',
|
||
$device->ip, $port;
|
||
next;
|
||
}
|
||
|
||
if (ref $c_ip->{$entry}) {
|
||
error sprintf ' [%s] neigh - Error! port %s has multiple neighbors - skipping',
|
||
$device->ip, $port;
|
||
next;
|
||
}
|
||
|
||
if ($portrow->manual_topo) {
|
||
debug sprintf ' [%s] neigh - %s has manually defined topology',
|
||
$device->ip, $port;
|
||
next;
|
||
}
|
||
|
||
my $remote_ip = $c_ip->{$entry};
|
||
my $remote_port = undef;
|
||
my $remote_type = Encode::decode('UTF-8', $c_platform->{$entry} || '');
|
||
my $remote_id = Encode::decode('UTF-8', $c_id->{$entry});
|
||
my $remote_cap = $c_cap->{$entry} || [];
|
||
|
||
next unless $remote_ip;
|
||
my $r_netaddr = NetAddr::IP::Lite->new($remote_ip);
|
||
|
||
if ($r_netaddr and ($r_netaddr->addr ne $remote_ip)) {
|
||
debug sprintf ' [%s] neigh - IP on %s: using %s as canonical form of %s',
|
||
$device->ip, $port, $r_netaddr->addr, $remote_ip;
|
||
$remote_ip = $r_netaddr->addr;
|
||
}
|
||
|
||
# a bunch of heuristics to search known devices if we don't have a
|
||
# useable remote IP...
|
||
|
||
if ((! $r_netaddr) or ($remote_ip eq '0.0.0.0') or
|
||
check_acl_no($remote_ip, 'group:__LOCAL_ADDRESSES__')) {
|
||
|
||
if ($remote_id) {
|
||
my $devices = schema('netdisco')->resultset('Device');
|
||
my $neigh = $devices->single({name => $remote_id});
|
||
debug sprintf
|
||
' [%s] neigh - bad address %s on port %s, searching for %s instead',
|
||
$device->ip, $remote_ip, $port, $remote_id;
|
||
|
||
if (!defined $neigh) {
|
||
my $mac = NetAddr::MAC->new(mac => $remote_id);
|
||
if ($mac and not $mac->errstr) {
|
||
$neigh = $devices->single({mac => $mac->as_ieee});
|
||
}
|
||
}
|
||
|
||
# some HP switches send 127.0.0.1 as remote_ip if no ip address
|
||
# on default vlan for HP switches remote_ip looks like
|
||
# "myswitchname(012345-012345)"
|
||
if (!defined $neigh) {
|
||
(my $tmpid = $remote_id) =~ s/.*\(([0-9a-f]{6})-([0-9a-f]{6})\).*/$1$2/;
|
||
my $mac = NetAddr::MAC->new(mac => $tmpid);
|
||
if ($mac and not $mac->errstr) {
|
||
debug sprintf
|
||
' [%s] neigh - trying to find neighbor %s by MAC %s',
|
||
$device->ip, $remote_id, $mac->as_ieee;
|
||
$neigh = $devices->single({mac => $mac->as_ieee});
|
||
}
|
||
}
|
||
|
||
if (!defined $neigh) {
|
||
(my $shortid = $remote_id) =~ s/\..*//;
|
||
$neigh = $devices->single({name => { -ilike => "${shortid}%" }});
|
||
}
|
||
|
||
if ($neigh) {
|
||
$remote_ip = $neigh->ip;
|
||
debug sprintf ' [%s] neigh - found %s with IP %s',
|
||
$device->ip, $remote_id, $remote_ip;
|
||
}
|
||
else {
|
||
debug sprintf ' [%s] neigh - could not find %s, skipping',
|
||
$device->ip, $remote_id;
|
||
next;
|
||
}
|
||
}
|
||
else {
|
||
debug sprintf ' [%s] neigh - skipping unuseable address %s on port %s',
|
||
$device->ip, $remote_ip, $port;
|
||
next;
|
||
}
|
||
}
|
||
|
||
# what we came here to do.... discover the neighbor
|
||
debug sprintf ' [%s] neigh - %s with ID [%s] on %s',
|
||
$device->ip, $remote_ip, ($remote_id || ''), $port;
|
||
|
||
if (is_discoverable($remote_ip, $remote_type, $remote_cap)) {
|
||
push @to_discover, [$remote_ip, $remote_id];
|
||
}
|
||
else {
|
||
debug sprintf
|
||
' [%s] neigh - skip: %s of type [%s] excluded by discover_* config',
|
||
$device->ip, $remote_ip, ($remote_type || '');
|
||
}
|
||
|
||
$remote_port = $c_port->{$entry};
|
||
if (defined $remote_port) {
|
||
# clean weird characters
|
||
$remote_port =~ s/[^\d\s\/\.,()\w:-]+//gi;
|
||
}
|
||
else {
|
||
debug sprintf ' [%s] neigh - no remote port found for port %s at %s',
|
||
$device->ip, $port, $remote_ip;
|
||
}
|
||
|
||
$portrow = $portrow->update({
|
||
remote_ip => $remote_ip,
|
||
remote_port => $remote_port,
|
||
remote_type => $remote_type,
|
||
remote_id => $remote_id,
|
||
is_uplink => \"true",
|
||
manual_topo => \"false",
|
||
})->discard_changes();
|
||
|
||
# update master of our aggregate to be a neighbor of
|
||
# the master on our peer device (a lot of iffs to get there...).
|
||
# & cannot use ->neighbor prefetch because this is the port insert!
|
||
if (defined $portrow->slave_of) {
|
||
|
||
my $peer_device = get_device($remote_ip);
|
||
my $master = schema('netdisco')->resultset('DevicePort')->single({
|
||
ip => $device->ip,
|
||
port => $portrow->slave_of
|
||
});
|
||
|
||
if ($peer_device and $peer_device->in_storage and $master
|
||
and not ($portrow->is_master or defined $master->slave_of)) {
|
||
|
||
my $peer_port = schema('netdisco')->resultset('DevicePort')->single({
|
||
ip => $peer_device->ip,
|
||
port => $portrow->remote_port,
|
||
});
|
||
|
||
$master->update({
|
||
remote_ip => ($peer_device->ip || $remote_ip),
|
||
remote_port => ($peer_port ? $peer_port->slave_of : undef ),
|
||
is_uplink => \"true",
|
||
is_master => \"true",
|
||
manual_topo => \"false",
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
return @to_discover;
|
||
}
|
||
|
||
# take data from the topology table and update remote_ip and remote_port
|
||
# in the devices table. only use root_ips and skip any bad topo entries.
|
||
sub set_manual_topology {
|
||
my $device = shift;
|
||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device) or return;
|
||
|
||
schema('netdisco')->txn_do(sub {
|
||
# clear manual topology flags
|
||
schema('netdisco')->resultset('DevicePort')
|
||
->search({ip => $device->ip})->update({manual_topo => \'false'});
|
||
|
||
my $topo_links = schema('netdisco')->resultset('Topology')
|
||
->search({-or => [dev1 => $device->ip, dev2 => $device->ip]});
|
||
debug sprintf ' [%s] neigh - setting manual topology links', $device->ip;
|
||
|
||
while (my $link = $topo_links->next) {
|
||
# could fail for broken topo, but we ignore to try the rest
|
||
try {
|
||
schema('netdisco')->txn_do(sub {
|
||
# only work on root_ips
|
||
my $left = get_device($link->dev1);
|
||
my $right = get_device($link->dev2);
|
||
|
||
# skip bad entries
|
||
return unless ($left->in_storage and $right->in_storage);
|
||
|
||
$left->ports
|
||
->single({port => $link->port1})
|
||
->update({
|
||
remote_ip => $right->ip,
|
||
remote_port => $link->port2,
|
||
remote_type => undef,
|
||
remote_id => undef,
|
||
is_uplink => \"true",
|
||
manual_topo => \"true",
|
||
});
|
||
|
||
$right->ports
|
||
->single({port => $link->port2})
|
||
->update({
|
||
remote_ip => $left->ip,
|
||
remote_port => $link->port1,
|
||
remote_type => undef,
|
||
remote_id => undef,
|
||
is_uplink => \"true",
|
||
manual_topo => \"true",
|
||
});
|
||
});
|
||
};
|
||
}
|
||
});
|
||
}
|
||
|
||
true;
|