test
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 9s

This commit is contained in:
2026-03-01 15:00:58 +01:00
parent e9ff417df1
commit 3fc5b6db48
2 changed files with 334 additions and 173 deletions

View File

@@ -1,5 +1,5 @@
# src/routers/sniffer.py
from fastapi import APIRouter, HTTPException, Query
from fastapi import APIRouter, HTTPException, Query, Body
from pydantic import BaseModel, Field
from typing import Dict, Any, Optional
@@ -18,20 +18,33 @@ router = APIRouter()
class SnifferStartRequest(BaseModel):
"""
Request model for starting the sniffer on a specific bridge OR interface.
Both fields are optional here; the endpoint will validate that exactly one is present.
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")
@@ -40,6 +53,8 @@ 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):
@@ -54,54 +69,60 @@ class SnifferStatusResponse(BaseModel):
@router.post("/start", response_model=SnifferStartResponse)
def sniffer_start(req: SnifferStartRequest):
"""
Start the AF_PACKET sniffer for the given bridge OR interface.
Exactly one of `bridge` or `interface` must be provided; validate here explicitly so this
is compatible across Pydantic versions.
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.
"""
# explicit validation (works with pydantic v1 & v2)
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:
start_afpacket_sniffer(req.interface, target_is_interface=True)
return SnifferStartResponse(started=True, target=req.interface, target_type="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:
start_afpacket_sniffer(req.bridge, target_is_interface=False)
return SnifferStartResponse(started=True, target=req.bridge, target_type="bridge")
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(
bridge: Optional[str] = Query(None, description="If provided, stop sniffer sockets for this bridge"),
interface: Optional[str] = Query(None, description="If provided, stop sniffer socket for this interface"),
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 the AF_PACKET sniffer.
Stop sniffer sessions.
- If no query params provided: stop the global sniffer thread and clear snapshot.
- If `?interface=eth0` provided: stop sniffing on that interface only (close socket).
- If `?bridge=br0` provided: stop snapshot/sockets associated with that bridge.
- 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 bridge and interface:
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 interface:
stop_afpacket_sniffer(target=interface, target_is_interface=True)
return SnifferStopResponse(stopped=True, target=interface, target_type="interface")
if bridge:
stop_afpacket_sniffer(target=bridge, target_is_interface=False)
return SnifferStopResponse(stopped=True, target=bridge, target_type="bridge")
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, target=None, target_type=None)
except HTTPException:
raise
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}")