feat(fingerprint): ToS/DSCP/ECN extraction in active + passive TCP fingerprint

Active prober now reads ip.tos from the SYN-ACK and emits tos/dscp/ecn
alongside the existing TTL/window/options fields. dscp is folded into the
fingerprint hash so different DSCP markings produce distinct signatures.

Passive sniffer logs the same three fields on tcp_syn_fingerprint events;
profiler rollup carries them into the attacker tcp_fingerprint snapshot;
AttackerDetail's TCP STACK panel now surfaces DSCP and ECN cells.
This commit is contained in:
2026-04-26 20:25:37 -04:00
parent 453ab177b4
commit b0b08754d0
7 changed files with 131 additions and 26 deletions

View File

@@ -181,6 +181,9 @@ def sniffer_rollup(events: list[LogEvent]) -> dict[str, Any]:
"options_sig": e.fields.get("options_sig", ""),
"has_sack": e.fields.get("has_sack") == "true",
"has_timestamps": e.fields.get("has_timestamps") == "true",
"tos": _int_or_none(e.fields.get("tos")),
"dscp": _int_or_none(e.fields.get("dscp")),
"ecn": _int_or_none(e.fields.get("ecn")),
}
tcp_fp_context = "syn"
@@ -236,6 +239,9 @@ def sniffer_rollup(events: list[LogEvent]) -> dict[str, Any]:
"options_sig": e.fields.get("options_order", ""),
"has_sack": e.fields.get("sack_ok") == "1",
"has_timestamps": e.fields.get("timestamp") == "1",
"tos": _int_or_none(e.fields.get("tos")),
"dscp": _int_or_none(e.fields.get("dscp")),
"ecn": _int_or_none(e.fields.get("ecn")),
}
tcp_fp_context = "synack" # prober sent SYN, captured attacker's SYN-ACK