From 89840906a0a500e86e57c733478e496797aba166 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Sun, 1 Dec 2024 12:13:56 +1100 Subject: [PATCH] =?UTF-8?q?ICMP=20plugin=20=F0=9F=86=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- front/plugins/icmp_scan/README.md | 7 + front/plugins/icmp_scan/config.json | 399 ++++++++++++++++++++++++++++ front/plugins/icmp_scan/icmp.py | 156 +++++++++++ front/plugins/ipneigh/README.md | 2 +- front/plugins/ipneigh/config.json | 2 +- server/helper.py | 2 +- 6 files changed, 565 insertions(+), 3 deletions(-) create mode 100755 front/plugins/icmp_scan/README.md create mode 100755 front/plugins/icmp_scan/config.json create mode 100755 front/plugins/icmp_scan/icmp.py diff --git a/front/plugins/icmp_scan/README.md b/front/plugins/icmp_scan/README.md new file mode 100755 index 000000000..65c21c6ad --- /dev/null +++ b/front/plugins/icmp_scan/README.md @@ -0,0 +1,7 @@ +## Overview + +Plugin for pinging existing devices via the [ping](https://linux.die.net/man/8/ping) network utility. The devices have to be accessible from the container. You can use this plugin with other suplementing plugins as described in the [subnets docs](https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md). + +### Usage + +- Check the Settings page for details. diff --git a/front/plugins/icmp_scan/config.json b/front/plugins/icmp_scan/config.json new file mode 100755 index 000000000..f4215c800 --- /dev/null +++ b/front/plugins/icmp_scan/config.json @@ -0,0 +1,399 @@ +{ + "code_name": "icmp_scan", + "unique_prefix": "ICMP", + "plugin_type": "other", + "execution_order" : "Layer_4", + "enabled": true, + "data_source": "script", + "show_ui": true, + "data_filters": [ + { + "compare_column": "Object_PrimaryID", + "compare_operator": "==", + "compare_field_id": "txtMacFilter", + "compare_js_template": "'{value}'.toString()", + "compare_use_quotes": true + } + ], + "localized": ["display_name", "description", "icon"], + "display_name": [ + { + "language_code": "en_us", + "string": "ICMP (Status check)" + } + ], + "icon": [ + { + "language_code": "en_us", + "string": "" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "A plugin to check the status of the device." + } + ], + "params": [ + { + "name": "ips", + "type": "sql", + "value": "SELECT devLastIP from DEVICES order by devMac", + "timeoutMultiplier": true + } + ], + "settings": [ + { + "function": "RUN", + "events": ["run"], + "type": { + "dataType": "string", + "elements": [ + { "elementType": "select", "elementOptions": [], "transformers": [] } + ] + }, + "default_value": "disabled", + "options": [ + "disabled", + "on_new_device", + "once", + "schedule", + "always_after_scan" + ], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "When to run" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Enable a regular scan of your devices with PING to determine their status. If you select schedule the interval from below is applied, for which the recommendation is to align all scan schedules otherwise false down reports will be generated." + } + ] + }, + { + "function": "CMD", + "type": { + "dataType": "string", + "elements": [ + { + "elementType": "input", + "elementOptions": [{ "readonly": "true" }], + "transformers": [] + } + ] + }, + "default_value": "python3 /app/front/plugins/icmp_scan/icmp.py", + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Command" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Command to run. This can not be changed" + } + ] + }, + { + "function": "ARGS", + "type": { + "dataType": "string", + "elements": [ + { + "elementType": "input", + "elementOptions": [], + "transformers": [] + } + ] + }, + "default_value": "-i 0.5 -c 3 -W 4 -w 5", + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Command arguments" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Arguments passed to the ping command. Please be careful modifying these." + } + ] + }, + { + "function": "RUN_SCHD", + "type": { + "dataType": "string", + "elements": [ + { "elementType": "input", "elementOptions": [], "transformers": [] } + ] + }, + "default_value": "*/5 * * * *", + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Schedule" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Only enabled if you select schedule in the ICMP_RUN setting. Make sure you enter the schedule in the correct cron-like format (e.g. validate at crontab.guru). For example entering 0 4 * * * will run the scan after 4 am in the TIMEZONE you set above. Will be run NEXT time the time passes." + } + ] + }, + { + "function": "RUN_TIMEOUT", + "type": { + "dataType": "integer", + "elements": [ + { + "elementType": "input", + "elementOptions": [{ "type": "number" }], + "transformers": [] + } + ] + }, + "default_value": 10, + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Run timeout" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted." + } + ] + } + ], + "database_column_definitions": [ + { + "column": "Index", + "css_classes": "col-sm-2", + "show": true, + "type": "none", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "Index" + } + ] + }, + { + "column": "Object_PrimaryID", + "mapped_to_column": "cur_MAC", + "css_classes": "col-sm-2", + "show": true, + "type": "device_name_mac", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "MAC" + } + ] + }, + { + "column": "Object_SecondaryID", + "mapped_to_column": "cur_IP", + "css_classes": "col-sm-2", + "show": true, + "type": "device_ip", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "IP" + } + ] + }, + { + "column": "Watched_Value1", + "mapped_to_column": "cur_Name", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "Name" + } + ] + }, + { + "column": "Watched_Value2", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "Output" + } + ] + }, + { + "column": "Watched_Value3", + "css_classes": "col-sm-2", + "show": false, + "type": "label", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "N/A" + } + ] + }, + { + "column": "Watched_Value4", + "css_classes": "col-sm-2", + "show": false, + "type": "label", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "N/A" + } + ] + }, + { + "column": "Dummy", + "mapped_to_column": "cur_ScanMethod", + "mapped_to_column_data": { + "value": "ICMP" + }, + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "Scan method" + } + ] + }, + { + "column": "DateTimeCreated", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "Created" + } + ] + }, + { + "column": "DateTimeChanged", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value": "", + "options": [], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "Changed" + } + ] + }, + { + "column": "Status", + "css_classes": "col-sm-1", + "show": true, + "type": "replace", + "default_value": "", + "options": [ + { + "equals": "watched-not-changed", + "replacement": "
" + }, + { + "equals": "watched-changed", + "replacement": "
" + }, + { + "equals": "new", + "replacement": "
" + }, + { + "equals": "missing-in-last-scan", + "replacement": "
" + } + ], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "Status" + } + ] + } + ] +} diff --git a/front/plugins/icmp_scan/icmp.py b/front/plugins/icmp_scan/icmp.py new file mode 100755 index 000000000..7eb72881f --- /dev/null +++ b/front/plugins/icmp_scan/icmp.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python +# test script by running: +# tbc + +import os +import pathlib +import argparse +import subprocess +import sys +import hashlib +import csv +import sqlite3 +import re +from io import StringIO +from datetime import datetime + +# Register NetAlertX directories +INSTALL_PATH="/app" +sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"]) + +from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64 +from logger import mylog, append_line_to_file +from helper import timeNowTZ, get_setting_value +from const import logPath, applicationPath, fullDbPath +from database import DB +from device import Device_obj +import conf +from pytz import timezone + +# Make sure the TIMEZONE for logging is correct +conf.tz = timezone(get_setting_value('TIMEZONE')) + +CUR_PATH = str(pathlib.Path(__file__).parent.resolve()) +LOG_FILE = os.path.join(CUR_PATH, 'script.log') +RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log') + +pluginName = 'ICMP' + +def main(): + + mylog('verbose', [f'[{pluginName}] In script']) + + + timeout = get_setting_value('ICMP_RUN_TIMEOUT') + args = get_setting_value('ICMP_ARGS') + + # Create a database connection + db = DB() # instance of class DB + db.open() + + # Initialize the Plugin obj output file + plugin_objects = Plugin_Objects(RESULT_FILE) + + # Create a Device_obj instance + device_handler = Device_obj(db) + + # Retrieve devices + all_devices = device_handler.getAll() + + mylog('verbose', [f'[{pluginName}] Devices to PING: {len(all_devices)}']) + + for device in all_devices: + is_online, output = execute_scan(device['devLastIP'], timeout, args) + + mylog('verbose', [f'[{pluginName}] ip: "{device['devLastIP']}" is_online: "{is_online}"']) + + if is_online: + plugin_objects.add_object( + # "MAC", "IP", "Name", "Output" + primaryId = device['devMac'], + secondaryId = device['devLastIP'], + watched1 = device['devName'], + watched2 = output.replace('\n',''), + watched3 = '', + watched4 = '', + extra = '', + foreignKey = device['devMac']) + + plugin_objects.write_result_file() + + + mylog('verbose', [f'[{pluginName}] Script finished']) + + return 0 + +#=============================================================================== +# Execute scan +#=============================================================================== +def execute_scan (ip, timeout, args): + """ + Execute the ICMP command on IP. + """ + + icmp_args = ['ping'] + args.split() + [ip] + + # Execute command + output = "" + + try: + # try runnning a subprocess with a forced (timeout) in case the subprocess hangs + output = subprocess.check_output (icmp_args, universal_newlines=True, stderr=subprocess.STDOUT, timeout=(timeout), text=True) + + mylog('verbose', [f'[{pluginName}] DEBUG OUTPUT : {output}']) + + # Parse output using case-insensitive regular expressions + #Synology-NAS:/# ping -i 0.5 -c 3 -W 8 -w 9 192.168.1.82 + # PING 192.168.1.82 (192.168.1.82): 56 data bytes + # 64 bytes from 192.168.1.82: seq=0 ttl=64 time=0.080 ms + # 64 bytes from 192.168.1.82: seq=1 ttl=64 time=0.081 ms + # 64 bytes from 192.168.1.82: seq=2 ttl=64 time=0.089 ms + + # --- 192.168.1.82 ping statistics --- + # 3 packets transmitted, 3 packets received, 0% packet loss + # round-trip min/avg/max = 0.080/0.083/0.089 ms + # Synology-NAS:/# ping -i 0.5 -c 3 -W 8 -w 9 192.168.1.82a + # ping: bad address '192.168.1.82a' + # Synology-NAS:/# ping -i 0.5 -c 3 -W 8 -w 9 192.168.1.92 + # PING 192.168.1.92 (192.168.1.92): 56 data bytes + + # --- 192.168.1.92 ping statistics --- + # 3 packets transmitted, 0 packets received, 100% packet loss + + # TODO: parse output and return True if online, False if Offline (100% packet loss, bad address) + is_online = True + + # Check for 0% packet loss in the output + if re.search(r"0% packet loss", output, re.IGNORECASE): + is_online = True + elif re.search(r"bad address", output, re.IGNORECASE): + is_online = False + elif re.search(r"100% packet loss", output, re.IGNORECASE): + is_online = False + + return is_online, output + + except subprocess.CalledProcessError as e: + # An error occurred, handle it + mylog('verbose', [f'[{pluginName}] ⚠ ERROR - check logs']) + mylog('verbose', [f'[{pluginName}]', e.output]) + + return False, output + + except subprocess.TimeoutExpired as timeErr: + mylog('verbose', [f'[{pluginName}] TIMEOUT - the process forcefully terminated as timeout reached']) + return False, output + + return False, output + + + + +#=============================================================================== +# BEGIN +#=============================================================================== +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/front/plugins/ipneigh/README.md b/front/plugins/ipneigh/README.md index ac205b7f7..528c5246b 100755 --- a/front/plugins/ipneigh/README.md +++ b/front/plugins/ipneigh/README.md @@ -13,7 +13,7 @@ To set up the plugin correctly, make sure to add in the plugin settings the name ### Usage -- Head to **Settings** > **IP Neigh** to adjust teh settings +- Head to **Settings** > **IP Neigh** to adjust the settings - Interfaces are extracted from the `SCAN_SUBNETS` setting (make sure you add interfaces in the prescribed format, e.g. `192.168.1.0/24 --interface=eth1`) ### Notes diff --git a/front/plugins/ipneigh/config.json b/front/plugins/ipneigh/config.json index 94c379b80..207dc6ca2 100755 --- a/front/plugins/ipneigh/config.json +++ b/front/plugins/ipneigh/config.json @@ -313,7 +313,7 @@ "column": "Dummy", "mapped_to_column": "cur_ScanMethod", "mapped_to_column_data": { - "value": "ip neighbor" + "value": "IPNEIGH" }, "css_classes": "col-sm-2", "show": true, diff --git a/server/helper.py b/server/helper.py index 5afece8d7..8c58c492c 100755 --- a/server/helper.py +++ b/server/helper.py @@ -372,7 +372,7 @@ def setting_value_to_python_type(set_type, set_value): transformers = element_with_input_value.get('transformers', []) # Convert value based on dataType and elementType - if dataType == 'string' and elementType in ['input', 'select']: + if dataType == 'string' and elementType in ['input', 'select', 'textarea']: value = reverseTransformers(str(set_value), transformers) elif dataType == 'integer' and (elementType == 'input' or elementType == 'select'):