Skip to content

release: netcode.cs 1.1.0 - #10

Merged
rowan-claude merged 1 commit into
mainfrom
rowan/release-next
Sep 14, 2026
Merged

rowan-claude merged 1 commit into
mainfrom
rowan/release-next

Conversation

@rowan-claude

@rowan-claude rowan-claude commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Cuts netcode.cs 1.1.0, the first release since v1.0.0 (2026-08-08). It carries
PR #9 (connect token reuse and restart handling, merged 22:49Z) and PR #8
(netcode#191's two create error codes and the callback refusals, merged 22:51Z).

Do not tag, release or publish from this PR; it is the version bump and the
notes draft only.

The version, and why 1.1.0

netcode.cs has no CLAUDE.md, so the family's written rule is the sibling port's.
netcode.go's CLAUDE.md is the one that states it, and the deciding sentence is:

Module/tag version: its own line, v1.0.0 -> v1.1.0. It does not track C's numbering.

So this port numbers itself and 1.4.8 is not a candidate. Minor rather than
patch because the public surface grew and nothing in it was removed or changed:
ServerConfig.MaxConnectTokenLifetime, Protocol.DefaultMaxConnectTokenLifetime,
ClientCreateError.MissingOverrideCallback and
ServerCreateError.MissingOverrideCallback. (The sibling ports shipped the same
fix as patches — netcode.rs 1.1.2, netcode.go 1.1.4 — and both also added API;
the numbers were never aligned across ports, so this repo's own semver governs.)

Version sites changed

Site Before After
src/Netcode.csproj <Version> (absent) 1.1.0
README.md status line v1.0.0, the first release v1.1.0, tested against the C reference at netcode 1.4.8

The csproj had no <Version> property at all, so the assembly reported the
SDK default 1.0.0.0 and would have kept reporting it through every future
release. Adding it is the one new site here; the build now stamps 1.1.0.0 and
1.1.0+<sha>.

Sites deliberately not touched:

  • Protocol.VersionInfo = "NETCODE 1.02" and Defines.VersionInfo — the wire
    protocol version. It does not move, and the interop gate would catch it if it did.
  • No C-version constant exists in this port (there is no VersionFull analogue
    as netcode.go has), so there is nothing to bump to 1.4.8 in code. The C
    reference version is recorded in prose in notes/port-map.md line 4, which
    already reads 1.4.8 (fc235d2), and now in the README status line too. Adding a
    public C-version constant would be new API and does not belong in a release PR.
  • LICENSE and all license prose: untouched (GATE-9).

Gates, as ci.yml runs them

Run locally on this bench (Apple Silicon, .NET SDK 10.0.401, Apple clang 21) at
194f150:

dotnet build tests/Tests.csproj -c Release
  Build succeeded.  0 Warning(s)  0 Error(s)

dotnet run -c Release --project tests/Tests.csproj -f net10.0 --no-build
  All tests passed.                          (53 tests)

dotnet run -c Release --project tests/Tests.csproj -f net8.0 --no-build
  BLOCKED: no .NET 8 runtime on this machine.
  "You must install or update .NET to run this application. ...
   framework=Microsoft.NETCore.App&framework_version=8.0.0&arch=arm64"
  Only Microsoft.NETCore.App 10.0.11/10.0.12 are installed. CI installs 8.0.x
  and runs this leg on the real LTS runtime.

DOTNET_ROLL_FORWARD=LatestMajor dotnet run -c Release --project tests/Tests.csproj -f net8.0 --no-build
  All tests passed.                          (the net8.0 binary, rolled forward)

dotnet run -c Release --project tests/Tests.csproj -f net10.0 --no-build -- soak 2000
  soak complete: 222 connects, client rx 25995, server rx 25782
  soak PASSED

dotnet build compat/Compat.csproj -c Release
  Build succeeded.  0 Warning(s)  0 Error(s)

CC=clang scripts/interop.sh ../c-netcode      (c-netcode at 47a156b1, the pin in ci.yml, = netcode 1.4.8)
  leg 1: 10/10 token and packet goldens identical, both directions
  leg 2: cross verify passed, both directions
  leg 3: C server <- C# client, token minted by C#, 19 payloads, clean disconnect
  leg 4: C# server <- C client, token minted by C, 19 payloads, clean disconnect
  INTEROP GATE PASSED

spec-sync (curl upstream STANDARD.md | diff)
  STANDARD.md matches upstream

The only red is the net8.0 leg, and it is this machine, not the code: the same
binary passes with the documented DOTNET_ROLL_FORWARD=LatestMajor. Hosted CI
on this PR runs it on a real 8.0.x runtime; that run is the gate that counts.

NuGet

This repo publishes nothing. There is no PackageId, no IsPackable, no
nuget.config, no dotnet pack or nuget push step, and no .nupkg anywhere;
.github/workflows/ holds exactly ci.yml and cla.yml, neither of which
packs or pushes. Distribution today is source plus the git tag, the same as
v1.0.0 — so no credential is needed for this release and nobody is blocked.

If NuGet publishing is ever wanted it is new work, not a release step: it needs
package metadata in src/Netcode.csproj, a publish workflow, and a nuget.org
API key for the mas-bandwidth account. No such key exists on this bench, and
minting one is Glenn's hand — the parallel is netcode.rs, whose crates.io token
sits on Glenn's personal macOS account and is deliberately not on the bench.
Worth a separate issue rather than a line in this PR.

Release notes (draft, Rowan edits)

Modelled on the netcode.rs v1.1.2 notes. Also saved uncommitted at
NOTES.md in the release working directory.

netcode.cs 1.1.0: a connect token cannot be reused after its client disconnects, and a config missing its callbacks is refused at create

A connect token now encrypts exactly one session. Before this release the C#
server admitted a token again once its client had gone away, as long as the
request came from the same address the token first arrived from. A second
session under the same token repeats AEAD nonces under the same keys, which is
the flaw published against the C library as GHSA-v29p-3vj4-vg4f and fixed there
in netcode 1.4.5. This port carried the pre-1.4.5 behaviour until now; it is
brought up to the connect-token lifecycle of the C reference at netcode 1.4.8,
the version it is tested against.

What changed. The server's token history tracks each entry as pending or
consumed. While an entry is pending, retransmitted connection requests from the
token's original address are admitted, so an ordinary handshake that loses a
packet still completes. The entry is consumed at the moment the client is
installed in a slot, and a consumed token is refused from every address. A full
history refuses new tokens instead of evicting an unexpired entry, so a flood of
tokens can no longer make room for a spent one. Entries are reclaimed only after
their tokens expire. On top of that, a server that has just started refuses any
token whose expiry predates the start, so a restart does not reopen tokens
issued to the process that came before.

What operators need to do. The restart guard is measured against a configured
maximum connect-token lifetime, 30 seconds by default. That setting must be at
least as long as the longest lifetime your backend issues. If it is shorter,
tokens your backend legitimately issued can survive a restart unrefused and the
guard is unsafe. If it is longer, nothing is unsafe: the server simply waits
longer after a restart before it will admit the oldest tokens. Set it on
`ServerConfig`:

    var config = new ServerConfig
    {
        ProtocolId = protocolId,
        PrivateKey = privateKey,
        MaxConnectTokenLifetime = 60,
    };
    using var server = new Server("10.0.0.1:40000", config);

A `ServerConfig` that does not set it keeps the 30 second default.

A misconfigured client or server is now refused at create. Setting
`OverrideSendAndReceive` without both `SendPacketOverride` and
`ReceivePacketOverride` used to succeed and then dereference the missing
callback on the first update; it now throws at create, carrying
`ClientCreateError.MissingOverrideCallback` (7) or
`ServerCreateError.MissingOverrideCallback` (8). The two codes are appended
after the last member of each enum and match the values of
`NETCODE_CLIENT_CREATE_ERROR_MISSING_OVERRIDE_CALLBACK` and
`NETCODE_SERVER_CREATE_ERROR_MISSING_OVERRIDE_CALLBACK` in the C header;
nothing that already had a value was renumbered. For the same reason
`Client.ConnectLoopback` and `Server.ConnectLoopbackClient` now refuse, without
changing any state, when `SendLoopbackPacket` is unset, in every build rather
than only in a debug one.

Nothing on the wire changes. No packet layout, field or byte differs from the
previous release; the golden vector tests and the live interoperability legs
against the C reference client and server pass unchanged. A client does not need
updating, and mixed fleets of old and new servers interoperate.

The token-reuse finding was reported as netcode.cs#6, the same class reviewed
for the sibling ports as security#39 (netcode.rs#24) and security#40
(netcode.go#26), and shipped there as netcode.rs 1.1.2 and netcode.go 1.1.4. The
create-time refusals mirror netcode#191, tracked here as netcode.cs#7. The C
reference this release is tested against is netcode 1.4.8.

🤖 Generated with Claude Code

The first release since v1.0.0, carrying the connect-token lifecycle fix
(netcode.cs#6, GHSA-v29p-3vj4-vg4f) and the create-time callback refusals
(netcode.cs#7, mirroring netcode#191). Public API is additive only and
nothing on the wire changes.

Version sites:

- src/Netcode.csproj gains <Version>1.1.0</Version>. The property did not
  exist before, so the assembly reported 1.0.0.0 by the SDK default and
  would have kept reporting it forever. It is this port's own number: it
  does not track the C reference's numbering, and "NETCODE 1.02" in
  Protocol.VersionInfo is the wire protocol version and does not move.
- README.md's status line: v1.0.0 -> v1.1.0, and it now names the C
  reference release this port is tested against (netcode 1.4.8).

notes/port-map.md already reads "C reference, version 1.4.8" (fc235d2), and
this port exposes no C-version constant of its own, so there is no
VersionFull-style site to bump.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rowan-claude
rowan-claude merged commit be7963d into main Sep 14, 2026
5 checks 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