test visualization with d3js
All checks were successful
Build and Deploy MITM Webserver / traffic_target (push) Successful in 1s
Build and Deploy MITM Webserver / build (push) Successful in 11s

This commit is contained in:
2026-03-30 21:34:40 +02:00
parent f758901f83
commit 0463075782
10 changed files with 1842 additions and 6 deletions

View File

@@ -20,11 +20,30 @@ class InterfaceHostEvidence(BaseModel):
destination_on_egress_count: int = Field(..., description="Packets where this endpoint appeared as the destination on egress.")
class ProtocolEvidence(BaseModel):
protocol: str = Field(..., description="Detected application or fallback transport/network protocol.")
packet_count: int = Field(..., description="Packet observations supporting this interface-host-protocol mapping.")
last_seen: datetime = Field(..., description="Most recent packet timestamp supporting this protocol mapping.")
accept_count: int = Field(0, description="Packets with verdict=accept for this protocol.")
drop_count: int = Field(0, description="Packets with verdict=drop for this protocol.")
reject_count: int = Field(0, description="Packets with verdict=reject for this protocol.")
unknown_count: int = Field(0, description="Packets with verdict pending/unknown or without a verdict.")
class InterfaceHostProtocolEvidence(InterfaceHostEvidence):
protocols: List[ProtocolEvidence] = Field(default_factory=list, description="Protocols observed for this host on the interface.")
class InterfaceAttachment(BaseModel):
interface: str = Field(..., description="MITM machine interface name.")
hosts: List[InterfaceHostEvidence] = Field(default_factory=list, description="Endpoints inferred to be attached to this interface.")
class InterfaceProtocolAttachment(BaseModel):
interface: str = Field(..., description="MITM machine interface name.")
hosts: List[InterfaceHostProtocolEvidence] = Field(default_factory=list, description="Endpoints inferred to be attached to this interface, with protocol breakdown.")
class InterfaceHostAnalysisResponse(BaseModel):
since: Optional[datetime] = Field(None, description="Only packets at or after this timestamp were analyzed.")
interfaces: List[InterfaceAttachment] = Field(default_factory=list)
@@ -37,6 +56,18 @@ class InterfaceHostAnalysisResponse(BaseModel):
)
class InterfaceHostProtocolAnalysisResponse(BaseModel):
since: Optional[datetime] = Field(None, description="Only packets at or after this timestamp were analyzed.")
interfaces: List[InterfaceProtocolAttachment] = Field(default_factory=list)
notes: List[str] = Field(
default_factory=lambda: [
"This is an inference from observed packet direction, not a kernel neighbor-table lookup.",
"Each host can carry multiple protocols; protocols prefer app_protocol and fall back to lower-layer protocol names.",
"Verdict counts are packet counts grouped per interface, host, and protocol.",
]
)
@router.get("/interface-hosts", response_model=InterfaceHostAnalysisResponse)
async def analysis_interface_hosts(
since_minutes: Optional[int] = Query(
@@ -68,3 +99,46 @@ async def analysis_interface_hosts(
interfaces = [InterfaceAttachment(**row) for row in rows]
return InterfaceHostAnalysisResponse(since=since, interfaces=interfaces)
@router.get("/interface-host-protocols", response_model=InterfaceHostProtocolAnalysisResponse)
async def analysis_interface_host_protocols(
since_minutes: Optional[int] = Query(
60,
ge=1,
le=60 * 24 * 30,
description="Analyze only packets seen within the last N minutes. Set to a large value to cover more history.",
),
limit_per_interface: int = Query(
50,
ge=1,
le=1000,
description="Maximum number of inferred hosts returned per interface.",
),
limit_protocols_per_host: int = Query(
12,
ge=1,
le=100,
description="Maximum number of top protocols returned per inferred host.",
),
) -> InterfaceHostProtocolAnalysisResponse:
"""Infer interface-host attachment and break observed traffic down by protocol."""
db = shared.db
if db is None:
raise HTTPException(status_code=503, detail="Database not available")
since: Optional[datetime] = None
if since_minutes is not None:
since = datetime.now(timezone.utc) - timedelta(minutes=since_minutes)
try:
rows = await db.infer_interface_host_protocols(
since=since,
limit_per_interface=limit_per_interface,
limit_protocols_per_host=limit_protocols_per_host,
)
except Exception as exc:
raise HTTPException(status_code=500, detail="Failed to infer interface host protocol mapping") from exc
interfaces = [InterfaceProtocolAttachment(**row) for row in rows]
return InterfaceHostProtocolAnalysisResponse(since=since, interfaces=interfaces)