test new flow stream analysis
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user