From 965990786f5c24ac3b4afa000ddfd0f92313c601 Mon Sep 17 00:00:00 2001 From: Oliver Gorwits Date: Thu, 4 Apr 2013 22:22:50 +0100 Subject: [PATCH] implementation of find_neighbors --- .../lib/App/Netdisco/Util/DiscoverAndStore.pm | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/Netdisco/lib/App/Netdisco/Util/DiscoverAndStore.pm b/Netdisco/lib/App/Netdisco/Util/DiscoverAndStore.pm index 6b2ef496..85cb6e0f 100644 --- a/Netdisco/lib/App/Netdisco/Util/DiscoverAndStore.pm +++ b/Netdisco/lib/App/Netdisco/Util/DiscoverAndStore.pm @@ -5,12 +5,14 @@ use Dancer::Plugin::DBIC 'schema'; use App::Netdisco::Util::DNS 'hostname_from_ip'; use NetAddr::IP::Lite ':lower'; +use Try::Tiny; use base 'Exporter'; our @EXPORT = (); our @EXPORT_OK = qw/ store_device store_interfaces store_wireless store_vlans store_power store_modules + find_neighbors /; our %EXPORT_TAGS = (all => \@EXPORT_OK); @@ -471,4 +473,155 @@ sub store_modules { }); } +=head2 find_neighbors( $device, $snmp ) + +Given a Device database object, and a working SNMP connection, discover and +store the device's port neighbors information. + +If any neighbor is unknown to Netdisco, a discover job for it will immediately +be queued (modulo configuration file C setting). + +The Device database object can be a fresh L object which is +not yet stored to the database. + +=cut + +sub find_neighbors { + my ($device, $snmp) = @_; + + my $c_ip = $snmp->c_ip; + unless ($snmp->hasCDP or scalar keys %$c_ip) { + # TODO log + return; + } + + 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; + + foreach my $entry (keys %$c_ip) { + my $port = $interfaces->{ $c_ip->{$entry} }; + if (!defined $port) { + # TODO log + next; + } + + my $remote_ip = $c_ip->{$entry}; + my $remote_ipad = NetAddr::IP::Lite->new($remote_ip); + my $remote_port = undef; + my $remote_type = $c_platform->{$entry}; + my $remote_id = $c_id->{$entry}; + + next unless length $remote_ip; + + # a bunch of heuristics to search known devices if we don't have a + # useable remote IP... + + if ($remote_ip eq '0.0.0.0' or + $remote_ipad->within(NetAddr::IP::Lite->new('127.0.0.0/8'))) { + + if ($remote_id) { + my $devices = schema('netdisco')->resultset('Device'); + my $neigh = $devices->single({name => $remote_id}); + # TODO log + + if (!defined $neigh) { + (my $shortid = $remote_id) =~ s/\..*//; + $neigh = $devices->single({name => { -ilike => "${shortid}%" }}); + } + + if ($neigh) { + $remote_ip = $neigh->ip; + # TODO log + } + else { + # TODO log + next; + } + } + else { + # TODO log + next; + } + } + + # hack for devices seeing multiple neighbors on the port + if (ref [] eq ref $remote_ip) { + foreach my $n (@$remote_ip) { + # TODO log + _enqueue_discover($n, $remote_type); + } + # set loopback as remote IP to suppress any further work + $remote_ip = $device->ip; + $remote_port = $port; + } + else { + $remote_port = $c_port->{$entry}; + + if (defined $remote_port) { + # clean weird characters + $remote_port =~ s/[^\d\/\.,()\w:-]+//gi; + } + else { + # TODO log + } + } + + # XXX too custom? IP Phone detection + if (defined $remote_type and $remote_type =~ m/(mitel.5\d{3})/i) { + $remote_type = 'IP Phone - '. $remote_type + if $remote_type !~ /ip phone/i; + } + + my $portrow = schema('netdisco')->resultset('DevicePort') + ->single({ip => $device->ip, port => $port}); + + if (!defined $portrow) { + # TODO log + next; + } + + $portrow->update({ + remote_ip => $remote_ip, + remote_port => $remote_port, + remote_type => $remote_type, + remote_id => $remote_id, + }); + + _enqueue_discover($remote_ip, $remote_type); + } +} + +# only enqueue if device is not already discovered, and +# discover_no_type config permits the discovery +sub _enqueue_discover { + my ($ip, $remote_type) = @_; + + my $device = get_device($ip); + return if $device->in_storage; + +# XXX should this be checked by process _taking_ the job? +# ok.. the job will sit queued, but nothing will ever action it. +# but that could still tie up workers :-( +# + my $remote_type_match = setting('discover_no_type'); + if ($remote_type and $remote_type_match + and $remote_type =~ m/$remote_type_match/) { + # TODO log + return; + } + + try { + # could fail if queued job already exists + schema('netdisco')->resultset('Admin')->create({ + device => $ip, + action => 'discover', + status => 'queued', + }); + # TODO log + }; +} + 1;