All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 9s
144 lines
6.1 KiB
Python
144 lines
6.1 KiB
Python
# src/routers/sniffer.py
|
|
from fastapi import APIRouter, HTTPException, Query, Body
|
|
from pydantic import BaseModel, Field
|
|
from typing import Dict, Any, Optional
|
|
|
|
from src.network_sniffer import (
|
|
get_sniffer_status,
|
|
start_afpacket_sniffer,
|
|
stop_afpacket_sniffer,
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
# ------------------------------
|
|
# Pydantic Models
|
|
# ------------------------------
|
|
class SnifferStartRequest(BaseModel):
|
|
"""
|
|
Request model for starting the sniffer on a specific bridge OR interface.
|
|
Exactly one of `bridge` or `interface` must be provided.
|
|
"""
|
|
bridge: Optional[str] = Field(None, example="br0", description="Name of the Linux bridge to sniff on")
|
|
interface: Optional[str] = Field(None, example="eth0", description="Name of the network interface to sniff on")
|
|
|
|
|
|
class SnifferStartResponse(BaseModel):
|
|
"""
|
|
Response model returned when sniffer starts successfully.
|
|
"""
|
|
started: bool = Field(..., description="Whether the sniffer was started successfully")
|
|
session_id: str = Field(..., description="Session identifier for this sniffer instance")
|
|
target: str = Field(..., description="Target that was started (bridge or interface)")
|
|
target_type: str = Field(..., description="Either 'bridge' or 'interface'")
|
|
|
|
|
|
class SnifferStopRequest(BaseModel):
|
|
"""
|
|
Optional body for stop — prefer session_id if you want to stop a specific session.
|
|
If omitted, stopping behavior will be determined by query params (bridge/interface) or global stop.
|
|
"""
|
|
session_id: Optional[str] = Field(None, description="Session id to stop")
|
|
|
|
|
|
class SnifferStopResponse(BaseModel):
|
|
stopped: bool = Field(..., description="Whether the sniffer was stopped successfully")
|
|
session_id: Optional[str] = Field(None, description="Session id stopped (if any)")
|
|
target: Optional[str] = Field(None, description="Target stopped; null if global stop")
|
|
target_type: Optional[str] = Field(None, description="'bridge' or 'interface' or None")
|
|
|
|
|
|
class InterfaceSnifferStatus(BaseModel):
|
|
running: bool = Field(..., description="Whether the sniffer thread/socket is active")
|
|
exists: bool = Field(..., description="Whether the interface exists in /sys/class/net")
|
|
up: bool = Field(..., description="Whether the interface is operationally UP")
|
|
session_id: Optional[str] = Field(None, description="Session id owning this interface")
|
|
session_label: Optional[str] = Field(None, description="Human label for the session")
|
|
|
|
|
|
class SnifferStatusResponse(BaseModel):
|
|
interfaces: Dict[str, InterfaceSnifferStatus] = Field(
|
|
..., description="Map of interface names to their sniffer status"
|
|
)
|
|
|
|
|
|
# ------------------------------
|
|
# Endpoints
|
|
# ------------------------------
|
|
@router.post("/start", response_model=SnifferStartResponse)
|
|
def sniffer_start(req: SnifferStartRequest):
|
|
"""
|
|
Start a sniffer session for the given bridge OR interface.
|
|
Exactly one of `bridge` or `interface` must be provided.
|
|
Returns a session_id to manage the session.
|
|
"""
|
|
if bool(req.bridge) == bool(req.interface):
|
|
raise HTTPException(status_code=400, detail="Exactly one of 'bridge' or 'interface' must be provided")
|
|
|
|
try:
|
|
if req.interface:
|
|
session_id = start_afpacket_sniffer(req.interface, target_is_interface=True)
|
|
return SnifferStartResponse(started=True, session_id=session_id, target=req.interface, target_type="interface")
|
|
else:
|
|
session_id = start_afpacket_sniffer(req.bridge, target_is_interface=False)
|
|
return SnifferStartResponse(started=True, session_id=session_id, target=req.bridge, target_type="bridge")
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=500, detail=f"Failed to start sniffer: {exc}")
|
|
|
|
|
|
@router.post("/stop", response_model=SnifferStopResponse)
|
|
def sniffer_stop(
|
|
q_bridge: Optional[str] = Query(None, alias="bridge", description="If provided, stop sniffer sockets for this bridge"),
|
|
q_interface: Optional[str] = Query(None, alias="interface", description="If provided, stop sniffer socket for this interface"),
|
|
body: SnifferStopRequest = Body(...),
|
|
):
|
|
"""
|
|
Stop sniffer sessions.
|
|
|
|
- If body.session_id is provided: stop that session (preferred).
|
|
- Else if query param `interface` provided: close socket for that interface across sessions.
|
|
- Else if query param `bridge` provided: remove snapshot / close sockets for that bridge across sessions.
|
|
- Else: stop all sessions (global stop).
|
|
"""
|
|
if body and body.session_id:
|
|
try:
|
|
stop_afpacket_sniffer(session_id=body.session_id)
|
|
return SnifferStopResponse(stopped=True, session_id=body.session_id, target=None, target_type=None)
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=500, detail=f"Failed to stop session {body.session_id}: {exc}")
|
|
|
|
# validate query params
|
|
if q_bridge and q_interface:
|
|
raise HTTPException(status_code=400, detail="Only one of 'bridge' or 'interface' may be provided")
|
|
|
|
try:
|
|
if q_interface:
|
|
stop_afpacket_sniffer(target=q_interface, target_is_interface=True)
|
|
return SnifferStopResponse(stopped=True, session_id=None, target=q_interface, target_type="interface")
|
|
if q_bridge:
|
|
stop_afpacket_sniffer(target=q_bridge, target_is_interface=False)
|
|
return SnifferStopResponse(stopped=True, session_id=None, target=q_bridge, target_type="bridge")
|
|
|
|
# global stop
|
|
stop_afpacket_sniffer()
|
|
return SnifferStopResponse(stopped=True, session_id=None, target=None, target_type=None)
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=500, detail=f"Failed to stop sniffer: {exc}")
|
|
|
|
|
|
@router.get("/status", response_model=SnifferStatusResponse)
|
|
def sniffer_status():
|
|
"""
|
|
Return the sniffer status information.
|
|
"""
|
|
try:
|
|
raw = get_sniffer_status()
|
|
# Convert raw dict → typed model
|
|
typed = {
|
|
k: InterfaceSnifferStatus(**v)
|
|
for k, v in raw.items()
|
|
}
|
|
return SnifferStatusResponse(interfaces=typed)
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=500, detail=f"Failed to query sniffer status: {exc}") |