Skip to main content

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 Init to next on 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:

FieldTypeDescription
namestringUser-chosen node name. Must be unique inside the config file.
typestringMust be exactly "ConnectionFisherClient".
nextstringRequired. 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​

SettingTypeDefaultDescription
simultaneous-tries-perlineinteger >= 12Number 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:

DirectionBytesMeaning
client to serverFISH?Sent after child upstream Init when transport permission allows.
server to clientFISH!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:

LinePurpose
main lineThe original incoming line from the previous node. It stays on the previous side.
child linesInternal 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:

  1. The main line state is initialized.
  2. Up to simultaneous-tries-perline child lines are created on the same worker; synchronous selection stops new attempts.
  3. Each child line is initialized toward next.
  4. Each child sends FISH? after Init when its transport is unpaused.
  5. If no child has already won synchronously, the main line schedules a 5000 ms selection timeout.
  6. Upstream payload on the main line is queued until a child wins.
  7. The first child to receive FISH! becomes the selected child.
  8. Losing child lines are closed.
  9. Any downstream bytes coalesced with FISH! are sent before application work can induce newer replies.
  10. 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​

LimitValueBehavior
Selection timeout5000 msMain line closes if no child is selected in time.
Pending upstream payload2 MiBMain line closes if its retained protocol/order backlog exceeds this.
Incomplete child reply4 bytesA complete five-byte reply is validated immediately.
Coalesced reply body / selection ordering2 MiBMarker bytes do not consume this application-data allowance.
Pending upstream entries1,024 buffersEmpty TCP input consumes no retained entry.
Required left padding0The 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 Est is 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-perline also increases connection load.
  • The probe is fixed and public; use TLS, encryption, authentication, or routing nodes around it when the path needs security.
  • Upstream Est and downstream Init are 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:

PropertyValue
node flagskNodeFlagSupportsSplice
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayer4
layer_group_next_nodekNodeLayer4
required_padding_left0 bytes