Skip to content

feat(net): header and idle timeouts on peer streams - #119

Merged
AlxcBTW merged 2 commits into
anyplaynet:mainfrom
fogydes:feat/tunnel-stream-timeouts
Oct 27, 2026
Merged

AlxcBTW merged 2 commits into
anyplaynet:mainfrom
fogydes:feat/tunnel-stream-timeouts

Conversation

@fogydes

@fogydes fogydes commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

What and why

When a peer opens a bidirectional stream via deliver_stream, it previously waited indefinitely for the 4-byte tunnel header (vport and source_vport). If a peer opened a stream and stayed silent, the task and stream resources hung indefinitely. Additionally, once established, the bidirectional pipe had no idle timeout, leaving inactive streams hanging if neither side transferred data.

This PR introduces:

  • STREAM_HEADER_TIMEOUT (5 seconds) to bound the wait for tunnel headers on peer-initiated streams. Silent peers that do not deliver the header in time are reset and dropped.
  • STREAM_IDLE_TIMEOUT (300 seconds / 5 minutes) to detect when bidirectional streams have no transfer in either direction and terminate idle connections cleanly, with ample headroom for quiet menus and game lobbies.
  • Activity marking on both read progress and completed writes in pipe() to ensure slow sinks moving data do not trigger false watchdog timeouts.
  • Unit tests:
    • silent_peer_stream_header_times_out
    • silent_peer_stream_idle_times_out
    • slow_live_stream_survives_idle_timeout (verifying periodic low-frequency writes survive across multiples of idle timeout)
    • silent_bridge_stream_header_times_out (verifying bridge drops silent loopback TCP clients failing to send traffic headers)
  • Re-exports of STREAM_HEADER_TIMEOUT and STREAM_IDLE_TIMEOUT from tunnel.rs.

Closes #

Checklist

  • cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings and cargo test --workspace pass.
  • New behaviour has a hermetic test (own servers on loopback, no internet, no game).
  • A change to a specification or a decision comes with an ADR in docs/adr/.
  • A change to the bridge protocol (spec/bridge-protocol.md, crates/bridge-protocol) states its wire effect: the version, and whether the AnyPS5 fork has to change with it.
  • It works for every game. No title ID in code; what is known per title is in specifications and tests.
  • It stays on the right side of ADR 0021, and no game data is attached: no dumps, keys, firmware, saves, tokens, or logs with game memory.

@AlxcBTW AlxcBTW left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks fago, the header timeout and the two silent-peer tests are the right shape, and resetting the stream on a bad header is good. Three things before merge, mostly about not dropping a live peer:

  1. pipe: the activity flag is only set when bytes are read, before the write. If the other side is slow and write_all blocks for longer than the idle time, the watchdog sees no activity and kills a stream that is still moving data. Please also mark activity after each completed write (or count progress on both the read and the write).
  2. 60 s with nothing in either direction is short for a live game connection that is simply quiet (menus, lobbies, waiting for a turn). Please either raise STREAM_IDLE_TIMEOUT a lot (several minutes) or tell me why 60 s is safe for games; I would rather the tunnel keep a quiet stream than drop it. A short comment next to the constant on the reasoning is enough.
  3. Please add a test that a slow but live stream survives: send one byte every ~idle/3 for a few multiples of the idle time and assert the stream is still open and the bytes arrive. Also open_stream_with (bridge side, silent TCP client sends no header) has no test; one small one would do.

Nit: read_head.is_err() || read_head.unwrap().is_err() reads better as a match or !matches!(read_head, Ok(Ok(_))).

@fogydes
fogydes force-pushed the feat/tunnel-stream-timeouts branch from 2b685e3 to 8532f81 Compare October 27, 2026 12:19
@fogydes

fogydes commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review and guidance @AlxcBTW! I have addressed all feedback in the latest commit:

  1. Write progress in pipe: Updated pipe() to mark stream activity after each completed write_all in both directions (act_out.store(true, Ordering::Relaxed) and act_in.store(true, Ordering::Relaxed)), ensuring that slow sinks moving data across writes do not trigger an erroneous watchdog timeout.
  2. STREAM_IDLE_TIMEOUT increased: Raised STREAM_IDLE_TIMEOUT from 60 seconds to 300 seconds (5 minutes) with an explanatory comment noting that quiet game menus, lobbies, and turn-based play may remain silent for several minutes without traffic, while 5 minutes still bounds resource cleanup on dropped peers.
  3. Stream survival test: Added slow_live_stream_survives_idle_timeout, which periodically sends 1 byte every ~idle/3 across multiple full timeout windows and asserts the stream remains open with all bytes delivered.
  4. Bridge silent TCP client test: Added silent_bridge_stream_header_times_out testing that open_stream_with cleanly rejects a silent client connecting on the loopback bridge without sending the traffic header.
  5. Nit: Refactored the header result check to !matches!(read_head, Ok(Ok(_))).

All unit tests and workspace clippy / fmt checks pass cleanly.

@AlxcBTW AlxcBTW left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three points are in: activity marked after each write, 300 s idle with the reasoning, and the slow-but-live and silent-bridge tests. Thanks fago, merging.

@AlxcBTW
AlxcBTW merged commit ed4f538 into anyplaynet:main Oct 27, 2026
7 checks passed
@AlxcBTW

AlxcBTW commented Oct 27, 2026

Copy link
Copy Markdown
Member

Merged. Thanks for the quick turnaround, fago.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants