Skip to content

Gateway

rebaserHEAD edited this page Oct 18, 2026 · 7 revisions

explanation, committed verified 2026-10-18 bootstrap c3409ba run 2026-10-18

How the gateway works as built, and why: the three client ports, what each connection does with the client, the gate every message passes, and the limits at the door. The facts to look up are on Gateway connections, Gateway sign-on, Gateway gate, Gateway limits and the generated Gateway opcode table. What's still to build is on Gateway design, and the conversation the client holds is on Client protocol.

How it fits together

The shape

flowchart TD
    C["Client"] -->|"STS, 6600"| STS["Sign-on connection"]
    C -->|"login, 23115"| LC["Login connection"]
    C -->|"world, 24000"| WC["World connection"]
    STS -->|"issue a game token"| AO["Account owner"]
    LC -->|"redeem the game token"| AO
    WC -->|"redeem the session key"| AO
    WC -->|"character select"| RO["Realm owner"]
Loading

Three ports, one gateway. Sign-on (STS), login and the world each listen on a port of their own, through a Kestrel connection handler that takes a slot from the per-address connection limit and hands the connection to its class.

The three connections pass the client along. Sign-on proves the password and ends in a game token from the account owner. The login connection redeems that token and hands the client the world's address and a session key. The world connection redeems the session key, then carries character select to the realm owner. Gateway sign-on and Gateway connections have each step.

The gateway runs the login exchange itself. It's the one part of the server that reads the database on a client's behalf, for the account's salt and verifier from the account directory (Persistence), and it can because it owns no game state. The account owner only decides: who may play, and the game tokens and session keys it issues and redeems.

Every game connection has one shape: a read loop that frames each message, answers the transport ping, checks the message and hands it on, and a single writer per session that drains its outbound queue in order under the session's current key. Keys switch exactly where the client switches them, so a message that arrives in the same read as the one that changed the key is decoded under the new one.

Nothing reaches an owner unchecked. Every client message passes the gate: a verified declaration, a row in the opcode table, and that row's rate limit. The row, not the message, names the owner it goes to.

Why it's built this way

The client holds one socket per session and tags most of its messages with where their first check runs, so one place has to hold the sockets and decide where every message goes. Keeping that place free of game state lets it do the slow part of a login, reading the account's credentials, without holding up an owner.

The ports face whoever can reach them, so the limits belong at the door. Every port caps the frame size and the connections per address, and times out a connection that hasn't logged in (Gateway limits). The frame is the one allocation a client controls: the codec's reader bounds every count and string by the bits left in the body, so capping the frame bounds them all. An unknown account's sign-on runs exactly like a real one and fails at the proof, so nothing tells an attacker which accounts exist, and no secret is ever logged.

Knobs

Setting Default Sets
Gateway:Sts, Gateway:Login, Gateway:World 127.0.0.1 on 6600, 23115 and 24000 Each listener's address and port; the ports have to differ
Gateway:WorldPublicAddress 127.0.0.1 The world address handed to the client, IPv4
Gateway:UnknownAccountSecret none, required At least 32 bytes in base64, the same across restarts
Gateway:MaxClientFrameBytes 65,536 The largest frame a client may send, 64 to 1,048,576
Gateway:ConnectionsPerAddress 8 Connections per address on each listener
Gateway:PreAuthTimeout 30 s Time to complete a login
Gateway:OutboundQueueCapacity 4,096 A session's outbound queue
Gateway:DisconnectGrace 5 s Time a disconnecting session gets, 0 to 1 minute
Gateway:LoginAttemptsPerMinute 10 Login attempts per address
Gateway:DisabledOpcodes none Client opcodes to drop, the kill switch

Sign-on's header and body caps and the writer's batch of 64 are fixed in code.

Vocabulary

Term Means here
Frame One message on the wire: length, opcode, body
Envelope The encrypted wrapper a message travels in after the first greeting
Game token What sign-on issues and the login connection redeems
Session key The key the login connection hands the client for the world connection, redeemed there once
Gate The checks every client message passes before it reaches a handler
Opcode table The list of client messages the gateway admits, each with its owner and rate

Known edges

  • Sign-on answers every refusal with one error, until the client's other codes are verified.
  • The opcode table holds pregame rows only; in-world routing comes with step 3's second stage.
  • A logged-in connection has no idle limit yet, and there's no overall message rate per session.
  • Switching an opcode off takes a restart.
  • Sending and handling still need Fields; the per-member rules (Message audit design) come with step 3's second stage.

Where the code lives

Piece Path
Frames, envelopes and the packet cipher Source/NexusTomorrow.Transport/
The catalog, the opcode table, the decoder and the rate gate Source/NexusTomorrow.Gateway/Catalog/
Sign-on Source/NexusTomorrow.Gateway/Sts/
The connection base and the login and world connections Source/NexusTomorrow.Gateway/Connections/
Limits and the Kestrel handlers Source/NexusTomorrow.Gateway/Limits/, Source/NexusTomorrow.Gateway/Kestrel/
Settings Source/NexusTomorrow.Gateway/GatewayOptions.cs
SRP6 and RC4 Source/NexusTomorrow.Cryptography/
Tests Tests/NexusTomorrow.Transport.Tests/, Tests/NexusTomorrow.Gateway.Tests/

Clone this wiki locally