Skip to main content

SniffRouter

SniffRouter is a layer-4 content router. It waits for the first upstream payload of a connection, sniffs visible bytes, and sends that whole connection to the first matching route.

It is simpler and more specialized than Router: use SniffRouter when the decision can be made from HTTP/1 Host, TLS ClientHello SNI, or the ReverseClient/ReverseServer reverse-link handshake. Use Router when you need source IP, port, GeoIP, GeoSite, authenticated user, network type, or mixed rule conditions.

Mental Model

SniffRouter is a deferred branch selector:

  • It does not forward upstream Init immediately.
  • It waits for the first upstream payload.
  • It buffers the first bytes while sniffing.
  • It checks routes in JSON order.
  • The first matching route wins.
  • If no route matches, the connection uses top-level next.
  • The selected branch is initialized and the buffered bytes are replayed to it.
  • Later payloads keep using the same selected branch.
warning

SniffRouter needs client-side bytes before it can choose a route. Protocols where the server must speak first cannot be routed until upstream payload arrives.

Typical Placement

Route by decrypted HTTP/1 Host after TLS termination:

TcpListener -> TlsServer -> SniffRouter
|-- host a.example.com -> backend_a
|-- host b.example.com -> backend_b
`-- default next -> camouflage_site

Route by TLS SNI before TLS termination:

TcpListener -> SniffRouter
|-- SNI a.example.com -> tls_site_a
|-- SNI b.example.com -> tls_site_b
`-- default next -> default_tls_site

Split reverse tunnel traffic from a camouflage site after the stream is decrypted:

TcpListener -> TlsServer -> SniffRouter
|-- reverse handshake -> ReverseServer
`-- default next -> TcpConnector nginx

Reverse detection must see the decrypted reverse handshake. If the route sits before TLS termination, the handshake is encrypted and cannot match.

Configuration Example

HTTP Host routing:

{
"name": "sniff-router",
"type": "SniffRouter",
"settings": {
"routes": [
{
"domains": [
"a.example.com",
"b.example.com"
],
"next": "example_backend"
},
{
"domains": [
"*.static.example.com"
],
"next": "static_backend"
}
]
},
"next": "default_backend"
}

TLS SNI routing:

{
"name": "sniff-router",
"type": "SniffRouter",
"settings": {
"routes": [
{
"detection": "tls",
"domains": [
"site-a.example.com"
],
"next": "tls_site_a"
},
{
"detection": "tls",
"domains": [
"site-b.example.com"
],
"next": "tls_site_b"
}
]
},
"next": "default_tls_site"
}

Required Fields

Top-level fields:

FieldTypeDescription
namestringUser-chosen node name. Must be unique inside the config file.
typestringMust be exactly "SniffRouter".
nextstringRequired. This is the fallback route when no configured route matches.

settings may be omitted or empty. With no routes, all connections use top-level next.

Settings

SettingTypeDefaultDescription
routesarrayunsetOrdered list of sniff routes. If present, it must be a non-empty array.
reverse-secret-lengthinteger640Reverse handshake length for reverse detection. Must be in 1..1024.
reverse-secretASCII stringunsetXOR secret used to derive the reverse handshake signature. Must match the Reverse nodes when used.

Reverse handshake settings are parsed even when no reverse route exists, but they matter only for routes whose detection includes reverse.

Route Object

Each route object accepts:

FieldTypeRequiredDescription
domainsstring or arrayRequired for http1 and tls detection. Optional for reverse-only routes.
domainstringAlias for a single domains entry. Cannot be used together with domains.
detectionstring or arrayOptional. Default is "http1".
nextstringRequired. Target node name for matching connections.
targetstringAlias for route next.

Detection values:

ValueMeaning
http1Sniff HTTP/1 request Host. This is the default.
tlsSniff TLS ClientHello SNI.
reverseDetect the ReverseClient/ReverseServer reverse-link handshake.
reverse-tlsAlias for reverse.
reverse-handshakeAlias for reverse.

Removed values:

Old valueUse instead
httphttp1
client-hellotls
tls-client-hellotls

If a route has both reverse and a domain-based detection mode, reverse detection ignores domains, while HTTP/SNI detection still requires and uses domains.

Route Order

Routes are checked in JSON order. The first matching route wins.

{
"routes": [
{
"domain": "api.example.com",
"next": "api_backend"
},
{
"domain": "*.example.com",
"next": "wildcard_backend"
}
]
}

In this example, api.example.com reaches api_backend because the exact route appears before the wildcard route. If the wildcard route came first, it would catch api.example.com only if the wildcard pattern matched it; note that *.example.com matches subdomains but not example.com itself.

Domain Matching

Domain matching is case-insensitive. Configured domain patterns are normalized to lowercase and trailing dots are removed.

Supported patterns:

PatternMeaning
example.comExact match.
*.example.comMatches subdomains such as www.example.com; does not match example.com.
*Matches any non-empty Host/SNI value.

Invalid domain patterns:

  • empty strings
  • *. without a suffix
  • wildcard forms other than *.example.com
  • URLs such as https://example.com
  • host:port values such as example.com:443
  • values containing / or \

HTTP Host values may include a port on the wire. The sniffer strips the port before matching, so Host: example.com:443 matches configured example.com.

HTTP Host Detection

The default route detection is http1.

{
"domains": [
"app.example.com"
],
"next": "app_backend"
}

With http1, SniffRouter expects the first visible payload to be an HTTP/1 request and reads the Host header. It then matches that host against the route domain patterns.

Use this after TLS termination if the inbound wire traffic is HTTPS:

TcpListener -> TlsServer -> SniffRouter

HTTP/2 cleartext prefaces do not carry an HTTP/1 Host header, so they fall back to next unless some other route detection matches.

TLS SNI Detection

Use detection: "tls" to match the TLS ClientHello SNI:

{
"detection": "tls",
"domains": [
"*.example.com"
],
"next": "tls_branch"
}

This mode is useful before TLS termination:

TcpListener -> SniffRouter -> TlsServer

The TLS ClientHello must arrive within the bounded sniff window. If no SNI is found, or the SNI does not match any route, the connection uses top-level next.

Reverse Detection

reverse detection recognizes the reverse-link handshake sent by ReverseClient before a connection is handed to ReverseServer.

{
"name": "sniff-router",
"type": "SniffRouter",
"settings": {
"routes": [
{
"detection": "reverse",
"next": "reverse_server"
}
]
},
"next": "tcp_to_nginx"
}

Use this when one TLS entry point carries both reverse tunnel traffic and a camouflage website with the same SNI. Host/SNI routing cannot separate those flows, but the reverse link has a recognizable content signature after TLS is terminated.

Default reverse signature:

640 bytes of 0xFF

If reverse-secret-length or reverse-secret is configured, SniffRouter derives the expected handshake exactly like ReverseClient and ReverseServer: default handshake bytes are repeated as needed and XORed with the ASCII bytes of reverse-secret.

These settings must match the Reverse nodes:

{
"settings": {
"reverse-secret-length": 640,
"reverse-secret": "shared-secret",
"routes": [
{
"detection": "reverse",
"next": "reverse_server"
}
]
},
"next": "camouflage_site"
}

Important reverse details:

  • The handshake must be at the very start of the decrypted stream.
  • SniffRouter only peeks; it replays the buffered bytes intact to ReverseServer.
  • ReverseServer re-validates and strips the handshake.
  • A reverse route ignores domains.
  • If the first payload contains only a prefix of the reverse handshake, SniffRouter logs a warning and immediately uses the default next route.
  • If a fronting proxy prepends a PROXY-protocol header, strip that header before SniffRouter. Otherwise the stream no longer starts with the reverse handshake.

First-Payload Buffering

SniffRouter buffers initial upstream bytes until it can classify the connection.

HTTP Host and TLS SNI sniffing can ask for more bytes while the first request or ClientHello is incomplete. The shared sniff window is bounded:

8192 bytes

Reverse detection is stricter: a partial reverse-handshake prefix in the first payload does not wait for more bytes. It falls back to top-level next.

After a route is selected, later payloads are forwarded directly to the selected target or to the default next. The connection is not reclassified.

Target Branches

Each route next or target is the name of a real node in the same config.

At chain construction time, SniffRouter folds route targets into its own chain so that:

  • target nodes get per-line state slots
  • downstream traffic from target branches returns through SniffRouter
  • lifecycle callbacks remain composable

Target rules:

  • the target node must exist
  • the target cannot be the SniffRouter itself
  • the target branch must not already be bound to an incompatible previous node
  • the target may be the same node as top-level next

Use a Bridge target when the branch lives elsewhere in the config.

Lifecycle Behavior

Source-backed behavior:

CallbackBehavior
upstream InitInitializes SniffRouter line state only. It does not initialize a branch yet.
first upstream PayloadBuffers bytes, classifies routes, initializes selected target or default next, replays buffered bytes.
later upstream PayloadGoes directly to the selected target or default next.
upstream Pause / ResumeForwarded only after a route has been selected.
upstream EstForwarded only to the default branch. Route targets are often adapter/server nodes where upstream Est is not valid.
upstream FinishDestroys local line state and finishes the selected target/default branch if one exists.
downstream Payload / Pause / Resume / EstForwarded back to the previous node.
downstream FinishDestroys local line state before forwarding finish to the previous node.

SniffRouter vs Router

Use SniffRouter when:

  • you only need HTTP Host routing
  • you only need TLS SNI routing
  • you need reverse-handshake detection for a reverse tunnel sharing one TLS entrypoint with a camouflage site
  • you want a small, content-only branch selector

Use Router when:

  • you need source or destination IP/port conditions
  • you need GeoIP or GeoSite
  • you need username/password routing
  • you need protocol or attributes combined with other conditions
  • you need multiple conditions combined with AND

Node Metadata

Source-backed metadata:

PropertyValue
node flags`kNodeFlagChainHead
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayerAnything
layer_group_next_nodekNodeLayerAnything
required_padding_left0 bytes

SniffRouter does not prepend protocol headers and does not need left padding.