From 24e6b34af0fb098104489121afcc1fd70a2b4fbc Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Thu, 12 Feb 2026 22:54:15 -0600 Subject: [PATCH 1/2] Fix discovery-arp module query Port to modern module while we are at it --- LibreNMS/Modules/DiscoveryArp.php | 142 +++++++++++++++++++++++ app/Models/Ipv4Mac.php | 10 +- includes/discovery/discovery-arp.inc.php | 83 ------------- includes/functions.php | 31 ----- 4 files changed, 151 insertions(+), 115 deletions(-) create mode 100644 LibreNMS/Modules/DiscoveryArp.php delete mode 100644 includes/discovery/discovery-arp.inc.php diff --git a/LibreNMS/Modules/DiscoveryArp.php b/LibreNMS/Modules/DiscoveryArp.php new file mode 100644 index 000000000000..48192a078a73 --- /dev/null +++ b/LibreNMS/Modules/DiscoveryArp.php @@ -0,0 +1,142 @@ +isEnabledAndDeviceUp($os->getDevice()); + } + + /** + * @inheritDoc + */ + public function shouldPoll(OS $os, ModuleStatus $status): bool + { + return false; + } + + /** + * @inheritDoc + */ + public function discover(OS $os): void + { + // Find all IPv4 addresses in the MAC table that haven't been discovered on monitored devices. + $entries = Ipv4Mac::query() + ->select(['ipv4_address', 'mac_address', 'port_id']) + ->whereHas('port', fn ($query) => $query->isNotDeleted()) + ->whereDoesntHave('ipv4Address') + ->orderBy('ipv4_address') + ->get(); + + $discoverable_names_ips = []; + $ignored = []; + $excluded = 0; + $debounced = 0; + + foreach ($entries as $entry) { + try { + $ip = IPv4::parse($entry->ipv4_address); + + // Even though match_network is done inside discover_new_device, we do it here + // as well in order to skip unnecessary reverse DNS lookups on discovered IPs. + if ($ip->inNetworks(LibrenmsConfig::get('autodiscovery.nets-exclude'))) { + $excluded++; + continue; + } + + if (! $ip->inNetworks(LibrenmsConfig::get('nets'))) { + $ignored[] = (string) $ip; + continue; + } + + // Attempt discovery of each IP only once per run. + if (! Cache::add('arp_discovery:' . $ip, true, 3600)) { + $debounced++; + continue; + } + + $discoverable_names_ips[] = gethostbyaddr((string) $ip); + } catch (InvalidIpException $e) { + Log::debug('Invalid IP address encountered during ARP discovery: ' . $e->getMessage()); + } + } + + $ignored_count = count($ignored); + Log::info(sprintf('Found %d discoverable IPs, ignored %d, excluded %d, skipped (recent) %d', count($discoverable_names_ips), $ignored_count, $excluded, $debounced)); + + // send a single eventlog per discovery with at most 5 IPs + if ($ignored_count) { + $ips = implode(',', array_slice($ignored, 0, 5)); + if ($ignored_count > 5) { + $ips = '...'; + } + Eventlog::log("ARP Discover: ignored $ignored_count IPs ($ips)", $os->getDeviceId(), 'discovery', Severity::Notice); + } + + // Run device discovery on each of the devices we've detected so far. + $device = $os->getDeviceArray(); + foreach ($discoverable_names_ips as $address) { + discover_new_device($address, $device, 'ARP'); + } + } + + /** + * @inheritDoc + */ + public function poll(OS $os, DataStorageInterface $datastore): void + { + // no polling + } + + /** + * @inheritDoc + */ + public function dataExists(Device $device): bool + { + return false; + } + + /** + * @inheritDoc + */ + public function cleanup(Device $device): int + { + return 0; + } + + /** + * @inheritDoc + */ + public function dump(Device $device, string $type): ?array + { + return null; // no testing for now + } +} diff --git a/app/Models/Ipv4Mac.php b/app/Models/Ipv4Mac.php index cc68461a43f9..dfe1e3de3d30 100644 --- a/app/Models/Ipv4Mac.php +++ b/app/Models/Ipv4Mac.php @@ -35,9 +35,17 @@ public function port(): BelongsTo return $this->belongsTo(Port::class, 'port_id'); } + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\Ipv4Address, $this> + */ + public function ipv4Address(): BelongsTo + { + return $this->belongsTo(Ipv4Address::class, 'ipv4_address', 'ipv4_address'); + } + // Ports in NMS with a matching MAC address and IP address. // This can match multiple ports if you have multiple sub-interfaces with the same - // IP address (e.g. different VRFs, or mutiple point to point links on Mikrotik) + // IP address (e.g. different VRFs, or multiple point to point links on Mikrotik) /** * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough<\App\Models\Port, Ipv4Mac, $this> */ diff --git a/includes/discovery/discovery-arp.inc.php b/includes/discovery/discovery-arp.inc.php deleted file mode 100644 index c27427bb99dd..000000000000 --- a/includes/discovery/discovery-arp.inc.php +++ /dev/null @@ -1,83 +0,0 @@ - -// -// Author: Paul Gear -// License: GPLv3 -// - -use App\Facades\LibrenmsConfig; -use App\Models\Eventlog; -use LibreNMS\Enum\Severity; - -$hostname = $device['hostname']; -$deviceid = $device['device_id']; - -// Find all IPv4 addresses in the MAC table that haven't been discovered on monitored devices. -$sql = ' - SELECT * - FROM ipv4_mac as m, ports as i - WHERE m.port_id = i.port_id - AND i.device_id = ? - AND i.deleted = 0 - AND NOT EXISTS ( - SELECT * FROM ipv4_addresses a - WHERE a.ipv4_address = m.ipv4_address - ) - GROUP BY ipv4_address - ORDER BY ipv4_address - '; - -// FIXME: Observium now uses ip_mac.ip_address in place of ipv4_mac.ipv4_address - why? -$names = []; -$ips = []; - -foreach (dbFetchRows($sql, [$deviceid]) as $entry) { - $ip = $entry['ipv4_address']; - $mac = $entry['mac_address']; - $if = $entry['port_id']; - - // Even though match_network is done inside discover_new_device, we do it here - // as well in order to skip unnecessary reverse DNS lookups on discovered IPs. - if (match_network(LibrenmsConfig::get('autodiscovery.nets-exclude'), $ip)) { - echo 'x'; - continue; - } - - if (! match_network(LibrenmsConfig::get('nets'), $ip)) { - echo 'i'; - Eventlog::log("Ignored $ip", $deviceid, 'interface', Severity::Notice, $if); - continue; - } - - // Attempt discovery of each IP only once per run. - if (Cache::get('arp_discovery:' . $ip)) { - echo '.'; - continue; - } - - Cache::put('arp_discovery:' . $ip, true, 3600); - - $name = gethostbyaddr($ip); - echo '+'; - $names[] = $name; - $ips[$name] = $ip; -} - -echo "\n"; - -// Run device discovery on each of the devices we've detected so far. -foreach ($names as $name) { - $remote_device_id = discover_new_device($name, $device, 'ARP'); -} - -unset($names); -unset($ips); diff --git a/includes/functions.php b/includes/functions.php index e592cc9d5fce..b7cd7824847d 100755 --- a/includes/functions.php +++ b/includes/functions.php @@ -129,37 +129,6 @@ function isDomainResolves($domain) return ! empty($records); } -function match_network($nets, $ip, $first = false) -{ - $return = false; - if (! is_array($nets)) { - $nets = [$nets]; - } - foreach ($nets as $net) { - $rev = (preg_match("/^\!/", (string) $net)) ? true : false; - $net = preg_replace("/^\!/", '', (string) $net); - $ip_arr = explode('/', (string) $net); - $net_long = ip2long($ip_arr[0]); - $x = ip2long($ip_arr[1]); - $mask = long2ip($x) == $ip_arr[1] ? $x : 0xFFFFFFFF << (32 - $ip_arr[1]); - $ip_long = ip2long($ip); - if ($rev) { - if (($ip_long & $mask) == ($net_long & $mask)) { - return false; - } - } else { - if (($ip_long & $mask) == ($net_long & $mask)) { - $return = true; - } - if ($first && $return) { - return true; - } - } - } - - return $return; -} - // FIXME port to LibreNMS\Util\IPv6 class function snmp2ipv6($ipv6_snmp) { From 763d629860d384d134327237a9622642d446380c Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 13 Feb 2026 04:54:33 +0000 Subject: [PATCH 2/2] Apply fixes from StyleCI --- LibreNMS/Modules/DiscoveryArp.php | 1 - 1 file changed, 1 deletion(-) diff --git a/LibreNMS/Modules/DiscoveryArp.php b/LibreNMS/Modules/DiscoveryArp.php index 48192a078a73..46f0cffcfa7d 100644 --- a/LibreNMS/Modules/DiscoveryArp.php +++ b/LibreNMS/Modules/DiscoveryArp.php @@ -18,7 +18,6 @@ class DiscoveryArp implements Module { - /** * @inheritDoc */