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 MiBinto 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:
| Field | Type | Description |
|---|---|---|
name | string | User-chosen node name. Must be unique inside the config file. |
type | string | Must be exactly "KeepAliveServer". |
settings | object | Optional. The current implementation has no server-specific settings. |
next | string | Required 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:
| Bytes | Field | Description |
|---|---|---|
0..3 | body length | Unsigned 32-bit big-endian integer. This length includes the 1-byte frame kind plus any payload bytes. |
4 | frame kind | 1 normal payload, 2 ping, or 3 pong. |
Frame kinds:
| Kind | Meaning | Body payload |
|---|---|---|
1 | normal payload | Original WaterWall payload bytes. |
2 | ping | Empty in normal keepalive traffic. |
3 | pong | Empty 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 callback | Behavior |
|---|---|
upstream Init | Initializes the read buffer, then forwards Init to next. |
upstream Payload | Treats bytes as framed input from KeepAliveClient; forwards decoded normal payloads to next. |
upstream Est / Pause / Resume | Forwards to next. |
upstream Finish | Destroys local state, then forwards Finish to next. |
downstream Payload | Encodes the payload into one or more normal frames and forwards them to the previous node. Empty payloads are recycled. |
downstream Est / Pause / Resume | Forwards to the previous node. |
downstream Finish | Destroys local state, then forwards Finish to the previous node. |
downstream Init | Disabled; 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,577is 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:
| 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 | 5 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.