UDP Listener
On this page
Namespace: Nalix.Network.Listeners.Udp
Assembly: Nalix.Network
Nalix.Network.Listeners.Udp provides two UDP listener implementations:
UdpListenerBase(public abstract partial class UdpListenerBase : IListener): Authenticated companion UDP listener that resolves each datagram to an existing TCP-authenticatedConnectioninIConnectionHubusing an 8-byteSessionTokenprefix and a 4-byte trailingXxHash32MAC tag.UdpPassthroughListener(public sealed class UdpPassthroughListener : UdpListenerBase): Raw connectionless UDP listener that bypasses token/MAC checks and forwards raw datagrams toIProtocolusing lightweightPassthroughConnectioninstances tracked inTimingWheel.
Source Mapping
src/Nalix.Network/Listeners/UdpListener/UdpListener.Core.cssrc/Nalix.Network/Listeners/UdpListener/UdpListener.PublicMethods.cssrc/Nalix.Network/Listeners/UdpListener/UdpListener.Receive.cssrc/Nalix.Network/Listeners/UdpListener/UdpListener.SocketConfig.cssrc/Nalix.Network/Listeners/UdpListener/UdpListener.Metrics.cssrc/Nalix.Network/Listeners/UdpListener/UdpPassthroughListener.cssrc/Nalix.Network/Connections/PassthroughConnection.cssrc/Nalix.Network/RateLimiting/Datagram.Guard.cssrc/Nalix.Network/Options/DatagramGuardOptions.cs
1. Authenticated Datagram Wire Layout (UdpListenerBase)
Every authenticated inbound UDP datagram has a 12-byte authentication envelope (8-byte SessionToken prefix + 4-byte XxHash32 MAC suffix) wrapping the Nalix packet:
+-----------------------------------+-----------------------------------+-----------------------------------+
| SessionToken (8B, LE UInt64) | Packet Payload (>= 6B Header) | UdpMacTag (4B, LE UInt32) |
| = ConnectionId (ISnowflake) | [OpCode(2)..SequenceId(2)..Body] | XxHash32(Secret, seed: Hash(0..N))|
+-----------------------------------+-----------------------------------+-----------------------------------+
Bytes 0..7 Bytes 8..(Length - 5) Bytes (Length - 4)..(Length - 1)
Multi-Gate Validation Pipeline (UdpListener.Receive.cs)
flowchart TD
Recv["ReceiveFromAsync (PooledUdpReceiveEventArgs)"] --> Rate{"DatagramGuard.TryAccept(ip)?"}
Rate -->|No| Drop["Drop Datagram"]
Rate -->|Yes| Size{"BytesTransferred <= MaxUdpDatagramSize (1440)?"}
Size -->|No| Drop
Size -->|Yes| MinLen{"Length >= 8B Token + 6B Header + 4B MAC (18B)?"}
MinLen -->|No| Drop
MinLen -->|Yes| Resolve["Hub.GetConnection(buffer[..8])"]
Resolve -->|Unknown / Disposed / Secret.IsZero| Drop
Resolve -->|Resolved| Mac["Verify 4B Trailing XxHash32 MAC Tag + Strip Tag"]
Mac -->|Mismatch| Drop
Mac -->|Valid| Pin{"Endpoint Pinning Match (SEC-30)?"}
Pin -->|Mismatch| Drop
Pin -->|Matched| Replay{"connection.UdpReplayWindow.TryCheck(header.SequenceId)?"}
Replay -->|Replayed / Stale| Drop
Replay -->|Valid| Hook{"IsAuthenticated(connection, remoteEndPoint, payload)?"}
Hook -->|False| Drop
Hook -->|True| Deliver["SocketUdpTransport.EnsureUDP + AsyncCallback.Invoke(CallbackLane.Process)"]- Per-IP Rate & Size Gate: Checks
_rateLimiter.TryAccept(ip)(DatagramGuard) and rejects datagrams larger thanNetworkSocketOptions.MaxUdpDatagramSize(1440bytes). - Session Resolution: Reads the 8-byte
SessionToken(buffer[..8]) and resolves the activeConnectionfromIConnectionHub. Rejects if the connection does not exist orconnection.Secret.IsZero(handshake not completed). - Keyed
XxHash32MAC Verification (UdpMacTagSize = 4):- Let
dataLen = lease.Length - 4. - Computes
uint payloadHash = XxHash32.Compute(lease.Span[..dataLen]). - Computes
uint computedTag = XxHash32.Compute(connection.Secret.AsSpan(), seed: payloadHash). - Compares
computedTagagainst the trailing 4-byte little-endian tag (lease.Span.Slice(dataLen, 4)) and strips the 4-byte tag (lease.CommitLength(dataLen)).
- Let
- Endpoint Pinning (
SEC-30): Verifies that the UDP source IP (and port, when pinned) matchesconnection.NetworkEndpoint. - Sliding Replay Window (
SEC-27): Validates the 16-bitheader.SequenceId(ushort) againstconnection.UdpReplayWindow(DatagramGuardOptions.UdpReplayWindowSize = 512bits). - Custom Hook & Frame Processing: Calls
IsAuthenticated(connection, remoteEndPoint, payload), bindsSocketUdpTransport.EnsureUDP, strips the 8-byteSessionTokenprefix, and dispatches the packet throughAsyncCallback(FramePipeline.ProcessInbound$\rightarrow$IProtocol.ProcessMessage).
2. Raw Passthrough Mode (UdpPassthroughListener)
Registered via builder.MapUdp<TProtocol>(port, passthrough: true) when implementing custom third-party UDP protocols (such as RakNet or custom binary UDP):
- Bypasses the 8-byte
SessionToken, 4-byteXxHash32MAC tag,UdpReplayWindow, andFramePipeline. - Tracks each remote
IPEndPointas aPassthroughConnection(public sealed class PassthroughConnection : IConnection, IConnectionTrafficMetrics, TimingWheel.ITimeoutTrackedConnection) admission-checked byIConnectionGuard, throttled toMaxPendingPackets = 64concurrent pending packets per endpoint, and registered inTimingWheelfor automatic idle eviction. PassthroughConnectionexposesUDP(IConnection.ITransport) bound to the shared listener socket for sending replies, while accessingPassthroughConnection.TCPthrowsNotSupportedException.
Public & Protected API (UdpListenerBase)
namespace Nalix.Network.Listeners.Udp;
public abstract partial class UdpListenerBase : IListener
{
protected UdpListenerBase(ushort port, IProtocol protocol, IConnectionHub hub);
protected UdpListenerBase(IProtocol protocol, IConnectionHub hub);
public bool IsListening { get; }
public UMetrics Metrics { get; }
protected IProtocol Protocol { get; }
protected Socket? ListenerSocket { get; }
public void Activate(CancellationToken cancellationToken = default);
public void Deactivate(CancellationToken cancellationToken = default);
public virtual bool TryResolveConnection(
IConnectionHub hub,
ReadOnlySpan<byte> sessionToken,
out Connection? connection);
public abstract bool IsAuthenticated(
IConnection connection,
EndPoint remoteEndPoint,
ReadOnlySpan<byte> payload);
protected virtual void ProcessDatagram(BufferLease lease, EndPoint? remoteEndPoint);
public string GenerateReport();
public void WriteReportData(Utf8JsonWriter writer);
public void Dispose();
protected virtual void Dispose(bool disposing);
}