ConnectionFisherClient
ConnectionFisherClient is the client-side peer for
ConnectionFisherServer. For every incoming WaterWall line, it creates several
simultaneous child lines toward next, sends a fixed 5-byte probe on each one,
and keeps the first child line that receives the expected server reply.
This is useful when the outbound path is unstable or when you intentionally want
to race several connection attempts and keep the first path that proves it
reached a matching ConnectionFisherServer.
Typical Placement
Simple TCP layout:
TcpListener -> ConnectionFisherClient -> TcpConnector
TcpListener -> ConnectionFisherServer -> TcpConnector
With other stream tunnels in between:
TesterClient -> ConnectionFisherClient -> MuxClient -> MuxServer -> ConnectionFisherServer -> TesterServer
The client and server must be paired in the same logical stream path. Any intermediate tunnels must preserve the fixed probe bytes end to end.
What It Does
- Reads
settings.simultaneous-tries-perline. - Creates up to that many internal child lines for each upstream
Init; synchronous selection stops new attempts. - Sends upstream
Inittonexton every child line. - Sends the fixed client probe
FISH?on every child line. - Waits for the fixed server reply
FISH!. - Buffers upstream payload on the main line while no child has been selected.
- Selects the first child that receives a valid reply.
- Closes all non-selected child lines.
- Flushes queued upstream payload to the selected child in order.
- Bridges downstream payload from the selected child back to the main line.
ConnectionFisherClient is a middle stream tunnel. It does not create sockets
directly and it does not create packet lines.
Configuration Example
{
"name": "fisher-client",
"type": "ConnectionFisherClient",
"settings": {
"simultaneous-tries-perline": 3
},
"next": "tcp-out"
}
Matching server side:
{
"name": "fisher-server",
"type": "ConnectionFisherServer",
"next": "service"
}
Required Fields
Top-level fields:
| Field | Type | Description |
|---|---|---|
name | string | User-chosen node name. Must be unique inside the config file. |
type | string | Must be exactly "ConnectionFisherClient". |
next | string | Required. The next stream node receives the internal candidate child lines. |
ConnectionFisherClient must also have a previous node in the chain. It is not
a chain head or chain end.
Optional Settings
| Setting | Type | Default | Description |
|---|---|---|---|
simultaneous-tries-perline | integer >= 1 | 2 | Number of candidate child lines created for each incoming main line. |
settings may be omitted. If present, values smaller than 1 are rejected.
Probe Exchange
The probe is fixed and has no configurable secret:
| Direction | Bytes | Meaning |
|---|---|---|
| client to server | FISH? | Sent after child upstream Init when transport permission allows. |
| server to client | FISH! | Required reply from ConnectionFisherServer. |
These markers only prove that the child line reached a matching
ConnectionFisherServer. They are not authentication, encryption, or traffic
integrity protection.
Line Model
ConnectionFisherClient uses two kinds of lines:
| Line | Purpose |
|---|---|
| main line | The original incoming line from the previous node. It stays on the previous side. |
| child lines | Internal candidate lines created by the client and sent only toward next. |
The original main line is never sent directly to next. Child lines are never
sent toward prev. After one child is selected, the tunnel manually bridges
payload, Est, Pause, Resume, and Finish between the main line and the
selected child.
Selection Flow
For each upstream Init on the main line:
- The main line state is initialized.
- Up to
simultaneous-tries-perlinechild lines are created on the same worker; synchronous selection stops new attempts. - Each child line is initialized toward
next. - Each child sends
FISH?after Init when its transport is unpaused. - If no child has already won synchronously, the main line schedules a 5000 ms selection timeout.
- Upstream payload on the main line is queued until a child wins.
- The first child to receive
FISH!becomes the selected child. - Losing child lines are closed.
- Any downstream bytes coalesced with
FISH!are sent before application work can induce newer replies. - Queued upstream payload drains in FIFO order while the selected transport is unpaused.
If no child validates before the timeout, the main line is closed.
Buffering and Limits
| Limit | Value | Behavior |
|---|---|---|
| Selection timeout | 5000 ms | Main line closes if no child is selected in time. |
| Pending upstream payload | 2 MiB | Main line closes if its retained protocol/order backlog exceeds this. |
| Incomplete child reply | 4 bytes | A complete five-byte reply is validated immediately. |
| Coalesced reply body / selection ordering | 2 MiB | Marker bytes do not consume this application-data allowance. |
| Pending upstream entries | 1,024 buffers | Empty TCP input consumes no retained entry. |
| Required left padding | 0 | The client does not prepend protocol headers to existing payload buffers. |
The FISH? probe is allocated as its own small buffer. User payload is queued
or forwarded as existing WaterWall buffers.
Lifecycle Behavior
Before selection:
- upstream payload from the main line is queued
- downstream payload from a child is treated as handshake data
- the first eligible associated transport
Estis forwarded to the main line once, independently of selection - an already-established child returned by next Init produces the same one-time transport notification
- child transport pressure is recorded before selection; application backlog independently pauses the main source immediately after its first retained payload
After selection:
- upstream payload on the main line is forwarded to the selected child with
tunnelNextUpStreamPayload - downstream payload on the selected child is forwarded to the previous node with
tunnelPrevDownStreamPayload - selection and handshake completion do not emit another
Est - pause and resume are bridged only across the selected child/main-line pair
If the selected child closes, the main line is closed. If every child closes before a winner is selected, the main line is also closed.
The upstream application backlog is bounded to 2 MiB and 1,024 buffers and admits buffers transactionally. Its first retained payload publishes source Pause after FIFO ownership is settled. This hold combines with selected-child transport pressure; neither an unrelated child Resume nor selection alone releases it. It affects the application source, so FISH probes and replies can still complete. Selection and Resume release it as an independent producer: Pause stops the drain, and nested application input appends behind older retained bytes. Empty stream buffers need no retained entry. Main-side reply Pause is recorded before selection and applied to the chosen backend after its handshake, so it cannot stall FISH!. Temporary downstream ordering during selection also has a 2 MiB bound and keeps coalesced reply bytes before reentrant replies. Close settles all retained bytes and exact child/main references.
Notes and Caveats
- Use it with a matching
ConnectionFisherServer. - It is intended for stream-style chains, not packet-line chains.
- It creates multiple outbound candidate lines for each incoming line, so
increasing
simultaneous-tries-perlinealso increases connection load. - The probe is fixed and public; use TLS, encryption, authentication, or routing nodes around it when the path needs security.
- Upstream
Estand downstreamInitare disabled callbacks for this node.
Splice Support and Setup Retirement
Both nodes accept ordinary buffers and private-pipe payloads. Complete deliveries entering handshake
or selection parsing are materialized with sbufEnsureOrdinary(); application FIFOs retain buffers
opaquely. A complete five-byte marker can carry up to 2 MiB of following application data. An incomplete
marker retains at most four bytes, and the marker does not consume the application-data allowance.
After setup and FIFO drain, the client releases its handshake streams and empty queue storage but keeps the main-to-selected-child mapping and endpoint flow control needed for payload, pressure and Finish. The server destroys and zeros its entire line state, then permanently bypasses itself after backend Init and replay settle. Ready payloads preserve their buffers without inspection, materialization or the startup backlog limit. Server backend initialization still waits for the first application payload, so losing candidates create no backend connection. Neither node requests ordinary reads for the established TCP stream.
Node Metadata
Source-backed metadata:
| Property | Value |
|---|---|
| node flags | kNodeFlagSupportsSplice |
can_have_prev | true |
can_have_next | true |
layer_group | kNodeLayer4 |
layer_group_prev_node | kNodeLayer4 |
layer_group_next_node | kNodeLayer4 |
required_padding_left | 0 bytes |