tc test
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 10s

This commit is contained in:
2026-03-07 16:36:24 +01:00
parent f55e899fc5
commit 0c69daf229
13 changed files with 459 additions and 609 deletions

View File

@@ -33,8 +33,6 @@ from src.utilities.interface_bridge_helpers import (
)
from src.config import settings
from src.utilities.bridge_telemetry import bridge_telemetry_manager
from src.utilities.capture_header import parse_capture_header
from src.utilities.capture_pipeline import capture_pipeline_manager
from src.utilities.ndpi_classifier import ndpi_classifier
from src.utilities.packet_identity import build_packet_uid
from src.utilities.packet_tracker import packet_tracker
@@ -68,6 +66,7 @@ class PacketInfo(TypedDict, total=False):
packet_id: Optional[str]
packet_uid: str
skb_mark: Optional[int]
capture_source: str
iface: str
capture_iface: Optional[str]
length: int
@@ -176,9 +175,10 @@ def parse_packet(pkt, bridge_label: str, capture_metadata: Optional[Dict[str, An
pkt_info: PacketInfo = {
"iface": pkt_iface,
"capture_iface": pkt_iface if capture_metadata else None,
"capture_iface": None,
"length": len(pkt),
"raw": bytes(pkt),
"capture_source": "tc_ingress_raw" if capture_metadata else "af_packet",
"packet_id": capture_metadata.get("packet_id") if capture_metadata else None,
"skb_mark": capture_metadata.get("skb_mark") if capture_metadata else None,
"src_mac": None,
@@ -412,6 +412,17 @@ def parse_packet(pkt, bridge_label: str, capture_metadata: Optional[Dict[str, An
logger.exception("Failed to schedule DB insert (unexpected)")
def parse_packet_bytes(
packet_bytes: bytes,
iface: str,
capture_metadata: Optional[Dict[str, Any]] = None,
) -> None:
"""Parse one raw Ethernet frame using the shared Scapy packet path."""
pkt = Ether(packet_bytes)
pkt.sniffed_on = iface
parse_packet(pkt, iface, capture_metadata=capture_metadata)
# -------------------------
# AF_PACKET socket utilities
# -------------------------
@@ -484,22 +495,20 @@ def _ensure_socket_for_session(sockets: Dict[str, socket.socket], iface: str, br
def _sync_bridge_telemetry() -> None:
interfaces = sorted({iface for session in sessions.values() for iface in session.get("ports", [])})
interfaces = sorted(
{
iface
for session in sessions.values()
if session.get("is_bridge")
for iface in session.get("ports", [])
}
)
try:
bridge_telemetry_manager.update_interfaces(interfaces)
except Exception:
logger.exception("Failed to update bridge telemetry collector")
def _managed_capture_ifaces() -> set[str]:
return {
iface
for session in sessions.values()
if session.get("is_bridge")
for iface in session.get("capture_ifaces", [])
}
# -------------------------
# Per-session reader loop
# -------------------------
@@ -575,16 +584,9 @@ def _session_reader_loop(session_id: str) -> None:
# parse with scapy
try:
capture_meta = None
packet_bytes = raw
if settings.capture_header_enabled:
capture_meta, packet_bytes = parse_capture_header(raw)
if capture_meta is None and iface in _managed_capture_ifaces():
logger.debug("Skipping unshimmed packet on managed capture interface %s", iface)
continue
pkt = Ether(packet_bytes)
pkt = Ether(raw)
pkt.sniffed_on = iface
parse_packet(pkt, label, capture_metadata=capture_meta)
parse_packet(pkt, label)
logger.debug("Captured packet on %s in session %s (len=%d)", iface, session_id, len(raw))
except Exception:
logger.exception("Failed to parse/process packet from %s in session %s", iface, session_id)
@@ -630,7 +632,6 @@ def start_afpacket_sniffer(target: str, target_is_interface: bool = False) -> st
"is_bridge": not target_is_interface,
"ports": [],
"capture_ifaces": [],
"capture_bridge": None,
}
# determine ports for this session
@@ -639,13 +640,7 @@ def start_afpacket_sniffer(target: str, target_is_interface: bool = False) -> st
capture_ifaces = [target]
else:
ports = get_bridge_ports_once(target)
capture_iface = None
if settings.capture_automanage:
capture_iface = capture_pipeline_manager.acquire(target)
session["capture_bridge"] = target
elif settings.capture_interface:
capture_iface = settings.capture_interface
capture_ifaces = [capture_iface] if capture_iface else list(ports)
capture_ifaces = []
session["ports"] = ports
session["capture_ifaces"] = capture_ifaces
@@ -662,17 +657,15 @@ def start_afpacket_sniffer(target: str, target_is_interface: bool = False) -> st
session["sockets"][iface] = s
except Exception:
sessions.pop(session_id, None)
if session.get("capture_bridge"):
try:
capture_pipeline_manager.release(session["capture_bridge"])
except Exception:
logger.exception("Failed to release capture pipeline after startup error")
raise
# start dedicated thread
t = threading.Thread(target=_session_reader_loop, args=(session_id,), daemon=True)
session["thread"] = t
t.start()
if capture_ifaces:
t = threading.Thread(target=_session_reader_loop, args=(session_id,), daemon=True)
session["thread"] = t
t.start()
else:
session["thread"] = None
_sync_bridge_telemetry()
logger.info(
"Started sniffer session %s label=%s ports=%s capture_ifaces=%s",
@@ -701,12 +694,6 @@ def stop_afpacket_sniffer(session_id: Optional[str] = None, target: Optional[str
t = s.get("thread")
if t and isinstance(t, threading.Thread):
t.join(timeout=settings.sniffer_thread_join_timeout_seconds)
capture_bridge = s.get("capture_bridge")
if capture_bridge:
try:
capture_pipeline_manager.release(capture_bridge)
except Exception:
logger.exception("Failed to release capture pipeline for bridge %s", capture_bridge)
_sync_bridge_telemetry()
logger.info("Stopped session %s", session_id)
return
@@ -715,10 +702,13 @@ def stop_afpacket_sniffer(session_id: Optional[str] = None, target: Optional[str
if target:
# For interface-mode: close that iface in any session that has it
for sid, s in list(sessions.items()):
if not target_is_interface and s.get("is_bridge") and s.get("label") == target:
stop_afpacket_sniffer(session_id=sid)
continue
if target_is_interface and not s.get("is_bridge") and s.get("label") == target:
stop_afpacket_sniffer(session_id=sid)
continue
if target in s.get("ports", []):
if s.get("capture_bridge") == target:
stop_afpacket_sniffer(session_id=sid)
continue
# close only that iface socket in that session
sock = s["sockets"].pop(target, None)
if sock:
@@ -774,6 +764,15 @@ def get_sniffer_status() -> Dict[str, Dict[str, object]]:
"session_id": sid,
"session_label": s.get("label"),
}
if not s.get("sockets") and s.get("is_bridge"):
for iface in s.get("ports", []):
out[iface] = {
"running": not s.get("stop_event").is_set(),
"exists": check_interface_exists(iface),
"up": check_interface_up(iface),
"session_id": sid,
"session_label": s.get("label"),
}
return out
@@ -786,16 +785,16 @@ def get_internal_debug_state() -> dict:
sid: {
"label": s.get("label"),
"is_bridge": s.get("is_bridge"),
"capture_bridge": s.get("capture_bridge"),
"ports": list(s.get("ports", [])),
"capture_ifaces": list(s.get("capture_ifaces", [])),
"sockets": list(s.get("sockets", {}).keys()),
"thread_alive": bool(s.get("thread") and s.get("thread").is_alive()),
"active": not s.get("stop_event").is_set(),
}
for sid, s in sessions.items()
},
"buffer_len": len(_PACKET_BUFFER),
"bridge_capture_mode": "tc_ingress_raw" if any(s.get("is_bridge") for s in sessions.values()) else "af_packet",
"telemetry_ports": sorted({iface for session in sessions.values() for iface in session.get("ports", [])}),
"capture_pipeline": capture_pipeline_manager.get_debug_snapshot(),
"packet_tracker": packet_tracker.get_debug_snapshot(),
}