mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-06-02 04:58:53 -04:00
BE+DOCS: ICMP, IPNEIGH handling VLANS #1662
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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`.
|
||||
|
||||
---
|
||||
|
||||
@@ -12,7 +12,7 @@ NetAlertX supports additional plugins to extend its functionality, each with its
|
||||
> 
|
||||
|
||||
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)
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user