feat(sniffer): ISN sequence classifier (reuses seq_class helper)

Mirrors the IP-ID classifier for TCP ISN values: per-source-IP rolling
deque (maxlen=8) populated from each inbound SYN's tcp.seq, classified
on every emission. A 'random' verdict is the modern norm; 'incremental',
'zero', or 'constant' indicates legacy stacks or hand-rolled raw-socket
tooling — a strong fingerprint signal.

Active prober now also captures server_isn (single sample, not classified
in-flight; downstream consumers correlating multi-probe results can apply
seq_class.classify_sequence themselves).

Profiler rollup carries the latest non-'unknown' label into
attacker.tcp_fingerprint. Dedup key already covers isn_class from
the previous commit, so transitions emit cleanly.

UI surfaces ISN class as a colour-coded tag with a ⚠ glyph for
non-random verdicts, since they're the genuinely interesting case.
This commit is contained in:
2026-04-26 20:30:24 -04:00
parent 0e40cc8ae1
commit c595d039bd
7 changed files with 67 additions and 0 deletions

View File

@@ -23,6 +23,7 @@ interface AttackerBehavior {
dscp?: number | null;
ecn?: number | null;
ipid_class?: string | null;
isn_class?: string | null;
} | null;
retransmit_count: number;
behavior_class: string | null;
@@ -771,6 +772,12 @@ const TcpStackBlock: React.FC<{ b: AttackerBehavior }> = ({ b }) => {
{fp.ipid_class && fp.ipid_class !== 'unknown' && (
<Tag color={seqClassColor(fp.ipid_class)}>IPID:{fp.ipid_class.toUpperCase()}</Tag>
)}
{fp.isn_class && fp.isn_class !== 'unknown' && (
<Tag color={seqClassColor(fp.isn_class)}>
{fp.isn_class !== 'random' && '⚠ '}
ISN:{fp.isn_class.toUpperCase()}
</Tag>
)}
</div>
{fp.options_sig && (
<div>