Skip to content

Commit 9b9b1ff

Browse files
committed
Cap pre-auth handshake recv at 5 s so bad-protocol clients fail fast
Both bare-TCP and WS hosts inherit the listener's 0.5 s socket timeout on accept(). Bumping that to 60 s covered the WS handshake on slow CI runners but introduced the inverse hang: a peer that never sends "\r\n\r\n" (the plain-TCP-into-WS rejection test) deadlocked the server for the full 60 s and exceeded pytest's per-test budget. Split the timeouts: 5 s for the pre-auth step (TLS wrap, WS upgrade) — microseconds is plenty on loopback, 5 s absorbs scheduler starvation, and shorter than pytest --timeout so wrong-protocol clients fail fast. The auth exchange that follows still uses the original 60 s budget set inside _ClientHandler._authenticate.
1 parent 0929195 commit 9b9b1ff

2 files changed

Lines changed: 29 additions & 2 deletions

File tree

je_auto_control/utils/remote_desktop/host.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@
3838
InputDispatcher = Callable[[Mapping[str, Any]], Any]
3939

4040
_AUTH_TIMEOUT_S = 60.0
41+
# Ceiling for the pre-auth handshake step (TLS wrap + WS upgrade GET).
42+
# Legitimate handshakes complete in milliseconds; 5 s is generous enough
43+
# to absorb scheduler starvation on slow CI runners but short enough to
44+
# fast-fail when a client speaks the wrong protocol (e.g. plain-TCP auth
45+
# bytes hitting a WS server). Kept distinct from _AUTH_TIMEOUT_S so the
46+
# subsequent auth-message exchange retains its longer budget.
47+
_HANDSHAKE_RECV_TIMEOUT_S = 5.0
4148
_DEFAULT_QUALITY = 70
4249

4350

@@ -554,6 +561,18 @@ def _accept_loop(self) -> None:
554561
continue
555562
except OSError:
556563
return
564+
# accept() returns a new socket that INHERITS the listener's
565+
# 0.5 s timeout. That is fine for the accept poll itself but
566+
# fatally tight for the handshake that follows: a slow CI
567+
# runner can't deliver the TLS / WS upgrade request inside
568+
# 500 ms, the recv times out, server drops, and the client's
569+
# separate auth wait ticks down to its own timeout. Promote
570+
# to a handshake-specific budget — long enough for runner
571+
# starvation, short enough to fast-fail on protocol mismatch.
572+
try:
573+
client_sock.settimeout(_HANDSHAKE_RECV_TIMEOUT_S)
574+
except OSError:
575+
pass
557576
wrapped = self._maybe_wrap_tls(client_sock, address)
558577
if wrapped is None:
559578
continue
@@ -594,7 +613,9 @@ def _maybe_wrap_tls(self, client_sock: socket.socket,
594613
if self._ssl_context is None:
595614
return client_sock
596615
try:
597-
client_sock.settimeout(_AUTH_TIMEOUT_S)
616+
# Use the handshake-specific budget so a peer that never
617+
# speaks TLS (or cuts off mid-ClientHello) fails fast.
618+
client_sock.settimeout(_HANDSHAKE_RECV_TIMEOUT_S)
598619
wrapped = self._ssl_context.wrap_socket(
599620
client_sock, server_side=True,
600621
)

je_auto_control/utils/remote_desktop/ws_host.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@
1717
WsProtocolError, server_handshake,
1818
)
1919

20-
_HANDSHAKE_TIMEOUT_S = 60.0
20+
# Ceiling for the WS upgrade exchange. A legitimate handshake on
21+
# loopback completes in microseconds; 5 s easily absorbs scheduler
22+
# starvation on a loaded CI runner while still letting the server
23+
# fast-fail when a peer never sends "\r\n\r\n" (e.g. a plain-TCP
24+
# viewer pointed at a WS host). The auth exchange that follows uses
25+
# its own, much longer budget defined in :mod:`host`.
26+
_HANDSHAKE_TIMEOUT_S = 5.0
2127

2228

2329
class WebSocketDesktopHost(RemoteDesktopHost):

0 commit comments

Comments
 (0)