diff --git a/net-mgmt/opnarplog/Makefile b/net-mgmt/opnarplog/Makefile new file mode 100644 index 0000000000..f722edb343 --- /dev/null +++ b/net-mgmt/opnarplog/Makefile @@ -0,0 +1,7 @@ +PLUGIN_NAME= OPNarplog +PLUGIN_VERSION= 0.1 +PLUGIN_REVISION= 1 +PLUGIN_COMMENT= arpwatch alternative for OPNsense +PLUGIN_MAINTAINER= github.com/mr-manuel + +.include "../../Mk/plugins.mk" diff --git a/net-mgmt/opnarplog/pkg-descr b/net-mgmt/opnarplog/pkg-descr new file mode 100644 index 0000000000..033a7a864e --- /dev/null +++ b/net-mgmt/opnarplog/pkg-descr @@ -0,0 +1,11 @@ +OPNarplog is a versatile network monitoring tool that tracks and logs changes in ARP (Address Resolution Protocol) and NDP (Neighbor Discovery Protocol) tables, capturing key details such as IPv4 and IPv6 addresses, hostnames, MAC addresses, and interface changes. The plugin is configurable, allowing administrators to selectively enable or disable tracking for different modifications based on specific needs, whether it’s for IP address shifts, hostname updates, new MAC detections, or interface changes. This customization and comprehensive logging ensure precise, up-to-date network visibility, ideal for securing networks and managing dynamic device inventories effectively. + +WWW: https://github.com/mr-manuel/opnsense_plugins + + +Plugin Changelog +================ + +0.1 + +* Initial release diff --git a/net-mgmt/opnarplog/src/bin/opnarplog.py b/net-mgmt/opnarplog/src/bin/opnarplog.py new file mode 100755 index 0000000000..dbf82e5aab --- /dev/null +++ b/net-mgmt/opnarplog/src/bin/opnarplog.py @@ -0,0 +1,424 @@ +#!/usr/local/bin/python3 + +""" + +Copyright (C) 2024 github.com/mr-manuel +All rights reserved. + +""" + + +import os +import subprocess +import time +import shutil +import logging +import configparser +import sqlite3 +import requests +import csv +import gc +from io import StringIO +from datetime import datetime + + +CONFIG_FILE = "/usr/local/etc/opnarplog.conf" +DB_FILE = "/var/db/opnarplog/opnarplog.db" +LOG_FILE = "/var/log/opnarplog.log" +MAC_VENDOR_FILE = "/var/db/opnarplog/oui.csv" + +# Create directories +os.makedirs(os.path.dirname(DB_FILE), exist_ok=True) + +# Set up logging +logging.basicConfig(filename=LOG_FILE, level=logging.INFO, format="%(asctime)s: %(message)s") + +# Create database +conn = sqlite3.connect(DB_FILE) +cursor = conn.cursor() +cursor.execute( + """ + CREATE TABLE IF NOT EXISTS arp_entries ( + mac TEXT, + ipv4 TEXT, + ipv6 TEXT, + interface TEXT, + hostname TEXT, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """ +) + + +# Read configuration file +config = configparser.ConfigParser() +with open(CONFIG_FILE) as f: + config.read_file(StringIO("[default]\n" + f.read())) + +protocols = config["default"].get("protocols") +interfaces = config["default"].get("interfaces").replace("_", ".").split(" ") if config["default"].get("interfaces") is not None else [] +suppress_mac = config["default"].get("suppress_mac").split(" ") if config["default"].get("suppress_mac") is not None else [] +log_new_entries = config["default"].getboolean("log_new_entries") +log_mac_changes = config["default"].getboolean("log_mac_changes") +log_ipv4_changes = config["default"].getboolean("log_ipv4_changes") +log_ipv6_changes = config["default"].getboolean("log_ipv6_changes") +log_hostname_changes = config["default"].getboolean("log_hostname_changes") +log_interface_changes = config["default"].getboolean("log_interface_changes") +retention_days = config["default"].getint("retention_days") + +# add firewall MAC addresses to suppress_mac +device_mac_addresses = ( + subprocess.run( + "ifconfig -a | grep ether | awk '{print $2}' | sort | uniq", + shell=True, + capture_output=True, + text=True, + ) + .stdout.strip() + .split("\n") +) + +suppress_mac.extend(device_mac_addresses) + + +def main(): + while True: + time_now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # create a dictionary with the current entries + current_entries_dict = {} + + # Check ARP table + if protocols == "all" or protocols == "ipv4_only": + # $2 = IPv4, $4 = MAC, $6 = Interface + filter = ' | grep -v \'incomplete\' | awk \'{gsub(/[()]/, "", $2); print $2 " " $4 " " $6}\'' + if len(interfaces) == 0: + result = subprocess.run( + "arp -an" + filter, + shell=True, + capture_output=True, + text=True, + ) + current_ipv4_entries = result.stdout.strip().split("\n") + else: + current_ipv4_entries = [] + for interface in interfaces: + result = subprocess.run( + "arp -i " + interface + " -an" + filter, + shell=True, + capture_output=True, + text=True, + ) + current_ipv4_entries.extend(result.stdout.strip().split("\n")) + + # Filter out empty strings + current_ipv4_entries = [entries for entries in current_ipv4_entries if entries] + + # populate the current_entries_dict + if len(current_ipv4_entries) > 0: + for entry in current_ipv4_entries: + ipv4, mac, interface = entry.split() + current_entries_dict[mac] = {"ipv4": ipv4, "ipv6": "", "interface": interface} + + # Check NDP table + if protocols == "all" or protocols == "ipv6_only": + # $1 = IPv6, $2 = MAC, $3 = Interface + filter = ' | grep -v "incomplete" | grep -v "Neighbor" | awk \'{gsub(/%.*/, "", $1); print $1 " " $2 " " $3}\'' + if len(interfaces) == 0: + result = subprocess.run( + "ndp -an" + filter, + shell=True, + capture_output=True, + text=True, + ) + current_ipv6_entries = result.stdout.strip().split("\n") + else: + current_ipv6_entries = [] + for interface in interfaces: + result = subprocess.run( + "ndp -an | grep " + interface + filter, + shell=True, + capture_output=True, + text=True, + ) + current_ipv6_entries.extend(result.stdout.strip().split("\n")) + + # Filter out empty strings + current_ipv6_entries = [entries for entries in current_ipv6_entries if entries] + + # populate the current_entries_dict + if len(current_ipv6_entries) > 0: + for entry in current_ipv6_entries: + ipv6, mac, interface = entry.split() + if mac in current_entries_dict: + current_entries_dict[mac]["ipv6"] = ipv6 + else: + current_entries_dict[mac] = {"ipv4": "", "ipv6": ipv6, "interface": interface} + + # Delete expired ARP entries + cursor.execute(f"DELETE FROM arp_entries WHERE timestamp < datetime('now', '-{retention_days} day')") + + # Select all current ARP entries + cursor.execute("SELECT mac, ipv4, ipv6, interface, hostname, timestamp FROM arp_entries") + saved_ipv4_entries = set(cursor.fetchall()) + + # Save entries in a dictionary with MAC as the key + saved_entries_dict_by_mac = { + entry[0]: {"ipv4": entry[1], "ipv6": entry[2], "interface": entry[3], "hostname": entry[4], "timestamp": entry[5]} for entry in saved_ipv4_entries + } + + # Save entries in a dictionary with MAC as the key + saved_ipv4_entries_dict_by_ip = {entry[1]: {"mac": entry[0], "interface": entry[3], "hostname": entry[4], "timestamp": entry[5]} for entry in saved_ipv4_entries} + + # Save entries in a dictionary with MAC as the key + saved_ipv6_entries_dict_by_ip = {entry[2]: {"mac": entry[0], "interface": entry[3], "hostname": entry[4], "timestamp": entry[5]} for entry in saved_ipv4_entries} + + # check if current_ipv4_entries_dict is empty + if len(current_entries_dict) > 0: + for mac, entry in current_entries_dict.items(): + + ipv4 = entry["ipv4"] + ipv6 = entry["ipv6"] + interface = entry["interface"] + + # Get hostname from IPv4 + hostname = ( + subprocess.run( + "host " + ipv4 + " | grep -v 'not found' | awk '{print $5}' | sed 's/\\.$//'", + shell=True, + capture_output=True, + text=True, + ) + .stdout.strip() + .split("\n") + ) + # Get hostname from IPv6 + hostname += ( + subprocess.run( + "host " + ipv6 + " | grep -v 'not found' | awk '{print $5}' | sed 's/\\.$//'", + shell=True, + capture_output=True, + text=True, + ) + .stdout.strip() + .split("\n") + ) + # Remove duplicates and sort the hostname list + hostname = sorted(set(hostname)) + # Filter out empty strings + hostname = [name for name in hostname if name] + + if not hostname: + hostname = "" + else: + # split by newline, sort and join with ; + hostname = "; ".join(sorted(hostname)) + + # Skip suppressed MAC addresses + if mac in suppress_mac: + continue + + # Check if the MAC entry already exists + if mac in saved_entries_dict_by_mac: + + changes = False + changes_message = [] + + # Check if the IPv4 address has changed + if (protocols == "all" or protocols == "ipv4_only") and log_ipv4_changes and ipv4 != saved_entries_dict_by_mac[mac]["ipv4"]: + changes = True + changes_message.append(f"OLD IPv4: {saved_entries_dict_by_mac[mac]['ipv4']}") + + # Check if the IPv6 address has changed + if (protocols == "all" or protocols == "ipv6_only") and log_ipv6_changes and ipv6 != saved_entries_dict_by_mac[mac]["ipv6"]: + changes = True + changes_message.append(f"OLD IPv6: {saved_entries_dict_by_mac[mac]['ipv6']}") + + # Check if the hostname has changed + if log_hostname_changes and hostname != saved_entries_dict_by_mac[mac]["hostname"]: + changes = True + changes_message.append(f"OLD Hostname: {saved_entries_dict_by_mac[mac]['hostname']}") + + # Check if the interface has changed + if log_interface_changes and interface != saved_entries_dict_by_mac[mac]["interface"]: + changes = True + changes_message.append(f"OLD Interface: {saved_entries_dict_by_mac[mac]['interface']}") + + # Update the entry + if changes: + cursor.execute( + "UPDATE arp_entries SET ipv4 = ?, ipv6 = ?, interface = ?, hostname = ?, timestamp = ? WHERE mac = ?", (ipv4, ipv6, interface, hostname, time_now, mac) + ) + logging.info( + "ARP - Changes detected! " + + (f"IPv4: {ipv4} | " if protocols == "all" or protocols == "ipv4_only" else "") + + (f"IPv6: {ipv6} | " if protocols == "all" or protocols == "ipv6_only" else "") + + f"Hostname: {hostname} | MAC: {mac} | Vendor: {mac_vendor_check(mac)} | Interface: {interface}" + + (" | " + " | ".join(changes_message) if len(changes_message) > 0 else "") + ) + # Update the timestamp + else: + cursor.execute("UPDATE arp_entries SET timestamp = ? WHERE ipv4 = ?", (time_now, ipv4)) + + # Check if the IPv4 entry already exists + # This check allows to see, if an address is spoofed or multiple devices have the same IP + elif ipv4 != "" and ipv4 in saved_ipv4_entries_dict_by_ip: + + changes = False + changes_message = [] + + # Check if the MAC address has changed + if log_mac_changes and mac != saved_ipv4_entries_dict_by_ip[ipv4]["mac"]: + changes = True + changes_message.append( + f"OLD MAC: {saved_ipv4_entries_dict_by_ip[ipv4]['mac']} | OLD vendor: {mac_vendor_check(saved_ipv4_entries_dict_by_ip[ipv4]['mac'])}" + ) + + # Check if the hostname has changed + if log_hostname_changes and hostname != saved_ipv4_entries_dict_by_ip[ipv4]["hostname"]: + changes = True + changes_message.append(f"OLD Hostname: {saved_ipv4_entries_dict_by_ip[ipv4]['hostname']}") + + # Check if the interface has changed + if log_interface_changes and interface != saved_ipv4_entries_dict_by_ip[ipv4]["interface"]: + changes = True + changes_message.append(f"OLD Interface: {saved_ipv4_entries_dict_by_ip[ipv4]['interface']}") + + # Update the entry + if changes: + cursor.execute( + "UPDATE arp_entries SET ipv4 = ?, ipv6 = ?, interface = ?, hostname = ?, timestamp = ? WHERE mac = ?", (ipv4, ipv6, interface, hostname, time_now, mac) + ) + logging.info( + "ARP - Changes detected! " + + (f"IPv4: {ipv4} | " if protocols == "all" or protocols == "ipv4_only" else "") + + (f"IPv6: {ipv6} | " if protocols == "all" or protocols == "ipv6_only" else "") + + f"Hostname: {hostname} | MAC: {mac} | Vendor: {mac_vendor_check(mac)} | Interface: {interface}" + + (" | " + " | ".join(changes_message) if len(changes_message) > 0 else "") + ) + # Update the timestamp + else: + cursor.execute("UPDATE arp_entries SET timestamp = ? WHERE ipv4 = ?", (time_now, ipv4)) + + # Check if the IPv6 entry already exists + # This check allows to see, if an address is spoofed or multiple devices have the same IP + elif ipv6 != "" and ipv6 in saved_ipv6_entries_dict_by_ip: + + changes = False + changes_message = [] + + # Check if the MAC address has changed + if log_mac_changes and mac != saved_ipv6_entries_dict_by_ip[ipv6]["mac"]: + changes = True + changes_message.append( + f"OLD MAC: {saved_ipv6_entries_dict_by_ip[ipv6]['mac']} | OLD vendor: {mac_vendor_check(saved_ipv6_entries_dict_by_ip[ipv6]['mac'])}" + ) + + # Check if the hostname has changed + if log_hostname_changes and hostname != saved_ipv6_entries_dict_by_ip[ipv6]["hostname"]: + changes = True + changes_message.append(f"OLD Hostname: {saved_ipv6_entries_dict_by_ip[ipv6]['hostname']}") + + # Check if the interface has changed + if log_interface_changes and interface != saved_ipv6_entries_dict_by_ip[ipv6]["interface"]: + changes = True + changes_message.append(f"OLD Interface: {saved_ipv6_entries_dict_by_ip[ipv6]['interface']}") + + # Update the entry + if changes: + cursor.execute( + "UPDATE arp_entries SET ipv4 = ?, ipv6 = ?, interface = ?, hostname = ?, timestamp = ? WHERE mac = ?", (ipv4, ipv6, interface, hostname, time_now, mac) + ) + logging.info( + "ARP - Changes detected! " + + (f"IPv4: {ipv4} | " if protocols == "all" or protocols == "ipv4_only" else "") + + (f"IPv6: {ipv6} | " if protocols == "all" or protocols == "ipv6_only" else "") + + f"Hostname: {hostname} | MAC: {mac} | Vendor: {mac_vendor_check(mac)} | Interface: {interface}" + + (" | " + " | ".join(changes_message) if len(changes_message) > 0 else "") + ) + # Update the timestamp + else: + cursor.execute("UPDATE arp_entries SET timestamp = ? WHERE ipv6 = ?", (time_now, ipv6)) + + elif log_new_entries: + logging.info( + "ARP - New entry detected! " + + (f"IPv4: {ipv4} | " if protocols == "all" or protocols == "ipv4_only" else "") + + (f"IPv6: {ipv6} | " if protocols == "all" or protocols == "ipv6_only" else "") + + f"Hostname: {hostname} | MAC: {mac} | Vendor: {mac_vendor_check(mac)} | Interface: {interface}" + ) + cursor.execute( + "INSERT INTO arp_entries (mac, ipv4, ipv6, interface, hostname, timestamp) VALUES (?, ?, ?, ?, ?, ?)", (mac, ipv4, ipv6, interface, hostname, time_now) + ) + + conn.commit() + + # check if the log need to be rotated + rotate_log() + + # Wait before next check + time.sleep(5) + + +def rotate_log(): + # Rotate log file if necessary + if os.path.exists(LOG_FILE) and os.path.getsize(LOG_FILE) > 102400: + if os.path.exists(LOG_FILE + ".1"): + os.remove(LOG_FILE + ".1") + shutil.move(LOG_FILE, LOG_FILE + ".1") + open(LOG_FILE, "a").close() + logging.info("Rotated log file") + + +def mac_vendor_list_download(): + # Download MAC address vendor list if older than 365 days + if not os.path.exists(MAC_VENDOR_FILE) or (time.time() - os.path.getmtime(MAC_VENDOR_FILE)) > 365 * 86400: + url = "https://maclookup.app/downloads/csv-database/get-db" + response = requests.get(url) + if response.status_code == 200: + with open(MAC_VENDOR_FILE, "wb") as f: + f.write(response.content) + logging.info("Downloaded MAC vendor list") + else: + logging.error("Failed to download MAC vendor list") + + +def mac_vendor_check(mac): + # Load MAC vendor list into a dictionary + mac_vendor = {} + with open(MAC_VENDOR_FILE, "r") as f: + reader = csv.reader(f) + for row in reader: + if len(row) >= 2: + mac_prefix = row[0].strip().replace(":", "").lower() + vendor = row[1].strip() + mac_vendor[mac_prefix] = vendor + + # Search for the vendor by progressively increasing the length of the MAC prefix + for length in range(6, 9): + matches = [vendor for prefix, vendor in mac_vendor.items() if mac.replace(":", "").lower().startswith(prefix[:length])] + if len(matches) == 1: + # Release memory + del mac_vendor + gc.collect() + return matches[0] + + # Release memory + del mac_vendor + gc.collect() + return "" + + +if __name__ == "__main__": + # check if the log need to be rotated + rotate_log() + + logging.info("*** Starting OPNarplog ***") + + logging.info(f"protocols: {protocols}") + logging.info(f"interfaces: {interfaces}") + logging.info(f"suppress_mac: {suppress_mac}") + + mac_vendor_list_download() + + main() diff --git a/net-mgmt/opnarplog/src/etc/inc/plugins.inc.d/opnarplog.inc b/net-mgmt/opnarplog/src/etc/inc/plugins.inc.d/opnarplog.inc new file mode 100644 index 0000000000..8166e92af3 --- /dev/null +++ b/net-mgmt/opnarplog/src/etc/inc/plugins.inc.d/opnarplog.inc @@ -0,0 +1,29 @@ + gettext('OPNarplog Daemon'), + 'configd' => array( + 'restart' => array('opnarplog restart'), + 'start' => array('opnarplog start'), + 'stop' => array('opnarplog stop'), + ), + 'name' => 'opnarplog', + 'pidfile' => '/var/run/opnarplog.pid', + ); + } + + return $services; +} diff --git a/net-mgmt/opnarplog/src/etc/rc.d/opnarplog b/net-mgmt/opnarplog/src/etc/rc.d/opnarplog new file mode 100755 index 0000000000..9d50ea5597 --- /dev/null +++ b/net-mgmt/opnarplog/src/etc/rc.d/opnarplog @@ -0,0 +1,23 @@ +#!/bin/sh +# +# $FreeBSD$ +# +# PROVIDE: opnarplog +# REQUIRE: SERVERS +# KEYWORD: shutdown +# + +. /etc/rc.subr + +name=opnarplog + +rcvar=opnarplog_enable +pidfile=/var/run/${name}.pid +command=/usr/sbin/daemon +command_args="-f -P ${pidfile} /usr/local/bin/python3 /usr/local/bin/opnarplog.py" + +load_rc_config ${name} + +: ${opnarplog_enable="NO"} + +run_rc_command $1 diff --git a/net-mgmt/opnarplog/src/opnsense/mvc/app/controllers/OPNsense/opnarplog/Api/GeneralController.php b/net-mgmt/opnarplog/src/opnsense/mvc/app/controllers/OPNsense/opnarplog/Api/GeneralController.php new file mode 100644 index 0000000000..72de67d0c2 --- /dev/null +++ b/net-mgmt/opnarplog/src/opnsense/mvc/app/controllers/OPNsense/opnarplog/Api/GeneralController.php @@ -0,0 +1,16 @@ +configdRun("opnarplog resetdb"); + return array("response" => $response); + } + +} diff --git a/net-mgmt/opnarplog/src/opnsense/mvc/app/controllers/OPNsense/opnarplog/GeneralController.php b/net-mgmt/opnarplog/src/opnsense/mvc/app/controllers/OPNsense/opnarplog/GeneralController.php new file mode 100644 index 0000000000..ee7e881796 --- /dev/null +++ b/net-mgmt/opnarplog/src/opnsense/mvc/app/controllers/OPNsense/opnarplog/GeneralController.php @@ -0,0 +1,17 @@ +view->generalForm = $this->getForm('general'); + $this->view->pick('OPNsense/Opnarplog/general'); + } +} diff --git a/net-mgmt/opnarplog/src/opnsense/mvc/app/controllers/OPNsense/opnarplog/forms/general.xml b/net-mgmt/opnarplog/src/opnsense/mvc/app/controllers/OPNsense/opnarplog/forms/general.xml new file mode 100644 index 0000000000..cf972832a8 --- /dev/null +++ b/net-mgmt/opnarplog/src/opnsense/mvc/app/controllers/OPNsense/opnarplog/forms/general.xml @@ -0,0 +1,75 @@ +
+ + general.enabled + + checkbox + Enable or disable OPNarplog. This will not automatically enable any mail notifications, see notes below + + + general.protocols + + dropdown + Select the protocols you want to watch + + + general.interfaces + + select_multiple + Leave empty to track on all interfaces or add the interfaces you want to track + All (default) + + + general.suppress_mac + + select_multiple + + true + + + The firewall MAC addresses are already excluded]]> + + + + general.log_new_entries + + checkbox + Enable or disable the logging of new entries + + + general.log_mac_changes + + checkbox + Enable or disable the logging of MAC address changes (helps to find duplicates or spoofing) + + + general.log_ipv4_changes + + checkbox + Enable or disable the logging of IPv4 address changes + + + general.log_ipv6_changes + + checkbox + Enable or disable the logging of IPv6 address changes + + + general.log_hostname_changes + + checkbox + Enable or disable the logging of hostname changes + + + general.log_interface_changes + + checkbox + Enable or disable the logging of interface changes + + + general.retention_days + + text + true + After how many days should an inactive MAC address be deleted from the database? After this period the device is recognized as new + +
diff --git a/net-mgmt/opnarplog/src/opnsense/mvc/app/models/OPNsense/opnarplog/ACL/ACL.xml b/net-mgmt/opnarplog/src/opnsense/mvc/app/models/OPNsense/opnarplog/ACL/ACL.xml new file mode 100644 index 0000000000..6ec20a5edd --- /dev/null +++ b/net-mgmt/opnarplog/src/opnsense/mvc/app/models/OPNsense/opnarplog/ACL/ACL.xml @@ -0,0 +1,9 @@ + + + Services: OPNarplog + + ui/opnarplog/* + api/opnarplog/* + + + diff --git a/net-mgmt/opnarplog/src/opnsense/mvc/app/models/OPNsense/opnarplog/FieldTypes/OpnarplogInterfaceField.php b/net-mgmt/opnarplog/src/opnsense/mvc/app/models/OPNsense/opnarplog/FieldTypes/OpnarplogInterfaceField.php new file mode 100644 index 0000000000..2d3931adec --- /dev/null +++ b/net-mgmt/opnarplog/src/opnsense/mvc/app/models/OPNsense/opnarplog/FieldTypes/OpnarplogInterfaceField.php @@ -0,0 +1,57 @@ +object(); + + foreach ($config->interfaces->children() as $key => $node) { + if (empty($node->virtual)) { + $this->internalOptionList[str_replace(".", "_", $node->if)] = !empty($node->descr) ? (string)$node->descr : strtoupper($key); + } + } + + natcasesort($this->internalOptionList); + } +} diff --git a/net-mgmt/opnarplog/src/opnsense/mvc/app/models/OPNsense/opnarplog/General.php b/net-mgmt/opnarplog/src/opnsense/mvc/app/models/OPNsense/opnarplog/General.php new file mode 100644 index 0000000000..7abf6f1f95 --- /dev/null +++ b/net-mgmt/opnarplog/src/opnsense/mvc/app/models/OPNsense/opnarplog/General.php @@ -0,0 +1,14 @@ + + //OPNsense/opnarplog/general + OPNarplog general configuration + 0.0.1 + + + 0 + Y + + + IPv4 and IPv6 + Y + + IPv4 and IPv6 + IPv4 only + IPv6 only + + + + N + Y + + + N + Y + + + 1 + + + 1 + + + 0 + + + 0 + + + 0 + + + 0 + + + 30 + Y + true + 1 + 365 + Retention needs to be an integer value between 1 and 365 + + + diff --git a/net-mgmt/opnarplog/src/opnsense/mvc/app/models/OPNsense/opnarplog/Menu/Menu.xml b/net-mgmt/opnarplog/src/opnsense/mvc/app/models/OPNsense/opnarplog/Menu/Menu.xml new file mode 100644 index 0000000000..f3876a6099 --- /dev/null +++ b/net-mgmt/opnarplog/src/opnsense/mvc/app/models/OPNsense/opnarplog/Menu/Menu.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/net-mgmt/opnarplog/src/opnsense/mvc/app/views/OPNsense/opnarplog/general.volt b/net-mgmt/opnarplog/src/opnsense/mvc/app/views/OPNsense/opnarplog/general.volt new file mode 100644 index 0000000000..15fea2f87e --- /dev/null +++ b/net-mgmt/opnarplog/src/opnsense/mvc/app/views/OPNsense/opnarplog/general.volt @@ -0,0 +1,142 @@ +{# + # Copyright (c) 2024 github.com/mr-manuel + # All rights reserved. + #} + +
+ {{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_general_settings'])}} + + + + +
+

⚠️ Enabling this plugin will NOT automatically send mail notifications

+

To get alerted by mail, you have to set up Monit with a Service and Service Test that monitors the /var/log/opnarplog.log logfile.

+

Click here to display the mail alerting setup instructions at the end of this page.

+

Do you like this plugin? Consider to make a donation.

+
+
+
+ + +
+ +
+ + diff --git a/net-mgmt/opnarplog/src/opnsense/service/conf/actions.d/actions_opnarplog.conf b/net-mgmt/opnarplog/src/opnsense/service/conf/actions.d/actions_opnarplog.conf new file mode 100644 index 0000000000..06ab31f5cd --- /dev/null +++ b/net-mgmt/opnarplog/src/opnsense/service/conf/actions.d/actions_opnarplog.conf @@ -0,0 +1,29 @@ +[start] +command:service opnarplog start +parameters: +type:script +message:starting OPNarplog + +[stop] +command:service opnarplog stop +parameters: +type:script +message:stopping OPNarplog + +[restart] +command:service opnarplog restart +parameters: +type:script +message:restarting OPNarplog + +[status] +command:service opnarplog status;exit 0 +parameters: +type:script_output +message:request OPNarplog status + +[resetdb] +command:rm -f /var/db/opnarplog/opnarplog.db; echo "`date '+%Y-%m-%d_%H:%M:%S,000'`: database reset" >> /var/log/opnarplog.log +parameters: +type:script +message:reset database diff --git a/net-mgmt/opnarplog/src/opnsense/service/templates/OPNsense/opnarplog/+TARGETS b/net-mgmt/opnarplog/src/opnsense/service/templates/OPNsense/opnarplog/+TARGETS new file mode 100644 index 0000000000..39582df3ea --- /dev/null +++ b/net-mgmt/opnarplog/src/opnsense/service/templates/OPNsense/opnarplog/+TARGETS @@ -0,0 +1,2 @@ +opnarplog:/etc/rc.conf.d/opnarplog +opnarplog.conf:/usr/local/etc/opnarplog.conf diff --git a/net-mgmt/opnarplog/src/opnsense/service/templates/OPNsense/opnarplog/opnarplog b/net-mgmt/opnarplog/src/opnsense/service/templates/OPNsense/opnarplog/opnarplog new file mode 100644 index 0000000000..72cf2cd22f --- /dev/null +++ b/net-mgmt/opnarplog/src/opnsense/service/templates/OPNsense/opnarplog/opnarplog @@ -0,0 +1,5 @@ +{% if helpers.exists('OPNsense.opnarplog.general.enabled') and OPNsense.opnarplog.general.enabled == '1' %} +opnarplog_enable="YES" +{% else %} +opnarplog_enable="NO" +{% endif %} diff --git a/net-mgmt/opnarplog/src/opnsense/service/templates/OPNsense/opnarplog/opnarplog.conf b/net-mgmt/opnarplog/src/opnsense/service/templates/OPNsense/opnarplog/opnarplog.conf new file mode 100644 index 0000000000..806864ce38 --- /dev/null +++ b/net-mgmt/opnarplog/src/opnsense/service/templates/OPNsense/opnarplog/opnarplog.conf @@ -0,0 +1,18 @@ +{% if helpers.exists('OPNsense.opnarplog.general.enabled') and OPNsense.opnarplog.general.enabled == '1' %} +{% if helpers.exists('OPNsense.opnarplog.general.protocols') and OPNsense.opnarplog.general.protocols != '' %} +protocols={{ OPNsense.opnarplog.general.protocols }} +{% endif %} +{% if helpers.exists('OPNsense.opnarplog.general.interfaces') and OPNsense.opnarplog.general.interfaces != '' %} +interfaces={{ OPNsense.opnarplog.general.interfaces.replace(',', ' ') }} +{% endif %} +{% if helpers.exists('OPNsense.opnarplog.general.suppress_mac') and OPNsense.opnarplog.general.suppress_mac != '' %} +suppress_mac={{ OPNsense.opnarplog.general.suppress_mac.replace(',', ' ') }} +{% endif %} +log_new_entries={{ OPNsense.opnarplog.general.log_new_entries }} +log_mac_changes={{ OPNsense.opnarplog.general.log_mac_changes }} +log_ipv4_changes={{ OPNsense.opnarplog.general.log_ipv4_changes }} +log_ipv6_changes={{ OPNsense.opnarplog.general.log_ipv6_changes }} +log_hostname_changes={{ OPNsense.opnarplog.general.log_hostname_changes }} +log_interface_changes={{ OPNsense.opnarplog.general.log_interface_changes }} +retention_days={{ OPNsense.opnarplog.general.retention_days }} +{% endif %}