feat(sniffer): IP-ID sequence classifier (random/incremental/zero/constant)
Adds a per-source-IP rolling sample buffer (deque, maxlen=8) for IP-ID values seen on attacker SYNs and a stdlib-only classifier in decnet/sniffer/seq_class.py. Each new SYN appends ip.id and re-classifies the buffer; the result is logged on tcp_syn_fingerprint events alongside sample count. The dedup key now folds in ipid_class so a transition from 'unknown' to a definitive verdict emits exactly one fresh event instead of being suppressed by the old (os|options) key. Profiler rollup carries the latest non-'unknown' label into attacker.tcp_fingerprint. UI surfaces it as a colour-coded tag in the TCP STACK panel: random neutral, incremental amber, zero/constant green (the strong signal).
This commit is contained in:
@@ -143,6 +143,7 @@ def sniffer_rollup(events: list[LogEvent]) -> dict[str, Any]:
|
||||
ttl_values: list[str] = []
|
||||
hops: list[int] = []
|
||||
tcp_fp: dict[str, Any] | None = None
|
||||
ipid_latest: str | None = None
|
||||
# Tracks which event set tcp_fp last — picks the provider "context"
|
||||
# (syn vs synack) when we feed the p0f-v2 matcher below.
|
||||
tcp_fp_context: str = "syn"
|
||||
@@ -185,6 +186,13 @@ def sniffer_rollup(events: list[LogEvent]) -> dict[str, Any]:
|
||||
"dscp": _int_or_none(e.fields.get("dscp")),
|
||||
"ecn": _int_or_none(e.fields.get("ecn")),
|
||||
}
|
||||
# Sequence classifications converge as samples accumulate; the
|
||||
# most recent non-"unknown" label wins so a later "unknown" event
|
||||
# (e.g. a deque reset) doesn't overwrite a confident verdict.
|
||||
ipid_class = e.fields.get("ipid_class")
|
||||
if ipid_class and ipid_class != "unknown":
|
||||
ipid_latest = ipid_class
|
||||
tcp_fp["ipid_class"] = ipid_latest
|
||||
tcp_fp_context = "syn"
|
||||
|
||||
elif e.event_type == _SNIFFER_FLOW_EVENT:
|
||||
|
||||
Reference in New Issue
Block a user