Skip to main content

IpManipulator

IpManipulator is a packet tunnel for mutating raw IPv4 packets inside WaterWall layer-3 chains.

It is one of WaterWall's main packet-side trick nodes. Use it when packet bytes are already raw IP packets, for example behind TunDevice, RawSocket, WireGuardDevice, or another packet tunnel. Do not place it in an ordinary TCP byte-stream chain.

warning

Most tricks in this node are packet-shaping and middlebox-behavior experiments. They are highly network-dependent. A trick that works on one path can be ignored, dropped, or harmful on another path.

What It Does

  • Reads packet payloads on upstream and downstream packet paths.
  • Applies enabled IPv4 packet tricks in place where possible.
  • Can rewrite IP protocol numbers for TCP and UDP.
  • Can rewrite TCP flag bits.
  • Can append original TCP/UDP ports to packet tails and rewrite live ports.
  • Can duplicate final outgoing packets.
  • Can create crafted TLS ClientHello-related packet shapes.
  • Can fragment and shuffle TLS ClientHello packets with SNI Blender.
  • Updates IPv4 header and TCP checksums in place for protocol swaps and simple TCP flag changes; size-changing or payload-crafting tricks request full checksum recalculation.

IpManipulator is created with WaterWall's packet-tunnel API. Normal connection-style Finish, Pause, Resume, and Est callbacks are not part of its intended runtime path.

Typical Placement

Packet-chain examples:

TunDevice -> IpManipulator -> RawSocket
TunDevice -> WireGuardDevice -> IpManipulator -> UdpStatelessSocket

Paired protocol-swap deployment:

edge A packet chain -> IpManipulator -> packet transport
edge B packet chain -> IpManipulator -> packet output

When a trick needs an auxiliary branch, such as real-sni-upstream-node, crafted-server-hello-upstream-node, or real-fin-upstream-node, configure that branch separately from the normal next path.

Configuration Example

Simple protocol-number swap:

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"protoswap-tcp": 253,
"protoswap-udp": 252
},
"next": "next-packet-node"
}

SNI Blender example:

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"sni-blender": true,
"sni-blender-packets": 4
},
"next": "next-packet-node"
}

Mixed advanced example:

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"protoswap-tcp": 253,
"protoswap-udp": 252,
"sni-blender": true,
"sni-blender-packets": 4,
"packet-duplicate": 1,
"source-port-ghost": true,
"dest-port-ghost": true
},
"next": "next-packet-node"
}

Required Fields

Top-level fields:

FieldTypeDescription
namestringUser-chosen node name. Must be unique inside the config file.
typestringMust be exactly "IpManipulator".
settingsobjectMust enable at least one supported trick.
nextstringUsually required for useful forwarding. Some tricks explicitly require a normal top-level next.

Startup fails if no trick is enabled:

IpManipulator: no tricks are enabled, nothing to do

Trick Summary

Trick familyMain settingsDirectionPurpose
Protocol swapprotoswap-tcp, protoswap-udpbothRewrite IPv4 protocol numbers and restore them on the opposite side.
SNI Blendersni-blender, sni-blender-packetsupstreamFragment and shuffle IPv4/TCP TLS ClientHello packets.
Packet duplicatepacket-duplicatefinal send pathSend extra copies of the final packet before the original.
first-snifirst-sniupstreamSend a crafted ClientHello copy before the original.
smuggle-snismuggle-sni, real-sni-upstream-nodeupstreamSend the real ClientHello on a helper branch and a delayed crafted copy on the normal path.
overlap-snioverlap-sni, crafted-server-hello-upstream-nodeupstream plus downstream trackingOverlap a generated ClientHello with captured real TLS bytes.
ech-sni-trickech-sni-trickupstreamSend the fake ClientHello embedded by TlsClient inside ECH payload bytes as an out-of-order TCP segment.
synfin-snisynfin-sniupstreamMix a real first TLS chunk, a close packet, fake SYN, crafted ClientHello, filler TLS bytes, and remaining real bytes.
smuggle-finsmuggle-fin, real-fin-upstream-nodeupstream plus downstream echo trackingInject a mirrored `FIN
TCP flag rewriteup-tcp-bit-*, dw-tcp-bit-*bothForce, toggle, or remap TCP flag bits.
Port ghostsource-port-ghost, dest-port-ghostupstream apply, downstream restoreCarry original ports in packet tails while rewriting live transport ports.

Protocol Swap

Protocol swap changes the IPv4 Protocol field. For example, TCP is normally protocol number 6 and UDP is normally 17.

SettingTypeDescription
protoswapintegerLegacy alias for protoswap-tcp.
protoswap-tcpintegerReplacement protocol number for TCP packets.
protoswap-udpintegerReplacement protocol number for UDP packets.

Valid protocol replacement values are 0 through 255.

When a packet comes back with a configured replacement number, IpManipulator restores it to normal TCP or UDP. For a symmetric tunnel, enable compatible protocol-swap settings on the peer-side packet chain too.

Replacement values must not be the literal TCP (6) or UDP (17) protocol numbers, even when only one family is configured, because genuine traffic using the reused number could not be distinguished from mapped traffic. When both families are enabled, their configured replacement values must also be distinct.

protoswap-tcp-2 has been removed and a stale configuration containing it is rejected. Migrate to the single reversible protoswap-tcp mapping. This also ensures every fragment of one IPv4 datagram receives the same mapped protocol number.

Upstream tricks run while the packet still carries its real protocol number. Immediately before normal egress, IpManipulator applies port ghost, completes the IPv4 and transport checksums using that real protocol, swaps the protocol number, and repairs the IPv4 header checksum. Downstream restores the protocol before port-ghost restoration or any TCP/TLS trick parses the packet.

If an upstream packet already carries this node's configured replacement, IpManipulator treats the node as the explicit unwrapping half of a chained transport pair: it restores the real protocol, completes checksum work under that protocol, and forwards the packet unmapped. To keep a packet wrapped past a later node, do not enable the same protocol swap on that node.

TCP Flag Rewrite

TCP flag rewriting can force, toggle, or remap TCP flag bits. The older Persian documentation described this as "playing with TCP bits"; that is still the core idea, but the current implementation accepts more values.

The key format is:

up-tcp-bit-<flag>
dw-tcp-bit-<flag>

Supported flags:

cwr, ece, urg, ack, psh, rst, syn, fin

Accepted values:

off
on
toggle
flip
switch
packet->cwr
packet->ece
packet->urg
packet->ack
packet->psh
packet->rst
packet->syn
packet->fin

flip and switch are aliases for toggle.

Example:

{
"up-tcp-bit-ack": "packet->fin",
"up-tcp-bit-fin": "packet->ack",
"dw-tcp-bit-ack": "packet->fin",
"dw-tcp-bit-fin": "packet->ack"
}

Bit actions are evaluated from the original packet flags, not line by line against already-mutated flags. That means you can safely swap two bits by copying each one from the other original bit.

If any flag changes in simple mode (without preserve-tcp-bitflags), the TCP flags byte is rewritten and the TCP checksum is updated incrementally in place with updateIpv4TransportChecksum16(). Simple flag changes preserve any pre-existing checksum recalculation flag without creating a new request.

Preserve TCP Bitflags

SettingTypeDefaultDescription
preserve-tcp-bitflagsbooleanfalseBacks up the original TCP flags byte before rewriting it, so the opposite packet direction can restore the packet later.

This mode is useful when you want a packet to travel through one side of a path with altered TCP control bits, then return to its original TCP flag shape on the reverse direction.

Direction workflow:

  1. In a direction with configured TCP-bit rewrite actions, IpManipulator stores the original full TCP flags byte as the last byte of the IPv4 packet.
  2. It then rewrites the live TCP flags according to up-tcp-bit-* or dw-tcp-bit-*.
  3. It increases the IPv4 total length by one byte and marks the packet for checksum recalculation.
  4. On the opposite direction, when that direction has no TCP-bit rewrite actions but the other direction does, IpManipulator reads the final packet byte, restores the TCP flags field from it, removes that byte, shrinks the IPv4 total length, and marks checksums for recalculation.

The configured TCP-bit actions select which direction backs up and which direction restores. Enabling this option does not assign a fixed role to upstream or downstream:

Configured TCP-bit actionsUpstream behaviorDownstream behavior
Upstream onlyBack up the original flags, append one byte, and apply the upstream actions.Restore the flags from the final byte and remove it.
Downstream onlyRestore the flags from the final byte and remove it.Back up the original flags, append one byte, and apply the downstream actions.
Upstream and downstreamBack up, append, and apply the upstream actions.Back up, append, and apply the downstream actions. Neither direction restores.
Neither directionNo backup or restoration.No backup or restoration.

Example upstream rewrite with downstream restore:

{
"preserve-tcp-bitflags": true,
"up-tcp-bit-ack": "packet->fin",
"up-tcp-bit-fin": "packet->ack"
}

With that shape, upstream TCP packets carry their original flags in the final packet byte before ACK/FIN are remapped. Downstream packets handled by the same configuration have no dw-tcp-bit-* rewrite actions, so the final byte is used to restore the original TCP flags and is then removed.

This mode only applies to whole, non-fragmented IPv4 TCP packets with complete IP and TCP headers. It assumes the extra final byte survives the path unchanged. If another node or network device strips, rewrites, pads, or interprets trailing payload bytes, restoration can fail or restore the wrong flags.

MTU and packet-size warning

Every rewrite operation with preserve-tcp-bitflags appends one byte. Before final egress, IpManipulator transport-segments eligible whole IPv4 TCP data packets when that byte, port-ghost bytes, or both would exceed GLOBAL_MTU_SIZE. Every segment gets independently derived live and saved flags, one complete set of trailers, and continuous sequence space that accounts for SYN consuming one sequence number. A 1500-byte TCP input can therefore remain behind a 1500-byte WaterWall MTU without reserving operator headroom merely for these trailers. An exact-fit buffer is grown when the final packet is otherwise valid.

IpManipulator does not implement IPv4 fragmentation or reassembly for this purpose. Oversized UDP, RST, unsupported URG, and other packets that cannot be safely transport-segmented are logged with rate limiting and dropped. UDP deployments still need operator-provided MTU headroom for port-ghost bytes.

In particular, enabling this option by itself does not append or remove bytes; at least one direction must have a TCP-bit rewrite action configured.

Stateful SNI composition rules

Only one of first-sni, smuggle-sni, overlap-sni, synfin-sni, and ech-sni-trick may be enabled in one IpManipulator. Any one of those stateful tricks is also rejected with same-instance sni-blender or packet-duplicate because the state machine emits its transcript directly.

Use two nodes when both operations are needed:

... -> IpManipulator(stateful SNI only)
-> IpManipulator(sni-blender and/or packet-duplicate)
-> ...

The stateful node must come first in upstream traversal. Duplication before the stateful node duplicates its inputs and is not equivalent to duplicating its final output. sni-blender plus packet-duplicate remains supported when no stateful SNI trick is enabled.

Rejected TCP-bit compositions

Configuration fails at creation for these combinations:

  • any up-tcp-bit-* action together with first-sni, smuggle-sni, overlap-sni, synfin-sni, or ech-sni-trick, with or without preserve-tcp-bitflags. Upstream TCP-bit actions run before stateful SNI inspection, while packets generated and replayed by those state machines do not consistently re-enter TCP-bit processing.
  • preserve-tcp-bitflags together with at least one up-tcp-bit-* action and sni-blender. The backup byte is appended before SNI Blender creates IPv4 fragments, but the peer restoration path skips fragments and cannot remove that byte.

These combinations remain accepted:

  • preserve-tcp-bitflags with no TCP-bit actions, which is a no-op encoder
  • downstream-only dw-tcp-bit-* actions with a stateful SNI trick or with sni-blender, because they do not touch the upstream flow-opening SYN or ClientHello
  • simple upstream TCP-bit actions with sni-blender when preserve-tcp-bitflags is disabled

preserve-tcp-bitflags remains unsupported with any future upstream stage that fragments or reshapes a metadata-bearing TCP packet.

SNI Blender

SNI Blender does not rewrite the SNI string. It changes the IP packet shape of an upstream TLS ClientHello.

SettingTypeRequiredDescription
sni-blenderbooleanyesEnables TLS ClientHello fragmentation and shuffle.
sni-blender-packetsintegeryes when enabledNumber of fragments. Valid range: 2 through 16.

Behavior:

  • detects upstream IPv4/TCP packets carrying a TLS ClientHello
  • skips packets that are already fragmented
  • preserves the TCP header in the first fragment
  • splits the IP payload into multiple IPv4 fragments
  • rounds fragment offsets to 8-byte boundaries as required by IPv4
  • sends the fragments in shuffled order

Practical notes from the older Persian docs still apply:

  • The traffic must contain a visible TLS ClientHello at the packet layer.
  • If the user's existing application already sends TLS, WaterWall does not need to create TLS only for this trick.
  • If the chain itself must produce TLS, put TlsClient/TlsServer in the appropriate stream path before packets reach this node.
  • In local or "serverless" experiments, the operating system routing/capture setup must make the relevant packets pass through WaterWall.

This trick is network-dependent. Some paths drop fragmented ClientHello packets; others may pass them.

Packet Duplicate

SettingTypeDescription
packet-duplicateinteger greater than 0Sends this many duplicate packets, then sends the original packet once.

Duplication happens in the final send path after the other enabled tricks have finished shaping the packet.

Port Ghost

Port ghost applies on upstream final send and restores on downstream receive.

SettingTypeDescription
source-port-ghostbooleanAppend the original source port to the end of whole IPv4 TCP/UDP packets, then rewrite the live source port to a deterministic pseudo-random high port.
dest-port-ghostbooleanAppend the original destination port to the end of whole IPv4 TCP/UDP packets, then rewrite the live destination port to a deterministic pseudo-random high port.

If both are enabled, the source port bytes are appended first and the destination port bytes second.

Downstream packets that still carry those tail bytes restore the original live ports and shrink the packet back to its original length. Fragmented IPv4 packets are skipped.

Ordinary and crafted whole TCP data packets share one final egress segmenter. Each emitted segment receives exactly one port-ghost trailer and, when configured, one independently restorable saved-flags byte. SYN/CWR appear only on the first segment, FIN/PSH only on the final segment, and ACK/applicable ECE on every segment. Every completed segment stays within GLOBAL_MTU_SIZE, then final packet duplication is applied to that segment.

Oversized UDP cannot use TCP segmentation and is rate-limited-log-and-drop. IpManipulator does not introduce IPv4 fragmentation/reassembly, so UDP port ghost still requires operator-provided MTU headroom.

Dedicated real-SNI, mirrored-FIN, and overlap-SNI server-hello helper branches preserve their original tuples and do not receive a port-ghost trailer.

first-sni

first-sni sends one or more crafted ClientHello copies before the original ClientHello.

SettingTypeDefaultDescription
first-sninon-empty stringrequired to enableSNI value written into the crafted ClientHello copy.
first-sni-countinteger greater than 01Number of crafted copies to send before the original.
first-sni-replay-delayinteger ms, >= 00Delay between crafted replays after the first one.
first-sni-final-delayinteger ms, >= 00Delay between the last crafted copy and the original packet.
first-sni-ttlinteger 0 through 255preserve originalIPv4 TTL for the crafted copy.
first-sni-random-tcp-sequencebooleanfalseRandomize the crafted copy's TCP sequence number.

Only the crafted copy is modified; the original ClientHello remains on the normal path. If the ClientHello has TLS 1.3 PSK binders and changing the SNI would require recomputing binders, the trick skips the crafted copy and leaves the original path alone.

Multi-segment capture begins only when a packet contains a recognizable ClientHello first segment. Unrelated and non-TLS TCP payloads fail open immediately without entering a speculative prestart queue. Recognized, in-order ClientHellos can still span multiple TCP segments.

IPv4 fragments, including an MF=1 first fragment and any nonzero-offset later fragment, never enter First-SNI capture and continue on the normal fail-open path. When replay/final output is delayed, the transcript tail is inserted into a bounded FIFO before later packets and released at one absolute deadline.

smuggle-sni

smuggle-sni captures and reassembles a TLS ClientHello record across single or multi-segment TCP payloads (up to 16 segments / 16 KB) on the 4-tuple, sends the real ClientHello through a helper upstream branch immediately in order, and sends a generated multi-segment fake ClientHello batch through the normal next path after smuggle-sni-delay-ms. The fake batch strictly preserves original TCP segment boundaries, original TCP payload lengths, original TCP sequence numbers, and any trailing payload bytes beyond the ClientHello record. If length mismatch, fragment timeout/discontinuity, or generation failure occurs, smuggle-sni fails open immediately to the normal path.

SettingTypeDefaultDescription
smuggle-sninon-empty stringrequired to enableSNI value written into the crafted ClientHello copy.
smuggle-sni-delay-msinteger ms, >= 00Delay before sending the crafted copy batch on the normal path.
real-sni-upstream-nodestringrequiredNode name that receives the real captured ClientHello immediately.

This trick requires a normal top-level next. The helper node must exist and must not point back to IpManipulator itself. In practice, make the helper a separate branch head rather than the same node as the normal next.

IpManipulator creates an internal no-chain TlsClient helper to generate the crafted ClientHello shape for this trick.

Later packets inside its delay window enter the same bounded FIFO and use the flow's absolute deadline. The fake transcript is scheduled before that FIFO; FIN and RST stay behind already queued data.

overlap-sni

overlap-sni captures early TLS packets, builds a generated Chrome-like ClientHello, sends a crafted server-side TLS packet through a helper branch, and overlaps generated and real bytes on the TCP sequence range.

SettingTypeDefaultDescription
overlap-sninon-empty stringrequired to enableSNI value for the generated ClientHello.
overlap-sni-delay-msinteger ms, >= 00Delay for packets after the fake TCP SYN and later upstream packets while the overlap window is active.
overlap-sni-syn-ttlinteger 0 through 255preserve originalIPv4 TTL for the fake TCP SYN.
crafted-server-hello-upstream-nodestringrequiredHelper branch that receives the crafted server-side TLS packet.

This trick requires a normal top-level next. The helper branch must exist and must not point back to IpManipulator.

Later upstream packets use the flow's absolute overlap-window deadline and a bounded FIFO instead of receiving a fresh copy of overlap-sni-delay-ms. The crafted overlap tail is scheduled before that FIFO can release.

ech-sni-trick

ech-sni-trick is designed to work with TlsClient.settings.ech-sni-trick. TlsClient embeds a fake ClientHello inside GREASE encrypted_client_hello payload bytes. IpManipulator then performs the packet-side out-of-order send while keeping the original ClientHello bytes unchanged.

SettingTypeDefaultDescription
ech-sni-trickstring, 1 through 255 bytesrequired to enableMust match the related TlsClient.settings.ech-sni-trick value for the same flow.
data-shard-1-delayinteger ms, >= 00Delay before releasing the first original held packet after sending the fake inner ClientHello segment.
data-shard-2-delayinteger ms, >= 00Additional delay before releasing original packets 2 through N consecutively in their original sequence order. A one-packet capture ignores this setting.

The combined shard delay must fit in the supported unsigned 32-bit delay range. The out-of-order fake-inner packet is sent with PSH set. If the crafted out-of-order packet would exceed GLOBAL_MTU_SIZE, the flow is rejected instead of reshaped.

Capture is sequence-aware and does not depend on packet ordinals. A payload-free TCP SYN, with or without ECN ECE/CWR, opens a flow. TCP Fast Open SYN data is unsupported and passes through. Capture starts only when upstream payload contains a validated complete or partial TLS ClientHello prefix; unrelated and non-TLS payloads are never placed in a speculative prestart queue. Contiguous bytes continue validation through the five-byte TLS record header and four-byte handshake header until the exact record length is complete. Payload-free packets, including ACK-only packets before or during capture, pass unchanged and do not advance or disable capture.

Capture is bounded to 16 packets, a 16384-byte TLS record, and a 1500 ms timeout. A sequence gap, malformed or unsupported record, packet or size limit, timeout, or overlap that cannot be proven to be an exact retransmission fails open: held originals are released unchanged in TCP sequence order and that flow generation becomes passthrough. During incomplete capture, an exact retransmission matching an already-held segment's sequence, length, and bytes is consumed without disabling capture; partial and ambiguous overlaps fail open.

After capture, IpManipulator requires a valid outer SNI, a valid encrypted_client_hello extension, and exactly one embedded inner TLS ClientHello whose SNI parses completely and matches ech-sni-trick byte for byte. Missing, malformed, truncated, mismatched, or ambiguous candidates fail open without emitting a fake inner packet. This mechanism remains GREASE camouflage rather than real encrypted ECH.

On success, the original ClientHello bytes, packet boundaries, and TCP sequence numbers remain unchanged. IpManipulator first sends one out-of-order TCP packet containing only the matching embedded ClientHello bytes. It then:

  1. releases original packet 1 after data-shard-1-delay;
  2. waits the additional data-shard-2-delay; and
  3. releases original packets 2 through N consecutively in their original order.

A one-packet capture uses only the first delay. A two-packet capture preserves the original two-shard timing, while a larger capture does not create multiple equal-deadline timer messages that could reorder packets.

During delayed release, only an exact retransmission of an original segment that is still pending is swallowed. ACK-only traffic, non-overlapping later payload, and partial or ambiguous overlaps pass unchanged. TCP FIN and RST remain connection-lifecycle traffic and are never swallowed. A matching close in either direction cancels every original packet still awaiting release; an upstream close during incomplete capture first releases held originals, while a downstream close disposes of the incomplete capture without later injection.

Each delayed original is bound to the nonzero generation of the ECH flow that captured it as well as its 4-tuple. Reusing the same 4-tuple starts a new generation and first invalidates the old generation's incomplete capture and pending originals, so stale capture or release timers cannot inject old ClientHello data. While originals are pending, the flow remains protected from ordinary idle expiry; normal idle expiry resumes after the final release.

synfin-sni

synfin-sni is an advanced upstream TLS ClientHello trick. It sends a real first TLS data chunk, then a close packet, a fake SYN, a crafted fake ClientHello, generated TLS-looking filler bytes, and finally the remaining real ClientHello bytes.

SettingTypeDefaultDescription
synfin-sninon-empty stringrequired to enableSNI value for the generated ClientHello.
synfin-sni-additional-range-mininteger 0 through 655350Minimum extra real ClientHello bytes added to the first real chunk.
synfin-sni-additional-range-maxinteger 0 through 65535same as min when min is set alone, otherwise 0Maximum extra real bytes; one random value is chosen per flow and clamped before the real SNI bytes.
synfin-sni-syn-ttlinteger 0 through 255preserve originalTTL for the fake SYN.
synfin-sni-fin-ttlinteger 0 through 255preserve originalTTL for the close packet.
synfin-sni-fake-ttlinteger 0 through 255preserve originalTTL for the full crafted fake ClientHello packet.
synfin-sni-random-syn-checksumbooleanfalseRandomize IPv4/TCP checksums on the fake SYN.
synfin-sni-random-fin-checksumbooleanfalseRandomize IPv4/TCP checksums on the close packet.
synfin-sni-random-syn-sequencebooleanfalseRandomize the fake SYN sequence number.
synfin-sni-random-fin-sequencebooleanfalseRandomize the close packet sequence number.
synfin-sni-use-rstbooleanfalseSend `RST

TTL value 0 is a real TTL override. Omit the TTL field entirely if you want to preserve the captured packet TTL.

The crafted sequence is emitted synchronously in the documented order with no artificial per-packet 20 ms sleep or configurable pacing delay.

smuggle-fin

smuggle-fin injects a mirrored FIN/ACK through a helper branch and temporarily queues traffic on the flow-owner worker until the expected downstream echo is seen and the optional delay expires. Matching reverse packets that arrive on another worker are copied to the owner worker before they enter the same ordered queue. Each queued packet retains its own checksum-recalculation intent.

SettingTypeDefaultDescription
smuggle-finbooleanrequired to enableEnables mirrored FIN/ACK injection.
fin-sni-delay-msinteger ms, >= 00Delay after the expected downstream echoed FIN/ACK before queued packets are replayed.
real-fin-upstream-nodestringrequiredHelper branch that receives the crafted mirrored FIN/ACK.

This trick requires a normal top-level next. The helper node must exist and must not point back to IpManipulator.

Packets without TCP payload, packets that are already SYN, FIN, or RST, non-TCP packets, and fragmented IPv4 packets are left alone.

Stateful Flow Tables

SettingTypeDefaultDescription
stateful-flow-limitinteger, 32 through 104857665536Hard upper bound on the active flows that each enabled stateful trick may track.

first-sni, smuggle-sni, overlap-sni, synfin-sni, ech-sni-trick, and smuggle-fin each receive their own bounded table with this limit. The tables never grow beyond it. When a shard is full, IpManipulator reclaims a small, fixed budget of entries whose idle deadlines have passed. If that does not free a slot, admission fails open, the packet continues unchanged, and a rate-limited warning is logged. A live flow is never evicted to admit another flow, which protects ECH and Smuggle-FIN records that own held packet buffers.

Every table uses one normalized four-tuple as its canonical key, so forward and reverse packets resolve to the same record, hash, shard, and lock, including across workers. A valid replacement SYN explicitly cancels the previous generation's timers, captures, and queues as applicable before reinitializing that sole record in the new orientation. Duplicate canonical entries are not admitted.

The shard count follows the worker count, capped at 64, and the configured limit is partitioned so shard limits sum to the requested total. Lookup uses hash buckets and idle expiry uses a per-shard deadline heap, avoiding whole-table scans in packet callbacks. Flow-entry pointers remain valid only while their shard mutex is held; timers and cross-worker messages therefore identify flows by normalized tuple and generation.

First-SNI, Smuggle-SNI, and Overlap-SNI delay barriers retain at most 16 packets and 256 KiB per flow. One tunnel-wide generation binds the release action to the exact tuple instance, so a stale timer cannot release a reused tuple. Batches are detached under the shard lock and forwarded outside it. Count/byte exhaustion fails open in FIFO order, and reset, timeout, or table destruction recycles every retained packet.

Mutual Exclusion

The parser rejects every pair within the five-member stateful SNI set: first-sni, smuggle-sni, overlap-sni, synfin-sni, and ech-sni-trick. It also rejects any of them with same-instance sni-blender or packet-duplicate. See the supported two-node chain under "Stateful SNI composition rules" above.

Runtime Pipeline

Upstream payload order:

  1. smuggle-fin
  2. TCP flag rewrite
  3. synfin-sni
  4. ech-sni-trick
  5. overlap-sni
  6. smuggle-sni
  7. first-sni
  8. SNI Blender
  9. final egress: port ghost, checksum under the real protocol, protocol swap, then packet forwarding/duplication

Downstream payload order:

  1. protocol restore
  2. port-ghost restore
  3. smuggle-fin
  4. ech-sni-trick
  5. overlap-sni
  6. TCP flag rewrite
  7. SNI Blender downstream hook
  8. smuggle-sni downstream logging hook
  9. final downstream send

For internal smuggle-fin replay, upstream packets restart at the upstream entry because smuggle-fin is the first stage. Downstream packets resume immediately after smuggle-fin; their protocol and port-ghost restoration already ran before queuing and are not repeated. This does not change the normal downstream order above.

Lifecycle and Packet-Line Semantics

IpManipulator is a pure packet tunnel:

  • its node metadata advertises kNodeLayer3, while both neighbor constraints remain kNodeLayerAnything
  • it has no per-packet connection lifecycle
  • packet payload callbacks are the intended runtime path
  • Finish, Pause, Resume, and Est callbacks are invalid for normal use
  • it must not destroy packet lines during runtime

On upstream packet-line Init, the normal next path is initialized. Helper branches for real-SNI, real-FIN, and overlap-SNI crafted server hellos are initialized too when configured. Aliased helper branches receive Init only once.

Limits and Padding

ValueSize
required_padding_left0 bytes
SNI Blender fragment count2 through 16
protocol replacement number0 through 255
stateful-flow-limit32 through 1048576
ECH hostname length1 through 255 bytes
ECH captureup to 16 packets and a 16384-byte TLS record

IpManipulator does not require chain left padding. Some tricks allocate or clone packet buffers internally when crafting replacement packets.

Notes and Caveats

  • The current implementation modifies IPv4 packets. IPv6 packets are generally ignored by the tricks described here.
  • Many tricks require whole, non-fragmented IPv4 TCP packets with a complete TCP header.
  • Most SNI-family tricks are upstream-only and operate on TLS ClientHello packets.
  • The node relies on later packet output code honoring line->recalculate_checksum.
  • Helper branch nodes should be separate branches, not the same node as the normal next.
  • sni-blender-packets is required when sni-blender is enabled and must be between 2 and 16.
  • trick_sni_blender_packets_delay_max exists in the C state struct, but the current JSON parser does not expose or use it.