2017-03-08 13:20:00 +01:00
#!/usr/bin/perl
# (c) Ártica ST 2014 <info@artica.es>
# Module for network topology discovery.
2017-03-13 11:13:45 +01:00
package PandoraFMS::Recon::Base ;
2017-03-08 13:20:00 +01:00
use strict ;
use warnings ;
# Default lib dir for RPM and DEB packages
use lib '/usr/lib/perl5' ;
use NetAddr::IP ;
use POSIX qw/ceil/ ;
2017-03-13 11:13:45 +01:00
use PandoraFMS::Recon::NmapParser ;
use PandoraFMS::Recon::Util ;
2017-03-08 13:20:00 +01:00
use Socket qw/inet_aton/ ;
2019-03-25 17:32:18 +01:00
# Constants.
use constant {
STEP_SCANNING = > 1 ,
STEP_AFT = > 2 ,
STEP_TRACEROUTE = > 3 ,
2019-04-03 18:13:19 +02:00
STEP_GATEWAY = > 4 ,
STEP_STATISTICS = > 1 ,
STEP_DATABASE_SCAN = > 2 ,
STEP_CUSTOM_QUERIES = > 3 ,
DISCOVERY_HOSTDEVICES = > 0 ,
DISCOVERY_HOSTDEVICES_CUSTOM = > 1 ,
DISCOVERY_CLOUD_AWS = > 2 ,
DISCOVERY_APP_VMWARE = > 3 ,
DISCOVERY_APP_MYSQL = > 4 ,
DISCOVERY_APP_ORACLE = > 5
2019-03-25 17:32:18 +01:00
} ;
2017-03-23 14:33:06 +01:00
# /dev/null
my $ DEVNULL = ( $^O eq 'MSWin32' ) ? '/Nul' : '/dev/null' ;
2017-03-08 13:20:00 +01:00
# Some useful OIDs.
2018-05-21 15:03:54 +02:00
our $ ATPHYSADDRESS = ".1.3.6.1.2.1.3.1.1.2" ;
2017-03-08 13:20:00 +01:00
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" ;
our $ DOT1DTPFDBPORT = ".1.3.6.1.2.1.17.4.3.1.2" ;
our $ IFDESC = ".1.3.6.1.2.1.2.2.1.2" ;
2017-03-15 08:46:11 +01:00
our $ IFHCINOCTECTS = ".1.3.6.1.2.1.31.1.1.1.6" ;
our $ IFHCOUTOCTECTS = ".1.3.6.1.2.1.31.1.1.1.10" ;
2017-03-08 13:20:00 +01:00
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" ;
2018-05-21 15:03:54 +02:00
our $ IFTYPE = ".1.3.6.1.2.1.2.2.1.3" ;
2017-03-08 13:20:00 +01:00
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 $ IPADENTIFINDEX = ".1.3.6.1.2.1.4.20.1.2" ;
2018-05-21 15:03:54 +02:00
our $ IPNETTOMEDIAPHYSADDRESS = ".1.3.6.1.2.1.4.22.1.2" ;
2017-03-08 13:20:00 +01:00
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" ;
our $ PRTMARKERINDEX = ".1.3.6.1.2.1.43.10.2.1.1" ;
our $ SYSDESCR = ".1.3.6.1.2.1.1.1" ;
our $ SYSSERVICES = ".1.3.6.1.2.1.1.7" ;
our $ SYSUPTIME = ".1.3.6.1.2.1.1.3" ;
our $ VTPVLANIFINDEX = ".1.3.6.1.4.1.9.9.46.1.3.1.1.18.1" ;
our @ ISA = ( "Exporter" ) ;
2019-03-25 17:32:18 +01:00
our % EXPORT_TAGS = ( 'all' = > [ qw( ) ] ) ;
2017-03-08 13:20:00 +01:00
our @ EXPORT_OK = ( @ { $ EXPORT_TAGS { 'all' } } ) ;
our @ EXPORT = qw(
2019-03-25 17:32:18 +01:00
$ DOT1DBASEBRIDGEADDRESS
$ DOT1DBASEPORTIFINDEX
$ DOT1DTPFDBADDRESS
$ DOT1DTPFDBPORT
$ IFDESC
$ IFHCINOCTECTS
$ IFHCOUTOCTECTS
$ IFINDEX
$ IFINOCTECTS
$ IFOPERSTATUS
$ IFOUTOCTECTS
$ IPADENTIFINDEX
$ IPENTADDR
$ IFNAME
$ IPNETTOMEDIAPHYSADDRESS
$ IFPHYSADDRESS
$ IPADENTIFINDEX
$ IPROUTEIFINDEX
$ IPROUTENEXTHOP
$ IPROUTETYPE
$ PRTMARKERINDEX
$ SYSDESCR
$ SYSSERVICES
$ SYSUPTIME
2017-03-08 13:20:00 +01:00
) ;
#######################################################################
# Create a new ReconTask object.
#######################################################################
sub new {
my $ class = shift ;
my $ self = {
# Known aliases (multiple IP addresses for the same host.
aliases = > { } ,
# Keep our own ARP cache to connect hosts to switches/routers.
arp_cache = > { } ,
2018-05-21 15:03:54 +02:00
# Found children.
children = > { } ,
2017-03-08 13:20:00 +01:00
# Working SNMP community for each device.
community_cache = > { } ,
2018-09-17 14:36:52 +02:00
# Cache of deviced discovered.
dicovered_cache = > { } ,
2017-03-08 13:20:00 +01:00
# Connections between devices.
connections = > { } ,
# Devices by type.
hosts = > [] ,
routers = > [] ,
switches = > [] ,
2017-03-23 14:33:06 +01:00
# Found interfaces.
ifaces = > { } ,
# Found parents.
parents = > { } ,
2017-03-08 13:20:00 +01:00
# Route cache.
routes = > [] ,
default_gw = > undef ,
# SNMP query cache.
snmp_cache = > { } ,
2017-03-23 14:33:06 +01:00
# Globally enable/disable SNMP scans.
snmp_enabled = > 1 ,
2019-02-13 14:56:10 +01:00
# Globally enable/disable WMI scans.
wmi_enabled = > 0 ,
auth_strings_array = > [] ,
wmi_timeout = > 3 ,
timeout_cmd = > '' ,
2017-03-08 13:20:00 +01:00
# Switch to switch connections. Used to properly connect hosts
# that are connected to a switch wich is in turn connected to another switch,
# since the hosts will show up in the latter's switch AFT too.
switch_to_switch = > { } ,
# Visited devices (initially empty).
visited_devices = > { } ,
# Per device VLAN cache.
vlan_cache = > { } ,
2017-03-23 14:33:06 +01:00
vlan_cache_enabled = > 1 , # User configuration. Globally disables the VLAN cache.
__vlan_cache_enabled__ = > 0 , # Internal state. Allows us to enable/disable the VLAN cache on a per SNMP query basis.
2017-03-08 13:20:00 +01:00
# Configuration parameters.
all_ifaces = > 0 ,
communities = > [] ,
icmp_checks = > 2 ,
icmp_timeout = > 2 ,
id_os = > 0 ,
id_network_profile = > 0 ,
nmap = > '/usr/bin/nmap' ,
parent_detection = > 1 ,
parent_recursion = > 5 ,
os_detection = > 0 ,
recon_timing_template = > 3 ,
recon_ports = > '' ,
resolve_names = > 0 ,
2018-09-17 14:36:52 +02:00
snmp_auth_user = > '' ,
snmp_auth_pass = > '' ,
snmp_auth_method = > '' ,
2017-03-08 13:20:00 +01:00
snmp_checks = > 2 ,
2018-09-17 14:36:52 +02:00
snmp_privacy_method = > '' ,
snmp_privacy_pass = > '' ,
snmp_security_level = > '' ,
2017-03-08 13:20:00 +01:00
snmp_timeout = > 2 ,
2018-09-17 14:36:52 +02:00
snmp_version = > 1 ,
2017-03-08 13:20:00 +01:00
subnets = > [] ,
2019-02-18 14:00:55 +01:00
autoconfiguration_enabled = > 0 ,
2019-03-25 17:32:18 +01:00
# Store progress summary - Discovery progress view.
step = > 0 ,
c_network_name = > '' ,
c_network_percent = > 0.0 ,
summary = > {
SNMP = > 0 ,
WMI = > 0 ,
discovered = > 0 ,
alive = > 0 ,
not_alive = > 0
} ,
2017-03-08 13:20:00 +01:00
@ _ ,
} ;
# Perform some sanity checks.
die ( "No subnet was specified." ) unless defined ( $ self - > { 'subnets' } ) ;
2019-04-02 16:25:29 +02:00
$ self = bless ( $ self , $ class ) ;
2018-09-17 14:36:52 +02:00
# Check SNMP params id SNMP is enabled
if ( $ self - > { 'snmp_enabled' } ) {
2019-03-25 17:32:18 +01:00
2018-09-17 14:36:52 +02:00
# Check SNMP version
2019-03-25 17:32:18 +01:00
if ( $ self - > { 'snmp_version' } ne '1'
&& $ self - > { 'snmp_version' } ne '2'
&& $ self - > { 'snmp_version' } ne '2c'
&& $ self - > { 'snmp_version' } ne '3' ) {
2018-09-17 14:36:52 +02:00
$ self - > { 'snmp_enabled' } = 0 ;
$ self - > call ( 'message' , "SNMP version " . $ self - > { 'snmp_version' } . " not supported (only 1, 2, 2c and 3)." , 5 ) ;
}
# Check the version 3 parameters
if ( $ self - > { 'snmp_version' } eq '3' ) {
2019-03-25 17:32:18 +01:00
2018-09-17 14:36:52 +02:00
# Fixed some vars
$ self - > { 'communities' } = [] ;
# SNMP v3 checks
2019-03-25 17:32:18 +01:00
if ( $ self - > { 'snmp_security_level' } ne 'noAuthNoPriv'
&& $ self - > { 'snmp_security_level' } ne 'authNoPriv'
&& $ self - > { 'snmp_security_level' } ne 'authPriv' ) {
2018-09-17 14:36:52 +02:00
$ self - > { 'snmp_enabled' } = 0 ;
$ self - > call ( 'message' , "Invalid SNMP security level " . $ self - > { 'snmp_security_level' } . "." , 5 ) ;
}
if ( $ self - > { 'snmp_privacy_method' } ne 'DES' && $ self - > { 'snmp_privacy_method' } ne 'AES' ) {
$ self - > { 'snmp_enabled' } = 0 ;
$ self - > call ( 'message' , "Invalid SNMP privacy method " . $ self - > { 'snmp_privacy_method' } . "." , 5 ) ;
}
if ( $ self - > { 'snmp_auth_method' } ne 'MD5' && $ self - > { 'snmp_auth_method' } ne 'SHA' ) {
$ self - > { 'snmp_enabled' } = 0 ;
$ self - > call ( 'message' , "Invalid SNMP authentication method " . $ self - > { 'snmp_auth_method' } . "." , 5 ) ;
}
} else {
2019-03-25 17:32:18 +01:00
2018-09-17 14:36:52 +02:00
# Fixed some vars
$ self - > { 'snmp_auth_user' } = '' ;
$ self - > { 'snmp_auth_pass' } = '' ;
$ self - > { 'snmp_auth_method' } = '' ;
$ self - > { 'snmp_privacy_method' } = '' ;
$ self - > { 'snmp_privacy_pass' } = '' ;
$ self - > { 'snmp_security_level' } = '' ;
# Disable SNMP scans if no community was given.
2019-02-27 13:56:06 +01:00
if ( ref ( $ self - > { 'communities' } ) ne "ARRAY" || scalar ( @ { $ self - > { 'communities' } } ) == 0 ) {
2018-09-17 14:36:52 +02:00
$ self - > { 'snmp_enabled' } = 0 ;
2019-04-02 16:25:29 +02:00
$ self - > call ( 'message' , "There is no SNMP community configured." , 5 ) ;
2018-09-17 14:36:52 +02:00
}
}
}
2019-02-13 14:56:10 +01:00
# Prepare auth array.
# WMI could be launched with '-N' - no pass - argument.
if ( $ self - > { 'wmi_enabled' } == 1 ) {
if ( defined ( $ self - > { 'auth_strings_str' } ) ) {
@ { $ self - > { 'auth_strings_array' } } = split ( ',' , $ self - > { 'auth_strings_str' } ) ;
}
# Timeout available only in linux environments.
if ( $^O =~ /lin/i && defined ( $ self - > { 'plugin_exec' } ) && defined ( $ self - > { 'wmi_timeout' } ) ) {
$ self - > { 'timeout_cmd' } = $ self - > { 'plugin_exec' } . ' ' . $ self - > { 'wmi_timeout' } . ' ' ;
}
}
2018-09-17 14:36:52 +02:00
# Remove all snmp related values if disabled
if ( ! $ self - > { 'snmp_enabled' } ) {
$ self - > { 'communities' } = [] ;
$ self - > { 'snmp_auth_user' } = '' ;
$ self - > { 'snmp_auth_pass' } = '' ;
$ self - > { 'snmp_auth_method' } = '' ;
$ self - > { 'snmp_privacy_method' } = '' ;
$ self - > { 'snmp_privacy_pass' } = '' ;
$ self - > { 'snmp_security_level' } = '' ;
}
2017-03-23 14:33:06 +01:00
2019-04-02 16:25:29 +02:00
return $ self ;
2017-03-08 13:20:00 +01:00
}
########################################################################################
# Add an address to a device.
########################################################################################
sub add_addresses ($$$) {
my ( $ self , $ device , $ ip_address ) = @ _ ;
$ self - > { 'visited_devices' } - > { $ device } - > { 'addr' } - > { $ ip_address } = '' ;
}
########################################################################################
# Add a MAC/IP address to the ARP cache.
########################################################################################
sub add_mac ($$$) {
my ( $ self , $ mac , $ ip_addr ) = @ _ ;
2017-03-23 14:33:06 +01:00
$ mac = parse_mac ( $ mac ) ;
2017-03-08 13:20:00 +01:00
$ self - > { 'arp_cache' } - > { $ mac } = $ ip_addr ;
}
2017-03-23 14:33:06 +01:00
########################################################################################
# Add an interface/MAC to the interface cache.
########################################################################################
sub add_iface ($$$) {
my ( $ self , $ iface , $ mac ) = @ _ ;
$ iface =~ s/"//g ;
$ self - > { 'ifaces' } - > { $ mac } = $ iface ;
}
########################################################################################
# Discover connectivity from address forwarding tables.
########################################################################################
sub aft_connectivity ($$) {
my ( $ self , $ switch ) = @ _ ;
my ( % mac_temp , @ aft_temp ) ;
2018-09-17 14:36:52 +02:00
return unless ( $ self - > is_snmp_discovered ( $ switch ) ) ;
2017-03-23 14:33:06 +01:00
$ self - > enable_vlan_cache ( ) ;
# Get the address forwarding table (AFT) of each switch.
my @ aft ;
foreach my $ mac ( $ self - > snmp_get_value_array ( $ switch , $ DOT1DTPFDBADDRESS ) ) {
push ( @ aft , parse_mac ( $ mac ) ) ;
}
2019-03-25 17:32:18 +01:00
2017-03-23 14:33:06 +01:00
# Search for matching entries.
foreach my $ aft_mac ( @ aft ) {
# Do we know who this is?
my $ host = $ self - > get_ip_from_mac ( $ aft_mac ) ;
next unless defined ( $ host ) and $ host ne '' ;
# Get the name of the host interface if available.
my $ host_if_name = $ self - > get_iface ( $ aft_mac ) ;
$ host_if_name = defined ( $ host_if_name ) ? $ host_if_name : 'ping' ;
# Get the interface associated to the port were we found the MAC address.
my $ switch_if_name = $ self - > get_if_from_aft ( $ switch , $ aft_mac ) ;
2019-03-25 17:32:18 +01:00
next unless defined ( $ switch_if_name ) and ( $ switch_if_name ne '' ) ;
2017-03-23 14:33:06 +01:00
# Do not connect a host to a switch twice using the same interface.
# The switch is probably connected to another switch.
next if ( $ self - > is_switch_connected ( $ host , $ host_if_name ) ) ;
$ self - > mark_switch_connected ( $ host , $ host_if_name ) ;
# The switch and the host are already connected.
next if ( $ self - > are_connected ( $ switch , $ switch_if_name , $ host , $ host_if_name ) ) ;
# Connect!
$ self - > mark_connected ( $ switch , $ switch_if_name , $ host , $ host_if_name ) ;
$ self - > call ( 'message' , "Switch $switch (if $switch_if_name) is connected to host $host (if $host_if_name)." , 5 ) ;
}
$ self - > disable_vlan_cache ( ) ;
}
2017-03-08 13:20:00 +01:00
########################################################################################
# Return 1 if the given devices are connected to each other, 0 otherwise.
########################################################################################
sub are_connected ($$$$$) {
my ( $ self , $ dev_1 , $ if_1 , $ dev_2 , $ if_2 ) = @ _ ;
# Check for aliases!
$ dev_1 = $ self - > { 'aliases' } - > { $ dev_1 } if defined ( $ self - > { 'aliases' } - > { $ dev_1 } ) ;
$ dev_2 = $ self - > { 'aliases' } - > { $ dev_2 } if defined ( $ self - > { 'aliases' } - > { $ dev_2 } ) ;
# Use ping modules when interfaces are unknown.
$ if_1 = "ping" if $ if_1 eq '' ;
$ if_2 = "ping" if $ if_2 eq '' ;
2019-03-25 17:32:18 +01:00
if ( defined ( $ self - > { 'connections' } - > { "${dev_1}\t${if_1}\t${dev_2}\t${if_2}" } )
|| defined ( $ self - > { 'connections' } - > { "${dev_2}\t${if_2}\t${dev_1}\t${if_1}" } ) ) {
2017-03-08 13:20:00 +01:00
return 1 ;
}
return 0 ;
}
########################################################################################
2017-03-23 14:33:06 +01:00
# Discover as much information as possible from the given device using SNMP.
2017-03-08 13:20:00 +01:00
########################################################################################
2017-03-23 14:33:06 +01:00
sub snmp_discovery ($$) {
2017-03-08 13:20:00 +01:00
my ( $ self , $ device ) = @ _ ;
2017-03-23 14:33:06 +01:00
# Have we already visited this device?
2017-03-08 13:20:00 +01:00
return if ( $ self - > is_visited ( $ device ) ) ;
# Mark the device as visited.
$ self - > mark_visited ( $ device ) ;
2017-03-23 14:33:06 +01:00
# Are SNMP scans enabled?
if ( $ self - > { 'snmp_enabled' } == 1 ) {
2017-03-08 13:20:00 +01:00
2017-03-23 14:33:06 +01:00
# Try to find the MAC with an ARP request.
$ self - > get_mac_from_ip ( $ device ) ;
2019-03-25 17:32:18 +01:00
2017-03-23 14:33:06 +01:00
# Check if the device responds to SNMP.
if ( $ self - > snmp_responds ( $ device ) ) {
2019-03-25 17:32:18 +01:00
$ self - > { 'summary' } - > { 'SNMP' } += 1 ;
2017-03-23 14:33:06 +01:00
# Fill the VLAN cache.
$ self - > find_vlans ( $ device ) ;
2019-03-25 17:32:18 +01:00
2017-03-23 14:33:06 +01:00
# Guess the device type.
$ self - > guess_device_type ( $ device ) ;
2019-03-25 17:32:18 +01:00
2017-03-23 14:33:06 +01:00
# Find aliases for the device.
$ self - > find_aliases ( $ device ) ;
2019-03-25 17:32:18 +01:00
2017-03-23 14:33:06 +01:00
# Find interfaces for the device.
$ self - > find_ifaces ( $ device ) ;
2018-05-21 15:03:54 +02:00
# Check remote ARP caches.
$ self - > remote_arp ( $ device ) ;
2017-03-08 13:20:00 +01:00
}
}
2017-03-23 14:33:06 +01:00
# Create an agent for the device and add it to the list of known hosts.
push ( @ { $ self - > { 'hosts' } } , $ device ) ;
2017-03-08 13:20:00 +01:00
$ self - > call ( 'create_agent' , $ device ) ;
}
#######################################################################
# Try to call the given function on the given object.
#######################################################################
sub call {
my $ self = shift ;
my $ func = shift ;
my @ params = @ _ ;
if ( $ self - > can ( $ func ) ) {
$ self - > $ func ( @ params ) ;
}
}
2017-03-23 14:33:06 +01:00
########################################################################################
# Disable the VLAN cache.
########################################################################################
sub disable_vlan_cache ($$) {
my ( $ self , $ device ) = @ _ ;
$ self - > { '__vlan_cache_enabled__' } = 0 ;
}
########################################################################################
# Enable the VLAN cache.
########################################################################################
sub enable_vlan_cache ($$) {
my ( $ self , $ device ) = @ _ ;
return if ( $ self - > { 'vlan_cache_enabled' } == 0 ) ;
$ self - > { '__vlan_cache_enabled__' } = 1 ;
}
2017-03-08 13:20:00 +01:00
##########################################################################
# Connect the given hosts to its gateway.
##########################################################################
sub gateway_connectivity ($$) {
my ( $ self , $ host ) = @ _ ;
my $ gw = $ self - > get_gateway ( $ host ) ;
return unless defined ( $ gw ) ;
# Check for aliases!
$ host = $ self - > { 'aliases' } - > { $ host } if defined ( $ self - > { 'aliases' } - > { $ host } ) ;
$ gw = $ self - > { 'aliases' } - > { $ gw } if defined ( $ self - > { 'aliases' } - > { $ gw } ) ;
# Same host, different IP addresses.
return if ( $ host eq $ gw ) ;
$ self - > call ( 'message' , "Host $host is reached via gateway $gw." , 5 ) ;
2017-03-10 10:36:37 +01:00
$ self - > mark_connected ( $ gw , '' , $ host , '' ) ;
2017-03-08 13:20:00 +01:00
}
########################################################################################
# Find IP address aliases for the given device.
########################################################################################
sub find_aliases ($$) {
my ( $ self , $ device ) = @ _ ;
# Get ARP cache.
my @ ip_addresses = $ self - > snmp_get_value_array ( $ device , $ IPENTADDR ) ;
foreach my $ ip_address ( @ ip_addresses ) {
# Skip broadcast and localhost addresses.
next if ( $ ip_address =~ m/\.255$|\.0$|127\.0\.0\.1$/ ) ;
# Sometimes we find the same IP address we had.
next if ( $ ip_address eq $ device ) ;
$ self - > add_addresses ( $ device , $ ip_address ) ;
2017-03-23 14:33:06 +01:00
# Try to find the MAC with an ARP request.
$ self - > get_mac_from_ip ( $ ip_address ) ;
2017-03-08 13:20:00 +01:00
$ self - > call ( 'message' , "Found address $ip_address for host $device." , 5 ) ;
2017-03-23 14:33:06 +01:00
# Is this address an alias itself?
$ device = $ self - > { 'aliases' } - > { $ device } if defined ( $ self - > { 'aliases' } - > { $ device } ) ;
next if ( $ ip_address eq $ device ) ;
2017-03-08 13:20:00 +01:00
# Link the two addresses.
$ self - > { 'aliases' } - > { $ ip_address } = $ device ;
}
}
2017-03-23 14:33:06 +01:00
########################################################################################
# Find all the interfaces for the given host.
########################################################################################
sub find_ifaces ($$) {
my ( $ self , $ device ) = @ _ ;
# Does it respond to SNMP?
2018-09-17 14:36:52 +02:00
return unless ( $ self - > is_snmp_discovered ( $ device ) ) ;
2017-03-23 14:33:06 +01:00
my @ output = $ self - > snmp_get_value_array ( $ device , $ PandoraFMS:: Recon:: Base:: IFINDEX ) ;
foreach my $ if_index ( @ output ) {
next unless ( $ if_index =~ /^[0-9]+$/ ) ;
2018-05-21 15:03:54 +02:00
# Ignore virtual interfaces.
next if ( $ self - > get_if_type ( $ device , $ if_index ) eq '53' ) ;
2017-03-23 14:33:06 +01:00
# Get the MAC.
my $ mac = $ self - > get_if_mac ( $ device , $ if_index ) ;
next unless ( defined ( $ mac ) && $ mac ne '' ) ;
# Save it.
$ self - > add_mac ( $ mac , $ device ) ;
# Get the name of the network interface.
my $ if_name = $ self - > snmp_get_value ( $ device , "$PandoraFMS::Recon::Base::IFNAME.$if_index" ) ;
next unless defined ( $ if_name ) ;
# Save it.
$ self - > add_iface ( $ if_name , $ mac ) ;
$ self - > call ( 'message' , "Found interface $if_name MAC $mac for host $device." , 5 ) ;
}
}
2017-03-08 13:20:00 +01:00
########################################################################################
# Find the device's VLANs and fill the VLAN cache.
########################################################################################
sub find_vlans ($$) {
my ( $ self , $ device ) = @ _ ;
my % vlan_hash ;
foreach my $ vlan ( $ self - > snmp_get_value_array ( $ device , $ VTPVLANIFINDEX ) ) {
next if $ vlan eq '0' ;
$ vlan_hash { $ vlan } = 1 ;
}
my @ vlans = keys ( % vlan_hash ) ;
$ self - > { 'vlan_cache' } - > { $ device } = [] ;
push ( @ { $ self - > { 'vlan_cache' } - > { $ device } } , @ vlans ) if ( scalar ( @ vlans ) > 0 ) ;
}
########################################################################################
# Return the addresses of the given device as an array.
########################################################################################
sub get_addresses ($$) {
my ( $ self , $ device ) = @ _ ;
if ( defined ( $ self - > { 'visited_devices' } - > { $ device } ) ) {
return keys ( % { $ self - > { 'visited_devices' } - > { $ device } - > { 'addr' } } ) ;
}
# By default return the given address.
return ( $ device ) ;
}
########################################################################################
# Return a device structure from an IP address.
########################################################################################
sub get_device ($$) {
my ( $ self , $ addr ) = @ _ ;
if ( defined ( $ self - > { 'visited_devices' } - > { $ addr } ) ) {
return $ self - > { 'visited_devices' } - > { $ addr } ;
}
return undef ;
}
########################################################################################
# Get the SNMP community of the given device. Returns undef if no community was found.
########################################################################################
sub get_community ($$) {
my ( $ self , $ device ) = @ _ ;
2018-09-17 14:36:52 +02:00
return '' if ( $ self - > { 'snmp_version' } eq "3" ) ;
2017-03-08 13:20:00 +01:00
if ( defined ( $ self - > { 'community_cache' } - > { $ device } ) ) {
return $ self - > { 'community_cache' } - > { $ device } ;
}
2018-09-17 14:36:52 +02:00
return '' ;
2017-03-08 13:20:00 +01:00
}
########################################################################################
# Return the connection hash.
########################################################################################
sub get_connections ($) {
my ( $ self ) = @ _ ;
return $ self - > { 'connections' } ;
}
2017-03-23 14:33:06 +01:00
########################################################################################
# Return the parent relationship hash.
########################################################################################
sub get_parents ($) {
my ( $ self ) = @ _ ;
return $ self - > { 'parents' } ;
}
2017-03-08 13:20:00 +01:00
########################################################################################
# Get the type of the given device.
########################################################################################
sub get_device_type ($$) {
my ( $ self , $ device ) = @ _ ;
if ( defined ( $ self - > { 'visited_devices' } - > { $ device } ) ) {
return $ self - > { 'visited_devices' } - > { $ device } - > { 'type' } ;
}
# Assume 'host' by default.
return 'host' ;
}
########################################################################################
# Return all known hosts that are not switches or routers.
########################################################################################
sub get_hosts ($) {
my ( $ self ) = @ _ ;
return $ self - > { 'hosts' } ;
}
2017-03-23 14:33:06 +01:00
########################################################################################
# Add an interface/MAC to the interface cache.
########################################################################################
sub get_iface ($$) {
my ( $ self , $ mac ) = @ _ ;
return undef unless defined ( $ self - > { 'ifaces' } - > { $ mac } ) ;
return $ self - > { 'ifaces' } - > { $ mac } ;
}
2017-03-08 13:20:00 +01:00
########################################################################################
# Get an interface name from an AFT entry. Returns undef on error.
########################################################################################
sub get_if_from_aft ($$$) {
my ( $ self , $ switch , $ mac ) = @ _ ;
# Get the port associated to the MAC.
my $ port = $ self - > snmp_get_value ( $ switch , "$DOT1DTPFDBPORT." . mac_to_dec ( $ mac ) ) ;
return '' unless defined ( $ port ) ;
# Get the interface index associated to the port.
my $ if_index = $ self - > snmp_get_value ( $ switch , "$DOT1DBASEPORTIFINDEX.$port" ) ;
return '' unless defined ( $ if_index ) ;
# Get the interface name.
my $ if_name = $ self - > snmp_get_value ( $ switch , "$IFNAME.$if_index" ) ;
return "if$if_index" unless defined ( $ if_name ) ;
$ if_name =~ s/"//g ;
return $ if_name ;
}
########################################################################################
# Get an interface name from an IP address.
########################################################################################
sub get_if_from_ip ($$$) {
my ( $ self , $ device , $ ip_addr ) = @ _ ;
# Get the port associated to the IP address.
my $ if_index = $ self - > snmp_get_value ( $ device , "$IPROUTEIFINDEX.$ip_addr" ) ;
2019-03-25 17:32:18 +01:00
return '' unless defined ( $ if_index ) ;
2017-03-08 13:20:00 +01:00
# Get the name of the interface associated to the port.
my $ if_name = $ self - > snmp_get_value ( $ device , "$IFNAME.$if_index" ) ;
2019-03-25 17:32:18 +01:00
return '' unless defined ( $ if_name ) ;
2017-03-08 13:20:00 +01:00
$ if_name =~ s/"//g ;
return $ if_name ;
}
########################################################################################
# Get an interface name from a MAC address.
########################################################################################
sub get_if_from_mac ($$$) {
my ( $ self , $ device , $ mac ) = @ _ ;
# Get the port associated to the IP address.
my @ output = $ self - > snmp_get ( $ device , $ IFPHYSADDRESS ) ;
foreach my $ line ( @ output ) {
chomp ( $ line ) ;
next unless $ line =~ /^IFPHYSADDRESS.(\S+)\s+=\s+\S+:\s+(.*)$/ ;
my ( $ if_index , $ if_mac ) = ( $ 1 , $ 2 ) ;
# Make sure the MAC addresses match.
next unless ( mac_matches ( $ mac , $ if_mac ) == 1 ) ;
# Pupulate the ARP cache.
$ self - > add_mac ( $ mac , $ device ) ;
# Get the name of the interface associated to the port.
my $ if_name = $ self - > snmp_get_value ( $ device , "$IFNAME.$if_index" ) ;
2019-03-25 17:32:18 +01:00
return '' unless defined ( $ if_name ) ;
2017-03-08 13:20:00 +01:00
$ if_name =~ s/"//g ;
return $ if_name ;
}
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
return '' ;
}
2017-03-23 14:33:06 +01:00
########################################################################################
# Get an interface name from a port number. Returns '' on error.
########################################################################################
sub get_if_from_port ($$$) {
my ( $ self , $ switch , $ port ) = @ _ ;
# Get the interface index associated to the port.
my $ if_index = $ self - > snmp_get_value ( $ switch , "$DOT1DBASEPORTIFINDEX.$port" ) ;
return '' unless defined ( $ if_index ) ;
# Get the interface name.
my $ if_name = $ self - > snmp_get_value ( $ switch , "$IFNAME.$if_index" ) ;
return "if$if_index" unless defined ( $ if_name ) ;
$ if_name =~ s/"//g ;
return $ if_name ;
}
2017-03-08 13:20:00 +01:00
########################################################################################
# Returns the IP address of the given interface (by index).
########################################################################################
sub get_if_ip ($$$) {
my ( $ self , $ device , $ if_index ) = @ _ ;
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
my @ output = $ self - > snmp_get ( $ device , $ IPADENTIFINDEX ) ;
foreach my $ line ( @ output ) {
2019-03-25 17:32:18 +01:00
chomp ( $ line ) ;
2018-05-21 15:03:54 +02:00
return $ 1 if ( $ line =~ m/^$IPADENTIFINDEX.(\S+)\s+=\s+\S+:\s+$if_index$/ ) ;
2017-03-08 13:20:00 +01:00
}
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
return '' ;
}
########################################################################################
# Returns the MAC address of the given interface (by index).
########################################################################################
sub get_if_mac ($$$) {
my ( $ self , $ device , $ if_index ) = @ _ ;
my $ mac = $ self - > snmp_get_value ( $ device , "$IFPHYSADDRESS.$if_index" ) ;
return '' unless defined ( $ mac ) ;
# Clean-up the MAC address.
$ mac = parse_mac ( $ mac ) ;
return $ mac ;
}
2018-05-21 15:03:54 +02:00
########################################################################################
# Returns the type of the given interface (by index).
########################################################################################
sub get_if_type ($$$) {
my ( $ self , $ device , $ if_index ) = @ _ ;
2019-03-25 17:32:18 +01:00
2018-05-21 15:03:54 +02:00
my $ type = $ self - > snmp_get_value ( $ device , "$IFTYPE.$if_index" ) ;
return '' unless defined ( $ type ) ;
return $ type ;
}
2017-03-08 13:20:00 +01:00
########################################################################################
# Get an IP address from the ARP cache given the MAC address.
########################################################################################
sub get_ip_from_mac ($$) {
my ( $ self , $ mac_addr ) = @ _ ;
if ( defined ( $ self - > { 'arp_cache' } - > { $ mac_addr } ) ) {
return $ self - > { 'arp_cache' } - > { $ mac_addr } ;
}
return undef ;
}
########################################################################################
2019-03-25 17:32:18 +01:00
# Attemtps to find
2017-03-08 13:20:00 +01:00
########################################################################################
2017-03-23 14:33:06 +01:00
sub get_mac_from_ip ($$) {
my ( $ self , $ host ) = @ _ ;
my $ mac = undef ;
eval {
$ mac = `arping -c 1 -r $host 2>$DEVNULL` ;
$ mac = undef unless ( $? == 0 ) ;
} ;
return unless defined ( $ mac ) ;
# Clean-up the MAC address.
chomp ( $ mac ) ;
$ mac = parse_mac ( $ mac ) ;
$ self - > add_mac ( $ mac , $ host ) ;
$ self - > call ( 'message' , "Found MAC $mac for host $host in the local ARP cache." , 5 ) ;
}
########################################################################################
# Get a port number from an AFT entry. Returns undef on error.
########################################################################################
sub get_port_from_aft ($$$) {
my ( $ self , $ switch , $ mac ) = @ _ ;
# Get the port associated to the MAC.
my $ port = $ self - > snmp_get_value ( $ switch , "$DOT1DTPFDBPORT." . mac_to_dec ( $ mac ) ) ;
return '' unless defined ( $ port ) ;
2017-03-08 13:20:00 +01:00
2017-03-23 14:33:06 +01:00
return $ port ;
2017-03-08 13:20:00 +01:00
}
########################################################################################
# Fill the route cache.
########################################################################################
sub get_routes ($) {
my ( $ self ) = @ _ ;
# Empty the current route cache.
$ self - > { 'routes' } = [] ;
# Parse route's output.
my @ output = `route -n 2>/dev/null` ;
foreach my $ line ( @ output ) {
chomp ( $ line ) ;
if ( $ line =~ /^0\.0\.0\.0\s+(\d+\.\d+\.\d+\.\d+).*/ ) {
$ self - > { 'default_gw' } = $ 1 ;
} elsif ( $ line =~ /^(\d+\.\d+\.\d+\.\d+)\s+(\d+\.\d+\.\d+\.\d+)\s+(\d+\.\d+\.\d+\.\d+).*/ ) {
push ( @ { $ self - > { 'routes' } } , { dest = > $ 1 , gw = > $ 2 , mask = > $ 3 } ) ;
}
}
# Replace 0.0.0.0 with the default gateway's IP.
2019-03-25 17:32:18 +01:00
return unless defined ( $ self - > { 'default_gw' } ) ;
2017-03-08 13:20:00 +01:00
foreach my $ route ( @ { $ self - > { 'routes' } } ) {
$ route - > { gw } = $ self - > { 'default_gw' } if ( $ route - > { 'gw' } eq '0.0.0.0' ) ;
}
}
########################################################################################
# Get the gateway to reach the given host.
########################################################################################
sub get_gateway ($) {
my ( $ self , $ host ) = @ _ ;
# Look for a specific route to the given host.
foreach my $ route ( @ { $ self - > { 'routes' } } ) {
if ( subnet_matches ( $ host , $ route - > { 'dest' } , $ route - > { 'mask' } ) ) {
return $ route - > { 'gw' } ;
}
}
# Return the default gateway.
return $ self - > { 'default_gw' } if defined ( $ self - > { 'default_gw' } ) ;
# Ops!
return undef ;
}
########################################################################################
# Return a pointer to an array containing configured subnets.
########################################################################################
sub get_subnets ($) {
my ( $ self ) = @ _ ;
return $ self - > { 'subnets' } ;
}
########################################################################################
# Get an array of all the visited devices.
# NOTE: This functions returns the whole device structures, not just address
# like get_hosts, get_switches, get_routers and get_all_devices.
########################################################################################
sub get_visited_devices ($) {
my ( $ self ) = @ _ ;
return $ self - > { 'visited_devices' } ;
}
########################################################################################
# Returns an array of found VLAN IDs.
########################################################################################
sub get_vlans ($$) {
my ( $ self , $ device ) = @ _ ;
2018-09-17 14:36:52 +02:00
# Disabled in verison 3
return ( ) if ( $ self - > { 'snmp_version' } eq "3" ) ;
2017-03-23 14:33:06 +01:00
# Is the VLAN cache disabled?
return ( ) unless ( $ self - > { '__vlan_cache_enabled__' } == 1 ) ;
2017-03-08 13:20:00 +01:00
return ( ) unless defined ( $ self - > { 'vlan_cache' } - > { $ device } ) ;
return @ { $ self - > { 'vlan_cache' } - > { $ device } } ;
}
########################################################################################
# Guess the type of the given device.
########################################################################################
sub guess_device_type ($$) {
my ( $ self , $ device ) = @ _ ;
# Get the value of sysServices.
my $ services = $ self - > snmp_get_value ( $ device , "$SYSSERVICES.0" ) ;
return unless defined ( $ services ) ;
# Check the individual bits.
my @ service_bits = split ( '' , unpack ( 'b8' , pack ( 'C' , $ services ) ) ) ;
# Check for layer 2 connectivity support.
my $ bridge_mib = $ self - > snmp_get_value ( $ device , $ DOT1DBASEBRIDGEADDRESS ) ;
# L2?
my $ device_type ;
if ( $ service_bits [ 1 ] == 1 ) {
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
# L3?
if ( $ service_bits [ 2 ] == 1 ) {
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
# Bridge MIB?
if ( defined ( $ bridge_mib ) ) {
$ device_type = 'switch' ;
} else {
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
# L7?
if ( $ service_bits [ 6 ] == 1 ) {
$ device_type = 'host' ;
} else {
$ device_type = 'router' ;
}
}
2019-03-25 17:32:18 +01:00
} else {
2017-03-08 13:20:00 +01:00
# Bridge MIB?
if ( defined ( $ bridge_mib ) ) {
$ device_type = 'switch' ;
} else {
$ device_type = 'host' ;
}
}
2019-03-25 17:32:18 +01:00
} else {
2017-03-08 13:20:00 +01:00
# L3?
if ( $ service_bits [ 2 ] == 1 ) {
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
# L4?
if ( $ service_bits [ 3 ] == 1 ) {
$ device_type = 'switch' ;
} else {
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
# L7?
if ( $ service_bits [ 6 ] == 1 ) {
$ device_type = 'host' ;
} else {
$ device_type = 'router' ;
}
}
2019-03-25 17:32:18 +01:00
} else {
2017-03-08 13:20:00 +01:00
# Printer MIB?
my $ printer_mib = $ self - > snmp_get_value ( $ device , $ PRTMARKERINDEX ) ;
if ( defined ( $ printer_mib ) ) {
$ device_type = 'printer' ;
} else {
$ device_type = 'host' ;
}
}
}
# Set the type of the device.
$ self - > set_device_type ( $ device , $ device_type ) ;
}
2018-05-21 15:03:54 +02:00
########################################################################################
# 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 ;
}
2017-03-08 13:20:00 +01:00
########################################################################################
2017-03-23 14:33:06 +01:00
# Return 1 if the given device has a parent.
2017-03-08 13:20:00 +01:00
########################################################################################
2017-03-23 14:33:06 +01:00
sub has_parent ($$) {
2017-03-08 13:20:00 +01:00
my ( $ self , $ device ) = @ _ ;
2017-03-23 14:33:06 +01:00
# Check for aliases!
$ device = $ self - > { 'aliases' } - > { $ device } if defined ( $ self - > { 'aliases' } - > { $ device } ) ;
2017-03-08 13:20:00 +01:00
2017-03-23 14:33:06 +01:00
return 1 if ( defined ( $ self - > { 'parents' } - > { $ device } ) ) ;
2017-03-08 13:20:00 +01:00
2017-03-23 14:33:06 +01:00
return 0 ;
2017-03-08 13:20:00 +01:00
}
########################################################################################
# Returns 1 if the device belongs to one of the scanned subnets.
########################################################################################
sub in_subnet ($$) {
my ( $ self , $ device ) = @ _ ;
$ device = ip_to_long ( $ device ) ;
# No subnets specified.
return 1 if ( scalar ( @ { $ self - > { 'subnets' } } ) <= 0 ) ;
foreach my $ subnet ( @ { $ self - > { 'subnets' } } ) {
if ( subnet_matches ( $ device , $ subnet ) ) {
return 1 ;
}
}
return 0 ;
}
##########################################################################
# Check for switches that are connected to other switches/routers and show
# up in a switch/router's port.
##########################################################################
sub is_switch_connected ($$$) {
my ( $ self , $ device , $ iface ) = @ _ ;
# Check for aliases!
$ device = $ self - > { 'aliases' } - > { $ device } if defined ( $ self - > { 'aliases' } - > { $ device } ) ;
2019-03-25 17:32:18 +01:00
return 1 if defined ( $ self - > { 'switch_to_switch' } - > { "${device}\t${iface}" } ) ;
2017-03-08 13:20:00 +01:00
return 0 ;
}
########################################################################################
# Returns 1 if the given device has already been visited, 0 otherwise.
########################################################################################
sub is_visited ($$) {
my ( $ self , $ device ) = @ _ ;
# Check for aliases!
$ device = $ self - > { 'aliases' } - > { $ device } if defined ( $ self - > { 'aliases' } - > { $ device } ) ;
if ( defined ( $ self - > { 'visited_devices' } - > { $ device } ) ) {
return 1 ;
}
return 0 ;
}
2018-09-17 14:36:52 +02:00
########################################################################################
# Returns 1 if the given device has responded successfully to a snmp request
# Returns 0 otherwise.
########################################################################################
sub is_snmp_discovered ($$) {
my ( $ self , $ device ) = @ _ ;
# Check if device is into discovered cache
return ( defined ( $ self - > { 'discovered_cache' } - > { $ device } ) ) ? 1 : 0 ;
}
2017-03-08 13:20:00 +01:00
########################################################################################
# Mark the given devices as connected to each other on the given interfaces.
########################################################################################
sub mark_connected ($$;$$$) {
2017-03-23 14:33:06 +01:00
my ( $ self , $ parent , $ parent_if , $ child , $ child_if ) = @ _ ;
2017-03-08 13:20:00 +01:00
# Check for aliases!
2017-03-23 14:33:06 +01:00
$ parent = $ self - > { 'aliases' } - > { $ parent } if defined ( $ self - > { 'aliases' } - > { $ parent } ) ;
$ child = $ self - > { 'aliases' } - > { $ child } if defined ( $ self - > { 'aliases' } - > { $ child } ) ;
2017-03-08 13:20:00 +01:00
2017-03-23 14:33:06 +01:00
# Use ping modules when interfaces are unknown.
$ parent_if = "ping" if $ parent_if eq '' ;
$ child_if = "ping" if $ child_if eq '' ;
2017-03-08 13:20:00 +01:00
2017-03-23 14:33:06 +01:00
# Do not connect devices using ping modules. A parent-child relationship is enough.
if ( $ parent_if ne "ping" || $ child_if ne "ping" ) {
$ self - > { 'connections' } - > { "${parent}\t${parent_if}\t${child}\t${child_if}" } = 1 ;
$ self - > call ( 'connect_agents' , $ parent , $ parent_if , $ child , $ child_if ) ;
}
2017-03-08 13:20:00 +01:00
2017-03-23 14:33:06 +01:00
# Prevent parent-child loops.
2019-03-25 17:32:18 +01:00
if ( ! defined ( $ self - > { 'parents' } - > { $ parent } )
|| $ self - > { 'parents' } - > { $ parent } ne $ child ) {
2017-03-23 14:33:06 +01:00
# A parent-child relationship is always created to help complete the map with
# layer 3 information.
$ self - > { 'parents' } - > { $ child } = $ parent ;
2018-05-21 15:03:54 +02:00
$ self - > { 'children' } - > { $ parent } = $ child ;
2017-03-23 14:33:06 +01:00
$ self - > call ( 'set_parent' , $ child , $ parent ) ;
2017-03-08 13:20:00 +01:00
}
}
########################################################################################
# Mark the given switch as having a connection on the given interface.
########################################################################################
sub mark_switch_connected ($$$) {
my ( $ self , $ device , $ iface ) = @ _ ;
# Check for aliases!
$ device = $ self - > { 'aliases' } - > { $ device } if defined ( $ self - > { 'aliases' } - > { $ device } ) ;
$ self - > { 'switch_to_switch' } - > { "${device}\t${iface}" } = 1 ;
}
########################################################################################
# Mark the given device as visited.
########################################################################################
sub mark_visited ($$) {
my ( $ self , $ device ) = @ _ ;
2019-03-25 17:32:18 +01:00
$ self - > { 'visited_devices' } - > { $ device } = {
'addr' = > { $ device = > '' } ,
'type' = > 'host'
} ;
2017-03-08 13:20:00 +01:00
}
########################################################################################
2018-09-17 14:36:52 +02:00
# Mark the given device as snmp discovered.
########################################################################################
sub mark_discovered ($$) {
my ( $ self , $ device ) = @ _ ;
$ self - > { 'discovered_cache' } - > { $ device } = 1 ;
}
########################################################################################
# Validate the configuration for the given device.
# Returns 1 if successfull snmp contact, 0 otherwise.
# Updates the SNMP community cache on v1, v2 and v2c.
2017-03-08 13:20:00 +01:00
########################################################################################
sub snmp_responds ($$) {
my ( $ self , $ device ) = @ _ ;
2018-09-17 14:36:52 +02:00
return 1 if ( $ self - > is_snmp_discovered ( $ device ) ) ;
return ( $ self - > { 'snmp_version' } eq "3" )
2019-03-25 17:32:18 +01:00
? $ self - > snmp_responds_v3 ( $ device )
: $ self - > snmp_responds_v122c ( $ device ) ;
2018-09-17 14:36:52 +02:00
}
########################################################################################
# Looks for a working SNMP community for the given device. Returns 1 if one is
# found, 0 otherwise. Updates the SNMP community cache.
########################################################################################
sub snmp_responds_v122c ($$) {
my ( $ self , $ device ) = @ _ ;
2017-03-08 13:20:00 +01:00
foreach my $ community ( @ { $ self - > { 'communities' } } ) {
2017-03-14 08:27:44 +01:00
# Clean blanks.
$ community =~ s/\s+//g ;
2018-09-17 14:36:52 +02:00
my $ command = $ self - > snmp_get_command ( $ device , ".0" , $ community ) ;
`$command` ;
2017-03-08 13:20:00 +01:00
if ( $? == 0 ) {
$ self - > set_community ( $ device , $ community ) ;
2018-09-17 14:36:52 +02:00
$ self - > mark_discovered ( $ device ) ;
2017-03-08 13:20:00 +01:00
return 1 ;
}
}
return 0 ;
}
2018-09-17 14:36:52 +02:00
########################################################################################
# Validate the SNMP v3 configuration for a device.
# Returns 1 if successfull snmp contact, 0 otherwise.
########################################################################################
sub snmp_responds_v3 ($$) {
my ( $ self , $ device ) = @ _ ;
my $ command = $ self - > snmp_get_command ( $ device , ".0" ) ;
`$command` ;
if ( $? == 0 ) {
$ self - > mark_discovered ( $ device ) ;
return 1 ;
}
return 0 ;
}
2018-05-21 15:03:54 +02:00
##############################################################################
# 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 ) ;
}
}
2017-03-08 13:20:00 +01:00
##############################################################################
# Ping the given host. Returns 1 if the host is alive, 0 otherwise.
##############################################################################
sub ping ($$$) {
my ( $ self , $ host ) = @ _ ;
2019-03-25 17:32:18 +01:00
my ( $ timeout , $ retries , $ packets ) = ( $ self - > { 'icmp_timeout' } , $ self - > { 'icmp_checks' } , 1 , ) ;
2017-03-08 13:20:00 +01:00
# Windows
if ( ( $^O eq "MSWin32" ) || ( $^O eq "MSWin32-x64" ) || ( $^O eq "cygwin" ) ) {
$ timeout *= 1000 ; # Convert the timeout to milliseconds.
for ( my $ i = 0 ; $ i < $ retries ; $ i + + ) {
my $ output = `ping -n $packets -w $timeout $host` ;
return 1 if ( $ output =~ /TTL/ ) ;
}
return 0 ;
}
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
# Solaris
if ( $^O eq "solaris" ) {
my $ ping_command = $ host =~ /\d+:|:\d+/ ? "ping -A inet6" : "ping" ;
for ( my $ i = 0 ; $ i < $ retries ; $ i + + ) {
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
# Note: There is no timeout option.
`$ping_command -s -n $host 56 $packets >/dev/null 2>&1` ;
return 1 if ( $? == 0 ) ;
}
return 0 ;
}
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
# FreeBSD
if ( $^O eq "freebsd" ) {
my $ ping_command = $ host =~ /\d+:|:\d+/ ? "ping6" : "ping -t $timeout" ;
for ( my $ i = 0 ; $ i < $ retries ; $ i + + ) {
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
# Note: There is no timeout option for ping6.
`$ping_command -q -n -c $packets $host >/dev/null 2>&1` ;
return 1 if ( $? == 0 ) ;
}
return 0 ;
}
# NetBSD
2019-03-25 17:32:18 +01:00
if ( $^O eq "netbsd" ) {
2017-03-08 13:20:00 +01:00
my $ ping_command = $ host =~ /\d+:|:\d+/ ? "ping6" : "ping -w $timeout" ;
for ( my $ i = 0 ; $ i < $ retries ; $ i + + ) {
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
# Note: There is no timeout option for ping6.
`$ping_command -q -n -c $packets $host >/dev/null 2>&1` ;
if ( $? == 0 ) {
return 1 ;
}
}
return 0 ;
}
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
# Assume Linux by default.
my $ ping_command = $ host =~ /\d+:|:\d+/ ? "ping6" : "ping" ;
for ( my $ i = 0 ; $ i < $ retries ; $ i + + ) {
2019-03-25 17:32:18 +01:00
`$ping_command -q -W $timeout -n -c $packets $host >/dev/null 2>&1` ;
2017-03-08 13:20:00 +01:00
return 1 if ( $? == 0 ) ;
}
return 0 ;
}
2017-03-23 14:33:06 +01:00
##########################################################################
# Scan the given subnet.
##########################################################################
sub scan_subnet ($) {
my ( $ self ) = @ _ ;
my $ progress = 1 ;
2017-03-08 13:20:00 +01:00
2017-03-23 14:33:06 +01:00
my @ subnets = @ { $ self - > get_subnets ( ) } ;
foreach my $ subnet ( @ subnets ) {
2019-03-25 17:32:18 +01:00
$ self - > { 'c_network_percent' } = 0 ;
$ self - > { 'c_network_name' } = $ subnet ;
2017-03-08 13:20:00 +01:00
2017-03-23 14:33:06 +01:00
# Clean blanks.
$ subnet =~ s/\s+//g ;
2017-03-08 13:20:00 +01:00
2019-03-25 17:32:18 +01:00
my $ net_addr = new NetAddr:: IP ( $ subnet ) ;
2017-03-23 14:33:06 +01:00
if ( ! defined ( $ net_addr ) ) {
$ self - > call ( 'message' , "Invalid network: $subnet" , 3 ) ;
next ;
2017-03-08 13:20:00 +01:00
}
2017-03-23 14:33:06 +01:00
# Save the network and broadcast addresses.
my $ network = $ net_addr - > network ( ) ;
my $ broadcast = $ net_addr - > broadcast ( ) ;
2017-03-08 13:20:00 +01:00
2017-03-23 14:33:06 +01:00
# fping scan.
if ( - x $ self - > { 'fping' } && $ net_addr - > num ( ) > 1 ) {
$ self - > call ( 'message' , "Calling fping..." , 5 ) ;
2019-03-25 17:32:18 +01:00
2018-10-30 13:23:01 +01:00
my @ hosts = `"$self->{'fping'}" -ga "$subnet" 2>DEVNULL` ;
2017-03-23 14:33:06 +01:00
next if ( scalar ( @ hosts ) == 0 ) ;
2019-03-25 17:32:18 +01:00
$ self - > { 'summary' } - > { 'discovered' } += scalar ( @ hosts ) ;
2017-03-23 14:33:06 +01:00
my $ step = 50.0 / scalar(@subnets) / scalar ( @ hosts ) ; # The first 50% of the recon task approx.
2019-03-25 17:32:18 +01:00
my $ subnet_step = 100.0 / scalar ( @ hosts ) ;
2017-03-23 14:33:06 +01:00
foreach my $ line ( @ hosts ) {
chomp ( $ line ) ;
my @ temp = split ( / / , $ line ) ;
2019-03-25 17:32:18 +01:00
if ( scalar ( @ temp ) != 1 ) {
# Junk is shown for broadcast addresses.
# Increase summary.not_alive hosts.
$ self - > { 'summary' } - > { 'not_alive' } += 1 ;
next ;
}
2017-03-23 14:33:06 +01:00
my $ host = $ temp [ 0 ] ;
# Skip network and broadcast addresses.
next if ( $ host eq $ network - > addr ( ) || $ host eq $ broadcast - > addr ( ) ) ;
2019-03-25 17:32:18 +01:00
# Increase self summary.alive hosts.
$ self - > { 'summary' } - > { 'alive' } += 1 ;
2017-03-23 14:33:06 +01:00
$ self - > call ( 'message' , "Scanning host: $host" , 5 ) ;
$ self - > call ( 'update_progress' , ceil ( $ progress ) ) ;
$ progress += $ step ;
2019-03-25 17:32:18 +01:00
$ self - > { 'c_network_percent' } += $ subnet_step ;
2017-03-23 14:33:06 +01:00
$ self - > snmp_discovery ( $ host ) ;
2019-02-13 14:56:10 +01:00
# Add wmi scan if enabled.
$ self - > wmi_scan ( $ host ) if ( $ self - > { 'wmi_enabled' } == 1 ) ;
2017-03-23 14:33:06 +01:00
}
}
2019-03-25 17:32:18 +01:00
2017-03-23 14:33:06 +01:00
# ping scan.
else {
my @ hosts = map { ( split ( '/' , $ _ ) ) [ 0 ] } $ net_addr - > hostenum ;
next if ( scalar ( @ hosts ) == 0 ) ;
2019-03-25 17:32:18 +01:00
$ self - > { 'summary' } - > { 'discovered' } += scalar ( @ hosts ) ;
2017-03-23 14:33:06 +01:00
my $ step = 50.0 / scalar(@subnets) / scalar ( @ hosts ) ; # The first 50% of the recon task approx.
2019-03-25 17:32:18 +01:00
my $ subnet_step = 100.0 / scalar ( @ hosts ) ;
2017-03-23 14:33:06 +01:00
foreach my $ host ( @ hosts ) {
2019-03-25 17:32:18 +01:00
2017-03-23 14:33:06 +01:00
$ self - > call ( 'message' , "Scanning host: $host" , 5 ) ;
$ self - > call ( 'update_progress' , ceil ( $ progress ) ) ;
$ progress += $ step ;
2019-03-25 17:32:18 +01:00
2017-03-23 14:33:06 +01:00
# Check if the host is up.
2019-03-25 17:32:18 +01:00
if ( $ self - > ping ( $ host ) == 0 ) {
$ self - > { 'summary' } - > { 'not_alive' } += 1 ;
next ;
}
$ self - > { 'summary' } - > { 'alive' } += 1 ;
$ self - > { 'c_network_percent' } += $ subnet_step ;
2017-03-23 14:33:06 +01:00
$ self - > snmp_discovery ( $ host ) ;
2019-02-13 14:56:10 +01:00
# Add wmi scan if enabled.
$ self - > wmi_scan ( $ host ) if ( $ self - > { 'wmi_enabled' } == 1 ) ;
2017-03-23 14:33:06 +01:00
}
2017-03-08 13:20:00 +01:00
}
}
}
2019-04-03 18:13:19 +02:00
##########################################################################
# Perform a DB scan.
##########################################################################
sub db_scan ($) {
my ( $ self ) = @ _ ;
my ( $ progress , $ step ) ;
my $ type = '' ;
if ( $ self - > { 'task_data' } - > { 'type' } == DISCOVERY_APP_MYSQL ) {
$ type = 'MySQL' ;
} elsif ( $ self - > { 'task_data' } - > { 'type' } == DISCOVERY_APP_ORACLE ) {
$ type = 'Oracle' ;
} else {
# Unrecognized task type.
call ( 'message' , 'Unrecognized task type' , 1 ) ;
$ self - > call ( 'update_progress' , - 1 ) ;
return ;
}
2019-04-03 22:44:06 +02:00
my @ targets = split /,/ , $ self - > { 'task_data' } - > { 'subnet' } ;
my $ i = 0 ;
foreach my $ target ( @ targets ) {
2019-04-04 12:28:17 +02:00
my @ data ;
my @ modules ;
2019-04-03 22:44:06 +02:00
call ( 'message' , 'Checking target ' . $ target , 10 ) ;
# Force target acquirement.
$ self - > { 'task_data' } - > { 'dbhost' } = $ target ;
$ self - > { 'task_data' } - > { 'target_index' } = $ i + + ;
# Connect to target.
my $ dbObj = PandoraFMS::Recon::Util:: enterprise_new (
'PandoraFMS::Recon::Applications::' . $ type ,
$ self - > { 'task_data' }
) ;
2019-04-03 18:13:19 +02:00
2019-04-04 12:28:17 +02:00
if ( ! $ dbObj - > is_connected ( ) ) {
2019-04-03 22:44:06 +02:00
call ( 'message' , 'Cannot connect to target ' . $ target , 3 ) ;
2019-04-03 22:52:37 +02:00
$ self - > { 'summary' } - > { 'not_alive' } += 1 ;
2019-04-04 12:28:17 +02:00
push @ modules , {
name = > 'mysql_connection' ,
type = > 'generic_proc' ,
data = > 0 ,
description = > 'MySQL availability'
} ;
2019-04-03 22:44:06 +02:00
2019-04-04 12:28:17 +02:00
} else {
2019-04-04 16:00:08 +02:00
my $ dbObjCfg = $ dbObj - > get_config ( ) ;
2019-04-04 12:28:17 +02:00
$ self - > { 'summary' } - > { 'discovered' } += 1 ;
$ self - > { 'summary' } - > { 'alive' } += 1 ;
push @ modules , {
name = > 'mysql_connection' ,
type = > 'generic_proc' ,
data = > 1 ,
description = > 'MySQL availability'
} ;
# Analyze.
$ self - > { 'step' } = STEP_STATISTICS ;
$ self - > { 'c_network_name' } = $ dbObj - > get_host ( ) ;
$ self - > call ( 'update_progress' , 10 ) ;
# Retrieve connection statistics.
# Retrieve uptime statistics
# Retrieve query stats
# Retrieve connections
# Retrieve innodb
# Retrieve cache
push @ modules , $ dbObj - > get_statistics ( ) ;
$ self - > call ( 'update_progress' , 50 ) ;
# Custom queries.
push @ modules , $ dbObj - > execute_custom_queries ( ) ;
$ self - > call ( 'update_progress' , 90 ) ;
2019-04-04 16:00:08 +02:00
if ( defined ( $ dbObjCfg - > { 'scan_databases' } )
&& $ dbObjCfg - > { 'scan_databases' } == 1 ) {
my $ __data = $ dbObj - > scan_databases ( ) ;
if ( ref ( $ __data ) eq "ARRAY" ) {
if ( defined ( $ dbObjCfg - > { 'agent_per_database' } )
&& $ dbObjCfg - > { 'agent_per_database' } == 1 ) {
# Agent per database detected.
push @ data , @ { $ __data } ;
} else {
# Merge modules into engine agent.
my @ _modules = map { $ _ - > { 'module_data' } } @ { $ __data } ;
push @ modules , @ _modules ;
}
}
}
2019-04-04 12:28:17 +02:00
}
2019-04-03 22:44:06 +02:00
2019-04-04 12:28:17 +02:00
# Put engine agent at the beginning of the list.
unshift @ data , {
'agent_data' = > {
'agent_name' = > $ dbObj - > get_agent_name ( ) ,
'os' = > $ type ,
'os_version' = > 'Discovery' ,
'interval' = > $ self - > { 'task_data' } - > { 'interval_sweep' } ,
'id_group' = > $ self - > { 'task_data' } - > { 'id_group' } ,
'address' = > $ dbObj - > get_host ( ) ,
'description' = > '' ,
} ,
'module_data' = > \ @ modules ,
} ;
$ self - > call ( 'create_agents' , \ @ data ) ;
2019-04-03 22:44:06 +02:00
# Destroy item.
undef ( $ dbObj ) ;
}
2019-04-03 18:13:19 +02:00
# Update progress.
# Done!
$ self - > { 'step' } = '' ;
$ self - > call ( 'update_progress' , - 1 ) ;
}
2017-03-08 13:20:00 +01:00
##########################################################################
# Perform a network scan.
##########################################################################
sub scan ($) {
my ( $ self ) = @ _ ;
2017-03-23 14:33:06 +01:00
my ( $ progress , $ step ) ;
2017-03-08 13:20:00 +01:00
# 1%
$ self - > call ( 'update_progress' , 1 ) ;
2019-04-03 18:13:19 +02:00
if ( defined ( $ self - > { 'task_data' } ) ) {
if ( $ self - > { 'task_data' } - > { 'type' } == DISCOVERY_APP_MYSQL
|| $ self - > { 'task_data' } - > { 'type' } == DISCOVERY_APP_ORACLE ) {
# Database scan.
return $ self - > db_scan ( ) ;
}
}
2017-03-08 13:20:00 +01:00
# Find devices.
2017-03-23 14:33:06 +01:00
$ self - > call ( 'message' , "[1/5] Scanning the network..." , 3 ) ;
2019-03-25 17:32:18 +01:00
$ self - > { 'step' } = STEP_SCANNING ;
$ self - > call ( 'update_progress' , $ progress ) ;
2017-03-23 14:33:06 +01:00
$ self - > scan_subnet ( ) ;
2017-03-14 08:27:44 +01:00
2018-05-21 15:03:54 +02:00
# Read the local ARP cache.
$ self - > local_arp ( ) ;
2017-03-23 14:33:06 +01:00
# Get a list of found hosts.
my @ hosts = @ { $ self - > get_hosts ( ) } ;
2017-03-23 16:14:31 +01:00
if ( scalar ( @ hosts ) > 0 && $ self - > { 'parent_detection' } == 1 ) {
2019-03-25 17:32:18 +01:00
# Delete previous connections.
2017-03-23 14:33:06 +01:00
$ self - > call ( 'delete_connections' ) ;
2019-03-25 17:32:18 +01:00
2017-03-23 14:33:06 +01:00
# Connectivity from address forwarding tables.
$ self - > call ( 'message' , "[1/4] Finding address forwarding table connectivity..." , 3 ) ;
2019-03-25 17:32:18 +01:00
$ self - > { 'step' } = STEP_AFT ;
2017-03-23 14:33:06 +01:00
( $ progress , $ step ) = ( 50 , 20.0 / scalar ( @ hosts ) ) ; # From 50% to 70%.
for ( my $ i = 0 ; defined ( $ hosts [ $ i ] ) ; $ i + + ) {
$ self - > call ( 'update_progress' , $ progress ) ;
$ progress += $ step ;
$ self - > aft_connectivity ( $ hosts [ $ i ] ) ;
2017-03-08 13:20:00 +01:00
}
2019-03-25 17:32:18 +01:00
2017-03-23 14:33:06 +01:00
# Connect hosts that are still unconnected using traceroute.
$ self - > call ( 'message' , "[3/4] Finding traceroute connectivity." , 3 ) ;
2019-03-25 17:32:18 +01:00
$ self - > { 'step' } = STEP_TRACEROUTE ;
2017-03-23 14:33:06 +01:00
( $ progress , $ step ) = ( 70 , 20.0 / scalar ( @ hosts ) ) ; # From 70% to 90%.
2017-03-08 13:20:00 +01:00
foreach my $ host ( @ hosts ) {
2017-03-23 14:33:06 +01:00
$ self - > call ( 'update_progress' , $ progress ) ;
2017-03-08 13:20:00 +01:00
$ progress += $ step ;
2018-05-21 15:03:54 +02:00
next if ( $ self - > has_parent ( $ host ) || $ self - > has_children ( $ host ) ) ;
2017-03-23 14:33:06 +01:00
$ self - > traceroute_connectivity ( $ host ) ;
2017-03-08 13:20:00 +01:00
}
2019-03-25 17:32:18 +01:00
2017-03-23 14:33:06 +01:00
# Connect hosts that are still unconnected using known gateways.
$ self - > call ( 'message' , "[4/4] Finding host to gateway connectivity." , 3 ) ;
2019-03-25 17:32:18 +01:00
$ self - > { 'step' } = STEP_GATEWAY ;
2017-03-23 14:33:06 +01:00
( $ progress , $ step ) = ( 90 , 10.0 / scalar ( @ hosts ) ) ; # From 70% to 90%.
$ self - > get_routes ( ) ; # Update the route cache.
foreach my $ host ( @ hosts ) {
$ self - > call ( 'update_progress' , $ progress ) ;
$ progress += $ step ;
next if ( $ self - > has_parent ( $ host ) ) ;
$ self - > gateway_connectivity ( $ host ) ;
}
2017-03-08 13:20:00 +01:00
}
2017-03-23 14:33:06 +01:00
# Done!
2019-03-25 17:32:18 +01:00
$ self - > { 'step' } = '' ;
2017-03-08 13:20:00 +01:00
$ self - > call ( 'update_progress' , - 1 ) ;
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
# Print debug information on found devices.
$ self - > call ( 'message' , "[Summary]" , 3 ) ;
foreach my $ host ( @ hosts ) {
my $ device = $ self - > get_device ( $ host ) ;
next unless defined ( $ device ) ;
# Print device information.
2019-03-25 17:32:18 +01:00
my $ dev_info = "Device: " . $ device - > { 'type' } . " (" ;
2017-03-08 13:20:00 +01:00
foreach my $ ip_address ( $ self - > get_addresses ( $ host ) ) {
$ dev_info . = "$ip_address," ;
}
chop ( $ dev_info ) ;
$ dev_info . = ')' ;
$ self - > call ( 'message' , $ dev_info , 3 ) ;
}
}
########################################################################################
# Set an SNMP community for the given device.
########################################################################################
sub set_community ($$$) {
my ( $ self , $ device , $ community ) = @ _ ;
$ self - > { 'community_cache' } - > { $ device } = $ community ;
}
########################################################################################
# Set the type of the given device.
########################################################################################
sub set_device_type ($$$) {
my ( $ self , $ device , $ type ) = @ _ ;
$ self - > { 'visited_devices' } - > { $ device } - > { 'type' } = $ type ;
}
########################################################################################
# Performs an SNMP WALK and returns the response as an array.
########################################################################################
sub snmp_get ($$$) {
my ( $ self , $ device , $ oid ) = @ _ ;
my @ output ;
2018-09-17 14:36:52 +02:00
return ( ) unless defined $ self - > is_snmp_discovered ( $ device ) ;
2017-03-08 13:20:00 +01:00
my $ community = $ self - > get_community ( $ device ) ;
# Check the SNMP query cache first.
if ( defined ( $ self - > { 'snmp_cache' } - > { "${device}_${oid}" } ) ) {
return @ { $ self - > { 'snmp_cache' } - > { "${device}_${oid}" } } ;
}
# Check VLANS.
my @ vlans = $ self - > get_vlans ( $ device ) ;
if ( scalar ( @ vlans ) == 0 ) {
2018-09-17 14:36:52 +02:00
my $ command = $ self - > snmp_get_command ( $ device , $ oid , $ community ) ;
@ output = `$command` ;
2019-03-25 17:32:18 +01:00
} else {
2017-03-08 13:20:00 +01:00
# Handle duplicate lines.
my % output_hash ;
foreach my $ vlan ( @ vlans ) {
2018-09-17 14:36:52 +02:00
my $ command = $ self - > snmp_get_command ( $ device , $ oid , $ community , $ vlan ) ;
2019-04-02 16:25:29 +02:00
foreach my $ line ( `$command` ) {
2017-03-08 13:20:00 +01:00
$ output_hash { $ line } = 1 ;
}
}
push ( @ output , keys ( % output_hash ) ) ;
}
# Update the SNMP query cache.
$ self - > { 'snmp_cache' } - > { "${device}_${oid}" } = [ @ output ] ;
return @ output ;
}
2018-09-17 14:36:52 +02:00
########################################################################################
# Get the snmpwalk command seing version 1, 2, 2c or 3.
########################################################################################
sub snmp_get_command {
my ( $ self , $ device , $ oid , $ community , $ vlan ) = @ _ ;
$ vlan = defined ( $ vlan ) ? "\@" . $ vlan : '' ;
my $ command = "snmpwalk -M/dev/null -r$self->{'snmp_checks'} -t$self->{'snmp_timeout'} -v$self->{'snmp_version'} -On -Oe " ;
if ( $ self - > { 'snmp_version' } eq "3" ) {
2019-02-15 16:02:55 +01:00
if ( $ self - > { 'community' } ) { # Context
2019-02-15 16:06:28 +01:00
$ command . = " -N $self->{'community'} " ;
2019-02-15 16:02:55 +01:00
}
2018-09-17 14:36:52 +02:00
$ command . = " -l$self->{'snmp_security_level'} " ;
if ( $ self - > { 'snmp_security_level' } ne "noAuthNoPriv" ) {
$ command . = " -u$self->{'snmp_auth_user'} -a$self->{'snmp_auth_method'} -A$self->{'snmp_auth_pass'} " ;
}
if ( $ self - > { 'snmp_security_level' } eq "authPriv" ) {
$ command . = " -x$self->{'snmp_privacy_method'} -X$self->{'snmp_privacy_pass'} " ;
}
} else {
$ command . = " -c$community$vlan " ;
}
return "$command $device $oid 2>/dev/null" ;
}
2017-03-08 13:20:00 +01:00
########################################################################################
# Performs an SNMP WALK and returns the value of the given OID. Returns undef
# on error.
########################################################################################
sub snmp_get_value ($$$) {
my ( $ self , $ device , $ oid ) = @ _ ;
my @ output = $ self - > snmp_get ( $ device , $ oid ) ;
foreach my $ line ( @ output ) {
2019-03-25 17:32:18 +01:00
chomp ( $ line ) ;
2017-03-08 13:20:00 +01:00
return $ 1 if ( $ line =~ /^$oid\s+=\s+\S+:\s+(.*)$/ ) ;
}
return undef ;
}
########################################################################################
# Performs an SNMP WALK and returns an array of values.
########################################################################################
sub snmp_get_value_array ($$$) {
my ( $ self , $ device , $ oid ) = @ _ ;
my @ values ;
my @ output = $ self - > snmp_get ( $ device , $ oid ) ;
foreach my $ line ( @ output ) {
2019-03-25 17:32:18 +01:00
chomp ( $ line ) ;
2017-03-08 13:20:00 +01:00
push ( @ values , $ 1 ) if ( $ line =~ /^$oid\S*\s+=\s+\S+:\s+(.*)$/ ) ;
}
return @ values ;
}
########################################################################################
# Performs an SNMP WALK and returns a hash of values.
########################################################################################
sub snmp_get_value_hash ($$$) {
my ( $ self , $ device , $ oid ) = @ _ ;
my % values ;
my @ output = $ self - > snmp_get_value_array ( $ device , $ oid ) ;
foreach my $ line ( @ output ) {
$ values { $ line } = '' ;
}
return % values ;
}
##########################################################################
2017-03-10 10:36:37 +01:00
# Connect the given host to its parent using traceroute.
2017-03-08 13:20:00 +01:00
##########################################################################
sub traceroute_connectivity ($$) {
my ( $ self , $ host ) = @ _ ;
# Perform a traceroute.
my $ nmap_args = '-nsP -PE --traceroute --max-retries ' . $ self - > { 'icmp_checks' } . ' --host-timeout ' . $ self - > { 'icmp_timeout' } . 's -T' . $ self - > { 'recon_timing_template' } ;
2017-03-13 11:13:45 +01:00
my $ np = PandoraFMS::Recon::NmapParser - > new ( ) ;
2019-03-25 17:32:18 +01:00
eval { $ np - > parsescan ( $ self - > { 'nmap' } , $ nmap_args , ( $ host ) ) ; } ;
2017-03-08 13:20:00 +01:00
return if ( $@ ) ;
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
# Get hops to the host.
2019-03-25 17:32:18 +01:00
my ( $ h ) = $ np - > all_hosts ( ) ;
return unless defined ( $ h ) ;
my @ hops = $ h - > all_trace_hops ( ) ;
2017-03-08 13:20:00 +01:00
# Skip the target host.
pop ( @ hops ) ;
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
# Reverse the host order (closest hosts first).
@ hops = reverse ( @ hops ) ;
2019-03-25 17:32:18 +01:00
2017-03-08 13:20:00 +01:00
# Look for parents.
my $ device = $ host ;
for ( my $ i = 0 ; $ i < $ self - > { 'parent_recursion' } ; $ i + + ) {
2017-03-23 14:33:06 +01:00
next unless defined ( $ hops [ $ i ] ) ;
2017-03-08 13:20:00 +01:00
my $ parent = $ hops [ $ i ] - > ipaddr ( ) ;
2017-03-23 14:33:06 +01:00
# Create an agent for the parent.
$ self - > call ( 'create_agent' , $ parent ) ;
$ self - > call ( 'message' , "Host $device is one hop away from host $parent." , 5 ) ;
2019-03-25 17:32:18 +01:00
$ self - > mark_connected ( $ parent , '' , $ device , '' ) ;
2017-03-08 13:20:00 +01:00
# Move on to the next hop.
$ device = $ parent ;
}
}
2019-02-13 14:56:10 +01:00
##########################################################################
# Returns the credentials with which the host responds to WMI queries or
# undef if it does not respond to WMI.
##########################################################################
sub responds_to_wmi {
my ( $ self , $ target ) = @ _ ;
foreach my $ auth ( @ { $ self - > { 'auth_strings_array' } } ) {
my @ output ;
if ( $ auth ne '' ) {
@ output = `$self->{'timeout_cmd'}$self->{'wmi_client'} -U $auth //$target "SELECT * FROM Win32_ComputerSystem" 2>&1` ;
} else {
@ output = `$self->{'timeout_cmd'}$self->{'wmi_client'} -N //$target "SELECT * FROM Win32_ComputerSystem" 2>&1` ;
}
foreach my $ line ( @ output ) {
chomp ( $ line ) ;
return $ auth if ( $ line =~ m/^CLASS: Win32_ComputerSystem$/ ) ;
}
}
return undef ;
}
##########################################################################
# Add wmi modules to the given host.
##########################################################################
sub wmi_scan {
my ( $ self , $ target ) = @ _ ;
$ self - > call ( 'message' , "[" . $ target . "] Checking WMI." , 5 ) ;
my $ auth = $ self - > responds_to_wmi ( $ target ) ;
return unless defined ( $ auth ) ;
2019-03-25 17:32:18 +01:00
$ self - > { 'summary' } - > { 'WMI' } += 1 ;
2019-02-13 14:56:10 +01:00
$ self - > call ( 'message' , "[" . $ target . "] WMI available." , 10 ) ;
2019-03-25 17:32:18 +01:00
2019-02-13 14:56:10 +01:00
# Create the agent if it does not exist.
my $ agent_id = $ self - > call ( 'create_agent' , $ target ) ;
next unless defined ( $ agent_id ) ;
# CPU.
my @ cpus = $ self - > wmi_get_value_array ( $ target , $ auth , 'SELECT DeviceId FROM Win32_Processor' , 0 ) ;
foreach my $ cpu ( @ cpus ) {
2019-03-25 17:32:18 +01:00
$ self - > call ( 'wmi_module' , ( $ agent_id , $ target , "SELECT LoadPercentage FROM Win32_Processor WHERE DeviceId='$cpu'" , $ auth , 1 , "CPU Load $cpu" , "Load for $cpu (%)" , 'generic_data' ) ) ;
2019-02-13 14:56:10 +01:00
}
# Memory.
my $ mem = $ self - > wmi_get_value ( $ target , $ auth , 'SELECT FreePhysicalMemory FROM Win32_OperatingSystem' , 0 ) ;
if ( defined ( $ mem ) ) {
2019-03-25 17:32:18 +01:00
$ self - > call ( 'wmi_module' , ( $ agent_id , $ target , "SELECT FreePhysicalMemory, TotalVisibleMemorySize FROM Win32_OperatingSystem" , $ auth , 0 , 'FreeMemory' , 'Free memory' , 'generic_data' , 'KB' ) ) ;
2019-02-13 14:56:10 +01:00
}
# Disk.
my @ units = $ self - > wmi_get_value_array ( $ target , $ auth , 'SELECT DeviceID FROM Win32_LogicalDisk' , 0 ) ;
foreach my $ unit ( @ units ) {
2019-03-25 17:32:18 +01:00
$ self - > call ( 'wmi_module' , ( $ agent_id , $ target , "SELECT FreeSpace FROM Win32_LogicalDisk WHERE DeviceID='$unit'" , $ auth , 1 , "FreeDisk $unit" , 'Available disk space in kilobytes' , 'generic_data' , 'KB' ) ) ;
2019-02-13 14:56:10 +01:00
}
}
##########################################################################
# Extra: WMI imported methods. DO NOT EXPORT TO AVOID DOUBLE DEF.
##########################################################################
##########################################################################
# Performs a wmi get requests and returns the response as an array.
##########################################################################
sub wmi_get {
my ( $ self , $ target , $ auth , $ query ) = @ _ ;
my @ output ;
if ( defined ( $ auth ) && $ auth ne '' ) {
@ output = `$self->{'timeout_cmd'}"$self->{'wmi_client'}" -U $auth //$target "$query" 2>&1` ;
} else {
@ output = `$self->{'timeout_cmd'}"$self->{'wmi_client'}" -N //$target "$query" 2>&1` ;
}
# Something went wrong.
return ( ) if ( $? != 0 ) ;
return @ output ;
}
##########################################################################
# Performs a WMI request and returns the requested column of the first row.
# Returns undef on error.
##########################################################################
sub wmi_get_value {
my ( $ self , $ target , $ auth , $ query , $ column ) = @ _ ;
my @ result ;
my @ output = $ self - > wmi_get ( $ target , $ auth , $ query ) ;
return undef unless defined ( $ output [ 2 ] ) ;
my $ line = $ output [ 2 ] ;
chomp ( $ line ) ;
my @ columns = split ( /\|/ , $ line ) ;
return undef unless defined ( $ columns [ $ column ] ) ;
return $ columns [ $ column ] ;
}
##########################################################################
# Performs a WMI request and returns row values for the requested column
# in an array.
##########################################################################
sub wmi_get_value_array {
my ( $ self , $ target , $ auth , $ query , $ column ) = @ _ ;
my @ result ;
my @ output = $ self - > wmi_get ( $ target , $ auth , $ query ) ;
foreach ( my $ i = 2 ; defined ( $ output [ $ i ] ) ; $ i + + ) {
my $ line = $ output [ $ i ] ;
chomp ( $ line ) ;
my @ columns = split ( /\|/ , $ line ) ;
next unless defined ( $ columns [ $ column ] ) ;
push ( @ result , $ columns [ $ column ] ) ;
}
return @ result ;
}
##########################################################################
# END: WMI imported methods.
##########################################################################
2017-03-08 13:20:00 +01:00
1 ;
__END__