PingClient
PingClient is a pure layer-3 packet tunnel that disguises packet payload as
ICMP-shaped traffic. It is not a latency probe and it is not related to
KeepAliveClient; it rewrites packets.
PingClient is created with WaterWall's packet-tunnel API, so it has no
per-line state and runs on worker packet lines supplied by the chain.
Direction Model
PingClient transforms payload in two directions:
| Direction | Behavior |
|---|---|
upstream Payload | Encapsulates or rewrites the packet according to strategy, then forwards with tunnelNextUpStreamPayload(). |
downstream Payload | Reverses the strategy for matching peer traffic, then forwards with tunnelPrevDownStreamPayload(). |
Direct PingClient -> PingServer adjacency is the canonical pair. PingClient
wraps the upstream request and PingServer restores it in its upstream callback
before continuing to the next node. On the return path, PingServer wraps the
downstream response and PingClient restores it in its downstream callback.
plain request -> PingClient -> PingServer -> plain request
plain response <- PingClient <- PingServer <- plain response
Typical Placement
With a packet transport:
TunDevice -> PingClient -> RawSocket
RawSocket -> PingServer -> TunDevice
In local tests, use one direct packet chain:
TesterClient(packet-mode=true) -> PingClient -> PingServer -> TesterServer(packet-mode=true)
Compatibility And Migration
This direction change is intentionally breaking for old PingServer topologies.
The client order above is unchanged, but an existing
TunDevice -> PingServer -> RawSocket server chain must become
RawSocket -> PingServer -> TunDevice. Replace paired-Bridge Ping layouts with
direct PingClient -> PingServer adjacency. The PingClient implementation,
settings, and wire format are unchanged, so an older PingClient remains
wire-compatible with the new PingServer when their settings match. The strict
requirement is to deploy the new PingServer binary together with the new server
topology; an old PingServer needs the old server topology. Upgrading both peers
together can still simplify operations, but it is not required by the protocol.
MTU requirements remain strategy-dependent.
Configuration Example
{
"name": "icmp-client",
"type": "PingClient",
"settings": {
"strategy": "wrap-in-new-ip-and-icmp-header",
"identifier": 4660,
"check-identifier": true,
"source": "198.51.100.10",
"dest": "203.0.113.20",
"xor-byte": 90,
"roundup-size": true,
"sequence-start": 0,
"ipv4-id-start": 0,
"ttl": 64,
"tos": 0
},
"next": "raw-out"
}
settings may be omitted or empty. Defaults are used when fields are not set.
If settings is present, it must be an object.
Strategies
wrap-in-new-ip-and-icmp-header
Wraps the whole inner IPv4 packet in a fresh outer IPv4 packet:
outer IPv4 header -> ICMP echo header -> original IPv4 packet
Details:
- input must be a complete, self-consistent IPv4 packet
- outer protocol is set to ICMP
- outer ICMP is echo request (
type 8) with code0 - outer IPv4
ttl,tos, and IPv4 ID come from settings/counters sourceanddestare optional- when
sourceis omitted, the outer source address is copied from the inner packet source - when
destis omitted, the outer destination address is copied from the inner packet destination - downstream decapsulation accepts ICMP echo request and echo reply packets
- downstream decapsulation checks
identifieronly whencheck-identifieris enabled - downstream decapsulation does not verify configured source/destination addresses
- after a matching envelope is found, the ICMP payload is stripped and forwarded even if the recovered bytes are not a valid IPv4 packet
If the input cannot be parsed as IPv4, the packet is forwarded unchanged. A valid IPv4 packet that is too large to wrap is also forwarded unchanged. If required left padding is not available, the packet is dropped and a warning is logged.
wrap-in-icmp-header-and-reuse-ipv4-addresses
Reuses the existing IPv4 header instead of adding a new outer IPv4 header. This is the default strategy.
existing IPv4 header -> ICMP echo header -> original transport bytes -> metadata trailer
Details:
- input must be a complete, self-consistent IPv4 packet
- source address, destination address, TOS, TTL, IPv4 ID, DF flag, and IPv4 options are preserved
- IPv4 protocol is changed to ICMP
- the original transport payload is moved into the ICMP payload
- a 5-byte trailer is appended to the ICMP payload
- trailer bytes include magic
0x57 0x50, original IPv4 protocol, and original transport length - fragmented IPv4 packets are forwarded unchanged because this strategy cannot restore them safely
- packets that cannot fit after ICMP wrapping are forwarded unchanged
On decapsulation, the peer reads the trailer, restores the original protocol and transport length, recalculates the IPv4 packet checksum, and forwards the restored packet.
wrap-in-only-icmp-header
Treats input as raw bytes and prepends only an ICMP echo header:
ICMP echo header -> raw payload
Details:
- input is not required to be an IPv4 packet
- no IPv4 header is emitted by this node
- output is ICMP frame data, not a complete IP packet
source,dest,ttl,tos, andipv4-id-startare not used- downstream decapsulation strips the matching ICMP echo header and restores the original raw payload
Use this only in a chain where the surrounding packet path expects ICMP frame data rather than a full IP packet.
change-only-ipv4-protocol-number
Only swaps the IPv4 protocol number in place. It does not add an ICMP header and does not prepend a new IPv4 header.
Details:
- requires
swap-protocol - upstream changes packets whose IPv4 protocol equals
swap-protocolto ICMP - downstream changes ICMP packets back to
swap-protocol - only the IPv4 header checksum is recalculated immediately
- transport bytes are left unchanged
identifier,check-identifier,sequence-start,ipv4-id-start,xor-byte, androundup-sizeare not used by this strategy
swap-protocol accepts "TCP", "UDP", "ICMP", or an integer protocol
number from 0 to 255.
Settings
| Setting | Type | Default | Description |
|---|---|---|---|
strategy | string | wrap-in-icmp-header-and-reuse-ipv4-addresses | Packet disguise mode. |
identifier | integer | 44975 (0xAFAF) | ICMP echo identifier for ICMP envelope modes. Range: 0..65535. |
check-identifier | boolean | true | When enabled, downstream ICMP envelopes must match identifier; mismatches are forwarded unchanged. |
sequence-start | integer | 0 | Initial ICMP echo sequence counter for ICMP envelope modes. Range: 0..65535. |
ipv4-id-start | integer | 0 | Initial outer IPv4 identification counter for wrap-in-new-ip-and-icmp-header. Range: 0..65535. |
ttl | integer | 64 | Outer IPv4 TTL for wrap-in-new-ip-and-icmp-header. Range: 0..255. |
tos | integer | 0 | Outer IPv4 TOS byte for wrap-in-new-ip-and-icmp-header. Range: 0..255. |
xor-byte | integer | disabled | Optional XOR byte applied to ICMP payload in ICMP envelope modes. Range: 0..255; unset/-1 disables it. |
roundup-size | boolean | false | Pads ICMP payload in ICMP envelope modes. |
roundup | boolean | false | Legacy alias for roundup-size. |
source | IPv4 string | copied from inner packet | Optional outer source for wrap-in-new-ip-and-icmp-header. |
dest | IPv4 string | copied from inner packet | Optional outer destination for wrap-in-new-ip-and-icmp-header. |
swap-protocol | string or integer | required for protocol-swap strategy | Protocol restored from ICMP on downstream path. |
swap-identifier | string or integer | unset | Legacy alias for swap-protocol. |
Roundup And XOR
xor-byte and roundup-size apply only to the ICMP envelope strategies.
When roundup-size is enabled:
wrap-in-new-ip-and-icmp-headerandwrap-in-only-icmp-headerstore the original payload length as a 2-byte prefix inside the ICMP payload, then add random paddingwrap-in-icmp-header-and-reuse-ipv4-addressesstores the original transport length in the 5-byte trailer and may add random padding before the trailer- decapsulation uses the prefix or trailer to remove padding
Current PingClient roundup buckets are:
56, 128, 256, 512, 1024
If none of those buckets can fit but the maximum ICMP payload can fit, the maximum payload size is used.
When xor-byte is set, the ICMP payload is XORed after padding/trailer
construction and XORed again before decapsulation metadata is read.
MTU And Packet-Size Limits
Warning: PingClient does not fragment packets, discover the path MTU, or
reduce an upstream TCP MSS. Its size checks use the compile-time
kMaxAllowedPacketLength, currently 1500 bytes; they do not use misc.mtu.
misc.mtu only becomes relevant here when another node, such as TunDevice,
uses it as an input MTU default.
At the current 1500-byte Ping limit, the largest input that each strategy can transform is:
| Strategy | Minimum added bytes | Maximum input, roundup-size=false | Maximum input, roundup-size=true |
|---|---|---|---|
wrap-in-new-ip-and-icmp-header | 28-byte outer IPv4 + ICMP headers | 1472 | 1470 |
wrap-in-icmp-header-and-reuse-ipv4-addresses | 8-byte ICMP header + 5-byte trailer | 1487 | 1487 |
wrap-in-only-icmp-header | 8-byte ICMP header | 1492 | 1490 |
change-only-ipv4-protocol-number | 0 | 1500 | not applicable |
The ICMP-only row measures its raw input and output frame; this strategy does not emit an IP header.
When a valid input exceeds the applicable limit, the current ICMP envelope strategies normally forward the original packet unchanged instead of fragmenting or wrapping it. This is not a safe substitute for MTU planning: the packet is no longer disguised as the configured ICMP envelope and may be filtered or routed differently.
For a common TunDevice -> PingClient -> RawSocket path whose real packet MTU
is 1500, configure TunDevice.settings.device-mtu no higher than the applicable
table value. Setting both misc.mtu and device-mtu to 1400 is a simple
conservative choice when the Ping strategy may change, but it is not required
when the exact overhead is known.
roundup-size can pad an accepted packet all the way to the 1500-byte Ping
limit. Consequently, a 1400-byte input is safe for a direct 1500-byte output
path, but roundup may consume the remaining space; it does not necessarily
leave 100 bytes for another encapsulation layer after PingClient. Account for
every later header as well, and use the smaller of the real path MTU and every
downstream node's packet limit.
Packet Handling Notes
- IPv4 packet strategies require complete IPv4 packets whose buffer length exactly matches IPv4 total length.
- IPv6/non-IPv4 packets that cannot be rewritten are generally forwarded unchanged.
- Fragmented IPv4 packets are forwarded unchanged in reuse-header mode.
- Matching downstream ICMP envelopes with malformed roundup metadata or malformed reuse trailers are dropped and logged.
- Matching downstream ICMP can be echo request or echo reply.
- If
check-identifieris enabled and the ICMP identifier does not match, the packet is forwarded unchanged rather than decapsulated. source/destare not checked during decapsulation.
Node Metadata
Source-backed metadata:
| Property | Value |
|---|---|
| node flags | kNodeFlagNone |
can_have_prev | true |
can_have_next | true |
layer_group | kNodeLayer3 |
layer_group_prev_node | kNodeLayer3 |
layer_group_next_node | kNodeLayer3 |
required_padding_left | 28 bytes |
| line state size | 0 |
required_padding_left = 28 covers the worst-case prepend path:
20-byte IPv4 header + 8-byte ICMP echo header
Compatibility Aliases
The parser still accepts several historical misspellings and aliases:
warp-in-new-ip-and-icmp-headerwrap-in-new-ipv4-and-icmp-headerwarp-in-new-ipv4-and-icmp-headerwarp-in-icmp-header-and-reuse-ipv4-addresseswrap-in-icmp-header-and-reuse-ip-addresseswarp-in-icmp-header-and-reuse-ip-addresseswrap-in-icmp-header-and-update-ipv4-headerwarp-in-icmp-header-and-update-ipv4-headerwarp-in-only-icmp-headerwrap-payload-in-only-icmp-headerwarp-payload-in-only-icmp-headerchange-only-ip4-protocol-numberchange-only-ip4-packet-identifier-number
Prefer the canonical strategy names in new configs.