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

This commit is contained in:
2026-03-07 15:13:08 +01:00
parent 2bdbe230d1
commit a282b89d45
17 changed files with 533 additions and 52 deletions

View File

@@ -33,6 +33,7 @@ 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.ndpi_classifier import ndpi_classifier
from src.utilities.packet_identity import build_packet_uid
from src.utilities.packet_tracker import packet_tracker
@@ -61,8 +62,13 @@ sessions: Dict[str, Dict[str, Any]] = {}
class PacketInfo(TypedDict, total=False):
"""TypedDict for parsed packet data used by persistence and telemetry."""
correlation_key: str
correlation_source: str
packet_id: Optional[str]
packet_uid: str
skb_mark: Optional[int]
iface: str
capture_iface: Optional[str]
length: int
raw: bytes
src_mac: Optional[str]
@@ -85,6 +91,7 @@ class PacketInfo(TypedDict, total=False):
app_is_encrypted: Optional[bool]
app_risk_score: Optional[int]
dpi_metadata: Optional[Dict[str, Any]]
capture_metadata: Optional[Dict[str, Any]]
ip_id: Optional[int]
icmp_type: Optional[int]
icmp_code: Optional[int]
@@ -155,7 +162,7 @@ def _safe_get_attr(layer, attr: str):
return None
def parse_packet(pkt, bridge_label: str) -> None:
def parse_packet(pkt, bridge_label: str, capture_metadata: Optional[Dict[str, Any]] = None) -> None:
"""
Parse a scapy Packet object into a normalized PacketInfo and schedule DB insert.
bridge_label indicates whether the packet was captured as part of a bridge-snapshot or single-interface.
@@ -168,8 +175,11 @@ def parse_packet(pkt, bridge_label: str) -> None:
pkt_info: PacketInfo = {
"iface": pkt_iface,
"capture_iface": pkt_iface if capture_metadata else None,
"length": len(pkt),
"raw": bytes(pkt),
"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,
"dst_mac": None,
"eth_type_raw": None,
@@ -190,6 +200,7 @@ def parse_packet(pkt, bridge_label: str) -> None:
"app_is_encrypted": None,
"app_risk_score": None,
"dpi_metadata": None,
"capture_metadata": capture_metadata,
"ip_id": None,
"icmp_type": None,
"icmp_code": None,
@@ -369,7 +380,13 @@ def parse_packet(pkt, bridge_label: str) -> None:
pkt_info["icmp_embedded_src_port"] = _safe_get_attr(inner[UDP], "sport")
pkt_info["icmp_embedded_dst_port"] = _safe_get_attr(inner[UDP], "dport")
pkt_info["packet_uid"] = build_packet_uid(pkt_info)
if pkt_info.get("packet_id"):
pkt_info["correlation_key"] = f"pid:{pkt_info['packet_id']}"
pkt_info["correlation_source"] = "kernel_mark"
else:
pkt_info["packet_uid"] = build_packet_uid(pkt_info)
pkt_info["correlation_key"] = f"uid:{pkt_info['packet_uid']}"
pkt_info["correlation_source"] = "legacy_hash"
try:
web_loop = getattr(shared_objects, "web_loop", None)
@@ -548,9 +565,13 @@ def _session_reader_loop(session_id: str) -> None:
# parse with scapy
try:
pkt = Ether(raw)
capture_meta = None
packet_bytes = raw
if settings.capture_header_enabled and settings.capture_interface and iface == settings.capture_interface:
capture_meta, packet_bytes = parse_capture_header(raw)
pkt = Ether(packet_bytes)
pkt.sniffed_on = iface
parse_packet(pkt, label)
parse_packet(pkt, label, capture_metadata=capture_meta)
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)
@@ -595,19 +616,23 @@ def start_afpacket_sniffer(target: str, target_is_interface: bool = False) -> st
"label": target,
"is_bridge": not target_is_interface,
"ports": [],
"capture_ifaces": [],
}
sessions[session_id] = session
# determine ports for this session
if target_is_interface:
ports = [target]
capture_ifaces = [target]
else:
ports = get_bridge_ports_once(target)
capture_ifaces = [settings.capture_interface] if settings.capture_interface else list(ports)
session["ports"] = ports
session["capture_ifaces"] = capture_ifaces
# create sockets for this session only
for iface in ports:
for iface in capture_ifaces:
if not check_interface_exists(iface):
logger.warning("Snapshot port %s missing for session %s, skipping", iface, session_id)
continue
@@ -620,7 +645,13 @@ def start_afpacket_sniffer(target: str, target_is_interface: bool = False) -> st
session["thread"] = t
t.start()
_sync_bridge_telemetry()
logger.info("Started sniffer session %s label=%s ports=%s", session_id, target, ports)
logger.info(
"Started sniffer session %s label=%s ports=%s capture_ifaces=%s",
session_id,
target,
ports,
capture_ifaces,
)
return session_id
@@ -718,6 +749,7 @@ def get_internal_debug_state() -> dict:
"label": s.get("label"),
"is_bridge": s.get("is_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()),
}