add more sankey options
This commit is contained in:
@@ -20,6 +20,17 @@ class InterfaceHostEvidence(BaseModel):
|
||||
destination_on_egress_count: int = Field(..., description="Packets where this endpoint appeared as the destination on egress.")
|
||||
|
||||
|
||||
class ProtocolLayerPathEvidence(BaseModel):
|
||||
ethernet_protocol: Optional[str] = Field(None, description="Ethernet protocol label for this path, if known.")
|
||||
ip_protocol: Optional[str] = Field(None, description="IP protocol label for this path, if known.")
|
||||
packet_count: int = Field(..., description="Packet observations supporting this path.")
|
||||
last_seen: datetime = Field(..., description="Most recent packet timestamp supporting this path.")
|
||||
accept_count: int = Field(0, description="Packets with verdict=accept for this path.")
|
||||
drop_count: int = Field(0, description="Packets with verdict=drop for this path.")
|
||||
reject_count: int = Field(0, description="Packets with verdict=reject for this path.")
|
||||
unknown_count: int = Field(0, description="Packets with verdict pending/unknown or without a verdict.")
|
||||
|
||||
|
||||
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.")
|
||||
@@ -30,23 +41,12 @@ class ProtocolEvidence(BaseModel):
|
||||
unknown_count: int = Field(0, description="Packets with verdict pending/unknown or without a verdict.")
|
||||
ethernet_protocol: Optional[str] = Field(None, description="Dominant Ethernet protocol associated with this protocol evidence.")
|
||||
ip_protocol: Optional[str] = Field(None, description="Dominant IP protocol associated with this protocol evidence.")
|
||||
layer_paths: List["ProtocolLayerPathEvidence"] = Field(
|
||||
layer_paths: List[ProtocolLayerPathEvidence] = Field(
|
||||
default_factory=list,
|
||||
description="Optional Ethernet/IP breakdown contributing to this protocol evidence.",
|
||||
)
|
||||
|
||||
|
||||
class ProtocolLayerPathEvidence(BaseModel):
|
||||
ethernet_protocol: Optional[str] = Field(None, description="Ethernet protocol label for this path, if known.")
|
||||
ip_protocol: Optional[str] = Field(None, description="IP protocol label for this path, if known.")
|
||||
packet_count: int = Field(..., description="Packet observations supporting this path.")
|
||||
last_seen: datetime = Field(..., description="Most recent packet timestamp supporting this path.")
|
||||
accept_count: int = Field(0, description="Packets with verdict=accept for this path.")
|
||||
drop_count: int = Field(0, description="Packets with verdict=drop for this path.")
|
||||
reject_count: int = Field(0, description="Packets with verdict=reject for this path.")
|
||||
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.")
|
||||
|
||||
|
||||
@@ -32,19 +32,35 @@ def _analysis_protocol_name(app_protocol: Any, ip_proto_raw: Any, eth_type_raw:
|
||||
if app_protocol not in (None, ""):
|
||||
return str(app_protocol)
|
||||
|
||||
ip_protocol_name = _analysis_ip_protocol_name(ip_proto_raw)
|
||||
if ip_protocol_name is not None:
|
||||
return ip_protocol_name
|
||||
|
||||
ethernet_protocol_name = _analysis_ethernet_protocol_name(eth_type_raw)
|
||||
if ethernet_protocol_name is not None:
|
||||
return ethernet_protocol_name
|
||||
|
||||
return "UNKNOWN"
|
||||
|
||||
|
||||
def _analysis_ip_protocol_name(ip_proto_raw: Any) -> Optional[str]:
|
||||
if ip_proto_raw is not None:
|
||||
try:
|
||||
return str(protocol_from_number(int(ip_proto_raw)))
|
||||
except Exception:
|
||||
return f"IP_PROTO_{ip_proto_raw}"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _analysis_ethernet_protocol_name(eth_type_raw: Any) -> Optional[str]:
|
||||
if eth_type_raw is not None:
|
||||
try:
|
||||
return str(ethertype_from_int(int(eth_type_raw)))
|
||||
except Exception:
|
||||
return f"ETH_TYPE_{eth_type_raw}"
|
||||
|
||||
return "UNKNOWN"
|
||||
return None
|
||||
|
||||
|
||||
def _serialize_row_for_broadcast(row: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@@ -958,6 +974,8 @@ class DatabasePool:
|
||||
)
|
||||
if protocol_name not in (None, ""):
|
||||
protocol_key = str(protocol_name)
|
||||
ip_protocol_name = _analysis_ip_protocol_name(record.get("ip_proto_raw"))
|
||||
ethernet_protocol_name = _analysis_ethernet_protocol_name(record.get("eth_type_raw"))
|
||||
protocol_last_seen_raw = record.get("protocol_last_seen")
|
||||
protocol_last_seen = (
|
||||
protocol_last_seen_raw.isoformat()
|
||||
@@ -975,6 +993,10 @@ class DatabasePool:
|
||||
"drop_count": 0,
|
||||
"reject_count": 0,
|
||||
"unknown_count": 0,
|
||||
"ethernet_protocol": ethernet_protocol_name,
|
||||
"ip_protocol": ip_protocol_name,
|
||||
"layer_paths": [],
|
||||
"_layer_index": {},
|
||||
}
|
||||
protocol_index[protocol_key] = protocol_record
|
||||
host_record["protocols"].append(protocol_record)
|
||||
@@ -989,11 +1011,54 @@ class DatabasePool:
|
||||
or str(protocol_last_seen) > str(protocol_record.get("last_seen"))
|
||||
):
|
||||
protocol_record["last_seen"] = protocol_last_seen
|
||||
if protocol_record.get("ethernet_protocol") is None and ethernet_protocol_name is not None:
|
||||
protocol_record["ethernet_protocol"] = ethernet_protocol_name
|
||||
if protocol_record.get("ip_protocol") is None and ip_protocol_name is not None:
|
||||
protocol_record["ip_protocol"] = ip_protocol_name
|
||||
|
||||
layer_key = f"{ethernet_protocol_name or 'no-eth'}|{ip_protocol_name or 'no-ip'}"
|
||||
layer_index = protocol_record["_layer_index"]
|
||||
layer_record = layer_index.get(layer_key)
|
||||
if layer_record is None:
|
||||
layer_record = {
|
||||
"ethernet_protocol": ethernet_protocol_name,
|
||||
"ip_protocol": ip_protocol_name,
|
||||
"packet_count": 0,
|
||||
"last_seen": protocol_last_seen,
|
||||
"accept_count": 0,
|
||||
"drop_count": 0,
|
||||
"reject_count": 0,
|
||||
"unknown_count": 0,
|
||||
}
|
||||
layer_index[layer_key] = layer_record
|
||||
protocol_record["layer_paths"].append(layer_record)
|
||||
|
||||
layer_record["packet_count"] += int(record.get("protocol_packet_count") or 0)
|
||||
layer_record["accept_count"] += int(record.get("accept_count") or 0)
|
||||
layer_record["drop_count"] += int(record.get("drop_count") or 0)
|
||||
layer_record["reject_count"] += int(record.get("reject_count") or 0)
|
||||
layer_record["unknown_count"] += int(record.get("unknown_count") or 0)
|
||||
if protocol_last_seen and (
|
||||
layer_record.get("last_seen") in (None, "")
|
||||
or str(protocol_last_seen) > str(layer_record.get("last_seen"))
|
||||
):
|
||||
layer_record["last_seen"] = protocol_last_seen
|
||||
|
||||
result: List[Dict[str, Any]] = []
|
||||
for iface, hosts in sorted(grouped.items()):
|
||||
for host in hosts.values():
|
||||
host.pop("_protocol_index", None)
|
||||
for protocol in host["protocols"]:
|
||||
protocol.pop("_layer_index", None)
|
||||
protocol["layer_paths"] = sorted(
|
||||
protocol["layer_paths"],
|
||||
key=lambda item: (
|
||||
-int(item.get("packet_count") or 0),
|
||||
str(item.get("last_seen") or ""),
|
||||
str(item.get("ethernet_protocol") or ""),
|
||||
str(item.get("ip_protocol") or ""),
|
||||
),
|
||||
)
|
||||
host["protocols"] = sorted(
|
||||
host["protocols"],
|
||||
key=lambda item: (
|
||||
|
||||
Reference in New Issue
Block a user