test new flow stream analysis
All checks were successful
Build and Deploy MITM Webserver / traffic_target (push) Successful in 1s
Build and Deploy MITM Webserver / build (push) Successful in 12s

This commit is contained in:
2026-04-01 18:29:18 +02:00
parent ae432b7437
commit ecb7d7f258
5 changed files with 1462 additions and 91 deletions

View File

@@ -7,6 +7,7 @@ from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel, Field
import src.shared_objects as shared
from src.Models.packets import PacketDBModel
router = APIRouter()
@@ -128,8 +129,11 @@ class ConversationEvidence(BaseModel):
ethernet_protocol: Optional[str] = None
ip_protocol: Optional[str] = None
hostnames: List[str] = Field(default_factory=list)
flow_ids: List[str] = Field(default_factory=list)
flow_count: int = 0
packet_count: int
byte_count: int
duration_ms: int = 0
first_seen: datetime
last_seen: datetime
accept_count: int = 0
@@ -150,6 +154,43 @@ class ConversationAnalysisResponse(BaseModel):
)
class ConversationFlowSummaryEvidence(BaseModel):
flow_id: str
protocol: str
packet_count: int
byte_count: int
first_seen: datetime
last_seen: datetime
client_label: str
server_label: str
request_count: int = 0
response_count: int = 0
class ConversationFlowEventEvidence(BaseModel):
flow_id: str
timestamp: datetime
kind: str
label: str
src_label: str
dst_label: str
packet_id: Optional[str] = None
class ConversationFlowDetailResponse(BaseModel):
since: Optional[datetime] = None
packets: List[PacketDBModel] = Field(default_factory=list)
flows: List[ConversationFlowSummaryEvidence] = Field(default_factory=list)
events: List[ConversationFlowEventEvidence] = Field(default_factory=list)
notes: List[str] = Field(
default_factory=lambda: [
"This detail view is reconstructed from ordered captured packets for one directional conversation or flow.",
"Events are inferred from HTTP metadata and TCP packet types, so lower-layer traffic may have fewer high-level annotations.",
"If multiple flow ids exist for the same directional tuple, the drawer shows all matching packets in timestamp order.",
]
)
class LabelCountEvidence(BaseModel):
label: str
packet_count: int
@@ -453,6 +494,69 @@ async def analysis_conversations(
return ConversationAnalysisResponse(since=since, conversations=conversations)
@router.get("/conversation-flow-detail", response_model=ConversationFlowDetailResponse)
async def analysis_conversation_flow_detail(
flow_id: Optional[str] = Query(
None,
description="Specific flow_id to inspect. If omitted, the directional conversation tuple is used.",
),
src_ip_address: Optional[str] = Query(None),
src_mac_address: Optional[str] = Query(None),
dst_ip_address: Optional[str] = Query(None),
dst_mac_address: Optional[str] = Query(None),
src_port: Optional[int] = Query(None, ge=0, le=65535),
dst_port: Optional[int] = Query(None, ge=0, le=65535),
protocol: Optional[str] = Query(None),
since_minutes: Optional[int] = Query(
None,
ge=1,
le=60 * 24 * 30,
description="Analyze only packets seen within the last N minutes. Omit to cover all captured history.",
),
limit_packets: int = Query(
1500,
ge=1,
le=10000,
description="Maximum number of packets returned for this conversation detail view.",
),
) -> ConversationFlowDetailResponse:
"""Return ordered packet detail, subflows, and derived request/response events for one conversation."""
db = shared.db
if db is None:
raise HTTPException(status_code=503, detail="Database not available")
if flow_id is None and all(
value is None
for value in [src_ip_address, src_mac_address, dst_ip_address, dst_mac_address, src_port, dst_port]
):
raise HTTPException(
status_code=400,
detail="Provide either flow_id or enough directional conversation fields to identify the conversation.",
)
since: Optional[datetime] = None
if since_minutes is not None:
since = datetime.now(timezone.utc) - timedelta(minutes=since_minutes)
try:
result = await db.fetch_conversation_flow_detail(
flow_id=flow_id,
src_ip_address=src_ip_address,
src_mac_address=src_mac_address,
dst_ip_address=dst_ip_address,
dst_mac_address=dst_mac_address,
src_port=src_port,
dst_port=dst_port,
protocol=protocol,
since=since,
limit_packets=limit_packets,
)
except Exception as exc:
raise HTTPException(status_code=500, detail=f"Failed to analyze conversation flow detail: {exc}") from exc
return ConversationFlowDetailResponse(since=since, **result)
@router.get("/host-intelligence", response_model=HostIntelligenceAnalysisResponse)
async def analysis_host_intelligence(
since_minutes: Optional[int] = Query(