Skip to main content

KeepAliveServer

KeepAliveServer is the server-side peer for KeepAliveClient. It decodes upstream keepalive frames from the client side, forwards normal payloads to next, answers ping frames with pong frames, and wraps downstream payloads in the same frame format on the way back.

Use it only as the matching peer for KeepAliveClient. By itself, it expects framed upstream input and will not understand ordinary unframed payload bytes.

KeepAliveServer is a normal composable tunnel. It does not create sockets, does not create or destroy line_t objects, and does not own any periodic timer.

Typical Placement​

Direct in-process test:

TesterClient -> KeepAliveClient -> KeepAliveServer -> TesterServer

Two-server TCP layout:

client side: TcpListener -> KeepAliveClient -> TcpConnector
server side: TcpListener -> KeepAliveServer -> TcpConnector

Any tunnels between the client and server must preserve the keepalive frame bytes exactly.

What It Does​

  • Initializes local line state on upstream Init.
  • Buffers upstream framed bytes until complete frames are available.
  • Decodes upstream normal frames and forwards their payload to next.
  • Answers upstream ping frames by sending downstream pong frames.
  • Ignores upstream pong frames.
  • Drops empty normal frames and unknown frame kinds.
  • Encodes downstream payloads as normal keepalive frames and sends them to the previous node.
  • Splits downstream payloads larger than 1 MiB into multiple normal frames.
  • Destroys local line state and forwards the received directional Finish.

The server does not have its own keepalive timer. Periodic ping generation is owned by KeepAliveClient.

Configuration Example​

{
"name": "ka-server",
"type": "KeepAliveServer",
"settings": {},
"next": "service"
}

Matching client side:

{
"name": "ka-client",
"type": "KeepAliveClient",
"settings": {
"ping-interval": 60000
},
"next": "transport"
}

Required Fields​

Top-level fields:

FieldTypeDescription
namestringUser-chosen node name. Must be unique inside the config file.
typestringMust be exactly "KeepAliveServer".
settingsobjectOptional. The current implementation has no server-specific settings.
nextstringRequired in normal use. The next node receives decoded upstream payloads.

KeepAliveServer must have a previous node and must be reached by a matching KeepAliveClient.

Settings​

KeepAliveServer currently has no meaningful settings. Keepalive timing is configured on KeepAliveClient with settings.ping-interval.

Frame Format​

The server uses the same 5-byte prefix as the client:

BytesFieldDescription
0..3body lengthUnsigned 32-bit big-endian integer. This length includes the 1-byte frame kind plus any payload bytes.
4frame kind1 normal payload, 2 ping, or 3 pong.

Frame kinds:

KindMeaningBody payload
1normal payloadOriginal WaterWall payload bytes.
2pingEmpty in normal keepalive traffic.
3pongEmpty response to a ping frame.

The body length includes the kind byte. Each normal frame carries at most 1 MiB (1,048,576 bytes) of payload; the maximum body length is 1,048,577. Larger downstream payloads are split into multiple frames. Both peers use the same five-byte prefix.

Direction Behavior​

Incoming callbackBehavior
upstream InitInitializes the read buffer, then forwards Init to next.
upstream PayloadTreats bytes as framed input from KeepAliveClient; forwards decoded normal payloads to next.
upstream Est / Pause / ResumeForwards to next.
upstream FinishDestroys local state, then forwards Finish to next.
downstream PayloadEncodes the payload into one or more normal frames and forwards them to the previous node. Empty payloads are recycled.
downstream Est / Pause / ResumeForwards to the previous node.
downstream FinishDestroys local state, then forwards Finish to the previous node.
downstream InitDisabled; reaching this callback is treated as a fatal invalid chain flow.

Framed Input Handling​

Upstream bytes are accumulated in a splice_stream_t. This means a frame can arrive split across multiple payload callbacks, or multiple frames can arrive in one callback.

The decoder behavior is conservative:

  • waits for more bytes when the prefix or full body has not arrived yet
  • closes the borrowed line through its owner when a frame body length outside 1..1,048,577 is seen
  • drops empty normal frames
  • drops unknown frame kinds
  • drains complete frames first, then closes if retained allocation charge still exceeds its limit after compaction

Buffer And Padding Notes​

KeepAliveServer prepends the 5-byte frame prefix while encoding downstream payloads, so its node metadata advertises:

required_padding_left = 5
layer_group = kNodeLayer4
layer_group_next_node = kNodeLayer4
layer_group_prev_node = kNodeLayer4

The caller must provide enough left padding for the prefix; Debug asserts this precondition. WaterWall's chain padding calculation reserves the advertised space.

Practical Notes​

  • Pair this node with KeepAliveClient; it is not useful by itself.
  • Ping and pong frames are internal to the KeepAlive pair and are not delivered as payload to surrounding nodes.
  • The server answers pings, but it does not measure client health or enforce a pong timeout.
  • This node does not encrypt, authenticate, compress, or rewrite payload bytes.
  • It is safest around transports that preserve the framed bytes between the client and server.

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_left5 bytes

Splice Support​

KeepAliveServer advertises kNodeFlagSupportsSplice. Its upstream decoder uses a five-byte splice_stream_t header cache; its downstream encoder prepends the same five-byte header in real left padding. Framing continues for the whole connection, with payload bodies eligible for private-pipe forwarding. Payloads larger than 1 MiB are split with representation-aware range operations. Pipe allocation or capacity pressure may select complete ordinary fallback. Pong control frames use ordinary buffers.

Nested encoder input stays behind the active payload under a shared 8 MiB logical-byte and 1,024-buffer reentry bound, counting the active suffix. The decoder serializes nested input, limits nested retained bytes to 8 MiB, and limits retained allocation charge to 16 MiB, attempting beneficial ordinary compaction before refusing excess charge. Complete frames drain before the retained-charge check. Validated frame lengths guarantee an incomplete remainder smaller than 1 MiB + 5 bytes, including the header. Pause is forwarded promptly and does not interrupt that synchronous batch. Pause and Resume relay transparently, including repeats; ready Pong output does not wait for Resume. The parser serializes nested Ping input behind the active reply under its existing storage limits, without a separate pending-Pong queue. Framing continues for the whole connection, so this node remains active and cannot permanently bypass itself.

Zero or oversized frame body lengths, admission failure and retained-storage overflow close the borrowed line through its owner. Finish releases incomplete frames, active encoder suffixes and queued reentrant input; this node never destroys line_t itself.