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.

Instance MTU​

Optional integer mtu defaults to core misc.mtu at construction (core fallback 1500). Its range is 68..65535 bytes: complete IPv4 packet length, including applicable trailers, used by its existing egress and trick paths. Each instance stores its validated value immutably; other instances and later core changes do not change its behavior. Null, wrong types, fractions, zero, negative or out-of-range values reject construction. An unsupported inherited default also fails; supply a supported node override. This is not measured path MTU or MTU negotiation. A valid enabled trick is still required. This setting is not a universal ingress filter and does not add generic UDP fragmentation.

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 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 decoy ClientHello copies with a replaced host name before the original.
smuggle-snismuggle-sni, real-sni-upstream-nodeupstreamSend the real ClientHello on a helper branch, an equal-length generated ClientHello on the normal path, and hold follow-on traffic behind an ordering deadline.
overlap-snioverlap-sniupstream plus downstream close cleanupRewrite one sequence range twice, separated by a fake SYN.
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, optional random/TLS-looking filler, 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.

With multiple workers, decoding through the normal packet path selects a worker from the restored TCP/UDP flow tuple after the applicable port transform. Downstream stateful tricks run after this handoff; upstream decoding resumes at final forwarding so transforms are not repeated. Protocol encoding itself adds no worker hop. Preserved fragments follow the restored first fragment's transport tuple; tails arriving first wait in bounded shared fragment storage. One-worker delivery is direct. Same-worker delivery can continue directly when its destination queue has no older accepted work; otherwise it joins that queue. This preserves queue admission order, not a global arrival order across readers.

ProtoSwap normalizes the TCP/UDP checksum's source and destination address contributions to zero before mapping, then restores the current addresses' contributions when decoding. The actual IP addresses stay intact. The mapped checksum still uses the native TCP (6) or UDP (17) pseudo-header protocol, not the mapped number. Address rewrites between endpoints therefore preserve valid checksums after decoding, without payload scans, packet expansion, or per-flow state. Both endpoints must use this checksum convention.

Without an existing recalculation request, the incremental adjustment preserves invalid checksum residuals and detection of payload corruption. Disabled IPv4 UDP checksum zero stays zero; enabled UDP arithmetic zero is transmitted as 0xffff. TCP zero is an ordinary checksum value.

Every fragment receives the protocol mapping. Only the fragment containing both transport checksum bytes receives the address adjustment, including a later TCP fragment after an 8- or 16-byte first fragment. Fragmentation may occur before encoding or between endpoints; checksum conversion itself needs no reassembly or ordering state. Decoded worker selection uses the fragment association described above. Fragments of one datagram must receive consistent final addresses. Malformed IPv4/transport lengths, partial checksum fields and invalid local fragment geometry pass through unchanged, with their pending request preserved. Trailing buffer bytes are excluded from the transform; final writers still validate the packet length.

Native shaping and port ghost run before upstream normalization. An existing checksum request is completed under the native protocol and actual addresses before encoding, in either callback direction; a failed request prevents mapping. For fragments, that request repairs only the IPv4 header. Decoding restores the address contribution and native protocol before downstream port restoration or TCP/TLS parsing. Existing downstream checksum requests are handed onward; upstream decoding can complete them immediately. ProtoSwap creates no new recalculation request. Arbitrary payload edits while mapped require a transform that understands this checksum convention.

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 any requested 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 mtu. 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 from 1 through 16Sends 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 mtu, 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 and mirrored-FIN helper branches preserve their original tuples and do not receive a port-ghost trailer.

first-sni​

first-sni puts one or more decoy copies of the client's own TLS ClientHello, with only the host name replaced, on the wire ahead of the real ClientHello of the same TCP connection. It is upstream-only and never touches downstream traffic.

Many inspection systems classify a TLS connection from the first ClientHello they observe on a 4-tuple and keep that verdict for the rest of the connection. first-sni targets that behavior: a device that latches onto the first ClientHello records the decoy name, while the destination server is expected to receive only the real one. This is the classic "fake packet" desynchronization idea applied to the SNI field. The real ClientHello itself is never rewritten, fragmented, split or reordered; it still travels intact, exactly once, at its real sequence number.

SettingTypeDefaultDescription
first-sninon-empty string, 1 through 65535 bytesrequired to enableHost name written into the decoy ClientHello copies. Only the first host_name entry of the copy is rewritten.
first-sni-countinteger from 1 through 161Number of decoy copies sent before the original.
first-sni-replay-delayinteger ms, >= 00Spacing between decoy copies after the first one. Only meaningful when the count is greater than 1.
first-sni-final-delayinteger ms, >= 00Extra delay between the last decoy copy and the original ClientHello.
first-sni-ttlinteger 0 through 255preserve originalIPv4 TTL for the decoy copies. 0 is a real override; omit the field to keep the original TTL.
first-sni-random-tcp-sequencebooleanfalseGive each decoy copy a fresh random 32-bit TCP sequence number.

Startup fails when (first-sni-count - 1) * first-sni-replay-delay, or that value plus first-sni-final-delay, exceeds the supported unsigned 32-bit millisecond range.

What goes on the wire​

With the default count of 1 and both delays at 0, everything happens inside one upstream packet callback:

  1. The TCP handshake is untouched. SYN, SYN|ACK and the first ACK pass through unchanged.
  2. The client sends its ClientHello in one segment: sequence S, payload length L, normally PSH|ACK.
  3. IpManipulator parses that payload. It requires a complete TLS handshake (0x16) record whose ClientHello message exactly fills the record body and contains a server_name extension with at least one host_name entry. A payload that only begins such a record enters the multi-segment capture described below; anything else fails open.
  4. The packet is cloned and only the first host_name string is replaced. Six length fields are corrected by the same delta: host_name length, server-name-list length, server_name extension length, extensions length, ClientHello handshake length and TLS record length, plus the IPv4 total length.
  5. Everything else in the copy is the original's bytes: the same IPv4 identification, flags and TTL, the same TCP ports, sequence number, acknowledgement number, window, flags and options, and the same TLS version, random, session id, cipher list and remaining extensions.
  6. first-sni-ttl then overwrites the copy's TTL if configured, and first-sni-random-tcp-sequence overwrites its TCP sequence number if enabled.
  7. The decoy is sent first with recomputed IPv4 and TCP checksums. There is no bad-checksum option in this trick; both packets always leave with valid checksums.
  8. The original ClientHello is forwarded immediately afterwards, unchanged, also with recomputed checksums.

Sequence numbers and keeping the decoy off the server​

Unless first-sni-random-tcp-sequence is enabled, the decoy carries the real ClientHello's sequence number S, so both segments claim the same position in the TCP byte stream. A configured name that is d bytes longer makes the decoy cover S to S + L + d against the real packet's S to S + L; a shorter name covers less. The two segments overlap, and each receiver keeps whichever copy its overlap handling accepts.

warning

That overlap is the point for an inspecting device and the problem for the server. A decoy that arrives as valid in-window data is delivered to the TLS server, and the real segment that follows is then treated as a duplicate, so the server answers the decoy host name. Because the client's TLS stack computes its transcript over its own ClientHello, such a connection normally fails with a certificate or handshake error instead of carrying traffic.

Configure at least one of the two mechanisms that keep the decoy away from the destination:

  • first-sni-ttl low enough that a router between the inspection point and the server discards the decoy. The value is path dependent and has to be measured, for example with a traceroute; the discarding router will normally return ICMP time-exceeded toward the client address. Too low and the inspector never sees the decoy, too high and the server does.
  • first-sni-random-tcp-sequence, which places the decoy far outside the server's receive window. A conforming TCP stack discards out-of-window data and answers at most with a duplicate or challenge ACK, while an inspector that reassembles by direction without validating sequence numbers still parses the decoy as this connection's ClientHello. It has no effect on inspectors that do track sequence numbers and windows.

Both can be combined. Leaving both unset is only sensible when the configured value equals the real host name, which turns the copy into a plain duplicate segment and removes the decoy effect.

What an intermediate device reconstructs depends on its own overlap handling: a per-packet inspector sees two ClientHellos and normally acts on the first, a reassembling inspector that keeps first-arrived bytes for a sequence range also ends up with the decoy, and one that validates windows or TTLs, or prefers later data for an overlapping range, sees the real name. No standard guarantees any of these outcomes, so the result is path specific.

Repeats and delays​

  • Copy 1 always leaves synchronously, ahead of everything else.
  • With first-sni-replay-delay at 0, all first-sni-count copies are crafted and sent back to back in the same callback with no pacing between them.
  • With a nonzero replay delay, copies 2 to N are scheduled at 1 x delay, 2 x delay, ... (N-1) x delay after copy 1 and are all crafted in advance, so each pending copy holds a full packet buffer until its send time. Keep the count modest.
  • The original ClientHello is released after (count - 1) * replay-delay + final-delay milliseconds, that is, immediately after the last decoy when first-sni-final-delay is 0.
  • While that window is open, later upstream packets of the same 4-tuple, ACKs and FIN/RST included, are queued behind the held original in a per-flow FIFO of at most 16 packets and 256 KiB, so nothing overtakes the delayed ClientHello. Downstream packets are never delayed. Delaying the flow also delays the client's own ACKs, so keep the totals in the tens of milliseconds.
  • FIFO overflow, a packet of the flow arriving on another worker, and a timer that cannot be armed all fail open in order: queued packets are flushed first, then the current packet.
  • A payload-free opening SYN on the same 4-tuple resets the flow record, so a reused tuple starts a fresh, delay-free flow. Anything the previous generation still held is discarded rather than forwarded.
  • With both delays at 0 no timer is armed and no delayed state is created; the trick is then purely synchronous.
  • The per-flow record is bounded by stateful-flow-limit and expires after 20 minutes of inactivity. If no record can be admitted, the schedule collapses to an immediate send, with a warning, still in the documented order.

Multi-segment ClientHello​

Capture begins only on a segment whose payload actually begins a recognizable ClientHello record, so unrelated and non-TLS TCP payloads fail open immediately without entering a speculative prestart queue. Strictly contiguous following segments are appended until the TLS record is complete, bounded by 16 packets, a 16384-byte record and a 1500 ms timeout since the last accepted segment.

On completion the trick works on one assembled packet: the first segment's IPv4 and TCP headers followed by all captured payload bytes. The original segments are dropped rather than forwarded. Two effects are visible on the wire: the destination receives the real ClientHello as one larger segment starting at the first segment's sequence number instead of the client's original segment pattern, and that coalesced packet carries the first segment's TCP flags, window and acknowledgement number. Byte content and sequence continuity are preserved. An assembled packet, or a decoy built from it, that exceeds mtu is transport-segmented again at final egress with continuous sequence numbers.

A sequence gap, an out-of-order or retransmitted segment, exceeding the packet or record limits, the capture timeout, or a same-flow packet arriving on another worker releases the held segments unchanged and in order.

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 first-sni does nothing​

The trick fails open, forwarding traffic unchanged, when:

  • the packet is not IPv4, not TCP, or is an IPv4 fragment
  • the payload is not, and does not begin, a TLS ClientHello record, the ClientHello spans more than one TLS record, or its handshake length does not match the record body
  • the ClientHello has no server_name extension or no host_name entry
  • the ClientHello carries a TLS 1.3 pre_shared_key extension with PSK binders and the configured value would actually change the SNI bytes. IpManipulator does not own the PSK secret needed to recompute valid binders, so it skips the copy and leaves the original path alone
  • the substitution would overflow a 16-bit TLS length field, the 24-bit handshake length or the IPv4 total length, or would produce a non-positive length. The reason is logged and the original packet is forwarded
  • no packet buffer is available for the copy

Capture slots are shared per tunnel, 16 per worker. When all of them are busy, the oldest capture is abandoned and its held segments are released unchanged.

How first-sni differs from the other SNI tricks​

  • sni-blender fragments the real ClientHello at the IPv4 layer and invents no host name. first-sni adds a whole extra packet and leaves the real one intact.
  • smuggle-sni sends the real ClientHello out through a separate helper branch and puts the crafted copy on the normal path. first-sni sends both on the normal path, decoy first.
  • overlap-sni builds a multi-packet transcript with a fake SYN and two versions of one sequence range. synfin-sni additionally uses a synthetic FIN/RST and TLS-looking filler. first-sni emits no synthetic control packets and never splits the real ClientHello.
  • ech-sni-trick reorders bytes that already exist inside the real ClientHello, whose decoy hello was embedded earlier by TlsClient; IpManipulator writes no host name of its own there.

Limitations and side effects​

  • Apart from the host name and its length fields, the decoy is byte identical to the real ClientHello, including the IPv4 identification and the TLS random and session id, so a device that correlates ClientHello contents can recognize the pair.
  • No padding extension is adjusted, so a configured name of a different length produces a decoy of a different size than the real packet.
  • Each configured copy re-sends the full ClientHello payload, and a long configured name can push a copy past the MTU and cause extra transport segmentation at egress.
  • The trick shapes only the client's ClientHello. It does nothing for DNS, address-based filtering, or anything the server sends.
  • All packets of one flow must stay on one worker; otherwise the trick fails open for that flow with a warning.

Example:

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"first-sni": "www.example.com",
"first-sni-count": 2,
"first-sni-replay-delay": 5,
"first-sni-final-delay": 10,
"first-sni-ttl": 6
},
"next": "next-packet-node"
}

smuggle-sni​

smuggle-sni creates a path-dependent split view of one TLS ClientHello. After capturing the real ClientHello, it sends the client's original TCP segments on real-sni-upstream-node. It sends a separately generated, equal-length ClientHello carrying the configured host name on the normal next branch. The two views occupy the same TCP sequence ranges, but they are routed to different branch entries rather than deliberately sent as an ordered overlap on one branch.

The intended topology lets an inspection device on the normal branch parse the generated SNI while the destination receives the real ClientHello through the helper branch. The technique does not itself ensure that separation. If the branches converge at the wrong point, packets are reordered, or the destination accepts the generated view, the connection can fail.

SettingTypeDefaultDescription
smuggle-snistring, 1 through 255 bytesabsent (disabled)Enables the trick and supplies the host name for the generated normal-branch ClientHello. The effective limit comes from the internal TlsClient.
smuggle-sni-delay-msinteger ms, >= 00Time after successful capture when the generated batch becomes eligible. It does not control the separate follow-on ordering window.
real-sni-upstream-nodestringrequired when enabledAuxiliary branch that receives the original captured ClientHello packets.

The shared stateful-flow-limit setting also applies, accepts 32 through 1048576, and defaults to 65536. The node requires both a normal top-level next and the helper. The named helper must exist, must not be this IpManipulator, must differ from next, and must be reachable and available for binding when the chain is constructed.

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"smuggle-sni": "www.example.com",
"smuggle-sni-delay-ms": 20,
"real-sni-upstream-node": "real-clienthello-path"
},
"next": "generated-clienthello-path"
}

The generated record uses the internal TlsClient fingerprint, default ALPN order h2, http/1.1, and has x25519mlkem768 disabled. The 50 ms speculative prestart timeout, 1500 ms fragmented-record timeout, 50-second ordering window, capture limits, generated fingerprint, and packet headers have no Smuggle-SNI-specific settings.

Which flow and ClientHello are selected​

Smuggle-SNI operates only on whole, non-fragmented IPv4 TCP packets and creates a flow record only after seeing a payload-free opening SYN. The accepted opener has SYN plus an optional combination of ECE and CWR; ACK, FIN, RST, PSH, URG, any other flag, or TCP Fast Open payload makes it ineligible. If this node did not observe that opener, later packets on the tuple pass without Smuggle-SNI processing.

The first two upstream packets in a tracked flow pass normally and count as warmup. Normally they are the opening SYN and the client's final handshake ACK; the downstream SYN|ACK is not counted. Capture begins with the next upstream packet. This ordinal rule means a ClientHello sent in the second upstream packet is not transformed on that transmission; only a later eligible copy, such as a retransmission after warmup, could become the capture candidate.

At the capture point, the TCP payload must begin a TLS handshake record whose handshake is a ClientHello with a usable first host_name SNI entry. A complete one-segment record is accepted immediately. An incomplete record is retained while exact contiguous continuations are collected. The starting segment must contain at least the first nine TLS bytes so the record and ClientHello lengths are known; a shorter prefix does not start capture.

  • every continuation must have nonzero payload and sequence previous.seq + previous.payload_len, using normal 32-bit TCP sequence arithmetic
  • the complete capture may use at most 16 TCP packets
  • the complete TLS record, including its five-byte record header, may be at most 16,384 bytes
  • the ClientHello handshake must exactly fill that one TLS record; a ClientHello split across TLS records or followed by another handshake message in the same record is unsupported
  • capture expires 1500 ms after the last captured fragment if no valid continuation advances it

An ACK-only packet, retransmission, gap, overlap, or out-of-order continuation does not get merged into an active capture. It ends the attempt and releases the retained packets followed by the current packet on the normal path. IPv4 fragments and unparseable packets bypass this capture and can overtake held segments; the capture remains pending until a valid continuation or timeout.

Smuggle-SNI also has a speculative prestart queue for packet reordering. If the first capture candidate has nonzero payload of at least 128 bytes, has no recognizable ClientHello prefix, and is not an explicitly unsupported TLS layout, it can be held until 50 ms after the last speculative update in case an earlier-sequence ClientHello start arrives afterward. Up to 16 distinct sequence-numbered packets can be retained. Once a start arrives, only packets that form the exact next sequence are drained into the ClientHello capture; unrelated remainder packets are released normally. Timeout, duplicate sequence, capacity pressure, or replacement of the speculative slot releases the held packets unchanged and makes that flow passthrough. A short non-TLS packet or an ACK-only candidate is not speculatively held and selects passthrough immediately. The prestart queue has no separate byte cap beyond its 16-packet bound and the size of each IPv4 packet.

Capture and prestart slots come from tunnel-wide pools sized at 16 of each per configured worker and shared with the other tricks that use TLS capture. If a pool is full, its oldest active slot is released unchanged to admit the new capture. All fragments of a capture must stay on one WaterWall worker. A cross-worker continuation fails open: each retained packet is returned through its owner worker, but original global arrival order cannot be guaranteed.

How the two ClientHello views are built​

Let the real ClientHello TLS record have length R, including its five-byte TLS record header. Let captured TCP packets P0 ... Pn carry contiguous payload lengths L0 ... Ln, beginning at TCP sequence S. The final captured packet may also contain bytes after the end of the ClientHello record, so L0 + ... + Ln can be greater than R.

After capture, the internal TlsClient creates a complete new ClientHello with the configured smuggle-sni. This is not an in-place SNI edit or a clone of the client's fingerprint. The generated record uses the internal client's TLS randomness, extensions, key share, and ALPN values described above. It must parse as exactly one ClientHello record, must contain the configured host name, and its complete length G must satisfy G = R. Changing the configured host can change G and therefore whether the equality succeeds, although generated padding and extension choices mean the length relationship is not necessarily one-for-one.

For every captured packet Pi, IpManipulator makes one normal-branch packet Fi by copying the entire original IPv4/TCP packet and replacing only the portion of its TCP payload that intersects the first R stream bytes with the corresponding generated bytes. Therefore, before final egress processing:

  • Fi has the same source and destination addresses and ports, TCP sequence and acknowledgement numbers, payload length, TCP flags, window and options, IPv4 identification and TTL, and packet boundary as Pi
  • concatenating the first R payload bytes of F0 ... Fn yields the generated ClientHello exactly
  • payload bytes after the real ClientHello record are copied unchanged from the original packet
  • checksums are recalculated because the TLS payload changed

The original P0 ... Pn packets are then sent, in reconstructed sequence order, through real-sni-upstream-node. That helper send preserves the original tuple and does not apply this node's port-ghost trailer. The generated F0 ... Fn batch uses the normal next branch, where compatible final protocol swap, port-ghost, checksum, and MTU handling can still run. Final MTU shaping may split an oversized packet, so the captured boundaries are preserved only up to that egress stage.

The helper originals and normal generated batch bypass the remaining Smuggle-SNI/stateful-SNI pipeline rather than being inspected again. Same-node packet duplication, SNI Blender, other stateful SNI tricks, and upstream TCP-bit rewrites are configuration-incompatible with Smuggle-SNI. Protocol swap, port ghost, downstream-only TCP-bit actions, and Smuggle-FIN may coexist subject to their own behavior; the real helper packets still use the tuple-preserving path described above.

Branch order, delay, and the 50-second barrier​

The first captured segment may already have waited for the remainder of the ClientHello. Once capture and generation succeed, the observable transcript is:

t0:        send real P0 ... Pn to real-sni-upstream-node
t0 + D: send generated F0 ... Fn to normal next
t0 + W: release later queued upstream packets to normal next

D = smuggle-sni-delay-ms
W = max(50000, D + 1) milliseconds

All real packets are submitted to the helper before the fake batch becomes eligible. At D = 0, the complete fake batch is emitted synchronously after the real batch. There is no pacing between segments inside either batch. Branches can have different latency, however, so this submission order is not a guarantee of arrival order after they traverse the network.

The D setting delays only the generated batch. It does not set the release time for later client traffic. A separate ordering barrier remains open until t0 + W; this is 50 seconds for every delay through 49,999 ms, including zero, and one millisecond after the fake due time for larger delays. Parseable same-flow upstream packets arriving during that window—including ACKs, retransmissions, application data, and FIN/RST—enter a FIFO behind the complete generated batch. Downstream packets are never put in this barrier. Unparseable packets, IPv4 fragments, and unrelated flows bypass it and can overtake retained traffic.

The follow-on FIFO holds at most 16 packets and 256 KiB. If it reaches either limit, a packet arrives on a different worker, the deadline is already past, or a timer cannot be scheduled, the barrier fails open: any unsent generated packets leave first, then the retained FIFO, then the current packet. This preserves per-owner-worker order but can send the generated transcript earlier than configured. A queued upstream FIN or RST remains behind earlier data and removes the flow record after release.

Speculative prestart packets that are not incorporated into the contiguous ClientHello are outside this post-success barrier. They are released through the normal path when prestart processing finishes and, in reordering cases, can appear before the generated batch.

What each participant may see​

  • Client: sends only the real ClientHello segments. WaterWall suppresses those segments from the normal branch, sends them through the helper, and can hold later client packets for the ordering window. The added capture and barrier delays can trigger TCP retransmissions, RTO backoff, congestion-window changes, or an application-visible stall.
  • Normal-path middlebox: is intended to reconstruct the generated record at the client's real sequence numbers and parse the configured SNI. Because its segment sizes, flags, acknowledgement values, and general header shape are copied from the client packets, the TCP envelope resembles the captured flow, although the TLS fingerprint and cryptographic contents come from WaterWall's internal TlsClient.
  • Helper-path device or destination: receives the original ClientHello segments after capture completes. If the helper route reaches the destination while the generated packets are kept from it, the server sees the client's real TLS random, extensions, key share, SNI, and transcript. Equal record lengths keep subsequent TCP sequence numbers aligned when traffic later uses the normal path again.

If the destination receives only the generated ClientHello, its ServerHello and key schedule correspond to WaterWall's independent ClientHello, not to the client's TLS state, so the client will generally be unable to continue the handshake. If it receives both views, they are conflicting bytes on identical sequence ranges; first-arrival/last-arrival reassembly, loss, retransmission, normalization, and path ordering determine which view survives. The real batch is submitted first, but that does not make server-side first-arrival behavior safe across separate branches.

The trick therefore depends on topology: the observer intended to see the fake must be on the normal branch, the endpoint intended to complete TLS must see the real branch view, and later traffic and responses must rejoin coherently. NAT, firewall state, asymmetric routing, anti-spoofing, TCP normalization, and different MTUs on the two branches can all defeat that arrangement.

Fail-open behavior, closes, and flow lifetime​

Before any real packet is sent to the helper, the attempt fails open to the normal branch when the flow opener was not observed or its table record cannot be admitted, the capture candidate is unsupported, the record is malformed or lacks SNI, sequence continuity or worker affinity is lost, a capture/prestart timeout or limit is reached, memory or timer setup fails, or the generated record is invalid or has a length other than R. Retained originals are forwarded unchanged by Smuggle-SNI through normal egress. On same-worker paths they are released in capture order; cross-worker recovery cannot promise global wire order.

If the real ClientHello contains a TLS 1.3 pre_shared_key extension with PSK binders and the configured SNI differs from the real SNI, Smuggle-SNI also fails open. WaterWall does not have the resumption secret needed to recompute binders for an altered ClientHello. The source applies this guard only when the SNI would change; an equal configured SNI is not rejected by this condition, though all other generated-record validation and equal-length requirements still apply.

Once the helper originals have been sent, a delay-scheduler failure cannot undo that branch transmission. It instead releases the generated batch immediately on the normal branch. Tunnel or flow-table destruction disposes of still-held capture, fake, and FIFO buffers rather than forwarding them during teardown.

An upstream FIN or RST during prestart or ClientHello capture flushes held originals to the normal path and removes the flow. On the normal same-worker path, those held packets are submitted before the close; cross-worker recovery cannot guarantee global order. During the post-success barrier, the close is queued behind earlier upstream traffic. A downstream FIN or RST is never delayed; it flushes an unfinished capture but removes a post-success flow immediately, which disposes of any still-delayed generated or follow-on upstream packets. Other downstream traffic is unchanged. A lightweight downstream hook can log a ServerHello that begins in one whole TCP payload, but it does not validate the branch outcome or alter the packet.

Downstream TCP-bit rewrites are allowed in the same node and run before that logging/close hook. Consequently, adding or removing FIN/RST with a dw-tcp-bit-* setting also changes whether Smuggle-SNI observes a downstream close and performs this cleanup.

For an admitted tracked flow, either success or ordinary fail-open leaves the normalized four-tuple in passthrough state and Smuggle-SNI does not retry. Activity can retain that record for up to 20 minutes of idle time. A valid new opening SYN starts a new generation; it discards any old capture/prestart packets and destroys an old delay barrier rather than replaying those stale bytes. An upstream or downstream FIN/RST normally removes the record as described above.

How smuggle-sni differs from the other tricks​

  • first-sni edits the real ClientHello's SNI and length fields in extra copies and sends both decoy and original on the normal branch. Smuggle-SNI generates the whole alternate record, requires equal record length, and routes the real packets through a helper.
  • overlap-sni and synfin-sni emit conflicting sequence ranges and synthetic TCP control packets on the normal branch. Smuggle-SNI uses no fake SYN, FIN, or RST; its split view comes from branch routing.
  • ech-sni-trick extracts a byte-identical decoy already embedded by TlsClient and sends it out of order. Smuggle-SNI creates a new conventional ClientHello from its configured host name.
  • sni-blender fragments and shuffles the real packet at IPv4 without creating another TLS transcript. smuggle-fin does not inspect TLS at all and routes a synthetic reverse FIN|ACK while holding normal traffic.

overlap-sni​

overlap-sni replaces two real ClientHello segments with a crafted transcript on the normal upstream path. It sends the real beginning of the ClientHello, then a fake TCP SYN, then a generated ClientHello carrying a different host name on the same sequence numbers, and finally the rest of the real ClientHello. The downstream path does not rewrite packets; it only observes FIN/RST to clean up the corresponding flow.

Where first-sni relies on a decoy packet being seen first, overlap-sni targets TCP reassembly. The intended split view is that the destination keeps the first-arriving real bytes for the overlapping range, while an intermediate device treats the fake SYN as a new flow or replaces earlier bytes with the later overlap and parses the generated host name. TCP overlap handling and middlebox state machines vary, so neither view is guaranteed.

warning

This is one of the most invasive tricks in the node. It changes the client's TCP segment boundaries, injects a non-handshake SYN into an established flow, delays subsequent upstream traffic, and deliberately blocks some ClientHello layouts. Test it with representative packet captures and on the intended path.

SettingTypeDefaultDescription
overlap-snistring, 1 through 255 bytesrequired to enableHost name for the generated ClientHello. A longer value prevents node startup because the internal TlsClient enforces its 255-byte host-name limit.
overlap-sni-delay-msinteger ms, >= 00Delay between the immediately sent real-prefix/fake-SYN pair and packet X, the generated ClientHello. Real continuation packets follow at fixed 2 ms intervals.
overlap-sni-hold-timeout-msinteger ms, > 050Maximum wait for the immediately following contiguous segment that completes the selected ClientHello; expiry releases the held segment unchanged.
overlap-sni-syn-ttlinteger 0 through 255copy the held packet's TTLIPv4 TTL for the fake TCP SYN only. 0 is a real override; omit the field to copy the held segment's TTL.

This trick requires a normal top-level next.

The generated hello is built by an internal TlsClient, not copied from the real client. It therefore uses that client's TLS fingerprint, default ALPN list (h2, http/1.1), and a non-post-quantum key share. Let G be its length. A longer configured host name normally increases G, enlarges the overlapping range, and makes the requirement that the real SNI start at or after offset G harder to satisfy. At runtime, generation fails open if G is greater than 900 bytes or greater than the two captured real payloads combined.

overlap-sni-delay-ms also contributes to an absolute post-transcript window of max(5000 ms, overlap-sni-delay-ms + 5 ms). Values from 0 through 4995 change the timing of packet X but not the five-second deadline for later upstream packets; larger values extend that deadline. Increasing the delay gives an observer more time to process the fake SYN and reduces the chance that X overtakes the real prefix in the network, at the cost of latency and greater retransmission/timeout risk.

overlap-sni-hold-timeout-ms should remain comfortably below the sender's TCP retransmission timeout. Raising it helps only when the next ClientHello segment arrives more than 50 ms later; a retransmission arriving first makes the pair fail open.

A low overlap-sni-syn-ttl can let an inspection device near IpManipulator observe the fake SYN while a later router expires it before the destination. The usable value depends on path length and where WaterWall sits. A value of 0 normally confines the packet to the local link. This field affects no other packet in the transcript.

The shared stateful-flow-limit also bounds the overlap flow table. If the table cannot admit an opening SYN, that flow remains untracked and passes unchanged.

Which flow and packets are selected​

A flow record is created only by a payload-free opening SYN (SYN, optionally with ECN ECE/CWR, but no ACK, FIN, RST, or payload). TCP Fast Open SYN data is therefore not eligible. Traffic on a 4-tuple whose opening SYN this node did not see passes unchanged. A later opening SYN on the same tuple starts a new flow generation.

  1. Warmup — the first two upstream packets of the flow pass unchanged. In a normal connection these are the SYN itself and the client's handshake- completing ACK, so the decision point is the third upstream packet, that is, the client's first data segment.
  2. Decision — the third packet is inspected. A payload-free packet, a payload that is not a TLS ClientHello, and a complete ClientHello record all move the flow straight to passthrough and are forwarded unchanged. Only a payload that begins a ClientHello record without completing it is held.
  3. Hold — the segment is retained inside IpManipulator and nothing is sent. A timer armed at overlap-sni-hold-timeout-ms releases it unchanged if the flow does not continue.
  4. Pair — the very next upstream packet must carry nonzero payload beginning exactly at held.seq + held.payload_len. The two payloads must together contain a parseable ClientHello, including its host name, in one TLS record. On success they are replaced by the transcript below. An ACK-only packet, sequence gap, overlap/retransmission, still-incomplete record, ClientHello split across TLS records, or malformed SNI releases both packets unchanged and makes the flow passthrough. The trick waits for exactly one completion segment, not an arbitrary number of segments.

Because only an incomplete first ClientHello is held, this trick does nothing for clients whose ClientHello fits in one TCP segment. Its packet-ordinal rule also means an extra client packet between the handshake ACK and ClientHello can disable it even when the ClientHello is split.

Successful wire transcript​

The held segment and the completing segment are validated as strictly contiguous — current.seq == held.seq + held.payload_len, both with nonzero payload — and virtually reassembled into one combined payload. IpManipulator then generates its own ClientHello for overlap-sni through an internal TlsClient and parses the real host name out of the combined bytes.

Let:

  • S be the held segment's first TCP sequence number
  • H be its TCP payload length
  • R be the two real payload lengths combined
  • G be the complete generated ClientHello length
  • O be the real host name's offset from the start of the combined TCP payload

A successful transcript requires 0 < G <= 900, G <= R, and O >= G. At the time the second real segment arrives, the normal upstream path emits or schedules:

  1. Packet Y, immediately — real bytes [0, G) at sequence S. It copies the held packet's IP/TCP headers and TCP flags. This is the real version of the overlapping range.
  2. Fake SYN, immediately after Y — a header-only packet on the same 4-tuple with sequence S - 1, acknowledgement 0, and only SYN set. It copies the held packet's remaining IPv4/TCP header fields, including its TCP header length, options, and window; only its length, sequence, acknowledgement, flags, identification, optional TTL, and checksums are changed.
  3. Packet X, after overlap-sni-delay-ms — the generated ClientHello, exactly G bytes at sequence S. It covers the same sequence interval as packet Y but contains the configured host name and internal TlsClient fingerprint.
  4. Real continuation, at 2 ms intervals after X — if G < H, one packet carries real bytes [G, H) at S + G, followed by a packet containing the completing segment at its original sequence S + H. If G >= H, only the still-needed part of the completing segment, real bytes [G, R), is sent at S + G; its earlier bytes were already included in packet Y.

Every packet is emitted with recomputed checksums and each carries its own incrementing IPv4 identification: packet Y uses the held packet's identification, then the fake SYN, packet X, and any continuation packets increment it by one. Packets Y and X use the held packet's flags. When a held-segment continuation is followed by a completing-segment continuation, the former keeps only CWR, ECE, URG, and ACK; the last packet keeps the completing segment's flags.

The original two packets are recycled and never appear on the wire. The replacement therefore contains four or five packets, depending on whether G reaches into the completing segment. Local scheduling preserves the order above, but the network can still drop or reorder packets. The configured delay is the only adjustable separation; the 2 ms continuation spacing is fixed.

What each participant sees​

  • The destination is intended to accept packet Y first and retain those real bytes. If the fake SYN reaches an established TCP endpoint, standards-oriented stacks commonly drop it and send a challenge or duplicate ACK, but other stacks, firewalls, and load balancers may reset or otherwise disturb the connection. If the connection remains established, a first-arrival-wins TCP receiver treats packet X as conflicting duplicate data for [S, S + G), and the continuation completes the real stream. Its TLS layer then receives the original ClientHello.
  • A reassembling middlebox receives an incomplete real prefix in packet Y; the enforced O >= G condition keeps the real host name out of that prefix. If the fake SYN resets its flow state, packet X is then a complete ClientHello with the configured host name. A device that does not reset but prefers later bytes for an overlap can reach the same result. The subsequent real bytes start at or after the end of packet X and may be ignored once the device has classified the complete generated hello.
  • The client did send the original two segments, but IpManipulator suppresses them and sends the replacement transcript on its behalf. The client can see duplicate/challenge ACKs or a reset caused by the fake SYN. If the server does not acknowledge the reconstructed real bytes, the client may retransmit its original ClientHello segments.

The desired split view depends on packet Y reaching the server before packet X, the fake SYN not terminating server-side state, and the middlebox using a different policy from the server. Loss, path reordering, TCP normalization, SYN proxies, and devices that validate receive windows can all defeat the technique. In particular, if packet X reaches the destination before packet Y, the destination may accept the generated bytes instead of the real ones and the TLS connection will not have the intended transcript.

Blocked flows​

The implementation requires the complete generated hello to end no later than the start of the real host name: O >= G. This keeps the real SNI wholly outside the ambiguous range and in the later real continuation. If O < G, the trick does not fall back to normal forwarding. It logs the real name and both offsets, drops the held and current packets, and marks the flow blocked.

A blocked flow drops every later upstream packet. An upstream FIN or RST removes the record but is itself dropped; a downstream FIN or RST is forwarded unchanged and also removes the record. IpManipulator does not inject a reset to tell the client why the data disappeared, so the usual visible result is a stalled connection followed by a client timeout or a close initiated by the server/path.

This is a deliberate fail-closed choice rather than a fail-open one, and it is the main risk of enabling overlap-sni. It becomes more likely when the generated hello is large relative to the real one. A longer configured name tends to increase G; the real client's extension ordering and padding determine O. Many normal ClientHellos place SNI too early for this condition, so representative packet captures should be checked before deployment.

Delay window, retransmissions, and close packets​

After a successful transcript the flow enters passthrough with a delay window of max(5000 ms, overlap-sni-delay-ms + 5 ms). While that window is open, later upstream packets of the flow, including ACKs and FIN/RST, are queued in the per-flow FIFO of at most 16 packets and 256 KiB, and released together at the window deadline. The scheduled transcript tail always leaves ahead of that FIFO. Downstream traffic is never delayed.

FIFO overflow, a packet of the flow arriving on another worker, or a timer that cannot be armed releases pending output in order rather than extending the stall: the remaining crafted transcript goes first, followed by queued packets and then the current packet. On a busy flow, the FIFO limit can therefore end the nominal five-second window early.

The FIFO also catches TCP retransmissions sent after the crafted transcript has been built. A retransmission or ACK-only packet arriving while the initial segment is merely held instead fails the required two-packet pairing, releases the held packet followed by the current packet unchanged, and makes the flow passthrough.

The downstream path never changes packet bytes. A downstream FIN or RST during the initial hold releases that held upstream segment unchanged and removes the flow. During the post-transcript delay window it removes the flow and cancels any still-scheduled crafted continuation and queued upstream packets; the downstream close packet itself continues unchanged. An upstream FIN or RST in the delay window remains ordered behind the crafted transcript and any older queued packets.

When overlap-sni does nothing​

The trick fails open, releasing the held segment and forwarding traffic unchanged, when:

  • the flow's opening SYN was never observed by this node
  • the packet is not IPv4, not TCP, or is an IPv4 fragment
  • the third packet has no payload, is not a TLS ClientHello, or already contains a complete ClientHello record
  • the completing segment does not arrive within overlap-sni-hold-timeout-ms
  • the very next packet is not sequence-contiguous with the held one, has no payload, or still does not complete a supported one-record ClientHello
  • the combined bytes do not parse as a ClientHello with a host-name entry
  • the internal TlsClient cannot generate a hello, the generated hello is longer than 900 bytes, or it is longer than the real combined payload
  • the replacement packets cannot be built, the hold timer cannot be armed, the flow table cannot admit the opening SYN, or retained state crosses workers

The O < G case described under "Blocked flows" is the intentional exception: it fails closed and does not release the two selected real packets.

How overlap-sni differs from the other SNI tricks​

  • first-sni copies the client's own ClientHello, changes its host name, and sends the decoy before the untouched real packet. It can operate on a single-segment ClientHello and uses TTL or a random sequence number to keep the decoy away from the server; it does not inject a fake handshake SYN.
  • smuggle-sni can capture up to 16 ClientHello segments, sends the real segments through an auxiliary branch, and later sends a generated copy on the normal branch while preserving the captured segmentation. overlap-sni accepts exactly two ClientHello segments and puts both views on the same normal path.
  • synfin-sni also starts from a two-segment ClientHello but adds a crafted FIN|ACK or RST|ACK, a fake SYN, a generated hello, and TLS-looking filler in one immediate transcript. overlap-sni has no synthetic close or filler packet and instead relies on the exact Y/X overlap plus configurable delay.
  • sni-blender fragments and reorders the real packet at the IPv4 layer without inventing another host name. ech-sni-trick reorders a fake inner ClientHello already embedded by TlsClient; it does not generate packet X here.

Limitations and side effects​

  • Only whole, non-fragmented IPv4 TCP packets are eligible. IPv6 and IPv4 fragments pass unchanged.
  • A normal top-level next is required. All crafted packets use that path; there is no overlap-specific helper branch.
  • The generated hello follows the internal TlsClient fingerprint, including its default ALPN list, rather than the real client's fingerprint. Only the generated packet contains the configured name; the real ClientHello bytes are never rewritten.
  • The client's two original segment boundaries and the completing segment's IPv4 identification are not preserved. The replacement is four or five packets with copied headers, sequential IPv4 identifiers, valid recomputed checksums, and fixed 2 ms tail spacing.
  • The fake SYN is a protocol violation on an established connection and may be normalized, challenged, dropped, or answered with a reset. A low overlap-sni-syn-ttl can keep it from the server but cannot guarantee how a nearer device interprets it.
  • Each flow's packets must remain on one WaterWall worker while a packet or delay barrier is retained. A worker mismatch logs a warning and fails open.
  • Flow records expire after 20 minutes of inactivity and are bounded by stateful-flow-limit. Tuple reuse begins a new generation so an old timer cannot release data into the replacement flow.
  • The trick cannot share one IpManipulator instance with another stateful SNI trick, sni-blender, packet-duplicate, or an upstream TCP-bit action. Use a separate node if another compatible packet-shaping stage is required.

Example:

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"overlap-sni": "www.example.com",
"overlap-sni-delay-ms": 5,
"overlap-sni-hold-timeout-ms": 50,
"overlap-sni-syn-ttl": 6
},
"next": "next-packet-node"
}

ech-sni-trick​

ech-sni-trick coordinates with TlsClient to expose a decoy ClientHello as an out-of-order TCP segment before the real outer ClientHello is released. It does not encrypt ECH or rewrite the real ClientHello. The data transformation is upstream-only; the downstream path leaves packet bytes untouched and watches only for a TCP FIN or RST that should cancel retained state.

SettingTypeDefaultDescription
ech-sni-trickstring, 1 through 255 bytesrequired to enableHost name that must appear in the embedded decoy ClientHello. Configure the same byte string in the corresponding TlsClient.
data-shard-1-delayinteger ms, >= 00Time from sending the out-of-order decoy to releasing original packet 1.
data-shard-2-delayinteger ms, >= 00Additional time after original packet 1 before originals 2 through N are released together in order. Ignored for a one-packet capture.
stateful-flow-limitinteger, 32 through 104857665536Shared setting that bounds the ECH flow table; see "Stateful Flow Tables."

The ech-sni-trick value is matched byte for byte and case-sensitively; no DNS- name normalization is performed. Changing it only in IpManipulator causes a runtime mismatch and makes the captured ClientHello fail open unchanged. Changing it in both nodes changes the decoy SNI and usually changes the embedded ClientHello length, its position within the outer hello, and the out-of-order packet size.

The two delays are relative. If the decoy leaves at time T, original packet 1 leaves at T + data-shard-1-delay, and originals 2 through N leave at T + data-shard-1-delay + data-shard-2-delay. Their sum must fit the supported unsigned 32-bit millisecond range. Increasing the first delay gives a packet- oriented observer more time to process the decoy before lower sequence numbers arrive. Increasing the second separates the first original shard from the rest. Both choices also prolong TCP reordering and TLS-handshake latency. At 0, the relevant packets retain the same send order but leave back to back.

This trick requires a normal top-level next. It has no TTL, random-sequence, packet-count, or helper-branch setting.

Example IpManipulator settings:

{
"name": "ech-packet-shaper",
"type": "IpManipulator",
"settings": {
"ech-sni-trick": "cover.example",
"data-shard-1-delay": 10,
"data-shard-2-delay": 10
},
"next": "next-packet-node"
}

The TlsClient that creates this flow must use the same decoy value while its ordinary sni remains the destination name:

{
"sni": "origin.example",
"ech-sni-trick": "cover.example"
}

What the trick is for​

This trick targets inspection devices that parse a TLS record found in an individual TCP segment, or that make an early classification from out-of-order data before the missing lower sequence range arrives. It puts a complete decoy ClientHello on the wire first, at a valid but future TCP sequence number. The ordinary outer ClientHello follows later and remains the byte stream delivered to a correct TCP/TLS endpoint.

Unlike an overlap trick, the early segment does not conflict with the eventual stream. It is a copy of bytes that already occur at the same sequence interval inside the outer ClientHello. A strict TCP reassembler therefore reconstructs only the original outer ClientHello. The hoped-for different view exists only at an observer that parses the early segment by itself or remembers its SNI before reassembly completes. This behavior is path- and middlebox-dependent and is not guaranteed.

How the decoy gets into the outer ClientHello​

The feature has two coordinated parts:

  1. TlsClient.settings.ech-sni-trick creates a separate, complete TLS ClientHello record whose SNI is the configured decoy name. It uses the configured TlsClient ALPN list in the same order and disables the x25519mlkem768 key share for this decoy so that it remains smaller.
  2. TlsClient places those raw record bytes, unencrypted, in the payload field of a GREASE encrypted_client_hello extension in the real outer ClientHello. The outer cleartext SNI remains TlsClient.settings.sni.
  3. IpManipulator captures the outer ClientHello, locates that already- embedded record, and copies it into the early TCP segment. It never generates the decoy, rewrites either SNI, or removes bytes from the outer hello.
warning

This is not standards-based ECH confidentiality. The extension has a GREASE ECH shape, but its payload is recognizable ClientHello plaintext rather than valid ECH ciphertext. Both the real outer SNI and the decoy SNI remain visible as clear bytes on the wire.

The expected destination behavior is to continue with the outer ClientHello when it has no matching ECH configuration, but servers, proxies, and security devices are not universally tolerant of unusual GREASE payloads.

Flow eligibility and ClientHello capture​

The trick is sequence-aware rather than packet-ordinal-based:

  1. A payload-free opening SYN creates the flow record. ECE and CWR may be present independently or together for ECN negotiation; ACK, FIN, RST, any other flag, or TCP Fast Open payload makes the packet ineligible as an opener. If this node did not see the opening SYN, later packets on that tuple pass unchanged.
  2. Payload-free upstream packets, including the TCP handshake's final ACK and ACKs interleaved between ClientHello segments, pass immediately and do not consume a capture slot.
  3. The first non-empty upstream payload must begin with a recognizable TLS handshake record and ClientHello prefix. A complete one-packet ClientHello can be used immediately. A recognizable partial prefix starts capture; an unrelated first payload makes this flow permanently pass through until a new opening SYN starts another generation.
  4. Further data segments are retained only when each begins exactly at the next expected TCP sequence number. Capture stops when the declared first TLS record is complete. A ClientHello spread across multiple TLS records is not supported. If the completing TCP segment also contains bytes after that record, the whole original segment is retained and later replayed, although only the first record is inspected.

At most 16 original packets may participate, and the declared TLS record, including its five-byte header, may not exceed 16,384 bytes. The capture has a 1,500 ms inactivity timeout, refreshed by accepted data or an exact retransmission. An exact retransmission of a packet already retained is discarded as a duplicate while capture continues. A gap, out-of-order segment, partial or conflicting overlap, unsupported TLS layout, resource failure, size or packet limit, timeout, or worker-affinity mismatch fails open: retained originals are forwarded before the current packet, and the rest of that flow passes normally.

After capture, IpManipulator requires all of the following:

  • a parseable outer ClientHello with a usable first host_name SNI entry;
  • a syntactically parseable outer-form encrypted_client_hello extension and its bounded payload field; and
  • exactly one complete TLS ClientHello record anywhere in that payload whose first SNI host name equals the configured ech-sni-trick value byte for byte.

Nonmatching ClientHello candidates do not count. Two matching candidates are ambiguous and fail open. No match, a malformed or truncated candidate, an outer hello without SNI or ECH, and an IpManipulator/TlsClient setting mismatch also fail open by releasing the originals without emitting the decoy segment.

Successful TCP transcript​

Let:

  • S be the first captured packet's TCP sequence number;
  • O be the decoy ClientHello's byte offset from the beginning of the captured TCP payload; and
  • L be the complete decoy ClientHello record length.

The decoy already occupies stream interval [S + O, S + O + L) inside the outer ClientHello. On success, the wire order is:

  1. All original ClientHello packets remain held. IpManipulator creates one packet with sequence S + O and payload equal to exactly those L embedded bytes. If the embedded record crossed original packet boundaries, this copy still coalesces it into one TCP segment.
  2. That packet is sent immediately. It copies its IPv4 and TCP header template from the captured data segment covering offset O, including addresses, ports, acknowledgement number, window, TCP options, TTL, and IPv4 identification. Its length and sequence number are adjusted, checksums are recomputed, and its flags preserve CWR, ECE, URG, and ACK from the template, add PSH, and omit SYN, FIN, and RST. Reusing the template's IPv4 identification means the decoy and one original can have the same ID.
  3. After data-shard-1-delay, original packet 1 is released with its original payload boundary, sequence number, and TCP flags.
  4. When there is more than one captured packet, IpManipulator waits the additional data-shard-2-delay, then releases original packets 2 through N consecutively in their captured order. There is no per-packet delay within this second group.

Thus the nominal send times are T, T + delay1, and T + delay1 + delay2. A one-packet ClientHello uses only the first delay. With both settings at 0, callback order still puts the decoy first, but all packets leave back to back. If a release timer cannot be scheduled, the affected originals are flushed immediately rather than left stranded.

The retained originals are not rewritten by this trick. Their payload bytes at [S + O, S + O + L) are identical to the early copy, so the destination never has to choose between conflicting overlap contents. Compatible final-egress features in the same node, such as protocol swap or port ghost, still run after this transcript is built and can change the on-wire wrapper or segment it as documented in their own sections.

What the client, middlebox, and server may see​

  • The client sends one ordinary outer ClientHello and does not know that IpManipulator retained it and injected a copy of an internal byte range. The early higher-sequence segment may cause the destination to return a duplicate ACK for S, and, when SACK was negotiated, a SACK block covering the received future range. These responses can influence the client's loss recovery even though the injected bytes equal data it already submitted.
  • A packet-oriented inspector sees a TCP payload beginning with a complete TLS ClientHello carrying the configured decoy SNI before it sees the lower- sequence outer hello. A device that parses out-of-order payloads or latches the first SNI may classify the flow from that decoy. A strict stream reassembler instead waits for the gap, reconstructs the original outer ClientHello, and can see the real outer SNI; a device that examines both may ignore, replace, or penalize the earlier verdict.
  • The destination TCP stack may buffer the early segment as out-of-order data, acknowledge it with SACK, or discard it because of its receive-window or normalization policy. Once the originals fill the gap, a tolerant stack reconstructs the same outer ClientHello bytes that TlsClient generated. Its TLS endpoint is then expected to use the outer SNI and treat the unusable GREASE ECH offer compatibly. TCP normalizers, SYN proxies, TLS gateways, and ECH-aware servers may behave differently.

The delays provide time for an early middlebox decision; they do not force one. Larger values also increase handshake latency and the duration of TCP reordering. Values near a sender's retransmission or application timeout can defeat the connection rather than improve the trick.

Retransmissions, later traffic, and connection close​

After the decoy is emitted, only an exact retransmission of a still-pending original segment—same sequence, payload length, and payload bytes—is swallowed. An ACK-only packet, later application data, or a partial/ambiguous overlap is forwarded immediately. Later data is not queued behind the two release timers, so it can itself overtake the held ClientHello and add more out-of-order traffic. Once an original has been released, its retransmissions are no longer swallowed.

Close handling preserves TCP ordering where a graceful upstream close still makes sense:

  • An upstream FIN during incomplete capture releases captured originals before the FIN. During delayed release it flushes every still-pending original before the FIN.
  • An upstream RST during delayed release discards pending originals and then passes the reset. During incomplete capture, the held originals are released before the reset packet.
  • A downstream FIN or RST passes unchanged but removes the flow record, disposes of an incomplete capture, and cancels any originals that have not yet been released.

Flow records are keyed by the TCP four-tuple and a connection generation. A new valid opening SYN on a reused tuple invalidates the preceding capture and release timers before starting the replacement flow, so stale ClientHello bytes are not injected into it. After release, the record remains in passthrough until it closes, is replaced, or expires after 20 minutes of inactivity.

How ech-sni-trick differs from the other SNI tricks​

  • first-sni makes one or more full copies of the client's outer ClientHello, rewrites the SNI in each copy, and uses TTL or a random sequence number to try to keep those conflicting decoys away from the server. ech-sni-trick uses one pre-embedded decoy at its real in-stream sequence interval, with no TTL or random-sequence option and no conflicting bytes.
  • smuggle-sni sends the real ClientHello over a configured helper branch and a generated decoy over the normal branch after a delay. ech-sni-trick needs no helper branch and sends both views on the normal path.
  • overlap-sni and synfin-sni replace captured segment boundaries with deliberately conflicting sequence ranges and inject fake SYN and close or filler packets. ech-sni-trick injects no TCP control packet and replays the original segmentation.
  • sni-blender fragments and reorders the real ClientHello at the IPv4 layer. ech-sni-trick does not create IPv4 fragments; it sends one extra TCP data segment copied from the GREASE ECH payload.

Limitations and side effects​

  • Only whole, non-fragmented IPv4 TCP packets are eligible. IPv6 and IPv4 fragments pass unchanged.
  • A normal top-level next is required. IpManipulator must see the opening SYN, and the first non-empty client payload must begin the supported one-record ClientHello layout.
  • The decoy plus copied IP/TCP headers must fit mtu. An oversized decoy fails open rather than being split by the ECH trick. A longer configured SNI can enlarge both the outer record and this packet.
  • The flow table is bounded by stateful-flow-limit; an unadmitted flow passes unchanged. Incomplete TLS captures also use a finite shared slot pool. Under pressure, an older incomplete capture can be released and moved to passthrough.
  • Every retained packet for a flow must remain on one WaterWall worker. IpManipulator establishes packet-line affinity internally before stateful processing; a remaining worker-affinity mismatch still fails open.
  • The early packet duplicates the template packet's IPv4 identification and can trigger duplicate ACKs, SACKs, retransmission heuristics, traffic-normalizer rules, or anomaly detection. The delays also hold memory and postpone the TLS handshake.
  • This is not real ECH and provides no SNI confidentiality: the real SNI is in the outer ClientHello and the decoy SNI is visible inside the GREASE payload.
  • The trick cannot share one IpManipulator with another stateful SNI trick, sni-blender, packet-duplicate, or an upstream TCP-bit action. Compatible operations that must shape its emitted packets belong in a following IpManipulator node.

synfin-sni​

synfin-sni is an upstream-only, stateful TCP overlap trick for a TLS ClientHello split across exactly two selected data segments. It suppresses those two original packets and emits a replacement transcript containing real bytes, a synthetic close, a fake SYN, a generated decoy ClientHello, optional filler, and the remaining real bytes. Downstream packets are not inspected or modified by this trick.

The intended split view uses both TCP connection state and conflicting overlap policy. A destination that retains the first bytes received for a sequence range can reconstruct the real ClientHello. An intermediate device may instead treat the close and following SYN as a flow boundary, or prefer the later bytes in an overlap, and parse the generated ClientHello carrying synfin-sni.

warning

This is one of the most protocol-invasive tricks in the node. It deliberately sends conflicting TCP data plus a close and another SYN on an established four-tuple. With default TTLs, sequences, and valid checksums, those synthetic packets can reach and close or reset the destination connection. Test against representative clients, servers, middleboxes, and packet captures.

SettingTypeDefaultDescription
synfin-snistring, 1 through 255 bytesrequired to enableHost name for the generated decoy ClientHello. The effective limit comes from the internal TlsClient.
synfin-sni-hold-timeout-msinteger ms, > 050Maximum wait for the immediate contiguous segment that completes the selected ClientHello. Expiry releases the held segment unchanged.
synfin-sni-additional-range-mininteger 0 through 655350Requested minimum extension beyond the generated hello length for the real-first/fake-filler overlap. If set without the maximum, it also becomes the maximum. Runtime safety clamping may reduce it.
synfin-sni-additional-range-maxinteger 0 through 655350, or the configured minimum when only the minimum is presentRequested maximum overlap extension. If set alone, the minimum remains 0; minimum must be no greater than maximum.
synfin-sni-syn-ttlinteger 0 through 255copy the opening SYN TTL when its template is availableIPv4 TTL for the fake SYN only. Construction falls back to the held data header if the opening-header copy is unavailable.
synfin-sni-fin-ttlinteger 0 through 255copy the held data packet TTLIPv4 TTL for the synthetic `FIN
synfin-sni-fake-ttlinteger 0 through 255copy the held data packet TTLIPv4 TTL for packet X, the complete generated ClientHello, only. It does not affect filler or real packets.
synfin-sni-random-syn-checksumbooleanfalsePut arbitrary random values in the fake SYN's IPv4 and TCP checksum fields instead of valid recomputed checksums.
synfin-sni-random-fin-checksumbooleanfalseApply the arbitrary-checksum behavior to the synthetic close packet only.
synfin-sni-random-syn-sequencebooleanfalseUse a random 32-bit fake-SYN sequence instead of S - 1. Packet X remains at S.
synfin-sni-random-fin-sequencebooleanfalseUse a random 32-bit close sequence instead of S + Y, the end of the overlap interval.
synfin-sni-use-rstbooleanfalseSend `RST

TTL value 0 is a real TTL override. Omit the TTL field entirely if you want to preserve the applicable template TTL. A value of 0 will normally prevent a routed packet from progressing beyond the local link, but exact handling remains path-dependent.

The generated record uses an internal TlsClient, with default ALPN order h2, http/1.1 and x25519mlkem768 disabled. It is not copied from the real client's fingerprint. Let G be its complete record length. Runtime generation fails open unless G is at most 900 bytes and no greater than the two selected real payloads combined. A longer synfin-sni value normally increases G and makes the real-SNI placement requirement below harder to meet.

The additional range is selected once per successful flow after clamping both configured ends to what the real payload and SNI position permit. For an actual selected value A, A < 5 produces A random filler bytes; A >= 5 produces one TLS Application Data record header (17 03 03, record length A - 5) followed by random bytes. The configured minimum is therefore a request rather than a guarantee when the safe range is smaller.

The complete replacement sequence is emitted synchronously and back to back; the hold timeout does not pace it, and there is no other SynFIN delay setting. The shared stateful-flow-limit bounds the SynFIN table and defaults to 65536. This trick requires a normal top-level next and has no helper branch.

Example showing all controls (values are illustrative, not path-independent recommendations):

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"synfin-sni": "www.example.com",
"synfin-sni-hold-timeout-ms": 50,
"synfin-sni-additional-range-min": 8,
"synfin-sni-additional-range-max": 24,
"synfin-sni-syn-ttl": 5,
"synfin-sni-fin-ttl": 5,
"synfin-sni-fake-ttl": 5,
"synfin-sni-random-syn-checksum": false,
"synfin-sni-random-fin-checksum": false,
"synfin-sni-random-syn-sequence": false,
"synfin-sni-random-fin-sequence": false,
"synfin-sni-use-rst": false
},
"next": "next-packet-node"
}

Which flow and packets are selected​

A record is created only when this node sees a payload-free opening SYN. ECE and CWR may accompany SYN; ACK, FIN, RST, any other flag, or TCP Fast Open payload makes the opener ineligible. Packets on a tuple whose opening SYN was not observed pass unchanged. A later valid opening SYN on the same tuple starts a replacement generation.

  1. Warmup — the first two non-close upstream packets pass unchanged and count as warmup. Normally these are the opening SYN and the client's final handshake ACK; the downstream SYN|ACK is not counted. The third upstream packet is therefore normally the first ClientHello segment.
  2. Decision — if that third packet is payload-free, is not the beginning of a recognizable TLS ClientHello, or already contains the complete first TLS record, it passes immediately and the flow becomes passthrough. Only a partial or fragmented one-record ClientHello beginning is held.
  3. Hold — the selected segment remains inside IpManipulator; nothing from it is sent while the hold timer runs. Timeout releases it unchanged and makes the flow passthrough.
  4. Pair — the very next upstream packet must have nonzero payload and begin exactly at held.seq + held.payload_len. The combined bytes must contain a parseable ClientHello whose handshake exactly fills one TLS record and has a usable first host_name SNI entry. An ACK, exact retransmission, gap, overlap, out-of-order segment, still-incomplete record, or malformed layout releases the held packet followed by the current packet unchanged and selects passthrough. The trick never waits for a third data segment.

The declared first TLS record may not exceed 16,384 bytes. A ClientHello split across TLS records is unsupported. If the completing TCP segment also contains bytes after the selected record, those bytes remain in the combined real payload and are preserved in the real continuation.

The packet-ordinal rule is strict. A client whose ClientHello fits in one segment is never transformed, and any extra client packet between the handshake ACK and the first ClientHello segment can make the third packet select passthrough.

Successful TCP transcript​

Let:

  • S be the held segment's first TCP sequence number
  • H be the held segment's payload length
  • R be the two real payload lengths combined
  • G be the generated decoy ClientHello record length
  • O be the real SNI host name's offset from the beginning of the combined TCP payload
  • A be the chosen additional range after runtime clamping
  • Y = G + A be the length of the deliberately overlapping interval

A successful transcript requires 0 < G <= 900, G <= R, and O >= G. IpManipulator clamps the additional range so that 0 <= A <= min(R - G, O - G). Consequently Y <= O: real packet Y can end exactly before the first real SNI byte, but never carries a byte of the real host name.

The original held and completing packets are recycled. Before any later egress segmentation, five through seven replacement packets leave synchronously in this order:

  1. Real packet Y — real bytes [0, Y) at sequence S. It copies the held packet's IP/TCP header and exact TCP flags. Its payload can cross the original boundary at H, coalescing bytes from both selected segments.
  2. Synthetic close — a header-only FIN|ACK, or RST|ACK when synfin-sni-use-rst is true. Its default sequence is S + Y. The acknowledgement number, window, TCP options, addresses, and ports come from the held data packet.
  3. Fake SYN — a header-only packet with only SYN set, acknowledgement 0, and default sequence S - 1. It normally uses the captured opening SYN as its header template, retaining that SYN's TCP header length, options, and window. If retaining the template failed, the held data header is used instead.
  4. Fake packet X — exactly the G generated ClientHello bytes at sequence S. Its flags preserve CWR, ECE, URG, and ACK from the held segment and add PSH; SYN, FIN, and RST are omitted. X conflicts with real Y over sequence interval [S, S + G).
  5. Optional fake filler — when A > 0, one packet covers [S + G, S + Y). It contains the random or TLS Application Data-shaped bytes described above and uses the same continuation-plus-PSH flag policy as X.
  6. One or two real tails — real bytes [Y, R) complete the original stream. If Y < H, the held segment's suffix leaves at S + Y, followed by the completing segment at its original sequence S + H. Otherwise only the unused suffix of the completing segment leaves at S + Y.

Real Y starts with the held packet's IPv4 identification. The close, fake SYN, X, filler, and tails receive consecutively increasing identifications, so the completing packet's original identification is not preserved. Rebuilt packets normally carry valid IPv4 and TCP checksums. When the held-segment suffix is followed by a second tail, that first suffix keeps only CWR, ECE, URG, and ACK; the final tail uses the completing segment's flags.

There is no artificial sleep or configurable pacing between these outputs. Compatible final-egress behavior in the same node, including port ghost, protocol swap, and MTU-driven TCP segmentation, runs after transcript construction and can alter the wrapper or increase the final packet count.

What each participant may see​

  • The client sent two normal ClientHello segments, but WaterWall suppresses them and sends the replacement transcript. Synthetic controls and conflicting data can provoke duplicate ACKs, challenge ACKs, SACK changes, or a reset. If acknowledgement progress does not match the client's original transmission, its TCP stack may retransmit the original segment boundaries; later retransmissions are not suppressed after the transcript succeeds.
  • A stateful middlebox first sees a real prefix ending before the real host name, followed by a close and another SYN. With default sequences, the new SYN at S - 1 makes X at S look like first data on a replacement flow. Such a device may parse the decoy SNI and optional TLS-looking filler. A device that ignores the controls but uses last-arrival-wins overlap can reach the same payload view. A strict first-arrival-wins reassembler retains Y instead.
  • The destination is intended to retain real Y for [S, S + Y), ignore X and filler as conflicting duplicates, and append the real tail, reconstructing the original ClientHello. That requires Y to arrive first and the synthetic controls not to terminate destination-side state. A valid, in-window FIN at S + Y can close the receive stream exactly where the real tail starts; an accepted RST terminates the connection immediately.

The desired result is path-dependent. Loss, network reordering, first-versus- last overlap policy, checksum validation, receive-window validation, TCP normalization, SYN proxies, NAT/firewall state, and server TCP behavior can all change the outcome. In particular, if X reaches a first-arrival-wins destination before Y, the generated bytes can poison the server-side TLS stream.

Effect of TTL, checksum, sequence, and close settings​

The three TTL settings are independent. synfin-sni-syn-ttl applies only to the fake SYN; synfin-sni-fin-ttl applies only to the close; synfin-sni-fake-ttl applies only to X, not to the filler. Lower TTLs can let a nearby observer see a packet that expires before the destination, but they do not target a particular middlebox and route changes can invalidate a chosen value.

The two random-checksum settings make both the IPv4 and TCP checksum fields on their selected control packet arbitrary. They will normally be invalid, though a random field can coincidentally match the correct value. A passive classifier may inspect such a packet while a validating endpoint drops it; a validating middlebox drops it too. Y, X, filler, and real tails retain valid checksums. A later egress operation that rebuilds packets, such as port-ghost tailing or MTU segmentation, can repair an arbitrary checksum, so account for following stages when bad checksums are essential.

synfin-sni-random-syn-sequence moves only the fake SYN. X remains at S, so the coherent default relationship SYN(S - 1) -> data(S) is lost. This can keep the control outside a destination window, but can also stop a sequence-aware middlebox from treating X as data in the new flow. Similarly, synfin-sni-random-fin-sequence moves only the close: it may become less likely to be accepted by the destination and less credible to a sequence-aware observer.

synfin-sni-use-rst changes FIN|ACK to RST|ACK without changing the default sequence position S + Y. An accepted RST is generally more destructive than an orderly FIN and does not represent a graceful half-close consuming one sequence number.

Increasing the additional range enlarges both real Y's first-arrival coverage and the later filler coverage, moving the close and real tail to S + Y. It can never move past the real SNI start. Actual ClientHello length and extension order may clamp even the configured minimum downward.

Fail-open and blocked behavior​

Most unsupported cases fail open. The held and current packets are released or forwarded without the crafted transcript when:

  • the opening SYN was not observed, the packet is not whole IPv4 TCP, or the flow table cannot admit a record
  • the third packet is payload-free, non-TLS, or already contains a complete ClientHello record
  • the hold expires or its timer cannot be armed
  • the immediate next packet has no payload, is not strictly contiguous, or does not finish a supported one-record ClientHello with SNI
  • the internal hello cannot be generated, G > 900, or G > R
  • a replacement packet cannot be built or retained state crosses workers

The O < G case is an intentional fail-closed exception. If the real host name begins before the generated record ends, IpManipulator logs the mismatch, drops both selected real packets, and marks the upstream flow blocked. Every later upstream packet is dropped. An upstream FIN or RST removes the record but is itself dropped. A new valid opening SYN on that tuple starts a new generation. No local reset is sent to explain the failure, so the usual symptom is a stalled connection.

Retransmissions, closes, and flow lifetime​

During the initial hold, an upstream FIN or RST releases the held segment before forwarding the close and removes the record. An ACK-only packet or a retransmission arriving as the expected completion instead breaks the two-packet pair, releases held then current unchanged, and makes the flow passthrough.

After a successful transcript, the flow immediately becomes passthrough. Later ACKs, application data, original ClientHello retransmissions, and closes are not queued behind a delay window. A later retransmission can therefore add the client's original segmentation and overlap policy back to the wire. An upstream FIN or RST removes a passthrough record and continues normally.

Downstream traffic, including a downstream FIN or RST, is not inspected by SynFIN and does not remove its record. Otherwise-unused records expire after 20 minutes of inactivity. Every retained pair must remain on one WaterWall worker; a cross-worker completion fails open, but the current packet continues on its worker while the held packet is returned to its owner worker, so original wire order cannot be promised on that error path.

A replacement opening SYN invalidates old hold-timer generations before starting warmup again. If tuple reuse happens during an active hold, the old held buffer is disposed rather than inserted into the replacement connection.

How synfin-sni differs from the other SNI tricks​

  • first-sni clones the real ClientHello, changes its host name, and tries to keep the decoy away from the server with TTL or a random sequence. It can work on one ClientHello segment and injects no connection-state controls.
  • smuggle-sni captures up to 16 segments and sends the real originals through a helper branch while a generated hello uses the normal branch after a delay. SynFIN accepts exactly one held segment plus its immediate contiguous completion, has no helper branch, and emits all views back to back.
  • overlap-sni uses the same two-segment selection and real-Y/fake-X overlap, but injects only a fake SYN, fixes the overlap length at G, and schedules X and the real tails with delays. SynFIN adds a preceding FIN|ACK/RST|ACK, can enlarge the overlap with filler, offers checksum and sequence controls, and has no post-transcript delay window.
  • ech-sni-trick sends an out-of-order, byte-identical copy of a decoy already embedded by TlsClient; it creates no conflicting TCP bytes or controls. sni-blender fragments and shuffles the real packet at IPv4 rather than replacing TCP segmentation or generating another ClientHello.

Limitations and side effects​

  • Only whole, non-fragmented IPv4 TCP packets are eligible. IPv6, IPv4 fragments, and TCP Fast Open SYN data pass unchanged.
  • The ClientHello must begin on the third upstream packet, be incomplete there, and become parseable after exactly one contiguous data segment. One-segment and three-or-more-segment ClientHellos are not transformed.
  • The generated hello uses the internal TlsClient fingerprint and default ALPN list rather than the real client's fingerprint. The configured name must be 1 to 255 bytes, and G, R, and O must satisfy the constraints above.
  • Original data boundaries and the completing packet's IPv4 identification are discarded. Final MTU shaping can split rebuilt data again, increasing the nominal five-to-seven packet count.
  • Synthetic close/SYN packets on an established connection may be normalized, challenged, rate-limited, dropped, or treated as an attack by hosts, firewalls, NATs, load balancers, SYN proxies, and intrusion systems.
  • The bounded flow table uses stateful-flow-limit; admission pressure fails open. Active holds retain packet memory until paired, timed out, closed upstream, replaced, or destroyed, and require consistent worker affinity.
  • A normal top-level next is required. This trick cannot share one IpManipulator with another stateful SNI trick, sni-blender, packet-duplicate, or an upstream TCP-bit action. Put compatible later packet shaping in another node and account for its effects on TTL, checksum, segmentation, tuple, and packet order.

smuggle-fin​

smuggle-fin is a two-branch ordering trick. It holds an ordinary data packet on the normal path, sends a plausible reverse-direction FIN|ACK through a separate helper path, and waits for an exact copy of that FIN to return on the downstream path. The intent is to let a stateful device on the helper path see a connection-closing signal before the original data is released. Whether that changes classification or connection state depends on the topology and the device; the trick does not guarantee that any middlebox will echo, accept, or act on the FIN.

Despite the name fin-sni-delay-ms, this trick does not parse TLS, a ClientHello, or SNI. It can trigger on any whole, non-fragmented IPv4 TCP packet that has ACK, has at least one byte of TCP payload, and has none of SYN, FIN, or RST. Flags such as PSH, ECE, CWR, and URG do not disqualify an otherwise eligible packet. IPv6, IPv4 fragments, non-TCP packets, ACK-only packets, and TCP Fast Open SYN data pass without starting this trick.

SettingTypeDefaultDescription
smuggle-finbooleanfalseEnables the trick. It is not a hostname or TLS selector.
fin-sni-delay-msinteger ms, >= 00Delay after consuming the exact downstream echoed `FIN
fin-pause-timeout-msinteger ms, > 01000Hard maximum pause measured from the triggering data packet. It releases the queue even if no exact echo arrives.
real-fin-upstream-nodestringrequired when enabledDistinct helper branch that receives the synthetic reverse `FIN

The shared stateful-flow-limit setting also applies and defaults to 65536. There is no setting for the mirrored FIN's TTL, flags, sequence numbers, acknowledgement number, or checksum; those values are derived from the trigger. The node must have a normal top-level next as well as the helper. The named helper must exist, must not be this IpManipulator, must differ from next, and must be reachable and available for binding when the chain is constructed.

{
"name": "ip-manipulator",
"type": "IpManipulator",
"settings": {
"smuggle-fin": true,
"fin-sni-delay-ms": 20,
"fin-pause-timeout-ms": 1000,
"real-fin-upstream-node": "fin-helper"
},
"next": "normal-path"
}

The numbers above are illustrative. In particular, choose the hard timeout from the expected helper round trip, intended delay, and acceptable application stall, rather than treating 1000 ms as universally suitable.

Trigger packet and mirrored FIN​

Let the triggering packet be:

client A:a -> server B:b
SEQ = S, ACK = K, payload length = L

IpManipulator first retains that complete packet instead of forwarding it on the normal next branch. It then copies the packet's IPv4 and TCP headers, removes all TCP payload, swaps both IP addresses and both TCP ports, and emits:

server B:b -> client A:a
SEQ = K, ACK = S + L, flags = FIN|ACK, payload length = 0

Sequence arithmetic is TCP's 32-bit sequence arithmetic. The trigger cannot contain SYN or FIN, so only its payload length advances the acknowledgement. The copied header retains such fields as the trigger's IPv4 identification and TTL and its TCP header length, options, and window. IPv4 total length is reduced to the header-only length, the flags and sequence fields are replaced as shown, and valid IPv4/TCP checksums are requested for egress. No setting changes these fields independently.

The reverse numbers are chosen to look plausible for the same live connection: K is the next server sequence the client said it expected, while S + L acknowledges all bytes in the held client packet. The client itself sent only the original data packet; it did not send this FIN. If the helper path actually delivers the spoofed reverse packet to the client and the client's TCP stack accepts it as in-window, the FIN can half-close the receive direction, advance the expected server sequence by one, provoke an ACK, or otherwise disrupt the connection. That endpoint effect is possible, not required by the trick.

The crafted packet is sent immediately through real-fin-upstream-node. It does not traverse this node's upstream TCP-bit, stateful-SNI, SNI Blender, packet duplication, or port-ghost stages. Protocol swap and final checksum handling can still apply. The original data packet remains queued for the normal path and, when released, continues through those later configured stages in their usual order.

Echo recognition and release timing​

For echo-confirmed release, the helper topology must return the crafted FIN as a downstream packet through this IpManipulator. After downstream protocol and port-ghost restoration, a packet matches only when all of these are true:

  • it is a whole, non-fragmented IPv4 TCP packet in direction B:b -> A:a
  • it has zero TCP payload
  • its sequence is exactly K and its acknowledgement is exactly S + L
  • after ignoring only ECE and CWR, its flags are exactly FIN|ACK

IPv4 identification, TTL, TCP window, and TCP options are not compared. A normal TCP response to a FIN is normally an ACK with different direction and sequence semantics, so it is not the required echo. Any additional flag other than ECE or CWR also prevents a match.

The first exact echo is consumed and never forwarded toward the client. Exact duplicates received while release is pending are consumed too. On the first match, fin-sni-delay-ms schedules release of the queue. The hard fin-pause-timeout-ms timer started when the trigger was captured remains active, however, so the effective sequence is:

t0: trigger is queued; hard timeout starts; mirrored FIN is sent
te: exact echoed FIN is consumed, if one arrives
release: earlier of (te + fin-sni-delay-ms) and
(t0 + fin-pause-timeout-ms)

Consequently, the pause timeout must exceed the expected helper round trip plus the desired post-echo delay if the full delay is important. A delay of 0 releases immediately on the owner worker. If no echo arrives, the hard timeout still releases the flow. Failure to arm the initial hard timer releases the queued trigger immediately before the helper FIN is sent; failure to arrange a cross-worker post-echo release leaves the original hard timer as the fallback. Timer callbacks are tied to the exact pause generation, so a stale callback cannot release a newer use of the same tuple. Once release has completed, a later packet matching the old FIN is ordinary downstream traffic and is no longer consumed by Smuggle-FIN.

What is queued and how it is replayed​

While the pause is active in the normal packet-chain orientation, every parseable upstream packet on the forward tuple and every parseable downstream packet on its reverse tuple is held, regardless of flags or payload. This includes retransmitted data, ACKs, later application data, and real FIN or RST packets. The trigger is the first queue entry; the exact synthetic-FIN echo is the exception because it is consumed as the release signal. Unparseable or fragmented packets bypass the queue and can therefore overtake held traffic. Unrelated flows are never held behind this flow.

The queue holds at most 256 packets and has no separate byte limit. If a 257th packet arrives, or the queue cannot grow, the existing batch is forcibly released in queue order, the current packet follows normally, and the flow is marked confirmed so it is not paused again. Timeout and other forced-release paths have the same no-retry behavior.

One flow-owner worker maintains the queue. Reverse packets received on another worker are copied to the owner worker before being queued. Replay order is the order in which the owner worker accepted the entries, which need not equal a single global wire-arrival order when multiple workers are involved. If a cross-worker handoff cannot be created or submitted, that reverse packet passes on its current worker while the pause remains active, so it can overtake the queue. Only one flow may be paused by smuggle-fin on each worker at a time; a new eligible packet for another flow on that worker passes normally.

Each queue entry remembers whether its checksum needed recalculation. On release, entries are replayed one at a time in the saved mixed-direction order:

  • upstream packets restart at the beginning of the upstream IpManipulator pipeline; the now-confirmed Smuggle-FIN record prevents reinjection, and later upstream tricks and normal egress run once
  • downstream packets resume immediately after smuggle-fin, because protocol and port-ghost restoration already ran before they were queued; later downstream stages run once

Smuggle-FIN does not split, overlap, rewrite, or reorder bytes within the original TCP packets. Its deliberate ordering change is temporal: the synthetic reverse FIN leaves before the held original data, and unrelated or bypassed traffic may pass during the pause. Holding both directions can add latency and can trigger retransmission timers, delayed-ACK behavior, congestion-window or receive-window changes, and application-visible stalls.

What each participant may see​

  • Client: sends its original data normally and sees none of WaterWall's queueing directly. It may receive the spoofed FIN if the helper path routes it there; accepting that FIN can disturb or close the connection as described above. It can retransmit held data when the pause approaches its RTO.
  • Helper-path middlebox: can see a server-to-client FIN|ACK with sequence and acknowledgement values derived from current client data before that data appears on the normal branch. A device that accepts this packet for its flow state may mark one direction closed, discard later packets, change its parser state, or produce the exact downstream echo used by WaterWall. Stateless devices and devices that validate a different path, window, checksum, or flow history may simply ignore it.
  • Destination server: is normally intended to receive the original client data later through next, unchanged by Smuggle-FIN itself. The synthetic packet has the reverse tuple and uses the helper branch, but routing and branch design ultimately determine whether it reaches a real endpoint or server-side observer.

The technique therefore relies on a path-specific difference: an intermediate device must treat the early plausible FIN as meaningful while the actual end-to-end connection remains usable long enough for the delayed original data to succeed. TCP normalization, asymmetric routing, NAT state, SYN/FIN proxies, sequence-window validation, anti-spoofing, checksum offload or validation, and endpoint behavior can all invalidate that assumption.

Flow lifetime and failure behavior​

After an exact echo or any forced release, the normalized four-tuple remains confirmed and bypasses further Smuggle-FIN injection. Activity keeps a confirmed record for up to 20 minutes of idle time. A same-orientation SYN, FIN, or RST does not explicitly reset that record, so rapid reuse of exactly the same tuple can continue to bypass the trick until it expires. In unusual chains where an eligible reverse-oriented data packet is delivered through the upstream callback, the sole canonical record can be reoriented; an active old queue is then discarded rather than replayed.

Because the exact echo is identified only by tuple, flags, payload length, sequence, and acknowledgement, a legitimate server FIN|ACK that happens to match all those fields is indistinguishable and will be consumed. Conversely, minor helper changes to sequence numbers or flags prevent confirmation and make the flow wait for the hard timeout. Tunnel shutdown disposes of retained packets rather than forwarding them during teardown.

stateful-flow-limit bounds the Smuggle-FIN table independently of the other stateful tables. Admission pressure fails open, leaving the candidate packet on the normal path without sending a mirrored FIN. Flow-affinity changes are tolerated specially for reverse packets through owner-worker handoff, but introduce the ordering qualifications above.

Differences and interactions​

Unlike the TLS/SNI tricks, smuggle-fin neither locates nor changes a ClientHello. Unlike smuggle-sni, it sends a newly constructed header-only FIN through the helper branch and withholds the original data; smuggle-sni sends captured real ClientHello packets through its helper. Unlike synfin-sni, it does not generate a TLS record, fake SYN, same-direction close, or overlapping TCP sequence ranges. Unlike packet duplication, its helper packet has the reverse tuple, different flags and sequence fields, no payload, and a different release schedule.

It may be configured alongside a stateful SNI trick, SNI Blender, TCP-bit rewrites, port ghost, protocol swap, or packet duplication when their own compatibility rules permit it. This does not mean the stages operate on the synthetic FIN: the helper FIN follows the bypass path described above, whereas the released original packets enter the ordinary pipeline. The added pause and replay timing can still change how those later stateful tricks classify a flow. Final MTU shaping may segment a released oversized TCP data packet even though Smuggle-FIN itself did not split it.

Stateful Flow Tables​

IpManipulator establishes its own packet-flow ownership when first-sni, smuggle-sni, overlap-sni, synfin-sni, ech-sni-trick, or smuggle-fin is enabled. Native upstream TCP/UDP packets reach their flow worker before stateful processing. Downstream packets are assigned after protocol and port restoration, before the stateful continuation. These internal dispatcher boundaries need no extra configured node and do not depend on RawSocket or TunDevice selecting the flow owner. Stateless tricks do not add this requirement. Plain ProtoSwap keeps both encoding and restoration on the current worker, including fragment output; it changes protocol/checksums without selecting a flow owner or staging fragments. Opaque protocols without a native TCP/UDP tuple also stay on their current worker.

Retained packets remain pinned to the worker that opened their capture, delay barrier, or held-packet group. Worker-mismatch fail-open checks remain in place; normal connection lines retain their original owner and are never migrated by the packet dispatcher. Smuggle-FIN retains its separate reverse-traffic owner handoff for an existing paused flow, subject to the ordering and handoff limits in its dedicated section.

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.

Each table uses a CSPRNG-generated per-tunnel hash seed, keeping bucket selection unpredictable from observed tuples.

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.

Every delayed release has a fail-open path: if its timer cannot be armed, held bytes are flushed immediately and in order instead of being stranded.

Overlap-SNI and SynFIN-SNI holds use separate per-flow fail-open timers keyed by the normalized tuple plus a nonzero hold generation. Timeout releases the held segment unchanged outside the shard lock. An upstream close releases either trick's live hold, and Overlap-SNI also releases it on a downstream close. If idle expiry or table teardown removes the record before its timer callback, the record destructor deliberately disposes of the held packet instead of forwarding from under the shard lock or during shutdown.

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 and real-FIN 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 sbufGetRecalculateChecksum(buf).
  • 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.

Node Metadata​

Source-backed metadata:

PropertyValue
node flagskNodeFlagNone
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer3
layer_group_prev_nodekNodeLayerAnything
layer_group_next_nodekNodeLayerAnything
required_padding_left0 bytes