test ebpf
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 1m40s
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 1m40s
This commit is contained in:
128
backend/src/utilities/bridge_telemetry.py
Normal file
128
backend/src/utilities/bridge_telemetry.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""Manage the eBPF telemetry subprocess used for bridge direction and verdict events."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from typing import Iterable, Optional
|
||||
|
||||
from src.utilities.packet_tracker import packet_tracker
|
||||
|
||||
logger = logging.getLogger("bridge_telemetry")
|
||||
|
||||
|
||||
class BridgeTelemetryManager:
|
||||
"""Run a single eBPF collector process for the active sniffed interfaces."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._interfaces: set[str] = set()
|
||||
self._process: Optional[subprocess.Popen[str]] = None
|
||||
self._reader_thread: Optional[threading.Thread] = None
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def update_interfaces(self, interfaces: Iterable[str]) -> None:
|
||||
"""Restart the collector when the active interface set changes."""
|
||||
normalized = {iface.strip() for iface in interfaces if iface and iface.strip()}
|
||||
with self._lock:
|
||||
if normalized == self._interfaces:
|
||||
return
|
||||
self._interfaces = normalized
|
||||
self._restart_locked()
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stop the collector process and reader thread."""
|
||||
with self._lock:
|
||||
self._interfaces = set()
|
||||
self._stop_process_locked()
|
||||
|
||||
def _restart_locked(self) -> None:
|
||||
self._stop_process_locked()
|
||||
if not self._interfaces:
|
||||
return
|
||||
|
||||
helper = Path(__file__).with_name("ebpf_bridge_events.py")
|
||||
python_bin = sys.executable or "python3"
|
||||
if "venv" in python_bin:
|
||||
python_bin = "python3"
|
||||
|
||||
env = os.environ.copy()
|
||||
env["PYTHONUNBUFFERED"] = "1"
|
||||
|
||||
cmd = [python_bin, str(helper), "--ifaces", ",".join(sorted(self._interfaces))]
|
||||
logger.info("Starting bridge telemetry collector for interfaces=%s", sorted(self._interfaces))
|
||||
try:
|
||||
self._process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
env=env,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Failed to start bridge telemetry collector")
|
||||
self._process = None
|
||||
return
|
||||
|
||||
self._reader_thread = threading.Thread(
|
||||
target=self._read_loop,
|
||||
args=(self._process,),
|
||||
daemon=True,
|
||||
name="bridge-telemetry-reader",
|
||||
)
|
||||
self._reader_thread.start()
|
||||
|
||||
def _stop_process_locked(self) -> None:
|
||||
process = self._process
|
||||
reader = self._reader_thread
|
||||
self._process = None
|
||||
self._reader_thread = None
|
||||
|
||||
if process is not None and process.poll() is None:
|
||||
try:
|
||||
process.send_signal(signal.SIGTERM)
|
||||
process.wait(timeout=3)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
except Exception:
|
||||
logger.exception("Failed to stop bridge telemetry collector cleanly")
|
||||
|
||||
if reader is not None and reader.is_alive():
|
||||
reader.join(timeout=2)
|
||||
|
||||
def _read_loop(self, process: subprocess.Popen[str]) -> None:
|
||||
stdout = process.stdout
|
||||
if stdout is None:
|
||||
return
|
||||
|
||||
for line in stdout:
|
||||
text = line.strip()
|
||||
if not text:
|
||||
continue
|
||||
try:
|
||||
event = json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
logger.info("bridge-telemetry: %s", text)
|
||||
continue
|
||||
|
||||
if "event_type" not in event:
|
||||
logger.info("bridge-telemetry: %s", event)
|
||||
continue
|
||||
|
||||
try:
|
||||
packet_tracker.observe_telemetry(event)
|
||||
except Exception:
|
||||
logger.exception("Failed to process telemetry event: %s", event)
|
||||
|
||||
rc = process.poll()
|
||||
if rc not in (0, None):
|
||||
logger.warning("Bridge telemetry collector exited with code %s", rc)
|
||||
|
||||
|
||||
bridge_telemetry_manager = BridgeTelemetryManager()
|
||||
Reference in New Issue
Block a user