UDP Security Guide¶
Advanced Topic
This page deals with low-level protocol multiplexing and custom listener implementations.
Learning Signals
- Level: Advanced
- Time: 10 minutes
- Prerequisites: Client Session Initialization
This guide explains the actual UDP session shape used by UdpListenerBase today, in a client-friendly way.
Use it when you already know you need UDP and want to understand the trust and replay rules before implementing a client.
What the runtime expects¶
When UdpListenerBase receives a datagram, it expects the first 8 bytes to be the session token:
- session token (
Snowflake, 8 bytes) - payload
In source, the listener validates:
- the datagram is at least 8 bytes long
- the payload is at least the 10-byte Nalix packet header
- the packet flags include
PacketFlags.UNRELIABLE - the token resolves to a connection in
ConnectionHub - the remote endpoint still matches the connection's pinned endpoint
- the UDP replay window accepts the packet sequence
- your overridden
IsAuthenticated(...)or hosting predicate returnstrue
High-level flow¶
flowchart TD
subgraph Net ["1. Network Phase"]
direction LR
Recv["Datagram Received"] --> Rate["Rate-limit IP"]
end
subgraph Validate ["2. Validation Phase"]
direction LR
Token["Read 8-byte Token"] --> Header["Check Header & Flags"]
end
subgraph Sess ["3. Session Phase"]
direction LR
Conn["Find Connection"] --> Pin["Verify Pinned IP"] --> Replay["Check Replay Window"]
end
subgraph Auth ["4. Routing Phase"]
direction LR
Hook["IsAuthenticated() Hook"] --> Inject["Bind Transport & Inject"]
end
Net --> Validate
Validate --> Sess
Sess --> Auth
Server shape¶
1. Subclass UdpListenerBase¶
public sealed class SampleUdpListener : UdpListenerBase
{
public SampleUdpListener(IProtocol protocol, IConnectionHub hub) : base(protocol, hub) { }
protected override bool IsAuthenticated(IConnection connection, EndPoint remoteEndPoint, ReadOnlySpan<byte> payload)
{
// Add your own checks here:
// - allowed endpoint
// - session state
// - region / shard ownership
return connection.Secret is not null && connection.Secret.Length > 0;
}
}
Preferred Modern Pattern
Instead of subclassing UdpListenerBase, use the hosting predicate before
calling Build():
2. Keep TCP and UDP tied to the same session¶
The UDP path assumes there is already a known connection in ConnectionHub.
That means a common pattern is:
- authenticate or handshake on TCP first
- create/populate the connection secret
- store the session in
ConnectionHub - allow UDP packets to reference that session ID
Conceptual client packet layout¶
The listener currently validates these parts:
The token is the Snowflake session ID assigned to the connection after TCP login.
Pseudocode for client-side send¶
byte[] payload = BuildGamePayload();
byte[] sessionToken = connectionId.ToBytes();
byte[] datagram = Concat(sessionToken, payload);
await udp.SendAsync(datagram, serverEndPoint);
Transport rules¶
The runtime does not add timestamp, nonce, or Poly1305 metadata to UDP datagrams. Instead, it uses the session token plus the connection/auth state already established through TCP.
For clients, that means:
- send the 8-byte session token first
- keep the payload small enough to fit
MaxUdpDatagramSize - treat UDP as a fast datagram path, not a second authentication protocol
What happens on failure¶
The listener records diagnostics for:
- short packets
- unknown sessions
- unauthenticated packets
- receive errors
Invalid packets are dropped before they reach your protocol logic.
Recommended rollout¶
For a simple deployment:
- establish session over TCP
- assign/store session secret
- return session ID to the client
- let the client send UDP datagrams prefixed with the session token
- verify in
IsAuthenticated(...)that the session is ready for UDP