BE+DOCS: ICMP, IPNEIGH handling VLANS #1662

This commit is contained in:
jokob-sk
2026-06-02 07:49:06 +10:00
parent f9b413d172
commit f1a9109697
8 changed files with 98 additions and 76 deletions

View File

@@ -7,7 +7,7 @@ Effective multi-network monitoring starts with understanding how NetAlertX "sees
* **A. Understand Network Accessibility:** Local ARP-based scanning (**ARPSCAN**) only discovers devices on directly accessible subnets due to Layer 2 limitations. It cannot traverse VPNs or routed borders without specific configuration.
* **B. Plan Subnet & Scan Interfaces:** Explicitly configure each accessible segment in `SCAN_SUBNETS` with the corresponding interfaces.
* **C. Remote & Inaccessible Networks:** For networks unreachable via ARP, use these strategies:
* **Alternate Plugins:** Supplement discovery with [SNMPDSC](SNMPDSC) or [DHCP lease imports](https://docs.netalertx.com/PLUGINS/?h=DHCPLSS#available-plugins).
* **Alternate Plugins:** Supplement discovery with [SNMPDSC](https://docs.netalertx.com/PLUGINS/?h=SNMPDSC#available-plugins) or [DHCP lease imports](https://docs.netalertx.com/PLUGINS/?h=DHCPLSS#available-plugins).
* **Sync Hub for MSP & Multi-Site Deployments:** Run secondary NetAlertX instances on isolated networks and aggregate data using the **SYNC plugin**. Use the [`SYNC_BEHAVIOR`](https://github.com/netalertx/NetAlertX/tree/main/front/plugins/sync/README.md#hub-device-write-behavior-sync_behavior) setting on the hub to control whether the hub inherits device config from nodes or manages it independently.
* **Manual Entry:** For static assets where only ICMP (ping) status is needed.
@@ -110,7 +110,7 @@ Don't let a massive device list overwhelm you. Use the [Multi-edit features](./D
As your environment grows, tuning the underlying engine is vital to maintain a snappy UI and reliable discovery cycles.
* **Plugin Scheduling:** Avoid "Scan Storms" by staggering plugin execution. Running intensive tasks like `NMAP` or `MASS_DNS` simultaneously can spike CPU and cause database locks.
* **Database Health:** Large-scale monitoring generates massive event logs. Use the **[DBCLNP (Database Cleanup)](https://www.google.com/search?q=https://docs.netalertx.com/PLUGINS/%23dbclnp)** plugin to prune old records and keep the SQLite database performant.
* **Database Health:** Large-scale monitoring generates massive event logs. Use the **[DBCLNP (Database Cleanup)](https://docs.netalertx.com/PLUGINS/?h=dbclnp#available-plugins)** plugin to prune old records and keep the SQLite database performant.
* **Resource Management:** For high-device counts, consider increasing the memory limit for the container and utilizing `tmpfs` for temporary files to reduce SD card/disk I/O bottlenecks.
* Enable the `DEEP_SLEEP` setting.

View File

@@ -77,11 +77,11 @@ PUSH mode is typically recommended for MSP deployments because remote customer e
The hub can operate in different synchronization ownership modes depending on your operational requirements.
| Mode | Best For |
| -------------- | -------------------------------------------------------------------- |
| `copy-new` | MSP environments where the hub becomes the long-term source of truth |
| `carbon-copy` | Fully managed remote appliances where nodes remain authoritative |
| `hub-defaults` | Centralized inventory management with hub-defined policies |
| Mode | Best For |
| -------------- | --------------------------------------------------------------------- |
| `copy-new` | After initial discovery the hub becomes the long-term source of truth |
| `carbon-copy` | Fully managed remote appliances where nodes remain authoritative |
| `hub-defaults` | Centralized inventory management with hub-defined policies |
This flexibility allows NetAlertX to support both:
@@ -121,14 +121,13 @@ For best results in multi-site environments:
* Use predefined "Down Devices" dashboards
* Enable Prometheus metrics export
* Use UI Filters to create site-specific views
* Configure notification throttling to reduce alert fatigue
---
# Related Documentation
* [Remote Networks](./REMOTE_NETWORKS.md)
* [Sync Hub Plugin](../front/plugins/sync/README.md)
* [Sync Hub Plugin](https://github.com/netalertx/NetAlertX/tree/main/front/plugins/sync/README.md)
* [Workflows](./WORKFLOWS.md)
* [Metrics API](./API_METRICS.md)
* [Eyes on Glass / NOC Dashboard](./ADVISORY_EYES_ON_GLASS.md)

View File

@@ -21,18 +21,22 @@ This functionality allows you to define **custom properties** for devices, which
Visible properties (`CUSTPROP_show: true`) are displayed as interactive icons in the device listing. Each icon can perform one of the following actions based on the `CUSTPROP_type`:
1. **Modals (e.g., Show Notes)**:
- Displays detailed information in a popup modal.
- Example: Firmware version details.
2. **Links**:
- Redirect to an external or internal URL.
- Example: Open a device's documentation or external site.
3. **Device Actions**:
- Manage devices with actions like delete.
- Example: Quickly remove a device from the network.
4. **Plugins**:
- Future placeholder for running custom plugin scripts.
- **Note**: Not implemented yet.
@@ -41,12 +45,15 @@ Visible properties (`CUSTPROP_show: true`) are displayed as interactive icons in
## Example Use Cases
1. **Device Documentation Link**:
- Add a custom property with `CUSTPROP_type` set to `link` or `link_new_tab` to allow quick navigation to the external documentation of the device.
2. **Firmware Details**:
- Use `CUSTPROP_type: show_notes` to display firmware versions or upgrade instructions in a modal.
3. **Device Removal**:
- Enable device removal functionality using `CUSTPROP_type: delete_dev`.
---

View File

@@ -12,7 +12,7 @@ NetAlertX supports additional plugins to extend its functionality, each with its
> ![Loaded plugins settings](./img/PLUGINS/enable_plugin.gif)
1. Pick your `🔍 dev scanner` plugin (e.g. `ARPSCAN` or `NMAPDEV`), or import devices into the application with an `📥 importer` plugin. (See **Enabling plugins** below)
2. Pick a `▶️ publisher` plugin, if you want to send notifications. If you don't see a publisher you'd like to use, look at the [📚_publisher_apprise](/front/plugins/_publisher_apprise/) plugin which is a proxy for over 80 notification services.
2. Pick a `▶️ publisher` plugin, if you want to send notifications. If you don't see a publisher you'd like to use, look at the [📚_publisher_apprise](https://docs.netalertx.com/PLUGINS/?h=APPRISE#available-plugins) plugin which is a proxy for over 80 notification services.
3. Setup your [Network topology diagram](./NETWORK_TREE.md)
4. Fine-tune [Notifications](./NOTIFICATIONS.md)
5. Setup [Workflows](./WORKFLOWS.md)

View File

@@ -34,6 +34,7 @@ You need to specify the network interface and the network mask. You can also con
> If the timeout is too short, you may see timeout errors in the log. To prevent the application from hanging due to unresponsive plugins, scans are canceled when they exceed the timeout limit.
>
> To fix this:
>
> - Reduce the subnet size (e.g., change `/16` to `/24`).
> - Increase the timeout (e.g., set `ARPSCAN_RUN_TIMEOUT` to `300` for a 5-minute timeout).
> - Extend the scan interval (e.g., set `ARPSCAN_RUN_SCHD` to `*/10 * * * *` to scan every 10 minutes).

View File

@@ -12,7 +12,7 @@ import ipaddress
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from plugin_helper import Plugin_Objects # noqa: E402 [flake8 lint suppression]
from plugin_helper import Plugin_Objects, parse_scan_subnets # noqa: E402 [flake8 lint suppression]
from logger import mylog, Logger # noqa: E402 [flake8 lint suppression]
from helper import get_setting_value # noqa: E402 [flake8 lint suppression]
from const import logPath # noqa: E402 [flake8 lint suppression]
@@ -34,52 +34,6 @@ LOG_FILE = os.path.join(LOG_PATH, f'script.{pluginName}.log')
RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log')
def parse_scan_subnets(subnets):
"""Extract subnet and interface from SCAN_SUBNETS"""
ranges = []
interfaces = []
for entry in subnets:
# Extract interface
interface_match = re.search(r'--interface=([^\s]+)', entry)
interface = interface_match.group(1) if interface_match else None
# Extract vlan
vlan_match = re.search(r'--vlan=(\d+)', entry)
vlan = vlan_match.group(1) if vlan_match else None
# If VLAN interface exists, use it
if interface and vlan:
vlan_interface = f"{interface}.{vlan}"
if os.path.exists(f"/sys/class/net/{vlan_interface}"):
interface = vlan_interface
mylog('verbose', [
f'[{pluginName}] Using VLAN interface: {interface}'
])
else:
mylog('verbose', [
f'[{pluginName}] VLAN interface {vlan_interface} not found, using {interface}'
])
if interface:
interfaces.append(interface)
# Remove interface/vlan options from subnet definition
subnet = re.sub(r'\s+--interface=[^\s]+', '', entry)
subnet = re.sub(r'\s+--vlan=\d+', '', subnet)
ranges.append(subnet.strip())
mylog('verbose', [f'[{pluginName}] SCAN_SUBNETS value: {subnets}'])
mylog('verbose', [f'[{pluginName}] Parsed subnets: {ranges}'])
mylog('verbose', [f'[{pluginName}] Parsed interfaces: {interfaces}'])
return ranges, interfaces
def get_device_by_ip(ip, all_devices):
"""Get existing device based on IP"""
for device in all_devices:
@@ -100,7 +54,14 @@ def main():
fakeMac = get_setting_value('ICMP_FAKE_MAC')
scan_subnets = get_setting_value("SCAN_SUBNETS")
subnets, interfaces = parse_scan_subnets(scan_subnets)
parsed = parse_scan_subnets(scan_subnets)
subnets = [x.subnet for x in parsed]
interfaces = [
x.resolved_interface
for x in parsed
if x.resolved_interface
]
# Initialize the Plugin obj output file
plugin_objects = Plugin_Objects(RESULT_FILE)

View File

@@ -10,7 +10,7 @@ from functools import reduce
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from plugin_helper import Plugin_Objects # noqa: E402 [flake8 lint suppression]
from plugin_helper import Plugin_Objects, parse_scan_subnets # noqa: E402 [flake8 lint suppression]
from logger import mylog, Logger # noqa: E402 [flake8 lint suppression]
from utils.datetime_utils import timeNowUTC # noqa: E402 [flake8 lint suppression]
from const import logPath # noqa: E402 [flake8 lint suppression]
@@ -38,14 +38,14 @@ def main():
mylog('verbose', [f'[{pluginName}] In script'])
# Retrieve configuration settings
SCAN_SUBNETS = get_setting_value('SCAN_SUBNETS')
scan_subnets = get_setting_value('SCAN_SUBNETS')
mylog('verbose', [f'[{pluginName}] SCAN_SUBNETS value: {SCAN_SUBNETS}'])
parsed = parse_scan_subnets(scan_subnets)
# Extract interfaces from SCAN_SUBNETS
interfaces = ','.join(
entry.split('--interface=')[-1].strip() for entry in SCAN_SUBNETS if '--interface=' in entry
)
interfaces = []
for entry in parsed:
if entry.resolved_interface and entry.resolved_interface not in interfaces:
interfaces.append(entry.resolved_interface)
mylog('verbose', [f'[{pluginName}] Interfaces value: "{interfaces}"'])
@@ -117,27 +117,28 @@ def get_neighbors(interfaces):
results = []
for interface in interfaces.split(","):
for interface in interfaces:
try:
# Ping all IPv6 devices in multicast to trigger NDP
mylog('verbose', [f'[{pluginName}] Pinging on interface: "{interface}"'])
command = f"ping ff02::1%{interface} -c 2".split()
subprocess.run(command)
mylog('verbose', [f'[{pluginName}] Pinging completed: "{interface}"'])
# Check the neighbourhood tables
mylog('verbose', [f'[{pluginName}] Scanning interface: "{interface}"'])
mylog('verbose', [f'[{pluginName}] Pinging completed, now scanning interface: "{interface}"'])
command = f"ip neighbor show nud all dev {interface}".split()
output = subprocess.check_output(command, universal_newlines=True)
output = subprocess.check_output(
command,
universal_newlines=True
)
results += output.split("\n")
mylog('verbose', [f'[{pluginName}] Scanning interface succeded: "{interface}"'])
except subprocess.CalledProcessError as e:
# An error occurred, handle it
error_type = type(e).__name__ # Capture the error type
error_type = type(e).__name__
mylog('verbose', [f'[{pluginName}] Scanning interface failed: "{interface}" ({error_type})'])
return results

View File

@@ -5,15 +5,68 @@ import os
import re
import base64
import json
from dataclasses import dataclass
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
sys.path.append(f"{INSTALL_PATH}/front/plugins")
sys.path.append(f'{INSTALL_PATH}/server')
from logger import mylog # noqa: E402 [flake8 lint suppression]
from logger import mylog, Logger # noqa: E402 [flake8 lint suppression]
from utils.datetime_utils import timeNowUTC # noqa: E402 [flake8 lint suppression]
from const import default_tz, fullConfPath # noqa: E402 [flake8 lint suppression]
from helper import get_setting_value # noqa: E402 [flake8 lint suppression]
# Make sure log level is initialized correctly
Logger(get_setting_value('LOG_LEVEL'))
# -------------------------------------------------------------------------------
@dataclass
class ScanSubnet:
raw: str
subnet: str
interface: str | None = None
vlan: str | None = None
resolved_interface: str | None = None
# -------------------------------------------------------------------------------
def parse_scan_subnets(scan_subnets):
"""Parse SCAN_SUBNETS entries into structured objects."""
results = []
for entry in scan_subnets:
interface_match = re.search(r'--interface=([^\s]+)', entry)
interface = interface_match.group(1) if interface_match else None
vlan_match = re.search(r'--vlan=(\d+)', entry)
vlan = vlan_match.group(1) if vlan_match else None
resolved_interface = interface
if interface and vlan:
vlan_interface = f"{interface}.{vlan}"
if os.path.exists(f"/sys/class/net/{vlan_interface}"):
resolved_interface = vlan_interface
subnet = re.sub(r'\s+--interface=[^\s]+', '', entry)
subnet = re.sub(r'\s+--vlan=\d+', '', subnet)
results.append(
ScanSubnet(
raw=entry,
subnet=subnet.strip(),
interface=interface,
vlan=vlan,
resolved_interface=resolved_interface,
)
)
return results
# -------------------------------------------------------------------------------