Skip to content

Handlers ​

Handlers form a processing chain. Each connection passes through handlers sequentially until one handles or drops it.

Handler chain ​

A handler can return one of three results:

ResultBehavior
ContinuePass connection to next handler
HandledStop processing, connection was handled
DropTerminate the connection

Example chain:

json
{
  "handlers": [
    {
      "type": "logsni"
    },
    {
      "type": "ratelimit-global",
      "config": {
        "max_parallel_connections": 10000
      }
    },
    {
      "type": "sni-router",
      "config": {
        "routes": {
          "play.example.com": "10.0.0.1:5520"
        }
      }
    },
    {
      "type": "forwarder"
    }
  ]
}

Processing order:

  1. logsni logs the SNI and returns Continue
  2. ratelimit-global checks connection count, returns Continue or Drop
  3. sni-router sets the backend address and returns Continue
  4. forwarder forwards packets and returns Handled

Built-in handlers ​

sni-router ​

Routes connections based on the SNI hostname.

json
{
  "type": "sni-router",
  "config": {
    "routes": {
      "play.example.com": "10.0.0.1:5520",
      "lobby.example.com": ["10.0.0.2:5520", "10.0.0.3:5520"]
    }
  }
}

Behavior:

  • Extracts SNI from the QUIC ClientHello
  • Looks up the hostname in routes
  • Single backend: sets that address
  • Multiple backends (array): selects one using round-robin
  • Unknown SNI: returns Drop

simple-router ​

Routes all connections to one or more backends. Does not inspect SNI.

json
{
  "type": "simple-router",
  "config": {
    "backend": "10.0.0.1:5520"
  }
}

For load balancing across multiple backends:

json
{
  "type": "simple-router",
  "config": {
    "backends": ["10.0.0.1:5520", "10.0.0.2:5520", "10.0.0.3:5520"]
  }
}

Backends are selected using round-robin.

ratelimit-global ​

Limits the total number of concurrent connections.

json
{
  "type": "ratelimit-global",
  "config": {
    "max_parallel_connections": 10000
  }
}

Behavior:

  • Tracks active connection count
  • Returns Continue if under limit
  • Returns Drop if limit reached

forwarder ​

Forwards packets between client and backend. This handler should be last in the chain.

json
{
  "type": "forwarder"
}

Behavior:

  • Reads backend address set by a router handler
  • Establishes UDP connection to backend
  • Copies packets bidirectionally
  • Returns Handled

logsni ​

Logs the SNI of each connection to stdout.

json
{
  "type": "logsni"
}

Useful for debugging or monitoring which hostnames clients connect to.

terminator ​

Terminates QUIC TLS and bridges to backend servers. Enables inspection of decrypted Hytale protocol traffic. Must be placed before forwarder.

json
{
  "type": "terminator",
  "config": {
    "listen": "auto",
    "certs": {
      "default": {
        "cert": "server.crt",
        "key": "server.key"
      }
    },
    "debug": false,
    "debug_packet_limit": 100
  }
}
FieldDescription
listenInternal listener address (auto for ephemeral port)
certs.defaultFallback certificate configuration
certs.targetsPer-backend certificate configurations
debugEnable Hytale protocol packet logging
debug_packet_limitMax packets to log per stream (0 = unlimited)

See TLS Termination for detailed configuration and packet handlers.

Writing custom handlers ​

Handlers implement the Handler interface:

go
type Handler interface {
    Handle(ctx *ConnContext) Result
}

The ConnContext contains connection metadata and methods:

  • ctx.SNI() — extracted Server Name Indication
  • ctx.SetBackend(addr) — set backend address for forwarder
  • ctx.Drop() — immediately terminate the session

Return values:

  • Continue — pass to next handler
  • Handled — stop chain, connection handled
  • Drop — terminate connection

Custom handlers require recompiling the project.