Skip to main content

KeepAliveClient

KeepAliveClient is the client-side peer for KeepAliveServer. It wraps upstream payloads in a small keepalive frame format and periodically sends internal ping frames on every live line tracked by this tunnel.

Use it when a WaterWall path should keep producing traffic so timeout-based middleboxes, NAT devices, or firewalls are less likely to treat the connection as idle. The matching peer must be KeepAliveServer; otherwise the remote side will receive framed bytes instead of the original payload.

KeepAliveClient is a normal composable tunnel. It does not create sockets, does not create or destroy line_t objects, and does not keep protocol state alive after a clean finish.

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.
  • Accounts initialized borrowed lines in their worker state.
  • Creates a worker-local idle table at the first transport Est on each worker.
  • Uses separate idle items for recurring pings and outstanding reply deadlines.
  • Schedules empty ping frames using ping-interval; sensitive mode starts after transport Est and waits for each reply.
  • Encodes upstream payloads as normal keepalive frames and sends them to next.
  • Splits upstream payloads larger than 1 MiB into multiple normal frames.
  • Buffers downstream framed bytes until complete frames are available.
  • Decodes downstream normal frames and forwards their payload to the previous node.
  • Answers downstream ping frames by sending upstream pong frames.
  • Uses empty downstream pong frames to acknowledge an outstanding ping when sensitive-mode is enabled; otherwise ignores them.
  • Drops empty normal frames and unknown frame kinds.
  • Untracks the line, destroys local line state, and forwards the received directional Finish.

The optional reply watchdog closes connections whose pong is missing or late. It is disabled by default.

Configuration Example​

{
"name": "ka-client",
"type": "KeepAliveClient",
"settings": {
"ping-interval": 30000,
"sensitive-mode": false,
"tolerance-ms": 90000
},
"next": "transport"
}

Matching server side:

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

Required Fields​

Top-level fields:

FieldTypeDescription
namestringUser-chosen node name. Must be unique inside the config file.
typestringMust be exactly "KeepAliveClient".
settingsobjectOptional. If omitted, default intervals are used and the reply watchdog is disabled.
nextstringRequired in normal use. The next node receives framed upstream data.

KeepAliveClient must have a previous node and must reach a matching KeepAliveServer in the logical path.

Settings​

SettingTypeDefaultDescription
ping-intervalinteger30000Periodic ping interval in milliseconds. Must be at least 1; startup fails if the value is lower.
sensitive-modebooleanfalseEnables one outstanding ping per line and closes on a missing or late pong.
tolerance-msinteger90000Reply deadline in milliseconds, including local Pause. Must be in 1..2147483647. Used when sensitive mode is enabled.

Avoid very small intervals unless you intentionally want high keepalive traffic. In both modes, the first ping is due one ping-interval after downstream transport Est. The worker timer sends it on the first eligible check at or after that deadline, including during advisory Pause. Application traffic can flow during the initial wait. In sensitive mode, the reply deadline starts immediately before the ping is handed to the next node, including time it may spend queued there.

With sensitive mode disabled, the ping item sends one empty ping when due, then schedules its next interval. An active encoding callback keeps the probe due for a later check. There is no catch-up burst.

Reply Watchdog​

In sensitive mode, only one ping may be outstanding. An empty pong on that exact line clears the wait. Application frames, peer pings, unknown kinds and nonempty pongs do not acknowledge it; unsolicited pongs are ignored.

A missing or late pong closes the borrowed connection through the existing Finish path. KeepAliveClient does not recreate the connection. Deadlines use the owner event loop's monotonic clock. The separate deadline item is canceled by a timely pong and is never extended by the ping item. Expiry is handled on the first idle-table check at or after the deadline, or when a late pong is decoded. New short deadlines may be checked about one second late; this node does not guarantee subsecond wakeup precision.

Pause and Resume in either direction are forwarded transparently, including repeated notifications. Due Ping and ready Pong output do not wait for Resume. The parser serializes nested Ping input behind the active reply under its existing storage limits; there is no separate pending-Pong queue. Any outstanding reply deadline continues, and a timely empty Pong can acknowledge it during Pause. Finish stops the parser, releases retained input and cancels both idle items.

Choose a tolerance that permits the path's throughput and latency. Pongs share the ordered stream with normal frames, so paused time, queueing and a 1 MiB body can delay a reply. KeepAliveServer already answers these pings.

Frame Format​

Every frame starts with a 5-byte prefix:

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 upstream payloads are split into multiple frames. Both peers use the same five-byte prefix.

Direction Behavior​

Incoming callbackBehavior
upstream InitInitializes the read buffer and tracking fields, tracks the line, then forwards Init to next.
upstream PayloadEncodes the payload into one or more normal frames and forwards them to next. Empty payloads are recycled.
upstream EstForwards to next.
upstream Pause / ResumeForwards every notification to next; outstanding reply deadlines continue.
upstream FinishUntracks the line, destroys local state, then forwards Finish to next.
downstream PayloadTreats bytes as framed input from KeepAliveServer; forwards decoded normal payloads to the previous node.
downstream EstPublishes transport readiness for the watchdog, then promptly forwards to the previous node.
downstream Pause / ResumeForwards every notification to the previous node; due Ping/Pong output and reply deadlines continue.
downstream FinishUntracks the line, destroys 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​

Downstream 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​

KeepAliveClient prepends the 5-byte frame prefix while encoding, 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 KeepAliveServer; 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.
  • This node keeps a path active; it is not an external health-check protocol.
  • This node does not encrypt, authenticate, compress, or rewrite payload bytes.
  • It is safest around datagram-preserving or stream-preserving transports that keep the framed bytes intact 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​

KeepAliveClient advertises kNodeFlagSupportsSplice. Framing continues for the whole connection. Upstream encoding writes only the five-byte header into real left padding; downstream decoding uses a five-byte splice_stream_t header cache. Payload bodies remain eligible for private-pipe forwarding. Payloads larger than 1 MiB are split using representation-aware range operations. Pipe allocation or capacity pressure may select complete ordinary fallback. Ping and pong 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. Repeated Pause/Resume pass through unchanged; due pings and ready replies proceed without Resume. Framing and watchdog duties last for the whole connection, so this node remains active and cannot permanently bypass itself. Pause and Resume never postpone an outstanding reply deadline.

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.