This commit is contained in:
@@ -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(),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user