Files
routeros-suricata-tzsp/app/state.py
T
2026-08-14 11:33:01 +02:00

91 lines
3.1 KiB
Python

from __future__ import annotations
import threading
from datetime import datetime, timezone
from typing import Any
class RuntimeStats:
def __init__(self) -> None:
self._lock = threading.Lock()
self._data = self._new_counters()
self._suricata: dict[str, int | float] = {}
self._suricata_timestamp: str | None = None
@staticmethod
def _new_counters() -> dict[str, Any]:
return {
"tzsp_datagrams": 0,
"tzsp_decode_errors": 0,
"tzsp_unsupported": 0,
"frames_injected": 0,
"inject_errors": 0,
"eve_events": 0,
"eve_alerts": 0,
"eve_parse_errors": 0,
"alerts_filtered": 0,
"alerts_filtered_low_priority": 0,
"alerts_filtered_ignored_sid": 0,
"alerts_filtered_ignored_category": 0,
"alerts_deduplicated": 0,
"block_attempts": 0,
"block_success": 0,
"block_errors": 0,
"last_packet_at": None,
"last_alert_at": None,
}
def inc(self, key: str, amount: int = 1) -> None:
with self._lock:
self._data[key] = int(self._data.get(key, 0)) + amount
def stamp(self, key: str) -> None:
with self._lock:
self._data[key] = datetime.now(timezone.utc).isoformat()
def update_suricata(self, stats: dict[str, Any], timestamp: str | None = None) -> None:
flattened: dict[str, int | float] = {}
_flatten_numeric("", stats, flattened, 240)
with self._lock:
self._suricata = flattened
self._suricata_timestamp = timestamp or datetime.now(timezone.utc).isoformat()
def reset(self) -> None:
with self._lock:
last_packet = self._data.get("last_packet_at")
last_alert = self._data.get("last_alert_at")
self._data = self._new_counters()
self._data["last_packet_at"] = last_packet
self._data["last_alert_at"] = last_alert
def snapshot(self) -> dict:
with self._lock:
data = dict(self._data)
data["suricata"] = dict(self._suricata)
data["suricata_stats_at"] = self._suricata_timestamp
datagrams = int(data.get("tzsp_datagrams", 0))
frames = int(data.get("frames_injected", 0))
data["tzsp_to_tap_loss"] = max(datagrams - frames, 0)
attempts = int(data.get("block_attempts", 0))
success = int(data.get("block_success", 0))
data["block_success_rate"] = round((success / attempts) * 100.0, 2) if attempts else None
return data
def _flatten_numeric(
prefix: str,
value: Any,
output: dict[str, int | float],
limit: int,
) -> None:
if len(output) >= limit:
return
if isinstance(value, dict):
for key, child in value.items():
name = f"{prefix}.{key}" if prefix else str(key)
_flatten_numeric(name, child, output, limit)
if len(output) >= limit:
return
elif isinstance(value, (int, float)) and not isinstance(value, bool):
output[prefix] = value