mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-06 17:15:38 -08:00
@@ -15,7 +15,7 @@ RUN apk add --no-cache bash shadow python3 python3-dev gcc musl-dev libffi-dev o
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
|
||||
RUN pip install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag git+https://github.com/foreign-sub/aiofreepybox.git
|
||||
RUN pip install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag zeroconf git+https://github.com/foreign-sub/aiofreepybox.git
|
||||
|
||||
# Append Iliadbox certificate to aiofreepybox
|
||||
|
||||
@@ -40,7 +40,7 @@ ENV S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0
|
||||
|
||||
RUN apk update --no-cache \
|
||||
&& apk add --no-cache bash libbsd zip lsblk gettext-envsubst sudo mtr tzdata s6-overlay \
|
||||
&& apk add --no-cache curl arp-scan iproute2 iproute2-ss nmap nmap-scripts traceroute nbtscan avahi avahi-tools openrc dbus net-tools net-snmp-tools bind-tools awake ca-certificates \
|
||||
&& apk add --no-cache curl arp-scan iproute2 iproute2-ss nmap nmap-scripts traceroute nbtscan net-tools net-snmp-tools bind-tools awake ca-certificates \
|
||||
&& apk add --no-cache sqlite php83 php83-fpm php83-cgi php83-curl php83-sqlite3 php83-session \
|
||||
&& apk add --no-cache python3 nginx \
|
||||
&& ln -s /usr/bin/awake /usr/bin/wakeonlan \
|
||||
|
||||
@@ -13,7 +13,7 @@ ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
COPY . ${INSTALL_DIR}/
|
||||
|
||||
RUN pip install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag git+https://github.com/foreign-sub/aiofreepybox.git \
|
||||
RUN pip install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag zeroconf git+https://github.com/foreign-sub/aiofreepybox.git \
|
||||
&& bash -c "find ${INSTALL_DIR} -type d -exec chmod 750 {} \;" \
|
||||
&& bash -c "find ${INSTALL_DIR} -type f -exec chmod 640 {} \;" \
|
||||
&& bash -c "find ${INSTALL_DIR} -type f \( -name '*.sh' -o -name '*.py' -o -name 'speedtest-cli' \) -exec chmod 750 {} \;"
|
||||
@@ -42,7 +42,7 @@ ENV S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0
|
||||
|
||||
RUN apk update --no-cache \
|
||||
&& apk add --no-cache bash libbsd zip lsblk gettext-envsubst sudo mtr tzdata s6-overlay \
|
||||
&& apk add --no-cache curl arp-scan iproute2 iproute2-ss nmap nmap-scripts traceroute nbtscan avahi avahi-tools openrc dbus net-tools net-snmp-tools bind-tools awake ca-certificates \
|
||||
&& apk add --no-cache curl arp-scan iproute2 iproute2-ss nmap nmap-scripts traceroute nbtscan net-tools net-snmp-tools bind-tools awake ca-certificates \
|
||||
&& apk add --no-cache sqlite php83 php83-fpm php83-cgi php83-curl php83-sqlite3 php83-session \
|
||||
&& apk add --no-cache python3 nginx \
|
||||
&& ln -s /usr/bin/awake /usr/bin/wakeonlan \
|
||||
|
||||
@@ -33,7 +33,7 @@ COPY --chmod=775 --chown=${USER_ID}:${USER_GID} . ${INSTALL_DIR}/
|
||||
RUN apt-get install -y \
|
||||
tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo \
|
||||
nginx-light php php-cgi php-fpm php-sqlite3 php-curl sqlite3 dnsutils net-tools php-openssl \
|
||||
python3 python3-dev iproute2 nmap python3-pip zip systemctl usbutils traceroute nbtscan avahi avahi-tools openrc dbus
|
||||
python3 python3-dev iproute2 nmap python3-pip zip systemctl usbutils traceroute nbtscan
|
||||
|
||||
# Alternate dependencies
|
||||
RUN apt-get install nginx nginx-core mtr php-fpm php8.2-fpm php-cli php8.2 php8.2-sqlite3 -y
|
||||
@@ -43,7 +43,7 @@ RUN phpenmod -v 8.2 sqlite3
|
||||
RUN apt-get install -y python3-venv
|
||||
RUN python3 -m venv myenv
|
||||
|
||||
RUN /bin/bash -c "source myenv/bin/activate && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 && pip3 install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag "
|
||||
RUN /bin/bash -c "source myenv/bin/activate && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 && pip3 install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag zeroconf "
|
||||
|
||||
# Create a buildtimestamp.txt to later check if a new version was released
|
||||
RUN date +%s > ${INSTALL_DIR}/front/buildtimestamp.txt
|
||||
|
||||
@@ -1,215 +1,133 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import json
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import socket
|
||||
import ipaddress
|
||||
from zeroconf import Zeroconf, ServiceBrowser, ServiceInfo, InterfaceChoice, IPVersion
|
||||
from zeroconf.asyncio import AsyncZeroconf
|
||||
|
||||
# Define the installation path and extend the system path for plugin imports
|
||||
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 plugin_utils import get_plugins_configs
|
||||
from plugin_helper import Plugin_Objects
|
||||
from logger import mylog, Logger
|
||||
from const import pluginsPath, fullDbPath, logPath
|
||||
from helper import timeNowTZ, get_setting_value
|
||||
from messaging.in_app import write_notification
|
||||
from const import logPath
|
||||
from helper import get_setting_value
|
||||
from database import DB
|
||||
from models.device_instance import DeviceInstance
|
||||
import conf
|
||||
from pytz import timezone
|
||||
|
||||
# Make sure the TIMEZONE for logging is correct
|
||||
conf.tz = timezone(get_setting_value('TIMEZONE'))
|
||||
# Configure timezone and logging
|
||||
conf.tz = timezone(get_setting_value("TIMEZONE"))
|
||||
Logger(get_setting_value("LOG_LEVEL"))
|
||||
|
||||
# Make sure log level is initialized correctly
|
||||
Logger(get_setting_value('LOG_LEVEL'))
|
||||
pluginName = "AVAHISCAN"
|
||||
|
||||
pluginName = 'AVAHISCAN'
|
||||
# Define log paths
|
||||
LOG_PATH = os.path.join(logPath, "plugins")
|
||||
LOG_FILE = os.path.join(LOG_PATH, f"script.{pluginName}.log")
|
||||
RESULT_FILE = os.path.join(LOG_PATH, f"last_result.{pluginName}.log")
|
||||
|
||||
# Define the current path and log file paths
|
||||
LOG_PATH = logPath + '/plugins'
|
||||
LOG_FILE = os.path.join(LOG_PATH, f'script.{pluginName}.log')
|
||||
RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log')
|
||||
|
||||
# Initialize the Plugin obj output file
|
||||
# Initialize plugin results
|
||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Helper functions
|
||||
# =============================================================================
|
||||
|
||||
def resolve_mdns_name(ip: str, timeout: int = 5) -> str:
|
||||
"""
|
||||
Attempts to resolve a hostname via multicast DNS using the Zeroconf library.
|
||||
|
||||
Args:
|
||||
ip (str): The IP address to resolve.
|
||||
timeout (int): Timeout in seconds for mDNS resolution.
|
||||
|
||||
Returns:
|
||||
str: Resolved hostname (or empty string if not found).
|
||||
"""
|
||||
mylog("debug", [f"[{pluginName}] Resolving mDNS for {ip}"])
|
||||
|
||||
# Convert string IP to an address object
|
||||
try:
|
||||
addr = ipaddress.ip_address(ip)
|
||||
except ValueError:
|
||||
mylog("none", [f"[{pluginName}] Invalid IP: {ip}"])
|
||||
return ""
|
||||
|
||||
# Reverse lookup name, e.g. "121.1.168.192.in-addr.arpa"
|
||||
if addr.version == 4:
|
||||
rev_name = ipaddress.ip_address(ip).reverse_pointer
|
||||
else:
|
||||
rev_name = ipaddress.ip_address(ip).reverse_pointer
|
||||
|
||||
try:
|
||||
zeroconf = Zeroconf()
|
||||
hostname = socket.getnameinfo((ip, 0), socket.NI_NAMEREQD)[0]
|
||||
zeroconf.close()
|
||||
if hostname and hostname != ip:
|
||||
mylog("debug", [f"[{pluginName}] Found mDNS name: {hostname}"])
|
||||
return hostname
|
||||
except Exception as e:
|
||||
mylog("debug", [f"[{pluginName}] Zeroconf lookup failed for {ip}: {e}"])
|
||||
finally:
|
||||
try:
|
||||
zeroconf.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Main logic
|
||||
# =============================================================================
|
||||
|
||||
def main():
|
||||
mylog('verbose', [f'[{pluginName}] In script'])
|
||||
mylog("verbose", [f"[{pluginName}] Script started"])
|
||||
|
||||
# timeout = get_setting_value('AVAHI_RUN_TIMEOUT')
|
||||
timeout = 20
|
||||
|
||||
# Create a database connection
|
||||
db = DB() # instance of class DB
|
||||
timeout = 10
|
||||
db = DB()
|
||||
db.open()
|
||||
|
||||
# Initialize the Plugin obj output file
|
||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
|
||||
# Create a DeviceInstance instance
|
||||
device_handler = DeviceInstance(db)
|
||||
devices = (
|
||||
device_handler.getAll()
|
||||
if get_setting_value("REFRESH_FQDN")
|
||||
else device_handler.getUnknown()
|
||||
)
|
||||
|
||||
# Retrieve devices
|
||||
if get_setting_value("REFRESH_FQDN"):
|
||||
devices = device_handler.getAll()
|
||||
else:
|
||||
devices = device_handler.getUnknown()
|
||||
|
||||
mylog('verbose', [f'[{pluginName}] Devices count: {len(devices)}'])
|
||||
|
||||
# Mock list of devices (replace with actual device_handler.getUnknown() in production)
|
||||
# devices = [
|
||||
# {'devMac': '00:11:22:33:44:55', 'devLastIP': '192.168.1.121'},
|
||||
# {'devMac': '00:11:22:33:44:56', 'devLastIP': '192.168.1.9'},
|
||||
# {'devMac': '00:11:22:33:44:57', 'devLastIP': '192.168.1.82'},
|
||||
# ]
|
||||
|
||||
if len(devices) > 0:
|
||||
# ensure service is running
|
||||
ensure_avahi_running()
|
||||
mylog("verbose", [f"[{pluginName}] Devices count: {len(devices)}"])
|
||||
|
||||
for device in devices:
|
||||
domain_name = execute_name_lookup(device['devLastIP'], timeout)
|
||||
ip = device["devLastIP"]
|
||||
mac = device["devMac"]
|
||||
|
||||
# check if found and not a timeout ('to')
|
||||
if domain_name != '' and domain_name != 'to':
|
||||
hostname = resolve_mdns_name(ip, timeout)
|
||||
|
||||
if hostname:
|
||||
plugin_objects.add_object(
|
||||
# "MAC", "IP", "Server", "Name"
|
||||
primaryId = device['devMac'],
|
||||
secondaryId = device['devLastIP'],
|
||||
watched1 = '', # You can add any relevant info here if needed
|
||||
watched2 = domain_name,
|
||||
watched3 = '',
|
||||
watched4 = '',
|
||||
extra = '',
|
||||
foreignKey = device['devMac'])
|
||||
primaryId=mac,
|
||||
secondaryId=ip,
|
||||
watched1="",
|
||||
watched2=hostname,
|
||||
watched3="",
|
||||
watched4="",
|
||||
extra="",
|
||||
foreignKey=mac,
|
||||
)
|
||||
|
||||
plugin_objects.write_result_file()
|
||||
|
||||
mylog('verbose', [f'[{pluginName}] Script finished'])
|
||||
|
||||
|
||||
mylog("verbose", [f"[{pluginName}] Script finished"])
|
||||
return 0
|
||||
|
||||
#===============================================================================
|
||||
# Execute scan
|
||||
#===============================================================================
|
||||
def execute_name_lookup(ip, timeout):
|
||||
"""
|
||||
Execute the avahi-resolve command on the IP.
|
||||
"""
|
||||
|
||||
args = ['avahi-resolve', '-a', ip]
|
||||
|
||||
# Execute command
|
||||
output = ""
|
||||
|
||||
try:
|
||||
mylog('debug', [f'[{pluginName}] DEBUG CMD :', args])
|
||||
|
||||
# Run the subprocess with a forced timeout
|
||||
output = subprocess.check_output(args, universal_newlines=True, stderr=subprocess.STDOUT, timeout=timeout)
|
||||
|
||||
mylog('debug', [f'[{pluginName}] DEBUG OUTPUT : {output}'])
|
||||
|
||||
domain_name = ''
|
||||
|
||||
# Split the output into lines
|
||||
lines = output.splitlines()
|
||||
|
||||
# Look for the resolved IP address
|
||||
for line in lines:
|
||||
if ip in line:
|
||||
parts = line.split()
|
||||
if len(parts) > 1:
|
||||
domain_name = parts[1] # Second part is the resolved domain name
|
||||
else:
|
||||
mylog('verbose', [f'[{pluginName}] ⚠ ERROR - Unexpected output format: {line}'])
|
||||
|
||||
mylog('debug', [f'[{pluginName}] Domain Name: {domain_name}'])
|
||||
|
||||
return domain_name
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
mylog('none', [f'[{pluginName}] ⚠ ERROR - {e.output}'])
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
mylog('none', [f'[{pluginName}] TIMEOUT - the process forcefully terminated as timeout reached'])
|
||||
|
||||
if output == "":
|
||||
mylog('none', [f'[{pluginName}] Scan: FAIL - check logs'])
|
||||
else:
|
||||
mylog('debug', [f'[{pluginName}] Scan: SUCCESS'])
|
||||
|
||||
return ''
|
||||
|
||||
# Function to ensure Avahi and its dependencies are running
|
||||
def ensure_avahi_running(attempt=1, max_retries=2):
|
||||
"""
|
||||
Ensure that D-Bus is running and the Avahi daemon is started, with recursive retry logic.
|
||||
"""
|
||||
mylog('debug', [f'[{pluginName}] Attempt {attempt} - Ensuring D-Bus and Avahi daemon are running...'])
|
||||
|
||||
# Check rc-status
|
||||
try:
|
||||
subprocess.run(['rc-status'], check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
mylog('none', [f'[{pluginName}] ⚠ ERROR - Failed to check rc-status: {e.output}'])
|
||||
return
|
||||
|
||||
# Create OpenRC soft level
|
||||
subprocess.run(['touch', '/run/openrc/softlevel'], check=True)
|
||||
|
||||
# Add Avahi daemon to runlevel
|
||||
try:
|
||||
subprocess.run(['rc-update', 'add', 'avahi-daemon'], check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
mylog('none', [f'[{pluginName}] ⚠ ERROR - Failed to add Avahi to runlevel: {e.output}'])
|
||||
return
|
||||
|
||||
# Start the D-Bus service
|
||||
try:
|
||||
subprocess.run(['rc-service', 'dbus', 'start'], check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
mylog('none', [f'[{pluginName}] ⚠ ERROR - Failed to start D-Bus: {e.output}'])
|
||||
return
|
||||
|
||||
# Check Avahi status
|
||||
status_output = subprocess.run(['rc-service', 'avahi-daemon', 'status'], capture_output=True, text=True)
|
||||
if 'started' in status_output.stdout:
|
||||
mylog('debug', [f'[{pluginName}] Avahi Daemon is already running.'])
|
||||
return
|
||||
|
||||
mylog('none', [f'[{pluginName}] Avahi Daemon is not running, attempting to start... (Attempt {attempt})'])
|
||||
|
||||
# Start the Avahi daemon
|
||||
try:
|
||||
subprocess.run(['rc-service', 'avahi-daemon', 'start'], check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
mylog('none', [f'[{pluginName}] ⚠ ERROR - Failed to start Avahi daemon: {e.output}'])
|
||||
|
||||
# Check status after starting
|
||||
status_output = subprocess.run(['rc-service', 'avahi-daemon', 'status'], capture_output=True, text=True)
|
||||
if 'started' in status_output.stdout:
|
||||
mylog('debug', [f'[{pluginName}] Avahi Daemon successfully started.'])
|
||||
return
|
||||
|
||||
# Retry if not started and attempts are left
|
||||
if attempt < max_retries:
|
||||
mylog('debug', [f'[{pluginName}] Retrying... ({attempt + 1}/{max_retries})'])
|
||||
ensure_avahi_running(attempt + 1, max_retries)
|
||||
else:
|
||||
mylog('none', [f'[{pluginName}] ⚠ ERROR - Avahi Daemon failed to start after {max_retries} attempts.'])
|
||||
|
||||
# rc-update add avahi-daemon
|
||||
# rc-service avahi-daemon status
|
||||
# rc-service avahi-daemon start
|
||||
|
||||
if __name__ == '__main__':
|
||||
# =============================================================================
|
||||
# Entrypoint
|
||||
# =============================================================================
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -30,4 +30,4 @@ source myenv/bin/activate
|
||||
update-alternatives --install /usr/bin/python python /usr/bin/python3 10
|
||||
|
||||
# install packages thru pip3
|
||||
pip3 install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag git+https://github.com/foreign-sub/aiofreepybox.git
|
||||
pip3 install openwrt-luci-rpc asusrouter asyncio aiohttp graphene flask flask-cors unifi-sm-api tplink-omada-client wakeonlan pycryptodome requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi speedtest-cli chardet python-nmap dnspython librouteros yattag zeroconf git+https://github.com/foreign-sub/aiofreepybox.git
|
||||
|
||||
@@ -22,4 +22,5 @@ python-nmap
|
||||
dnspython
|
||||
librouteros
|
||||
yattag
|
||||
zeroconf
|
||||
git+https://github.com/foreign-sub/aiofreepybox.git
|
||||
|
||||
@@ -22,4 +22,5 @@ python-nmap
|
||||
dnspython
|
||||
librouteros
|
||||
yattag
|
||||
zeroconf
|
||||
git+https://github.com/foreign-sub/aiofreepybox.git
|
||||
|
||||
Reference in New Issue
Block a user