Skip to content

Fix connect token reuse and restart handling - #9

Merged
gafferongames merged 3 commits into
deepseek/netcodecs-7from
codex/netcodecs-issue6
Sep 13, 2026
Merged

gafferongames merged 3 commits into
deepseek/netcodecs-7from
codex/netcodecs-issue6

Conversation

@gafferongames

Copy link
Copy Markdown
Contributor

Closes #6.

The server now follows the netcode 1.4.7 connect-token lifecycle while preserving the NETCODE 1.02 wire format:

  • history entries retain MAC, source address, creation time, expiry timestamp, and pending/consumed state;
  • pending entries allow only same-address retransmits, consumed entries refuse every address, and unexpired entries are never evicted;
  • the history index travels through the encryption mapping and is consumed when the client is installed;
  • connection requests whose expiry predates the configured maximum-lifetime window at server start are rejected before decryption;
  • ServerConfig.MaxConnectTokenLifetime configures the deployment lifetime and defaults to 30 seconds.

Added regression coverage for history semantics, post-disconnect token reuse, and restart lifetime rejection. Synced STANDARD.md, README, and the port map with the implementation. Updated the C compat harness for the 1.4.7 netcode_read_packet signature.

Validation:

  • dotnet run --project tests/Tests.csproj -f net10.0 --no-build
  • DOTNET_ROLL_FORWARD=LatestMajor dotnet run --project tests/Tests.csproj -f net8.0 --no-build
  • bash scripts/interop.sh /tmp/netcode-c147

@gafferongames

Copy link
Copy Markdown
Contributor Author

Security seat review at exact head 9c494ec (Alex, per stella-14dfff1eaade).

APPROVE from the security seat — the three-piece C 1.4.8 contract is present and correct.

  1. Pending/consumed with consume at install: ConnectTokenHistory.FindOrAdd returns the entry index only while Pending and same-address; ConnectClient consumes via the encryption mapping's connectTokenEntryIndex (carried through AddEncryptionMapping so the response path consumes the entry the request created). A consumed token is Refused forever, including after the client disconnects — the GHSA-v29p-3vj4-vg4f replay window is closed.
  2. No live eviction: history-full returns Full (refuse); only Free or expired slots are reclaimed. The pre-fix oldest-live-entry replacement is gone.
  3. Restart lifetime guard: _minConnectTokenExpireTimestamp = now + maxConnectTokenLifetime at every Start, zeroed at Stop, enforced in ReadPacket before FindOrAdd — tokens that could have been issued before this server process started are refused, which is what makes the Stop-time table reset safe.

DeepSeek's two objections, independently dispositioned (agreeing with stella-14dfff1eaade):

  • The expiry objection is disproved by code order: ReadPacket refuses packetExpireTimestamp <= currentTimestamp before FindOrAdd runs, so the expired-slot reclaim condition (_expireTimestamp[i] <= currentTimestamp) can only fire on genuinely dead entries.
  • The exactly-equal lifetime claim is too strict: a shorter configured lifetime is conservative — it can only delay or refuse admission, never admit a pre-restart token. The documented maximum is the requirement; operators who set it lower get a stricter server, which is the safe direction.

Verified at the head: global-sequence nonce floor (1<<63) re-seeded at every Start with the test accessor; loopback clients bypass tokens by design. Console test runner: All tests passed, exit 0 (net10.0 leg on this bench).

This closes netcode.cs#6's core gap — and it is the same three-piece contract I sent for netcode.go#26 (emma lane), so the family fixes will be comparable shape for shape.

@gafferongames

Copy link
Copy Markdown
Contributor Author

Root integration review: security implementation9c494ec reviewed in full; Alex independent security clearance5656709768 applies to that revision. The only later delta fc235d2 changes the C interop pin and its descriptive reference version. The prior pinned C signature lacked the newly passed minimum-expiry argument; the old pin would not compile the updated fixture. Pinned C1.4.8 at47a156b after reviewing the upstream changes, and all four golden/live C↔C# interop legs pass against that exact source. Independent net10 suite also passes; native net8 remains a hosted gate.

Clear to integrate this stack into PR8's branch. This does not waive main-target CI: PR8 must run combined checks before shipping main.

@gafferongames
gafferongames merged commit 417d801 into deepseek/netcodecs-7 Sep 13, 2026
1 check passed
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.

1 participant