AF_PACKET terminology cleanup
All checks were successful
Build and Deploy MITM Webserver / traffic_target (push) Successful in 0s
Build and Deploy MITM Webserver / build (push) Successful in 10s

This commit is contained in:
2026-04-12 17:41:22 +02:00
parent cb4c6ff564
commit 9ee47284e0
11 changed files with 69 additions and 45 deletions

0
.codex Normal file
View File

View File

@@ -1,4 +1,4 @@
"""HTTP API for starting, stopping, and inspecting sniffer sessions.""" """HTTP API for starting, stopping, and inspecting packet capture sessions."""
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
@@ -7,16 +7,16 @@ from pydantic import BaseModel, Field
from src.network_sniffer import ( from src.network_sniffer import (
get_internal_debug_state, get_internal_debug_state,
get_sniffer_status, get_capture_session_status,
start_afpacket_sniffer, start_capture_session,
stop_afpacket_sniffer, stop_capture_session,
) )
router = APIRouter() router = APIRouter()
class SnifferStartRequest(BaseModel): class SnifferStartRequest(BaseModel):
"""Request payload for starting a sniffer session.""" """Request payload for starting a packet capture session."""
bridge: Optional[str] = Field( bridge: Optional[str] = Field(
None, None,
@@ -60,7 +60,7 @@ class InterfaceSnifferStatus(BaseModel):
running: bool = Field(..., description="Whether a sniffer is currently active.") running: bool = Field(..., description="Whether a sniffer is currently active.")
exists: bool = Field(..., description="Whether the interface exists on the host.") exists: bool = Field(..., description="Whether the interface exists on the host.")
up: bool = Field(..., description="Whether the interface is operationally up.") up: bool = Field(..., description="Whether the interface is operationally up.")
session_id: Optional[str] = Field(None, description="Owning sniffer session ID.") session_id: Optional[str] = Field(None, description="Owning capture session ID.")
session_label: Optional[str] = Field(None, description="Human-readable session label.") session_label: Optional[str] = Field(None, description="Human-readable session label.")
@@ -75,13 +75,13 @@ class SnifferStatusResponse(BaseModel):
@router.post("/start", response_model=SnifferStartResponse) @router.post("/start", response_model=SnifferStartResponse)
def sniffer_start(req: SnifferStartRequest) -> SnifferStartResponse: def sniffer_start(req: SnifferStartRequest) -> SnifferStartResponse:
"""Start one sniffer session for exactly one target.""" """Start one packet capture session for exactly one target."""
if bool(req.bridge) == bool(req.interface): if bool(req.bridge) == bool(req.interface):
raise HTTPException(status_code=400, detail="Exactly one of 'bridge' or 'interface' must be provided") raise HTTPException(status_code=400, detail="Exactly one of 'bridge' or 'interface' must be provided")
try: try:
if req.interface: if req.interface:
session_id = start_afpacket_sniffer(req.interface, target_is_interface=True) session_id = start_capture_session(req.interface, target_is_interface=True)
return SnifferStartResponse( return SnifferStartResponse(
started=True, started=True,
session_id=session_id, session_id=session_id,
@@ -89,7 +89,7 @@ def sniffer_start(req: SnifferStartRequest) -> SnifferStartResponse:
target_type="interface", target_type="interface",
) )
session_id = start_afpacket_sniffer(req.bridge, target_is_interface=False) session_id = start_capture_session(req.bridge, target_is_interface=False)
return SnifferStartResponse( return SnifferStartResponse(
started=True, started=True,
session_id=session_id, session_id=session_id,
@@ -117,7 +117,7 @@ def sniffer_stop(
"""Stop by session ID, target query, or globally when no selector is given.""" """Stop by session ID, target query, or globally when no selector is given."""
if body and body.session_id: if body and body.session_id:
try: try:
stop_afpacket_sniffer(session_id=body.session_id) stop_capture_session(session_id=body.session_id)
return SnifferStopResponse(stopped=True, session_id=body.session_id, target=None, target_type=None) return SnifferStopResponse(stopped=True, session_id=body.session_id, target=None, target_type=None)
except Exception as exc: except Exception as exc:
raise HTTPException(status_code=500, detail=f"Failed to stop session {body.session_id}: {exc}") from exc raise HTTPException(status_code=500, detail=f"Failed to stop session {body.session_id}: {exc}") from exc
@@ -127,14 +127,14 @@ def sniffer_stop(
try: try:
if q_interface: if q_interface:
stop_afpacket_sniffer(target=q_interface, target_is_interface=True) stop_capture_session(target=q_interface, target_is_interface=True)
return SnifferStopResponse(stopped=True, session_id=None, target=q_interface, target_type="interface") return SnifferStopResponse(stopped=True, session_id=None, target=q_interface, target_type="interface")
if q_bridge: if q_bridge:
stop_afpacket_sniffer(target=q_bridge, target_is_interface=False) stop_capture_session(target=q_bridge, target_is_interface=False)
return SnifferStopResponse(stopped=True, session_id=None, target=q_bridge, target_type="bridge") return SnifferStopResponse(stopped=True, session_id=None, target=q_bridge, target_type="bridge")
stop_afpacket_sniffer() stop_capture_session()
return SnifferStopResponse(stopped=True, session_id=None, target=None, target_type=None) return SnifferStopResponse(stopped=True, session_id=None, target=None, target_type=None)
except Exception as exc: except Exception as exc:
raise HTTPException(status_code=500, detail=f"Failed to stop sniffer: {exc}") from exc raise HTTPException(status_code=500, detail=f"Failed to stop sniffer: {exc}") from exc
@@ -142,9 +142,9 @@ def sniffer_stop(
@router.get("/status", response_model=SnifferStatusResponse) @router.get("/status", response_model=SnifferStatusResponse)
def sniffer_status() -> SnifferStatusResponse: def sniffer_status() -> SnifferStatusResponse:
"""Return current sniffer status per interface.""" """Return current capture-session status per interface."""
try: try:
raw: Dict[str, Dict[str, Any]] = get_sniffer_status() raw: Dict[str, Dict[str, Any]] = get_capture_session_status()
typed = {key: InterfaceSnifferStatus(**value) for key, value in raw.items()} typed = {key: InterfaceSnifferStatus(**value) for key, value in raw.items()}
return SnifferStatusResponse(interfaces=typed) return SnifferStatusResponse(interfaces=typed)
except Exception as exc: except Exception as exc:
@@ -153,7 +153,7 @@ def sniffer_status() -> SnifferStatusResponse:
@router.get("/debug") @router.get("/debug")
def sniffer_debug() -> Dict[str, Any]: def sniffer_debug() -> Dict[str, Any]:
"""Return internal sniffer and packet-tracker debug state.""" """Return internal capture-session and packet-tracker debug state."""
try: try:
return get_internal_debug_state() return get_internal_debug_state()
except Exception as exc: except Exception as exc:

View File

@@ -82,11 +82,11 @@ async def on_startup() -> None:
async def shutdown_event() -> None: async def shutdown_event() -> None:
"""Stop network resources and release shared runtime objects.""" """Stop network resources and release shared runtime objects."""
try: try:
from src.network_sniffer import stop_afpacket_sniffer from src.network_sniffer import stop_capture_session
stop_afpacket_sniffer() stop_capture_session()
except Exception: except Exception:
logging.exception("Failed to stop sniffer sessions during shutdown") logging.exception("Failed to stop capture sessions during shutdown")
try: try:
network_api.shutdown_network_api() network_api.shutdown_network_api()

View File

@@ -41,7 +41,7 @@ from src.Models.etherType import EtherTypeEnum, ethertype_from_int
from src.Models.ip_protocol import IPProtocolEnum, protocol_from_number from src.Models.ip_protocol import IPProtocolEnum, protocol_from_number
# ---- Logging ---------------------------------------------------------- # ---- Logging ----------------------------------------------------------
logger = logging.getLogger("af_packet_sniffer") logger = logging.getLogger("packet_capture")
# ---- Session model ------------------------------------------- # ---- Session model -------------------------------------------
# sessions: session_id -> session dict # sessions: session_id -> session dict
@@ -471,11 +471,11 @@ def parse_packet_bytes(
# ------------------------- # -------------------------
# AF_PACKET socket utilities # Raw socket utilities for interface capture
# ------------------------- # -------------------------
def _create_af_packet_socket(ifname: str, rx_buf: Optional[int] = None) -> Optional[socket.socket]: def _create_af_packet_socket(ifname: str, rx_buf: Optional[int] = None) -> Optional[socket.socket]:
""" """
Create and bind an AF_PACKET raw socket to interface. Create and bind an AF_PACKET raw socket for direct interface capture.
Non-blocking socket returned or None on failure. Non-blocking socket returned or None on failure.
""" """
try: try:
@@ -677,12 +677,13 @@ def _session_reader_loop(session_id: str) -> None:
# ------------------------- # -------------------------
# Public API: start/stop/status # Public API: start/stop/status
# ------------------------- # -------------------------
def start_afpacket_sniffer(target: str, target_is_interface: bool = False) -> str: def start_capture_session(target: str, target_is_interface: bool = False) -> str:
""" """
Start a sniffer session. Returns session_id string. Start a packet capture session. Returns session_id string.
If target_is_interface == False: target is treated as bridge name and ports are snapshotted. If target_is_interface == True, capture uses an AF_PACKET raw socket on that interface.
If target_is_interface == True: target is treated as interface name. If target_is_interface == False, target is treated as a bridge and capture uses the
tc/eBPF bridge telemetry path for the bridge ports.
""" """
session_id = str(uuid4()) session_id = str(uuid4())
session: Dict[str, Any] = { session: Dict[str, Any] = {
@@ -728,7 +729,7 @@ def start_afpacket_sniffer(target: str, target_is_interface: bool = False) -> st
session["thread"] = None session["thread"] = None
_sync_bridge_telemetry() _sync_bridge_telemetry()
logger.info( logger.info(
"Started sniffer session %s label=%s ports=%s capture_ifaces=%s", "Started capture session %s label=%s ports=%s capture_ifaces=%s",
session_id, session_id,
target, target,
ports, ports,
@@ -737,7 +738,11 @@ def start_afpacket_sniffer(target: str, target_is_interface: bool = False) -> st
return session_id return session_id
def stop_afpacket_sniffer(session_id: Optional[str] = None, target: Optional[str] = None, target_is_interface: bool = False) -> None: def stop_capture_session(
session_id: Optional[str] = None,
target: Optional[str] = None,
target_is_interface: bool = False,
) -> None:
""" """
Stop either a specific session by session_id (preferred), or stop sockets associated with target. Stop either a specific session by session_id (preferred), or stop sockets associated with target.
- If session_id provided: stop that session and clean up. - If session_id provided: stop that session and clean up.
@@ -770,10 +775,10 @@ def stop_afpacket_sniffer(session_id: Optional[str] = None, target: Optional[str
# For interface-mode: close that iface in any session that has it # For interface-mode: close that iface in any session that has it
for sid, s in list(sessions.items()): for sid, s in list(sessions.items()):
if not target_is_interface and s.get("is_bridge") and s.get("label") == target: if not target_is_interface and s.get("is_bridge") and s.get("label") == target:
stop_afpacket_sniffer(session_id=sid) stop_capture_session(session_id=sid)
continue continue
if target_is_interface and not s.get("is_bridge") and s.get("label") == target: if target_is_interface and not s.get("is_bridge") and s.get("label") == target:
stop_afpacket_sniffer(session_id=sid) stop_capture_session(session_id=sid)
continue continue
if target in s.get("ports", []): if target in s.get("ports", []):
# close only that iface socket in that session # close only that iface socket in that session
@@ -797,7 +802,7 @@ def stop_afpacket_sniffer(session_id: Optional[str] = None, target: Optional[str
# Global stop: stop all sessions # Global stop: stop all sessions
for sid in list(sessions.keys()): for sid in list(sessions.keys()):
stop_afpacket_sniffer(session_id=sid) stop_capture_session(session_id=sid)
# attempt to close db pool if shared.web_loop is available; otherwise leave to main # attempt to close db pool if shared.web_loop is available; otherwise leave to main
try: try:
@@ -808,14 +813,14 @@ def stop_afpacket_sniffer(session_id: Optional[str] = None, target: Optional[str
except Exception: except Exception:
logger.exception("Failed to schedule DB pool close") logger.exception("Failed to schedule DB pool close")
logger.info("All sniffer sessions stopped") logger.info("All capture sessions stopped")
try: try:
bridge_telemetry_manager.stop() bridge_telemetry_manager.stop()
except Exception: except Exception:
logger.exception("Failed to stop bridge telemetry collector") logger.exception("Failed to stop bridge telemetry collector")
def get_sniffer_status() -> Dict[str, Dict[str, object]]: def get_capture_session_status() -> Dict[str, Dict[str, object]]:
""" """
Return simple status per managed interface, including which session owns it. Return simple status per managed interface, including which session owns it.
Output format: Output format:
@@ -870,3 +875,22 @@ def get_internal_debug_state() -> dict:
"tshark": tshark_manager.get_debug_snapshot(), "tshark": tshark_manager.get_debug_snapshot(),
"packet_tracker": packet_tracker.get_debug_snapshot(), "packet_tracker": packet_tracker.get_debug_snapshot(),
} }
def start_afpacket_sniffer(target: str, target_is_interface: bool = False) -> str:
"""Backward-compatible wrapper for start_capture_session()."""
return start_capture_session(target, target_is_interface=target_is_interface)
def stop_afpacket_sniffer(
session_id: Optional[str] = None,
target: Optional[str] = None,
target_is_interface: bool = False,
) -> None:
"""Backward-compatible wrapper for stop_capture_session()."""
stop_capture_session(session_id=session_id, target=target, target_is_interface=target_is_interface)
def get_sniffer_status() -> Dict[str, Dict[str, object]]:
"""Backward-compatible wrapper for get_capture_session_status()."""
return get_capture_session_status()

View File

@@ -17,7 +17,7 @@ from src.Models.etherType import EtherTypeEnum, ethertype_from_int
from src.Models.ip_protocol import protocol_from_number from src.Models.ip_protocol import protocol_from_number
from src.Models.packets import PacketDBModel from src.Models.packets import PacketDBModel
logger = logging.getLogger("af_packet_sniffer") logger = logging.getLogger("packet_capture")
def _db_text(value: Any) -> Any: def _db_text(value: Any) -> Any:

View File

@@ -6,7 +6,7 @@ import subprocess
import time import time
# ---- Logging ---------------------------------------------------------- # ---- Logging ----------------------------------------------------------
logger = logging.getLogger("af_packet_sniffer") logger = logging.getLogger("packet_capture")
_ETHTOOL_BIN = "/usr/sbin/ethtool" if os.path.exists("/usr/sbin/ethtool") else "ethtool" _ETHTOOL_BIN = "/usr/sbin/ethtool" if os.path.exists("/usr/sbin/ethtool") else "ethtool"
_ETHERNET_PROFILE_CACHE_TTL_SECONDS = 3.0 _ETHERNET_PROFILE_CACHE_TTL_SECONDS = 3.0
_ETHERNET_PROFILE_CACHE: Dict[str, tuple[float, Dict[str, Any]]] = {} _ETHERNET_PROFILE_CACHE: Dict[str, tuple[float, Dict[str, Any]]] = {}

View File

@@ -1,4 +1,4 @@
"""Helpers for stable packet identity across AF_PACKET and eBPF events.""" """Helpers for stable packet identity across capture and telemetry events."""
from __future__ import annotations from __future__ import annotations

View File

@@ -1,4 +1,4 @@
"""Aggregate AF_PACKET observations and kernel telemetry into one packet record.""" """Aggregate packet observations and kernel telemetry into one packet record."""
from __future__ import annotations from __future__ import annotations

View File

@@ -135,11 +135,11 @@ export default function SnifferManager(props: SnifferManagerProps): ReactElement
return ( return (
<div className="sniffing-manager"> <div className="sniffing-manager">
<Card <Card
title="Sniffer sessions" title="Capture sessions"
style={{ marginBottom: 16 }} style={{ marginBottom: 16 }}
extra={ extra={
<Space> <Space>
<Tooltip title="Start a new sniffer session"> <Tooltip title="Start a new capture session">
<Button icon={<PlusOutlined />} onClick={onOpenStartModal} /> <Button icon={<PlusOutlined />} onClick={onOpenStartModal} />
</Tooltip> </Tooltip>
<Tooltip title="Refresh status"> <Tooltip title="Refresh status">
@@ -243,7 +243,7 @@ export default function SnifferManager(props: SnifferManagerProps): ReactElement
</Card> </Card>
<Modal <Modal
title="Start sniffer session" title="Start capture session"
open={isModalOpen} open={isModalOpen}
onCancel={onCloseModal} onCancel={onCloseModal}
onOk={() => form.submit()} onOk={() => form.submit()}

View File

@@ -55,9 +55,9 @@ export default function Sniffing(): ReactElement {
<Row justify="space-between" align="middle" style={{ marginBottom: 12 }}> <Row justify="space-between" align="middle" style={{ marginBottom: 12 }}>
<Col> <Col>
<Title level={2} style={{ margin: 0 }}> <Title level={2} style={{ margin: 0 }}>
Sniffing Packet Capture
</Title> </Title>
<Text type="secondary">Start, stop and view AF_PACKET sniffer sessions.</Text> <Text type="secondary">Start, stop and view live capture sessions across interfaces and bridges.</Text>
</Col> </Col>
</Row> </Row>

View File

@@ -1,5 +1,5 @@
/** /**
* Request to start a sniffer session. * Request to start a packet capture session.
* Exactly one of `bridge` or `interface` should be provided. * Exactly one of `bridge` or `interface` should be provided.
*/ */
export interface SnifferStartRequest { export interface SnifferStartRequest {
@@ -8,7 +8,7 @@ export interface SnifferStartRequest {
} }
/** /**
* Response returned when starting a sniffer session. * Response returned when starting a packet capture session.
*/ */
export interface SnifferStartResponse { export interface SnifferStartResponse {
started: boolean; started: boolean;
@@ -18,7 +18,7 @@ export interface SnifferStartResponse {
} }
/** /**
* Optional body when stopping a sniffer session. * Optional body when stopping a packet capture session.
* Prefer providing session_id to stop a specific session. * Prefer providing session_id to stop a specific session.
*/ */
export interface SnifferStopRequest { export interface SnifferStopRequest {