Files
shadowsocks-libev/tests/experiments/analyze.py
Max LvandClaude 553cdc7945 Add opt-in printable TCP salt experiment for ss-local (#3063)
Add `ss-local --printable-salt` / `"printable_salt": true`, disabled by
default. For each outgoing TCP salt it picks a prefix length uniformly
from 6-12 bytes and samples those bytes as printable ASCII (0x20-0x7e)
immediately before key derivation. The salt stays 32 bytes with no
framing or markers, so the wire format is unchanged and any stock
server interoperates. Only chacha20-ietf-poly1305 and aes-256-gcm are
accepted; other ciphers fail at startup. UDP, server responses, and
AEAD-2022 are untouched.

Crypto unit tests cover length distribution, empty first writes, exact
framing overhead, salt-tail preservation, byte-at-a-time decryption,
tampering and replay rejection. CLI tests check local-only exposure and
cipher rejection via flag, config key and bad config type. interop.py
gains --printable-salt and --server-bin for independent stock servers.

docs/printable-salt.md records the design, entropy bound, fingerprint
caveats and route-experiment procedure; docs/encoding-experiments.md
and tests/experiments/ hold the Base85/lowpop85/TLS outer-transport
prototypes. docs/measurements/printable-salt-2026-09-23/ archives the
sanitized results of the aborted field trials.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-13 09:09:51 +08:00

104 lines
5.9 KiB
Python

#!/usr/bin/env python3
"""Summarize transport screening and captured first TCP payloads."""
import argparse
from collections import Counter, defaultdict
from datetime import datetime
import json
import hashlib
from pathlib import Path
import statistics
import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from check_printable_pcap import initial_payloads # noqa: E402
from benchmark import exemptions # noqa: E402
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('directory')
args = parser.parse_args()
root = Path(args.directory)
protocol = json.loads((root / 'protocol.json').read_text())
events = [json.loads(line) for line in (root / 'events.jsonl').read_text().splitlines()]
phases = [(datetime.fromisoformat(e['utc'].replace('Z', '+00:00')).timestamp(), e)
for e in events if e['event'] == 'phase_start']
summary = dict(protocol=protocol, complete=any(e['event'] == 'complete' for e in events),
aborts=[e for e in events if e['event'] == 'aborted'],
completed_phases=sum(e['event'] == 'phase_complete' for e in events),
outcome_logging_limit='Runs before the durable-outcome fix can omit in-flight outcomes at abort.',
health_checks=sum(e['event'] in ('health', 'phase_start') for e in events),
health_failures=sum(e.get('healthy') is False for e in events), modes={})
for mode in protocol['modes']:
records = [e for e in events if e.get('mode') == mode and e['event'] == 'transfer']
small = [e['seconds'] * 1000 for e in records if e['size'] == 1024 and e['success']]
large = [e['mbps'] for e in records if e['size'] > 1024 and e['success']]
summary['modes'][mode] = dict(recorded_outcomes=len(records), success=sum(e['success'] for e in records),
errors=[e for e in records if not e['success']],
small_median_ms=statistics.median(small) if small else None,
small_p95_ms=sorted(small)[min(len(small) - 1, int(len(small) * .95))] if small else None,
large_median_mbps=statistics.median(large) if large else None)
expected_source = next((e.get('client_address') for e in events if e['event'] == 'start'), None)
for filename in ('client.pcap', 'remote-server.pcap'):
counts = defaultdict(Counter)
flows = list(initial_payloads(root / filename, protocol['ports']))
external = [f for f in flows if not f['source'].startswith('127.')]
source = expected_source if filename.startswith('remote-') else None
if source is None and external:
source = Counter(f['source'] for f in external).most_common(1)[0][0]
unexpected = [f for f in external if f['source'] != source]
replays = []
for probe in unexpected:
matches = [f for f in external if f['source'] == source and f['time'] < probe['time']
and f['port'] == probe['port'] and f['payload_hex'] == probe['payload_hex']
and f['payload_complete'] and probe['payload_complete']]
if matches:
original_phase = [event for stamp, event in phases if stamp <= matches[-1]['time']][-1]
replay_mode = original_phase['port_modes'][protocol['ports'].index(probe['port'])]
replays.append(dict(mode=replay_mode, phase=original_phase['phase'], port=probe['port'], delay_seconds=probe['time'] - matches[-1]['time'],
payload_bytes=len(bytes.fromhex(probe['payload_hex'])),
payload_sha256=hashlib.sha256(bytes.fromhex(probe['payload_hex'])).hexdigest()))
summary[filename + '/unsolicited'] = dict(connections=len(unexpected), exact_replays=replays)
for flow in flows:
if flow['source'] != source:
continue
previous = [event for stamp, event in phases if stamp <= flow['time']]
if not previous:
continue
phase = previous[-1]
mode = phase['port_modes'][protocol['ports'].index(flow['port'])]
values = counts[mode]
values['connections'] += 1
values['sequence_ok'] += flow['sequence_ok']
values['complete_first_payload'] += flow['payload_complete']
payload = bytes.fromhex(flow['payload_hex'])
rules = exemptions(payload)
rules['tls_header'] = len(payload) >= 3 and payload[0] in (22, 23) and payload[1] == 3 and payload[2] <= 9
if flow['payload_complete']:
values['historical_exemption'] += any(rules.values())
for rule, passed in rules.items():
values[rule] += passed
summary[filename] = {mode: dict(values) for mode, values in counts.items()}
stream_counts = defaultdict(Counter)
for flow in initial_payloads(root / filename, protocol['ports'], require_sequence=True):
if flow['source'] != source:
continue
previous = [event for stamp, event in phases if stamp <= flow['time']]
if not previous:
continue
mode = previous[-1]['port_modes'][protocol['ports'].index(flow['port'])]
payload = bytes.fromhex(flow['payload_hex'])
rules = exemptions(payload)
tls = len(payload) >= 3 and payload[0] in (22, 23) and payload[1] == 3 and payload[2] <= 9
stream_counts[mode]['connections'] += 1
stream_counts[mode]['complete_first_payload'] += flow['payload_complete']
if flow['payload_complete']:
stream_counts[mode]['historical_exemption'] += any(rules.values()) or tls
summary[filename + '/stream_start'] = {mode: dict(values) for mode, values in stream_counts.items()}
(root / 'summary.json').write_text(json.dumps(summary, indent=2) + '\n')
print(json.dumps(summary, indent=2))
if __name__ == '__main__':
main()