From 8bda3751df170950c2ec53cabc9bf0364f720e2f Mon Sep 17 00:00:00 2001 From: Ramon Novoa Date: Mon, 21 May 2018 15:03:54 +0200 Subject: [PATCH] Improvements to the Pandora FMS Recon Server. - Ignore virtual interfaces. - Improved scan of ARP caches. --- pandora_server/lib/PandoraFMS/Recon/Base.pm | 109 +++++++++++++++++--- 1 file changed, 92 insertions(+), 17 deletions(-) diff --git a/pandora_server/lib/PandoraFMS/Recon/Base.pm b/pandora_server/lib/PandoraFMS/Recon/Base.pm index 1236054528..e0d6ae8fe1 100644 --- a/pandora_server/lib/PandoraFMS/Recon/Base.pm +++ b/pandora_server/lib/PandoraFMS/Recon/Base.pm @@ -19,6 +19,7 @@ use Socket qw/inet_aton/; my $DEVNULL = ($^O eq 'MSWin32') ? '/Nul' : '/dev/null'; # Some useful OIDs. +our $ATPHYSADDRESS = ".1.3.6.1.2.1.3.1.1.2"; our $DOT1DBASEBRIDGEADDRESS = ".1.3.6.1.2.1.17.1.1.0"; our $DOT1DBASEPORTIFINDEX = ".1.3.6.1.2.1.17.1.4.1.2"; our $DOT1DTPFDBADDRESS = ".1.3.6.1.2.1.17.4.3.1.1"; @@ -30,11 +31,12 @@ our $IFINDEX = ".1.3.6.1.2.1.2.2.1.1"; our $IFINOCTECTS = ".1.3.6.1.2.1.2.2.1.10"; our $IFOPERSTATUS = ".1.3.6.1.2.1.2.2.1.8"; our $IFOUTOCTECTS = ".1.3.6.1.2.1.2.2.1.16"; +our $IFTYPE = ".1.3.6.1.2.1.2.2.1.3"; our $IPENTADDR = ".1.3.6.1.2.1.4.20.1.1"; our $IFNAME = ".1.3.6.1.2.1.31.1.1.1.1"; our $IFPHYSADDRESS = ".1.3.6.1.2.1.2.2.1.6"; -our $IPNETTOMEDIAPHYSADDRESS = ".1.3.6.1.2.1.4.22.1.2"; our $IPADENTIFINDEX = ".1.3.6.1.2.1.4.20.1.2"; +our $IPNETTOMEDIAPHYSADDRESS = ".1.3.6.1.2.1.4.22.1.2"; our $IPROUTEIFINDEX = ".1.3.6.1.2.1.4.21.1.2"; our $IPROUTENEXTHOP = ".1.3.6.1.2.1.4.21.1.7"; our $IPROUTETYPE = ".1.3.6.1.2.1.4.21.1.8"; @@ -88,6 +90,9 @@ sub new { # Keep our own ARP cache to connect hosts to switches/routers. arp_cache => {}, + # Found children. + children => {}, + # Working SNMP community for each device. community_cache => {}, @@ -289,20 +294,9 @@ sub snmp_discovery($$) { # Find interfaces for the device. $self->find_ifaces($device); - - # Try to learn more MAC addresses from the device's ARP cache. - my @output = $self->snmp_get($device, $IPNETTOMEDIAPHYSADDRESS); - foreach my $line (@output) { - next unless ($line =~ /^$IPNETTOMEDIAPHYSADDRESS.\d+.(\S+)\s+=\s+\S+:\s+(.*)$/); - my ($ip_addr, $mac_addr) = ($1, $2); - - # Skip broadcast, net and local addresses. - next if ($ip_addr =~ m/\.255$|\.0$|127\.0\.0\.1$/); - - $mac_addr = parse_mac($mac_addr); - $self->add_mac($mac_addr, $ip_addr); - $self->call('message', "Found MAC $mac_addr for host $ip_addr in the ARP cache of host $device.", 5); - } + + # Check remote ARP caches. + $self->remote_arp($device); } } @@ -411,6 +405,9 @@ sub find_ifaces($$) { next unless ($if_index =~ /^[0-9]+$/); + # Ignore virtual interfaces. + next if ($self->get_if_type($device, $if_index) eq '53'); + # Get the MAC. my $mac = $self->get_if_mac($device, $if_index); next unless (defined($mac) && $mac ne ''); @@ -636,7 +633,7 @@ sub get_if_ip($$$) { my @output = $self->snmp_get($device, $IPADENTIFINDEX); foreach my $line (@output) { chomp ($line); - return $1 if ($line =~ m/^IPADENTIFINDEX.(\S+)\s+=\s+\S+:\s+$if_index$/); + return $1 if ($line =~ m/^$IPADENTIFINDEX.(\S+)\s+=\s+\S+:\s+$if_index$/); } return ''; @@ -657,6 +654,18 @@ sub get_if_mac($$$) { return $mac; } +######################################################################################## +# Returns the type of the given interface (by index). +######################################################################################## +sub get_if_type($$$) { + my ($self, $device, $if_index) = @_; + + my $type = $self->snmp_get_value($device, "$IFTYPE.$if_index"); + return '' unless defined($type); + + return $type; +} + ######################################################################################## # Get an IP address from the ARP cache given the MAC address. ######################################################################################## @@ -858,6 +867,20 @@ sub guess_device_type($$) { $self->set_device_type($device, $device_type); } +######################################################################################## +# Return 1 if the given device has children. +######################################################################################## +sub has_children($$) { + my ($self, $device) = @_; + + # Check for aliases! + $device = $self->{'aliases'}->{$device} if defined($self->{'aliases'}->{$device}); + + return 1 if (defined($self->{'children'}->{$device})); + + return 0; +} + ######################################################################################## # Return 1 if the given device has a parent. ######################################################################################## @@ -949,6 +972,7 @@ sub mark_connected($$;$$$) { # A parent-child relationship is always created to help complete the map with # layer 3 information. $self->{'parents'}->{$child} = $parent; + $self->{'children'}->{$parent} = $child; $self->call('set_parent', $child, $parent); } } @@ -999,6 +1023,54 @@ sub snmp_responds($$) { return 0; } +############################################################################## +# Parse the local ARP cache. +############################################################################## +sub local_arp($) { + my ($self) = @_; + + my @output = `arp -an 2>/dev/null`; + foreach my $line (@output) { + next unless ($line =~ m/\((\S+)\) at ([0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+)/); + $self->add_mac(parse_mac($2), $1); + } +} + +############################################################################## +# Parse remote SNMP ARP caches. +############################################################################## +sub remote_arp($$) { + my ($self, $device) = @_; + + # Try to learn more MAC addresses from the device's ARP cache. + my @output = $self->snmp_get($device, $IPNETTOMEDIAPHYSADDRESS); + foreach my $line (@output) { + next unless ($line =~ /^$IPNETTOMEDIAPHYSADDRESS\.\d+\.(\S+)\s+=\s+\S+:\s+(.*)$/); + my ($ip_addr, $mac_addr) = ($1, $2); + + # Skip broadcast, net and local addresses. + next if ($ip_addr =~ m/\.255$|\.0$|127\.0\.0\.1$/); + + $mac_addr = parse_mac($mac_addr); + $self->add_mac($mac_addr, $ip_addr); + $self->call('message', "Found MAC $mac_addr for host $ip_addr in the ARP cache of host $device.", 5); + } + + # Look in atPhysAddress for MAC addresses too. + @output = $self->snmp_get($device, $ATPHYSADDRESS); + foreach my $line (@output) { + next unless ($line =~ m/^$ATPHYSADDRESS\.\d+\.\d+\.(\S+)\s+=\s+\S+:\s+(.*)$/); + my ($ip_addr, $mac_addr) = ($1, $2); + + # Skip broadcast, net and local addresses. + next if ($ip_addr =~ m/\.255$|\.0$|127\.0\.0\.1$/); + + $mac_addr = parse_mac($mac_addr); + $self->add_mac($mac_addr, $ip_addr); + $self->call('message', "Found MAC $mac_addr for host $ip_addr in the ARP cache (atPhysAddress) of host $device.", 5); + } +} + ############################################################################## # Ping the given host. Returns 1 if the host is alive, 0 otherwise. ############################################################################## @@ -1152,6 +1224,9 @@ sub scan($) { $self->call('message', "[1/5] Scanning the network...", 3); $self->scan_subnet(); + # Read the local ARP cache. + $self->local_arp(); + # Get a list of found hosts. my @hosts = @{$self->get_hosts()}; if (scalar(@hosts) > 0 && $self->{'parent_detection'} == 1) { @@ -1173,7 +1248,7 @@ sub scan($) { foreach my $host (@hosts) { $self->call('update_progress', $progress); $progress += $step; - next if ($self->has_parent($host)); + next if ($self->has_parent($host) || $self->has_children($host)); $self->traceroute_connectivity($host); }