AF_PACKET terminology cleanup
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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]]] = {}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user