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:
@@ -32,6 +32,7 @@ def _make_synack(
|
||||
tcp_flags: int = 0x12, # SYN-ACK
|
||||
options: list | None = None,
|
||||
ack: int = 1,
|
||||
seq: int = 0,
|
||||
) -> SimpleNamespace:
|
||||
"""Build a fake scapy-like SYN-ACK packet for testing."""
|
||||
if options is None:
|
||||
@@ -52,6 +53,7 @@ def _make_synack(
|
||||
options=options,
|
||||
dport=12345,
|
||||
ack=ack,
|
||||
seq=seq,
|
||||
)
|
||||
ip_layer = SimpleNamespace(
|
||||
ttl=ttl,
|
||||
@@ -198,6 +200,11 @@ class TestParseSynack:
|
||||
assert result["dscp"] == 10
|
||||
assert result["ecn"] == 2
|
||||
|
||||
def test_server_isn_captured(self):
|
||||
resp = _make_synack(seq=0xDEADBEEF)
|
||||
result = _parse_synack(resp)
|
||||
assert result["server_isn"] == 0xDEADBEEF
|
||||
|
||||
def test_tos_ce_marked(self):
|
||||
# ECN CE bit set, no DSCP marking → ToS = 0x03
|
||||
resp = _make_synack(tos=0x03)
|
||||
|
||||
Reference in New Issue
Block a user