From 6d7410e286ce0fde31f89185c095fe90e85597f3 Mon Sep 17 00:00:00 2001 From: EnricoGuccii Date: Sat, 10 Jan 2026 22:43:36 +0100 Subject: bloat removed --- .../detector_profile_HST.cpython-312.pyc | Bin 11545 -> 0 bytes .../detector_profiles_manager.cpython-312.pyc | Bin 10030 -> 0 bytes .../back/__pycache__/flow_features.cpython-312.pyc | Bin 1145 -> 0 bytes .../back/__pycache__/flow_table.cpython-312.pyc | Bin 9184 -> 0 bytes .../notification_service.cpython-312.pyc | Bin 2912 -> 0 bytes .../__pycache__/scanner_profile.cpython-312.pyc | Bin 7203 -> 0 bytes .../scanner_profiles_manager.cpython-312.pyc | Bin 12392 -> 0 bytes .../back/__pycache__/window.cpython-312.pyc | Bin 9507 -> 0 bytes src/netmonitor/back/detector_profile_HST.py | 215 ------------------ src/netmonitor/back/detector_profiles_manager.py | 153 ------------- src/netmonitor/back/notification_service.py | 49 ---- src/netmonitor/back/scanner_profile.py | 138 ----------- src/netmonitor/back/scanner_profiles_manager.py | 177 --------------- src/netmonitor/back/window.py | 251 --------------------- 14 files changed, 983 deletions(-) delete mode 100644 src/netmonitor/back/__pycache__/detector_profile_HST.cpython-312.pyc delete mode 100644 src/netmonitor/back/__pycache__/detector_profiles_manager.cpython-312.pyc delete mode 100644 src/netmonitor/back/__pycache__/flow_features.cpython-312.pyc delete mode 100644 src/netmonitor/back/__pycache__/flow_table.cpython-312.pyc delete mode 100644 src/netmonitor/back/__pycache__/notification_service.cpython-312.pyc delete mode 100644 src/netmonitor/back/__pycache__/scanner_profile.cpython-312.pyc delete mode 100644 src/netmonitor/back/__pycache__/scanner_profiles_manager.cpython-312.pyc delete mode 100644 src/netmonitor/back/__pycache__/window.cpython-312.pyc delete mode 100644 src/netmonitor/back/detector_profile_HST.py delete mode 100644 src/netmonitor/back/detector_profiles_manager.py delete mode 100644 src/netmonitor/back/notification_service.py delete mode 100644 src/netmonitor/back/scanner_profile.py delete mode 100644 src/netmonitor/back/scanner_profiles_manager.py delete mode 100644 src/netmonitor/back/window.py (limited to 'src/netmonitor/back') diff --git a/src/netmonitor/back/__pycache__/detector_profile_HST.cpython-312.pyc b/src/netmonitor/back/__pycache__/detector_profile_HST.cpython-312.pyc deleted file mode 100644 index 6dc15c8..0000000 Binary files a/src/netmonitor/back/__pycache__/detector_profile_HST.cpython-312.pyc and /dev/null differ diff --git a/src/netmonitor/back/__pycache__/detector_profiles_manager.cpython-312.pyc b/src/netmonitor/back/__pycache__/detector_profiles_manager.cpython-312.pyc deleted file mode 100644 index b17c763..0000000 Binary files a/src/netmonitor/back/__pycache__/detector_profiles_manager.cpython-312.pyc and /dev/null differ diff --git a/src/netmonitor/back/__pycache__/flow_features.cpython-312.pyc b/src/netmonitor/back/__pycache__/flow_features.cpython-312.pyc deleted file mode 100644 index b5e28d0..0000000 Binary files a/src/netmonitor/back/__pycache__/flow_features.cpython-312.pyc and /dev/null differ diff --git a/src/netmonitor/back/__pycache__/flow_table.cpython-312.pyc b/src/netmonitor/back/__pycache__/flow_table.cpython-312.pyc deleted file mode 100644 index ffb1702..0000000 Binary files a/src/netmonitor/back/__pycache__/flow_table.cpython-312.pyc and /dev/null differ diff --git a/src/netmonitor/back/__pycache__/notification_service.cpython-312.pyc b/src/netmonitor/back/__pycache__/notification_service.cpython-312.pyc deleted file mode 100644 index 71ee4ce..0000000 Binary files a/src/netmonitor/back/__pycache__/notification_service.cpython-312.pyc and /dev/null differ diff --git a/src/netmonitor/back/__pycache__/scanner_profile.cpython-312.pyc b/src/netmonitor/back/__pycache__/scanner_profile.cpython-312.pyc deleted file mode 100644 index 0c8b662..0000000 Binary files a/src/netmonitor/back/__pycache__/scanner_profile.cpython-312.pyc and /dev/null differ diff --git a/src/netmonitor/back/__pycache__/scanner_profiles_manager.cpython-312.pyc b/src/netmonitor/back/__pycache__/scanner_profiles_manager.cpython-312.pyc deleted file mode 100644 index f9dc637..0000000 Binary files a/src/netmonitor/back/__pycache__/scanner_profiles_manager.cpython-312.pyc and /dev/null differ diff --git a/src/netmonitor/back/__pycache__/window.cpython-312.pyc b/src/netmonitor/back/__pycache__/window.cpython-312.pyc deleted file mode 100644 index c6971ec..0000000 Binary files a/src/netmonitor/back/__pycache__/window.cpython-312.pyc and /dev/null differ diff --git a/src/netmonitor/back/detector_profile_HST.py b/src/netmonitor/back/detector_profile_HST.py deleted file mode 100644 index 267b307..0000000 --- a/src/netmonitor/back/detector_profile_HST.py +++ /dev/null @@ -1,215 +0,0 @@ -import threading -import queue -import time -import os -from pathlib import Path -from scapy.all import wrpcap, AsyncSniffer - -from river.anomaly import HalfSpaceTrees -from tinydb import TinyDB -from tinydb.table import Document - -from .window import Window -from .notification_service import notification_service - -XDG_DATA_HOME = Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local/share")) -LOGS_PATH = f"{XDG_DATA_HOME}/netmonitor/detector/profiles_logs" -PCAP_PATH = f"{XDG_DATA_HOME}/netmonitor/detector/profiles_pcaps" - - -class DetectorProfileHST: - - def __init__( - self, - profile_name: str, - input_data: dict - ): - self.profile_name = profile_name - self.features = input_data.get("features", []) - self.params = input_data.get("params", {}) - self.n_trees = int(self.params.get("trees", 10)) - self.height = int(self.params.get("height", 8)) - self.window_size = int(self.params.get("window", 250)) - self.seed = int(self.params.get("seed", 42)) - self.threshold = float(self.params.get("threshold", 0.7)) - self.window_duration = float(self.params.get("window_duration", 10.0)) - self.bpf_filter = self.params.get("bpf_filter", "") - self.interface = self.params.get("interface", None) - self.queue_size = int(self.params.get("queue_size", 10000)) - self.logs_path = f"{LOGS_PATH}/{profile_name}.json" - os.makedirs(os.path.dirname(self.logs_path), exist_ok=True) - - self.is_active = False - - self.notify_enabled = False - self._init_runtime_objects() - - - def _init_runtime_objects(self): - self.queue = queue.Queue(maxsize=self.queue_size) - - os.makedirs(f"{LOGS_PATH}", exist_ok=True) - self.db = TinyDB(f"{LOGS_PATH}/{self.profile_name}.json") - - self.model = HalfSpaceTrees( - n_trees=self.n_trees, - height=self.height, - window_size=self.window_size, - seed=self.seed - ) - - self.window = Window( - window_duration=self.window_duration, - enabled_features=self.features - ) - - self.processor_thread = None - - self.packets_read = 0 - self.windows_analyzed = 0 - - if not hasattr(self, 'plot_data'): - self.plot_data = [] - - - def __getstate__(self): - state = self.__dict__.copy() - cols_to_remove = ['sniffer_thread', 'processor_thread', 'queue', 'db', 'window'] - for col in cols_to_remove: - if col in state: - del state[col] - return state - - def __setstate__(self, state): - self.__dict__.update(state) - self._init_runtime_objects() - self.is_active = False - - def __repr__(self): - return f"" - - def turn_on(self): - if self.is_active: - return - - self.is_active = True - os.makedirs(os.path.dirname(self.logs_path), exist_ok=True) - self.window.window_duration = self.window_duration - self.window.window_start = time.time() - - self.sniffer = AsyncSniffer( - iface=self.interface, - filter=self.bpf_filter, - store=False, - prn=self._add_to_queue - ) - self.sniffer.start() - - self.processor_thread = threading.Thread(target=self._process_thread, daemon=True) - - self.processor_thread.start() - - def turn_off(self): - self.is_active = False - if self.sniffer: - self.sniffer.stop() - - time.sleep(0.2) - - def _add_to_queue(self, pkt): - if self.queue: - try: - self.queue.put_nowait(pkt) - self.packets_read += 1 - except queue.Full: - pass - - def _process_thread(self): - while self.is_active: - try: - pkt = self.queue.get(timeout=1) - except queue.Empty: - continue - - result = self.window.add_packet(pkt) - - if result is None: - continue - - features, raw_packets = result - self.windows_analyzed += 1 - - if not features: - continue - - sample = {feat: 0.0 for feat in self.features} - for k, v in features.items(): - if k in sample: - sample[k] = float(v) - - score = self.model.score_one(sample) - self.model.learn_one(sample) - - self.plot_data.append(score) - if len(self.plot_data) > 30: - self.plot_data.pop(0) - - if score > self.threshold: - self._handle_anomaly(score, sample, raw_packets) - - def _handle_anomaly(self, score: float, features: dict, raw_packets: list): - timestamp = time.strftime("%Y-%m-%d_%H-%M-%S") - - os.makedirs(os.path.dirname(f"{PCAP_PATH}/{self.profile_name}"), exist_ok=True) - filename = f"{PCAP_PATH}/{self.profile_name}/anom_{timestamp}.pcap" - - if raw_packets: - try: - wrpcap(filename, raw_packets) - except Exception as e: - print(f"Error saving pcap: {e}") - - if self.notify_enabled: - msg = f"*Anomaly detected: {self.profile_name}*\nScore: `{score:.4f}`\nSaved: `{filename}`" - notification_service.send_message(message=msg) - - if self.db: - self.db.insert( - Document({ - "ts": time.time(), - "timestamp": timestamp, - "profile": self.profile_name, - "score": float(score), - "pcap": filename, - "pkt_rate": features.get("pkt_rate", 0), - "proto_info": f"TCP:{features.get('proto_tcp_ratio',0):.2f} UDP:{features.get('proto_udp_ratio',0):.2f}", - }, doc_id=None) - ) - - def to_dict(self): - return { - "profile name": self.profile_name, - "logs_path": self.logs_path, - "pcap_path": f"{PCAP_PATH}/{self.profile_name}", - "params": self.params, - "features": self.features, - } - - def get_logs(self): - if self.db: - return self.db.all() - return [] - - def clear_logs(self): - if self.db: - self.db.truncate() - - def get_runtime_stats(self): - return { - "is_active": self.is_active, - "notify_enabled": self.notify_enabled, - "packets_captured": getattr(self, "packets_read", 0), - "queue_size": self.queue.qsize() if hasattr(self, "queue") and self.queue else 0, - "windows_processed": getattr(self, "windows_analyzed", 0), - "window_duration": self.window_duration - } diff --git a/src/netmonitor/back/detector_profiles_manager.py b/src/netmonitor/back/detector_profiles_manager.py deleted file mode 100644 index 1d1c5ea..0000000 --- a/src/netmonitor/back/detector_profiles_manager.py +++ /dev/null @@ -1,153 +0,0 @@ -import re -from pathlib import Path -from typing import Callable, Literal -import pickle -from netmonitor.back.detector_profile_HST import DetectorProfileHST - - -SeverityLevel = Literal["information", "warning", "error"] -VALID_NAME_REGEX = r"^[a-zA-Z0-9_-]+$" - -class DetectorProfilesManager: - def __init__(self, profiles_file: str): - self.profiles_file = Path(profiles_file) - self.profiles: list[DetectorProfileHST] = [] - - self.on_message: Callable[[str, str, str], None] | None = None - self.on_refresh: Callable[[], None] | None = None - - self.load_profiles() - - - def _notify(self, msg: str, title: str = "Profile Manager", level: SeverityLevel = "information"): - if self.on_message: - self.on_message(msg, title, level) - - - def _refresh_front(self): - if self.on_refresh: - self.on_refresh() - - - def _fail(self, msg: str, level: SeverityLevel = "error", notify: bool = True) -> bool: - if notify: - self._notify(msg, level=level) - return False - - - def _ok(self, msg: str | None = None, level: SeverityLevel = "information", notify: bool = True) -> bool: - if msg and notify: - self._notify(msg, level=level) - return True - - - def try_save_profiles(self, notify: bool = True) -> bool: - try: - self.profiles_file.parent.mkdir(parents=True, exist_ok=True) - - with open(self.profiles_file, "wb") as f: - pickle.dump(self.profiles, f, protocol=pickle.HIGHEST_PROTOCOL) - - return self._ok("Profiles saved successfully.", notify=notify) - except Exception as e: - import traceback - traceback.print_exc() - return self._fail(f"Error saving profiles: {e}", notify=notify) - - - def load_profiles(self, notify: bool = True) -> bool: - if not self.profiles_file.exists(): - if notify: - self._notify("Profiles file not found. Creating a new one.", level="warning") - if not self.try_save_profiles(notify=False): - return self._fail("Failed to create profiles file!", notify=notify) - return self._ok("New profiles file created.", notify=notify) - - try: - with open(self.profiles_file, "rb") as f: - self.profiles = pickle.load(f) - self._refresh_front() - return self._ok(f"Loaded {len(self.profiles)} profiles.", notify=notify) - except Exception as e: - return self._fail(f"Error loading profiles: {e}", notify=notify) - - - def add_profile(self, profile_name: str, input_data: dict, notify: bool = True) -> bool: - if not profile_name: - return self._fail("Name can't be blank.", "error", notify) - - if not re.match(VALID_NAME_REGEX, profile_name): - return self._fail(f"Bad input '{profile_name}'. Use only letters, numbers, '_' and '-'.", "error", notify) - - if any(p.profile_name == profile_name for p in self.profiles): - return self._fail(f"Profile {profile_name} already exists.", "warning", notify) - - new_profile = DetectorProfileHST(profile_name=profile_name, input_data = input_data) - self.profiles.append(new_profile) - - if not self.try_save_profiles(notify=False): - self.profiles.remove(new_profile) - return self._fail(f"Failed to save profile {profile_name}.", notify=notify) - - self._refresh_front() - return self._ok(f"Added profile {profile_name}.", notify=notify) - - - def delete_profile(self, profile_name: str, notify: bool = True) -> bool: - before = len(self.profiles) - self.profiles = [p for p in self.profiles if p.profile_name != profile_name] - - if len(self.profiles) == before: - return self._fail(f"Profile {profile_name} not found.", "warning", notify) - - if not self.try_save_profiles(notify=False): - return self._fail(f"Failed to save changes after deleting {profile_name}.", notify=notify) - - self._refresh_front() - return self._ok(f"Deleted profile {profile_name}.", notify=notify) - - - def get_profile(self, profile_name: str) -> DetectorProfileHST | None: - return next((p for p in self.profiles if p.profile_name == profile_name), None) - - - def update_profile(self, profile_name: str, field: str, value, notify: bool = True) -> bool: - p = self.get_profile(profile_name) - if not p: - return self._fail(f"Profile {profile_name} does not exist.", notify=notify) - - setattr(p, field, value) - if self.try_save_profiles(notify=False): - return self._ok(f"Updated profile {profile_name}.", notify=notify) - else: - return self._fail(f"Failed to save updated profile {profile_name}.", notify=notify) - - - def turn_on_profile(self, profile_name: str, app=None, notify: bool = True) -> bool: - p = self.get_profile(profile_name) - if not p: - return self._fail(f"Profile {profile_name} not found.", notify=notify) - try: - p.turn_on() - return self._ok(f"Profile {profile_name} activated.", notify=notify) - except Exception as e: - return self._fail(f"Error activating profile: {e}", notify=notify) - - - def turn_off_profile(self, profile_name: str, notify: bool = True) -> bool: - p = self.get_profile(profile_name) - if not p: - return self._fail(f"Profile {profile_name} not found.", notify=notify) - try: - p.turn_off() - return self._ok(f"Profile {profile_name} deactivated.", notify=notify) - except Exception as e: - return self._fail(f"Error deactivating profile: {e}", notify=notify) - - - def get_profile_logs(self, profile_name: str, notify: bool = True): - p = self.get_profile(profile_name) - if not p: - self._fail(f"Profile {profile_name} not found.", "error", notify) - return None - return p.get_logs() diff --git a/src/netmonitor/back/notification_service.py b/src/netmonitor/back/notification_service.py deleted file mode 100644 index e347faf..0000000 --- a/src/netmonitor/back/notification_service.py +++ /dev/null @@ -1,49 +0,0 @@ -import json -import requests -from pathlib import Path - -CONFIG_FILE = Path("data/global_config.json") - -class NotificationService: - def __init__(self): - self.webhook_url = "" - self.load_config() - - def load_config(self): - if CONFIG_FILE.exists(): - try: - with open(CONFIG_FILE, "r") as f: - data = json.load(f) - self.webhook_url = data.get("discord_webhook_url", "") - except Exception as e: - print(f"Error loading notification config: {e}") - - def save_config(self, webhook_url: str): - self.webhook_url = webhook_url - - CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True) - try: - with open(CONFIG_FILE, "w") as f: - json.dump({ - "discord_webhook_url": self.webhook_url - }, f, indent=4) - return True - except Exception as e: - print(f"Error saving config: {e}") - return False - - def send_message(self, message: str) -> bool: - if not self.webhook_url: - return False - - payload = { - "content": message - } - try: - response = requests.post(self.webhook_url, json=payload, timeout=5) - return response.status_code in [200, 204] - except Exception as e: - print(f"Discord send error: {e}") - return False - -notification_service = NotificationService() diff --git a/src/netmonitor/back/scanner_profile.py b/src/netmonitor/back/scanner_profile.py deleted file mode 100644 index 6a20b5b..0000000 --- a/src/netmonitor/back/scanner_profile.py +++ /dev/null @@ -1,138 +0,0 @@ -import os -from pathlib import Path -from datetime import datetime -from tinydb import TinyDB -import nmap -from .notification_service import notification_service - - -XDG_DATA_HOME = Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local/share")) -LOGS_PATH = f"{XDG_DATA_HOME}/netmonitor/scanner/profiles_logs" - -class ScannerProfile: - def __init__(self, profile_name: str, nmap_input=None, scheduler=None, cve=None): - self.profile_name = profile_name - self.nmap_input = nmap_input or {} - self.scheduler = scheduler - self.cve = cve - self.is_active = False - - self.notify_enabled = False - self.notify_only_cve = False - - self.nm = None - self.db = None - self.profile_results_path = f"{LOGS_PATH}/{profile_name}.json" - os.makedirs(os.path.dirname(self.profile_results_path), exist_ok=True) - - @property - def nmap(self): - if self.nm is None: - self.nm = nmap.PortScanner() - return self.nm - - @property - def tinydb(self): - if self.db is None: - self.db = TinyDB(self.profile_results_path) - return self.db - - def __getstate__(self): - state = self.__dict__.copy() - state['nm'] = None - state['db'] = None - return state - - def __setstate__(self, state): - self.__dict__.update(state) - self.nm = None - self.db = None - - def __repr__(self): - return f"" - - - def scan(self): - targets = self.nmap_input.get("targets", "") - arguments = self.nmap_input.get("arguments", "") - ports = self.nmap_input.get("ports", "") - - if self.cve: - arguments += " --script=vulners " - - if ports == '': - self.nmap.scan(hosts=targets, arguments=arguments) - else: - self.nmap.scan(hosts=targets, ports=ports, arguments=arguments) - - xml_result = self.nmap.get_nmap_last_output() - analyzed_results = self.nmap.analyse_nmap_xml_scan(xml_result) - - if isinstance(analyzed_results, dict): - analyzed_results['_timestamp'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - self.tinydb.insert(analyzed_results) - - if self.notify_enabled: - self._handle_notification(analyzed_results, targets) - - return analyzed_results - - def _handle_notification(self, results, targets): - scan_str = str(results).lower() - found_issues = "cve" in scan_str or "vulnerab" in scan_str - should_send = False - msg = f"*Scanner report: {self.profile_name}*\nTargets: `{targets}`" - summary = "" - try: - if 'scan' in results: - for host, data in results['scan'].items(): - summary += f"\n *{host}*" - - if 'tcp' in data: - open_ports = [f"{p}/tcp" for p, info in data['tcp'].items() if info.get('state') == 'open'] - if open_ports: - summary += f"\n Open ports: {', '.join(open_ports)}" - if 'udp' in data: - open_ports = [f"{p}/udp" for p, info in data['udp'].items() if info.get('state') == 'open'] - if open_ports: - summary += f"\n Open ports (UDP): {', '.join(open_ports)}" - if 'osmatch' in data and data['osmatch']: - os_name = data['osmatch'][0].get('name', 'Unknown') - summary += f"\n OS detected: {os_name}" - except Exception as e: - summary += f"\n(Error building summary: {e})" - - if self.notify_only_cve: - if found_issues: - should_send = True - msg += "\nCVE detected!" - msg += "\n---" + summary - else: - should_send = True - msg += "\nScan completed." - if found_issues: - msg += "\nPotential vulnerabilities detected." - - if summary: - msg += "\n---" + summary - - if should_send: - if len(msg) > 1900: - msg = msg[:1900] + "\n...." - notification_service.send_message(msg) - - - def to_dict(self): - return { - "profile_name": self.profile_name, - "nmap_input": self.nmap_input, - "scheduler": self.scheduler, - "is_active": self.is_active, - "notify_enabled": getattr(self, 'notify_enabled', False), - "notify_only_cve": getattr(self, 'notify_only_cve', False), - "results_path": self.profile_results_path, - } - - def get_logs(self): - return self.tinydb.all() diff --git a/src/netmonitor/back/scanner_profiles_manager.py b/src/netmonitor/back/scanner_profiles_manager.py deleted file mode 100644 index 7748835..0000000 --- a/src/netmonitor/back/scanner_profiles_manager.py +++ /dev/null @@ -1,177 +0,0 @@ -import re -from pathlib import Path -from typing import Callable, Literal -from apscheduler.schedulers.background import BackgroundScheduler -from apscheduler.triggers.cron import CronTrigger -import pickle - -from .scanner_profile import ScannerProfile - -SeverityLevel = Literal["information", "warning", "error"] -VALID_NAME_REGEX = r"^[a-zA-Z0-9_-]+$" - -class ScannerProfilesManager: - def __init__(self, profiles_file: str, scheduler: BackgroundScheduler): - self.profiles_file = Path(profiles_file) - self.scheduler = scheduler - self.profiles: list[ScannerProfile] = [] - - self.on_message: Callable[[str, str, str], None] | None = None - self.on_refresh: Callable[[], None] | None = None - - self.load_profiles() - - def _notify(self, msg: str, title: str = "Profile Manager", level: SeverityLevel = "information"): - if self.on_message: - self.on_message(msg, title, level) - - def _refresh_front(self): - if self.on_refresh: - self.on_refresh() - - def _fail(self, msg: str, level: SeverityLevel = "error", notify: bool = True) -> bool: - if notify: - self._notify(msg, level=level) - return False - - def _ok(self, msg: str | None = None, level: SeverityLevel = "information", notify: bool = True) -> bool: - if msg and notify: - self._notify(msg, level=level) - return True - - def try_save_profiles(self, notify: bool = True) -> bool: - try: - self.profiles_file.parent.mkdir(parents=True, exist_ok=True) - with open(self.profiles_file, "wb") as f: - pickle.dump(self.profiles, f) - return self._ok("Profiles saved successfully.", notify=notify) - except Exception as e: - return self._fail(f"Error saving profiles: {e}", notify=notify) - - def load_profiles(self, notify: bool = True) -> bool: - if not self.profiles_file.exists(): - if notify: - self._notify("Profiles file not found. Creating a new one.", level="warning") - if not self.try_save_profiles(notify=False): - return self._fail("Failed to create profiles file!", notify=notify) - return self._ok("New profiles file created.", notify=notify) - - try: - with open(self.profiles_file, "rb") as f: - self.profiles = pickle.load(f) - self._refresh_front() - return self._ok(f"Loaded {len(self.profiles)} profiles.", notify=notify) - except Exception as e: - return self._fail(f"Error loading profiles: {e}", notify=notify) - - - def add_profile(self, profile_name: str, notify: bool = True) -> bool: - if not profile_name: - return self._fail("Name can't be blank.", "error", notify) - - if not re.match(VALID_NAME_REGEX, profile_name): - return self._fail(f"Bad input '{profile_name}'. Use only letters, numbers, '_' and '-'.", "error", notify) - - if any(p.profile_name == profile_name for p in self.profiles): - return self._fail(f"Profile {profile_name} already exists.", "warning", notify) - - new_profile = ScannerProfile(profile_name=profile_name) - self.profiles.append(new_profile) - - if not self.try_save_profiles(notify=False): - self.profiles.remove(new_profile) - return self._fail(f"Failed to save profile {profile_name}.", notify=notify) - - self._refresh_front() - return self._ok(f"Added profile {profile_name}.", notify=notify) - - def delete_profile(self, profile_name: str, notify: bool = True) -> bool: - before = len(self.profiles) - self.profiles = [p for p in self.profiles if p.profile_name != profile_name] - - if len(self.profiles) == before: - return self._fail(f"Profile {profile_name} not found.", "warning", notify) - - if not self.try_save_profiles(notify=False): - return self._fail(f"Failed to save changes after deleting {profile_name}.", notify=notify) - - self._refresh_front() - return self._ok(f"Deleted profile {profile_name}.", notify=notify) - - def get_profile(self, profile_name: str) -> ScannerProfile | None: - return next((p for p in self.profiles if p.profile_name == profile_name), None) - - def update_profile(self, profile_name: str, field: str, value, notify: bool = True) -> bool: - p = self.get_profile(profile_name) - if not p: - return self._fail(f"Profile {profile_name} does not exist.", notify=notify) - - setattr(p, field, value) - if self.try_save_profiles(notify=False): - return self._ok(f"Updated profile {profile_name}.", notify=notify) - else: - return self._fail(f"Failed to save updated profile {profile_name}.", notify=notify) - - - def run_profile_once(self, profile_name: str, notify: bool = True) -> bool: - p = self.get_profile(profile_name) - if not p: - return self._fail(f"Profile {profile_name} not found.", notify=notify) - - try: - p.scan() - return self._ok(f"Scan completed for {profile_name}.", notify=notify) - except Exception as e: - return self._fail(f"Scan error: {e}", notify=notify) - - def turn_on_profile(self, profile_name: str, notify: bool = True) -> bool: - p = self.get_profile(profile_name) - if not p: - return self._fail(f"Profile {profile_name} not found.", notify=notify) - - try: - job_id = f"profile_{p.profile_name}" - self.scheduler.add_job( - lambda: self.run_profile_once(p.profile_name, notify=False), - trigger=CronTrigger.from_crontab(p.scheduler), - id=job_id, - replace_existing=True - ) - p.is_active = True - return self._ok(f"Profile {profile_name} activated.", notify=notify) - except Exception as e: - return self._fail(f"Error activating profile: {e}", notify=notify) - - def turn_off_profile(self, profile_name: str, notify: bool = True) -> bool: - job_id = f"profile_{profile_name}" - try: - self.scheduler.remove_job(job_id) - p = self.get_profile(profile_name) - if p: - p.is_active = False - return self._ok(f"Profile {profile_name} deactivated.", notify=notify) - except Exception as e: - return self._fail(f"Error deactivating profile: {e}", notify=notify) - - def set_validated_scheduler(self, profile_name: str, cron_str: str, notify: bool = True) -> bool: - p = self.get_profile(profile_name) - if not p: - return self._fail(f"Profile {profile_name} not found.", notify=notify) - - try: - CronTrigger.from_crontab(cron_str) - p.scheduler = cron_str - if not self.try_save_profiles(notify=False): - return self._fail(f"Failed to save scheduler for {profile_name}.", notify=notify) - return self._ok(f"Scheduler updated for {profile_name}.", notify=notify) - except ValueError as ve: - return self._fail(f"Invalid CRON: {ve}", notify=notify) - except Exception as e: - return self._fail(f"Unexpected error: {e}", notify=notify) - - def get_profile_logs(self, profile_name: str, notify: bool = True): - p = self.get_profile(profile_name) - if not p: - self._fail(f"Profile {profile_name} not found.", "error", notify) - return None - return p.get_logs() diff --git a/src/netmonitor/back/window.py b/src/netmonitor/back/window.py deleted file mode 100644 index dfa8e7d..0000000 --- a/src/netmonitor/back/window.py +++ /dev/null @@ -1,251 +0,0 @@ -import time -from collections import defaultdict -from statistics import mean, pstdev -import math - -from scapy.all import IP, IPv6, TCP, UDP, ICMP - -class Window: - def __init__(self, window_duration: float, enabled_features: list[str]): - - self.window_duration = float(window_duration) - self.enabled = set(enabled_features) - - self.window_start = time.time() - - self.raw_packets_buffer = [] - - self.flows = defaultdict(lambda: { - "pkt_count": 0, - "byte_count": 0, - "dst_ports": defaultdict(int), - "src_ports": defaultdict(int), - "tcp_flags": { - "syn": 0, "fin": 0, "rst": 0, "ack": 0, - "psh": 0, "urg": 0, "xmas": 0, "null": 0, - }, - "sizes": [], - "start_ts": None, - "end_ts": None, - "tcp_pkts": 0, - "udp_pkts": 0, - "icmp_pkts": 0, - }) - - def add_packet(self, pkt): - now = time.time() - - if now - self.window_start >= self.window_duration: - features = self._finish_window() - - self.window_start = now - - raw = list(self.raw_packets_buffer) - - self.raw_packets_buffer.clear() - self.flows.clear() - - self._process_single_packet(pkt) - - return features, raw - - self._process_single_packet(pkt) - return None - - def _process_single_packet(self, pkt): - self.raw_packets_buffer.append(pkt) - - proto = None - if IP in pkt: - src = pkt[IP].src - dst = pkt[IP].dst - proto = pkt[IP].proto - elif IPv6 in pkt: - src = pkt[IPv6].src - dst = pkt[IPv6].dst - proto = pkt[IPv6].nh - else: - return - - key = (src, dst, proto) - f = self.flows[key] - - now = time.time() - if f["start_ts"] is None: - f["start_ts"] = now - f["end_ts"] = now - - size = len(pkt) - f["pkt_count"] += 1 - f["byte_count"] += size - f["sizes"].append(size) - - if TCP in pkt: - f["tcp_pkts"] += 1 - dport = pkt[TCP].dport - sport = pkt[TCP].sport - f["dst_ports"][dport] += 1 - f["src_ports"][sport] += 1 - - flags = pkt[TCP].flags - if flags & 0x02: f["tcp_flags"]["syn"] += 1 - if flags & 0x01: f["tcp_flags"]["fin"] += 1 - if flags & 0x04: f["tcp_flags"]["rst"] += 1 - if flags & 0x10: f["tcp_flags"]["ack"] += 1 - if flags & 0x08: f["tcp_flags"]["psh"] += 1 - if flags & 0x20: f["tcp_flags"]["urg"] += 1 - - if flags in [0x29, 0x3F, 0x3B]: - f["tcp_flags"]["xmas"] += 1 - if flags == 0: - f["tcp_flags"]["null"] += 1 - - elif UDP in pkt: - f["udp_pkts"] += 1 - dport = pkt[UDP].dport - sport = pkt[UDP].sport - f["dst_ports"][dport] += 1 - f["src_ports"][sport] += 1 - - elif ICMP in pkt: - f["icmp_pkts"] += 1 - - def _finish_window(self): - if not self.flows: - return {} - - total_flows = len(self.flows) - total_packets = 0 - total_bytes = 0 - - tcp_flags_global = { - "syn": 0, "fin": 0, "rst": 0, "ack": 0, - "psh": 0, "urg": 0, "xmas": 0, "null": 0 - } - - all_sizes = [] - proto_tcp = 0 - proto_udp = 0 - proto_icmp = 0 - - dst_port_counts = defaultdict(int) - src_port_counts = defaultdict(int) - - for f in self.flows.values(): - total_packets += f["pkt_count"] - total_bytes += f["byte_count"] - - for p, c in f["dst_ports"].items(): - dst_port_counts[p] += c - for p, c in f["src_ports"].items(): - src_port_counts[p] += c - - for k in tcp_flags_global: - tcp_flags_global[k] += f["tcp_flags"][k] - - all_sizes.extend(f["sizes"]) - proto_tcp += f["tcp_pkts"] - proto_udp += f["udp_pkts"] - proto_icmp += f["icmp_pkts"] - - window_len = self.window_duration - total_pkts = total_packets if total_packets > 0 else 1 - - feat = {} - - if "flow_count" in self.enabled: feat["flow_count"] = total_flows - if "total_packets" in self.enabled: feat["total_packets"] = total_packets - if "total_bytes" in self.enabled: feat["total_bytes"] = total_bytes - if "avg_bytes_per_flow" in self.enabled: feat["avg_bytes_per_flow"] = total_bytes / total_flows if total_flows else 0 - if "pkt_rate" in self.enabled: feat["pkt_rate"] = total_packets / window_len - if "byte_rate" in self.enabled: feat["byte_rate"] = total_bytes / window_len - - if "syn_count" in self.enabled: feat["syn_count"] = tcp_flags_global["syn"] - if "fin_count" in self.enabled: feat["fin_count"] = tcp_flags_global["fin"] - if "rst_count" in self.enabled: feat["rst_count"] = tcp_flags_global["rst"] - if "ack_count" in self.enabled: feat["ack_count"] = tcp_flags_global["ack"] - if "psh_count" in self.enabled: feat["psh_count"] = tcp_flags_global["psh"] - if "urg_count" in self.enabled: feat["urg_count"] = tcp_flags_global["urg"] - - if "syn_ratio" in self.enabled: feat["syn_ratio"] = tcp_flags_global["syn"] / total_pkts - if "fin_ratio" in self.enabled: feat["fin_ratio"] = tcp_flags_global["fin"] / total_pkts - if "xmas_total" in self.enabled: feat["xmas_total"] = tcp_flags_global["xmas"] - if "null_scan_total" in self.enabled: feat["null_scan_total"] = tcp_flags_global["null"] - - if "unique_dst_ports" in self.enabled: feat["unique_dst_ports"] = len(dst_port_counts) - if "unique_src_ports" in self.enabled: feat["unique_src_ports"] = len(src_port_counts) - if "port_entropy_dst" in self.enabled: feat["port_entropy_dst"] = entropy(dst_port_counts) - if "port_entropy_src" in self.enabled: feat["port_entropy_src"] = entropy(src_port_counts) - - if all_sizes: - if "avg_pkt_size" in self.enabled: feat["avg_pkt_size"] = mean(all_sizes) - if "min_pkt_size" in self.enabled: feat["min_pkt_size"] = min(all_sizes) - if "max_pkt_size" in self.enabled: feat["max_pkt_size"] = max(all_sizes) - if "std_pkt_size" in self.enabled: feat["std_pkt_size"] = pstdev(all_sizes) - else: - for k in ["avg_pkt_size", "min_pkt_size", "max_pkt_size", "std_pkt_size"]: - if k in self.enabled: feat[k] = 0 - - if "avg_packets_per_flow" in self.enabled: - feat["avg_packets_per_flow"] = total_packets / total_flows if total_flows else 0 - if "avg_bytes_per_packet" in self.enabled: - feat["avg_bytes_per_packet"] = total_bytes / total_pkts - - if "proto_tcp_ratio" in self.enabled: feat["proto_tcp_ratio"] = proto_tcp / total_pkts - if "proto_udp_ratio" in self.enabled: feat["proto_udp_ratio"] = proto_udp / total_pkts - if "proto_icmp_ratio" in self.enabled: feat["proto_icmp_ratio"] = proto_icmp / total_pkts - - return feat - - -FEATURE_LIST = [ - "flow_count", - "total_packets", - "total_bytes", - "avg_bytes_per_flow", - "pkt_rate", - "byte_rate", - - "syn_count", - "fin_count", - "rst_count", - "ack_count", - "psh_count", - "urg_count", - "syn_ratio", - "fin_ratio", - "xmas_total", - "null_scan_total", - - "unique_dst_ports", - "unique_src_ports", - "port_entropy_dst", - "port_entropy_src", - - "avg_pkt_size", - "min_pkt_size", - "max_pkt_size", - "std_pkt_size", - - "avg_packets_per_flow", - "avg_bytes_per_packet", - "proto_tcp_ratio", - "proto_udp_ratio", - "proto_icmp_ratio", -] - - -def entropy(values): - if not values: - return 0.0 - - total = sum(values.values()) - if total == 0: - return 0.0 - - entropy_val = 0.0 - for count in values.values(): - p = count / total - entropy_val -= p * math.log2(p) - - return entropy_val -- cgit v1.2.3