test af_packet mode on bridges
This commit is contained in:
@@ -6,6 +6,8 @@ from fastapi import APIRouter, Body, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from src.network_sniffer import (
|
||||
BRIDGE_CAPTURE_MODE_AF_PACKET,
|
||||
BRIDGE_CAPTURE_MODE_TC_EBPF,
|
||||
get_internal_debug_state,
|
||||
get_capture_session_status,
|
||||
start_capture_session,
|
||||
@@ -28,6 +30,11 @@ class SnifferStartRequest(BaseModel):
|
||||
example="eth0",
|
||||
description="Interface name to sniff.",
|
||||
)
|
||||
bridge_capture_mode: Optional[str] = Field(
|
||||
None,
|
||||
example="tc_ebpf",
|
||||
description="Bridge capture mode: 'tc_ebpf' or 'af_packet'. Ignored for interface capture.",
|
||||
)
|
||||
|
||||
|
||||
class SnifferStartResponse(BaseModel):
|
||||
@@ -37,6 +44,7 @@ class SnifferStartResponse(BaseModel):
|
||||
session_id: str = Field(..., description="Unique session identifier.")
|
||||
target: str = Field(..., description="Started target name.")
|
||||
target_type: str = Field(..., description="Either 'bridge' or 'interface'.")
|
||||
capture_mode: str = Field(..., description="The effective capture mode used by the session.")
|
||||
|
||||
|
||||
class SnifferStopRequest(BaseModel):
|
||||
@@ -62,6 +70,7 @@ class InterfaceSnifferStatus(BaseModel):
|
||||
up: bool = Field(..., description="Whether the interface is operationally up.")
|
||||
session_id: Optional[str] = Field(None, description="Owning capture session ID.")
|
||||
session_label: Optional[str] = Field(None, description="Human-readable session label.")
|
||||
capture_mode: Optional[str] = Field(None, description="Capture mode used by the owning session.")
|
||||
|
||||
|
||||
class SnifferStatusResponse(BaseModel):
|
||||
@@ -87,14 +96,23 @@ def sniffer_start(req: SnifferStartRequest) -> SnifferStartResponse:
|
||||
session_id=session_id,
|
||||
target=req.interface,
|
||||
target_type="interface",
|
||||
capture_mode="af_packet",
|
||||
)
|
||||
|
||||
session_id = start_capture_session(req.bridge, target_is_interface=False)
|
||||
effective_capture_mode = req.bridge_capture_mode or BRIDGE_CAPTURE_MODE_TC_EBPF
|
||||
if effective_capture_mode not in {BRIDGE_CAPTURE_MODE_TC_EBPF, BRIDGE_CAPTURE_MODE_AF_PACKET}:
|
||||
raise HTTPException(status_code=400, detail="bridge_capture_mode must be 'tc_ebpf' or 'af_packet'")
|
||||
session_id = start_capture_session(
|
||||
req.bridge,
|
||||
target_is_interface=False,
|
||||
bridge_capture_mode=effective_capture_mode,
|
||||
)
|
||||
return SnifferStartResponse(
|
||||
started=True,
|
||||
session_id=session_id,
|
||||
target=req.bridge,
|
||||
target_type="bridge",
|
||||
capture_mode=effective_capture_mode,
|
||||
)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to start sniffer: {exc}") from exc
|
||||
|
||||
@@ -56,6 +56,9 @@ sessions: Dict[str, Dict[str, Any]] = {}
|
||||
# "ports": List[str],
|
||||
# }
|
||||
|
||||
BRIDGE_CAPTURE_MODE_TC_EBPF = "tc_ebpf"
|
||||
BRIDGE_CAPTURE_MODE_AF_PACKET = "af_packet"
|
||||
|
||||
# -------------------------
|
||||
# PacketInfo typing
|
||||
# -------------------------
|
||||
@@ -545,7 +548,7 @@ def _sync_bridge_telemetry() -> None:
|
||||
bridge_session_interfaces = {
|
||||
session_id: list(session.get("ports", []))
|
||||
for session_id, session in sessions.items()
|
||||
if session.get("is_bridge")
|
||||
if session.get("is_bridge") and session.get("capture_mode") == BRIDGE_CAPTURE_MODE_TC_EBPF
|
||||
}
|
||||
try:
|
||||
bridge_telemetry_manager.update_sessions(bridge_session_interfaces)
|
||||
@@ -558,7 +561,11 @@ def _sync_bridge_telemetry() -> None:
|
||||
for session in sessions.values()
|
||||
for iface in (
|
||||
list(session.get("capture_ifaces", []))
|
||||
+ (list(session.get("ports", [])) if session.get("is_bridge") else [])
|
||||
+ (
|
||||
list(session.get("ports", []))
|
||||
if session.get("is_bridge") and session.get("capture_mode") == BRIDGE_CAPTURE_MODE_TC_EBPF
|
||||
else []
|
||||
)
|
||||
)
|
||||
if iface
|
||||
}
|
||||
@@ -677,20 +684,31 @@ def _session_reader_loop(session_id: str) -> None:
|
||||
# -------------------------
|
||||
# Public API: start/stop/status
|
||||
# -------------------------
|
||||
def start_capture_session(target: str, target_is_interface: bool = False) -> str:
|
||||
def start_capture_session(
|
||||
target: str,
|
||||
target_is_interface: bool = False,
|
||||
bridge_capture_mode: str = BRIDGE_CAPTURE_MODE_TC_EBPF,
|
||||
) -> str:
|
||||
"""
|
||||
Start a packet capture session. Returns session_id string.
|
||||
|
||||
If target_is_interface == True, capture uses an AF_PACKET raw socket on that interface.
|
||||
If target_is_interface == False, target is treated as a bridge and capture uses the
|
||||
tc/eBPF bridge telemetry path for the bridge ports.
|
||||
configured bridge capture mode for the bridge ports.
|
||||
"""
|
||||
effective_capture_mode = (
|
||||
BRIDGE_CAPTURE_MODE_AF_PACKET
|
||||
if target_is_interface or bridge_capture_mode == BRIDGE_CAPTURE_MODE_AF_PACKET
|
||||
else BRIDGE_CAPTURE_MODE_TC_EBPF
|
||||
)
|
||||
|
||||
session_id = str(uuid4())
|
||||
session: Dict[str, Any] = {
|
||||
"stop_event": threading.Event(),
|
||||
"sockets": {},
|
||||
"label": target,
|
||||
"is_bridge": not target_is_interface,
|
||||
"capture_mode": effective_capture_mode,
|
||||
"ports": [],
|
||||
"capture_ifaces": [],
|
||||
}
|
||||
@@ -701,7 +719,7 @@ def start_capture_session(target: str, target_is_interface: bool = False) -> str
|
||||
capture_ifaces = [target]
|
||||
else:
|
||||
ports = get_bridge_ports_once(target)
|
||||
capture_ifaces = []
|
||||
capture_ifaces = ports if effective_capture_mode == BRIDGE_CAPTURE_MODE_AF_PACKET else []
|
||||
|
||||
session["ports"] = ports
|
||||
session["capture_ifaces"] = capture_ifaces
|
||||
@@ -729,9 +747,10 @@ def start_capture_session(target: str, target_is_interface: bool = False) -> str
|
||||
session["thread"] = None
|
||||
_sync_bridge_telemetry()
|
||||
logger.info(
|
||||
"Started capture session %s label=%s ports=%s capture_ifaces=%s",
|
||||
"Started capture session %s label=%s capture_mode=%s ports=%s capture_ifaces=%s",
|
||||
session_id,
|
||||
target,
|
||||
effective_capture_mode,
|
||||
ports,
|
||||
capture_ifaces,
|
||||
)
|
||||
@@ -839,6 +858,7 @@ def get_capture_session_status() -> Dict[str, Dict[str, object]]:
|
||||
"up": check_interface_up(iface),
|
||||
"session_id": sid,
|
||||
"session_label": s.get("label"),
|
||||
"capture_mode": s.get("capture_mode"),
|
||||
}
|
||||
if not s.get("sockets") and s.get("is_bridge"):
|
||||
for iface in s.get("ports", []):
|
||||
@@ -848,6 +868,7 @@ def get_capture_session_status() -> Dict[str, Dict[str, object]]:
|
||||
"up": check_interface_up(iface),
|
||||
"session_id": sid,
|
||||
"session_label": s.get("label"),
|
||||
"capture_mode": s.get("capture_mode"),
|
||||
}
|
||||
return out
|
||||
|
||||
@@ -861,6 +882,7 @@ def get_internal_debug_state() -> dict:
|
||||
sid: {
|
||||
"label": s.get("label"),
|
||||
"is_bridge": s.get("is_bridge"),
|
||||
"capture_mode": s.get("capture_mode"),
|
||||
"ports": list(s.get("ports", [])),
|
||||
"capture_ifaces": list(s.get("capture_ifaces", [])),
|
||||
"sockets": list(s.get("sockets", {}).keys()),
|
||||
@@ -870,16 +892,35 @@ def get_internal_debug_state() -> dict:
|
||||
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", [])}),
|
||||
"bridge_capture_mode": (
|
||||
"tc_ingress_raw"
|
||||
if any(s.get("is_bridge") and s.get("capture_mode") == BRIDGE_CAPTURE_MODE_TC_EBPF for s in sessions.values())
|
||||
else "af_packet"
|
||||
),
|
||||
"telemetry_ports": sorted(
|
||||
{
|
||||
iface
|
||||
for session in sessions.values()
|
||||
if session.get("capture_mode") == BRIDGE_CAPTURE_MODE_TC_EBPF
|
||||
for iface in session.get("ports", [])
|
||||
}
|
||||
),
|
||||
"tshark": tshark_manager.get_debug_snapshot(),
|
||||
"packet_tracker": packet_tracker.get_debug_snapshot(),
|
||||
}
|
||||
|
||||
|
||||
def start_afpacket_sniffer(target: str, target_is_interface: bool = False) -> str:
|
||||
def start_afpacket_sniffer(
|
||||
target: str,
|
||||
target_is_interface: bool = False,
|
||||
bridge_capture_mode: str = BRIDGE_CAPTURE_MODE_TC_EBPF,
|
||||
) -> str:
|
||||
"""Backward-compatible wrapper for start_capture_session()."""
|
||||
return start_capture_session(target, target_is_interface=target_is_interface)
|
||||
return start_capture_session(
|
||||
target,
|
||||
target_is_interface=target_is_interface,
|
||||
bridge_capture_mode=bridge_capture_mode,
|
||||
)
|
||||
|
||||
|
||||
def stop_afpacket_sniffer(
|
||||
|
||||
Reference in New Issue
Block a user