Skip to content

UDP Security Guide

Advanced Topic

This page deals with low-level protocol multiplexing and custom listener implementations.

Learning Signals

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 returns true

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():

using var app = NetworkApplication.CreateBuilder()
    .BindUdp<MyProtocol>().WithAuthentication((conn, ep, data) =>
        conn.Level >= PermissionLevel.USER).Bind()
    .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:

  1. authenticate or handshake on TCP first
  2. create/populate the connection secret
  3. store the session in ConnectionHub
  4. allow UDP packets to reference that session ID

Conceptual client packet layout

The listener currently validates these parts:

[session-token][payload]

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.

For a simple deployment:

  1. establish session over TCP
  2. assign/store session secret
  3. return session ID to the client
  4. let the client send UDP datagrams prefixed with the session token
  5. verify in IsAuthenticated(...) that the session is ready for UDP