c2e2e0d8f22f505fa99d61801ac35247bd908ef2
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
84e81f2037 |
tls: extract a shared listener; move FIFA 17's profile into its adapter
The redirector was the only host that spoke TLS, so its TLS lived inside
it. The roster host needs the same listener, and that made the choice
explicit: share this code or copy it.
Copying it is what already went wrong. On 2026-08-11 the Rust redirector
served one certificate while the container served another. ProtoSSL
caches the server certificate per backend, so the redirector -- the first
TLS connection of a session -- decided what the client expected, and
every later service failed its handshake. Silently: Python's socketserver
swallows ssl.SSLError as OSError. Three gates went to it. One place to
configure TLS is the structural fix, so it exists before the second host
does rather than after.
Split along the line the architecture already draws:
openfut-tls how to build an acceptor. Game-independent.
Knows nothing about which suites any client
offers.
adapter-fifa17::tls what FIFA 17 was OBSERVED to offer: the six
enabled suites, the two refused, the TLS 1.2
window, the EA SNI. Plain strings, so the
adapter keeps its lean dependencies -- reading
a card table should not build OpenSSL.
redirector-host joins the two. Chooses no cipher of its own.
Behaviour is unchanged, and shown to be:
* tests/fifa17_tls_profile.rs carries over every case from the deleted
module -- FIFA's eight suites negotiate AES256-GCM-SHA384, each enabled
suite works alone, RC4-only is refused, ECDHE-only is refused. Deleting
a module must not quietly delete its evidence.
* one test pins the composed values literally against the host as it was
when gates 1-14 passed. A "pure refactor" that cannot fail is not a
claim, it is an assumption.
* the rebuilt binary self-tests to the same TLSv1.2 / AES256-GCM-SHA384
the retail client negotiated at 17:09 today.
Two improvements fall out of having one place to look:
* the startup banner now prints cert_sha256. The mismatch above raised no
error at startup and broke the client much later with nothing logged;
it is now the first line of the log.
* tls_min/tls_max print as TLSv1.2 rather than SslVersion(771). This line
is gate evidence and gets read by people.
Nothing deployed and nothing restarted: FIFA is mid-session on the
running redirector, which is untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
e2c4ca6d56 |
redirector-host: reproduce the oracle's connection lifecycle, not just its bytes
Two live gate attempts failed with "An error occurred downloading the FUT
Squad Update" while the redirect response was verified byte-identical to the
Python oracle. Rolling back to the Python redirector fixed it, so the response
bytes were never the whole contract.
Log archaeology found the discriminator: the client polls
/fifa17/fut/rosterupdate.xml ~4x/min in every successful FUT session, and the
only gap in 300 recorded fetches is 03:47-03:58 -- exactly the two
Rust-redirector sessions. The Blaze RPC sequence over those sessions is
identical (msgNum 0-53), so the divergence is entirely outside Blaze.
A differential lifecycle probe against both redirectors found the two
behaviours this host never reproduced:
* the oracle drains the request body per Content-Length; this host stopped
at the header terminator, leaving unread data in the receive queue, which
makes Linux close with RST rather than FIN
* the oracle holds the connection open ~300ms before closing
(time.sleep(0.3)); this host closed at 0ms
Both are now reproduced. The dwell is a named constant, ORACLE_CLOSE_DWELL,
overridable only so the causal experiment -- set it to 0, confirm the failure
returns -- can be run without a rebuild.
The suite could not have caught either: it sent Content-Length: 0, so there
was never a body to drain. It now POSTs a body, and asserts a split-write body
is fully consumed.
Testing the drain via client-visible symptoms does NOT work -- verified by
mutation: with the drain removed the client still reads the buffered response
and sees close_notify before any reset. So the host records a per-connection
ConnOutcome and the test asserts on that. Both mutations (no-dwell, no-drain)
are now each caught by exactly one test.
This does not yet prove causation for the FUT Squad Update failure; it removes
the only two measured divergences. Gate 6 is the test.
|
||
|
|
89f77470f3 |
redirector: Rust host on vendored OpenSSL; shared typed config extracted
TLS DEPENDENCY, as directed: the openssl crate directly with the `vendored` feature. NOT native-tls. native-tls abstracts over whatever the platform provides; here the requirement is the opposite -- precise, evidenced behaviour for one legacy client -- which needs explicit control of the cipher list, protocol floor/ceiling and security level. Vendored so a distro libssl update cannot silently change whether FIFA 17 can connect. Scoped to this crate alone. Neither OpenFUT Core nor the generic protocol crates gain an OpenSSL dependency. CIPHERS driven by the captured retail ClientHello, not by generic legacy assumptions. The six RSA+AES suites it offers are enabled; RC4 and MD5 are deliberately NOT, even though the client offers them -- it already negotiates AES256-GCM-SHA384, so resurrecting RC4 for completeness would weaken the service for nothing. TLS 1.2 floor and ceiling, matching the observed client; the floor is not dropped to 1.0 pre-emptively because "the oracle permits it" is not "the client requires it". SECURITY LEVEL IS NOT LOWERED. Tried the default policy first, as directed, and OpenSSL 3.6.3 accepts static-RSA/AES without weakening. No SECLEVEL change was needed and none is applied; it remains overridable per-listener with evidence. CERTIFICATE: the proven Python redirector's material is reused, so the TLS implementation stays the only variable in an A/B. Verified RSA-2048, CN winter15.gosredirector.ea.com, cert/key modulus match; the key stays gitignored. SHARED CONFIG. New openfut-host-config is now the only crate that reads the environment, and both hosts resolve endpoints through it. Two hosts each parsing OPENFUT_ADVERTISE would be exactly the "separate helpers constructing endpoints from different sources of truth" the address audit forbids. VERIFICATION BY REAL HANDSHAKE, not by enumeration. The crate exposes no accessor for a context's configured suites at this version, which turned out better: the host now rehearses the retail handshake at startup with a client restricted to exactly FIFA's eight suites and REFUSES TO SERVE if it fails, so a cipher/version misconfiguration surfaces at boot rather than as an unexplained failure during a live gate. Gates 1-5 pass: TLS config unit tests; a FIFA-suite-only client negotiates TLSv1.2/AES256-GCM-SHA384; each enabled RSA+AES suite negotiable alone; an RC4-only client is refused; an ECDHE-only client is refused (proving no modern policy was silently inherited); a full HTTPS round-trip returns bytes IDENTICAL to the Python oracle's recorded response. Cargo.lock committed for reproducibility: openssl 0.10.81, openssl-sys 0.9.117, openssl-src 300.6.1+3.6.3 (OpenSSL 3.6.3). Updating openssl-src is NOT a routine bump -- it requires re-running the FIFA compatibility gates. Gates 6-14 need the retail client and are next. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |