Desync Engine -- RIPDPI
SkillDev toolsUse when designing or reviewing DPI desync evasion chains, TcpChainStep/UdpChainStep configurations, OffsetExpr/OffsetBase expressions, fake-packet injection, TLS-prelude steps, strategy-probe candidate behavior, fake-TTL semantics across TUN vs proxy mode, or anything in the ripdpi-config/ripdpi-desync/ripdpi-desync-runtime/ripdpi-proxy-runtime desync pipeline. Triggers on "desync", "DesyncMode", "fake packet", "TTL", "OOB", "tlsrec", "QUIC initial split", or DPI-bypass strategy questions.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Desync Engine -- RIPDPI skill
What this skill tells your AI
The instructions your AI receives, as published by po4yka/ripdpi in .agents/skills/desync-engine/SKILL.md and read by ahel’s review.
Purpose
Document the three-layer DPI desync pipeline that RIPDPI uses to evade middlebox inspection: configuration model (ripdpi-config), planning (ripdpi-desync), and execution (ripdpi-desync-runtime through ripdpi-proxy-runtime). Apply this skill when modifying any layer of the pipeline, adding new desync techniques, or reasoning about why a strategy works (or doesn't) on a specific Android deployment mode.
Conceptual model
DPI (Deep Packet Inspection) systems like Russia's middlebox inspect the first packets of a TCP/UDP connection to fingerprint the protocol and destination. They look for TLS ClientHello SNI fields, HTTP Host headers, and QUIC Initial packet metadata. If the destination matches a blocklist, the DPI injects a RST or drops the connection.
Desync evasion works by making the first packets unreadable to the DPI while remaining valid to the destination server. Techniques include:
- Splitting the payload across multiple TCP segments so no single packet contains the full SNI/Host value.
- Disordering segments so the DPI reassembly engine sees garbage first.
- Injecting fake packets with low TTL that reach the DPI but expire before the destination, poisoning the DPI's reassembly buffer.
- TLS record fragmentation to split the ClientHello across TLS records within a single TCP segment.
- UDP/QUIC fake bursts and SNI splitting for QUIC Initial packets.
Architecture overview
The desync engine is a three-layer pipeline:
Layer 1: Configuration model (ripdpi-config)
Files: native/rust/crates/ripdpi-config/src/model/ (start at mod.rs; group and runtime settings are split by domain)
Defines the data model: DesyncGroup, DesyncGroupActionSettings,
TcpChainStep, TcpChainStepKind, UdpChainStep, UdpChainStepKind,
OffsetBase, OffsetExpr, ActivationFilter, DesyncMode, EntropyMode,
QuicFakeProfile, SeqOverlapFakeMode. These are pure data types with no
I/O -- they represent what the user configured.
Key relationships:
DesyncGroupownsDesyncGroupMatchSettings(when to activate) andDesyncGroupActionSettings(what to do).DesyncGroupActionSettings.tcp_chain: Vec<TcpChainStep>defines the ordered chain of TCP desync operations.- Each
TcpChainStephas akind: TcpChainStepKindand anoffset: OffsetExprthat determines where to split.
Layer 2: Planning (ripdpi-desync)
Files:
native/rust/crates/ripdpi-desync/src/plan_tcp.rs--plan_tcp()native/rust/crates/ripdpi-desync/src/plan_udp.rs--plan_udp()native/rust/crates/ripdpi-desync/src/offset.rs--resolve_offset(),gen_offset()native/rust/crates/ripdpi-desync/src/fake.rs--build_fake_packet(),build_hostfake_bytes()native/rust/crates/ripdpi-desync/src/tls_prelude.rs--apply_tls_prelude_steps()native/rust/crates/ripdpi-desync/src/types.rs--DesyncAction,DesyncPlan,PlannedStep
The planner takes a DesyncGroup + raw payload + ActivationContext and
produces a DesyncPlan containing a Vec<DesyncAction>. No I/O happens here
-- pure computation over bytes.
Layer 3: Execution (ripdpi-desync-runtime via proxy runtime adapters)
Files:
native/rust/crates/ripdpi-desync-runtime/src/tcp.rsnative/rust/crates/ripdpi-desync-runtime/src/tcp_plan/native/rust/crates/ripdpi-proxy-runtime/src/runtime/desync.rs
The proxy runtime builds the runtime desync context and delegates TCP execution to ripdpi-desync-runtime, which takes the DesyncPlan and executes DesyncAction variants against a real TcpStream, calling platform-specific socket operations (TTL, MD5 sig, window clamp, IP fragmentation).
The desync pipeline
Step-by-step flow when a TCP payload is sent through send_with_group():
-
Activation filter check --
activation_filter_matches()intypes.rschecks round number, payload size, and stream byte range against the group'sActivationFilter. If the filter does not match, the payload is sent as-is (no desync). -
Adaptive hints resolution --
resolve_tcp_hints_with_evolver()in the runtime resolves adaptive parameters (split offset base, TLS record offset, entropy mode) from the strategy evolution cache. -
Entropy padding --
apply_entropy_padding()optionally prepends padding bytes to counter popcount/Shannon entropy detection by the DPI. -
Chain splitting --
split_tcp_chain()inplan_tcp.rsseparates TLS prelude steps (TlsRec,TlsRandRec) from send steps. Prelude steps must come before all send steps -- mixing is an error. -
TLS prelude application --
apply_tls_prelude_steps()intls_prelude.rsfragments the TLS record layer. Also applies HTTP modifications (mod_http) and TLS minor version overrides (tlsminor). -
Offset resolution -- For each send step,
resolve_offset()inoffset.rscomputes the byte position where the split occurs. Adaptive offsets (AutoBalanced,AutoHost, etc.) useresolve_adaptive_offset()which evaluates multiple candidate bases and picks the best one within a budget. -
Step planning --
plan_tcp()iterates send steps, resolves offsets, and for eachTcpChainStepKindvariant builds the appropriate sequence ofDesyncActionvalues: writes, fake injections, TTL changes, etc. -
Execution -- The runtime walks
plan.actionsand executes eachDesyncActionagainst the socket.AwaitWritablepolls the socket between segments to ensure ordering.
TCP chain steps
| Kind | What it does | Key detail |
|---|---|---|
Split | Splits payload into two TCP segments at offset | Simplest technique; just packet boundary |
SeqOverlap | Sends overlapping TCP segments with fake prefix | Falls back to Split if unsupported |
Disorder | Sends first chunk with low TTL (expires before dest) | DPI sees garbage; real data follows |
MultiDisorder | Multiple disorder splits across several offsets | Requires 3+ resulting segments |
Fake | Injects a fake packet (wrong content) at low TTL | Uses build_fake_packet() for content |
FakeSplit | Split + inject fake copy of second segment | Degrades to Split at boundaries |
FakeDisorder | Disorder + inject fake copy | Degrades to Disorder at boundaries |
HostFake | Injects fake Host/SNI region with wrong hostname | Wraps real host with fake before/after |
Oob | Sends chunk as TCP urgent (OOB) data | Urgent byte from oob_data config |
Disoob | Disorder + OOB combined | Low TTL + urgent data |
TlsRec | TLS record-layer split (prelude step) | Splits TLS record, not TCP segment |
TlsRandRec | Random TLS record fragmentation (prelude step) | Multiple random-sized TLS records |
IpFrag2 | IP-layer fragmentation of the TCP segment | Only on round 1; kernel reassembly trick |
See references/chain-step-catalog.md for the full catalog with config fields.
Strategy probe candidates
Candidates are ordered modern-first (most effective on censored networks tested first):
- TLS-record techniques:
tlsrec_split_host,tlsrec_hostfake_split,tlsrec_fake_rich - Split:
split_host - Disorder/OOB (see below)
- Legacy parser tricks:
parser_only,parser_unixeol,parser_methodeol - ECH:
ech_split,ech_tlsrec
New TCP candidates added
| ID | Steps | requires_fake_ttl |
|---|---|---|
disorder_host | disorder at host+2 | true |
tlsrec_disorder | tlsrec + disorder at host+2 | true |
oob_host | oob at host+2 (MSG_OOB, no TTL) | false |
tlsrec_oob | tlsrec + oob at host+2 | false |
disoob_host | disoob at host+2 | true |
tlsrec_disoob | tlsrec + disoob at host+2 | true |
tlsrandrec_split | tlsrandrec at sniext+4 + split at host+2 | false |
tlsrandrec_disorder | tlsrandrec at sniext+4 + disorder at host+2 | true |
tlsrec_hostfake_random | tlsrec + hostfake with random_fake_host=true | false |
split_delayed_50ms | split at host+2 + Delay(50) between segments | false |
split_delayed_150ms | split at host+2 + Delay(150) between segments | false |
TTL requirements
config_requires_fake_ttl() checks for: "fake", "fakedsplit", "fakeddisorder", "hostfake", "disorder", "disoob". Pure "oob" does NOT require TTL (uses MSG_OOB flag).
Non-rooted Android constraints
Not feasible without root (SOCK_RAW + TCP_REPAIR): FakeRst, MultiDisorder. All other techniques work on non-rooted Android.
TTL fallback on Android VPN/tun
send_fake_tcp() gracefully falls back to sending original data (without fake packet) when setsockopt(IP_TTL) fails on Android VPN/tun mode, instead of aborting the connection.
Tournament bracket
Round 1 qualifier tests each candidate against 1 domain (2 probes: HTTP+HTTPS). Candidates failing both are eliminated before the full-matrix Round 2, saving ~70% of probe time on censored networks.
UDP desync
UDP desync (plan_udp() in plan_udp.rs) targets QUIC Initial packets.
The chain uses UdpChainStepKind variants:
-
FakeBurst -- Sends N copies of a fake QUIC packet before the real one. Fake content comes from
udp_fake_payload()which selects betweenQuicFakeProfile::CompatDefault(static fake),RealisticInitial(crafted QUIC Initial with fake host), or profile-based bytes. Burst count is adjusted byAdaptiveUdpBurstProfile(Conservative/ Balanced/Aggressive). -
DummyPrepend -- Sends random 64-byte non-QUIC packets (high bit cleared) to confuse DPI state machines.
-
QuicSniSplit -- Sends a tampered copy of the QUIC Initial with the SNI split at
host_start, causing DPI SNI extraction to fail. -
QuicFakeVersion -- Sends a copy with a fake QUIC version number, poisoning DPI version detection.
-
IpFrag2Udp -- IP-layer fragmentation of the UDP datagram (round 1 only, QUIC traffic only).
All UDP fake packets are sent at low TTL (default 8) so they expire before reaching the destination.
Offset system
The offset system determines WHERE in the payload to split. OffsetExpr
combines an OffsetBase (what the offset is relative to) with a delta
(signed adjustment), proto filter (Any or TlsOnly), and optional
repeats/skip for multi-split.
Key categories:
- Absolute:
Abs-- raw byte position (negative = from end) - Payload-relative:
PayloadEnd,PayloadMid,PayloadRand - Host/SNI-relative:
Host,EndHost,HostMid,HostRand - SLD (second-level domain):
Sld,MidSld,EndSld - Protocol markers:
Method(HTTP),ExtLen,EchExt,SniExt(TLS) - Adaptive:
AutoBalanced,AutoHost,AutoMidSld, etc. -- evaluated at planning time by trying multiple candidates within a budget
See references/offset-system.md for the full table.
Fake packet mechanics
Fake packets are the core deception mechanism. They must reach the DPI but NOT the destination server. Several mechanisms ensure this:
TTL manipulation
DesyncAction::SetTtl(fake_ttl) sets the IP TTL to a value that expires
between the DPI and the destination. Default fake TTL is 8 (configurable via
group.actions.ttl). disorder_ttl() in plan_tcp.rs uses the configured
fake_ttl, falling back to 1 for disorder steps. After sending the fake,
RestoreDefaultTtl restores the original TTL. Auto-TTL
(AutoTtlConfig) can dynamically compute the right TTL from traceroute data.
MD5 signature
DesyncAction::SetMd5Sig { key_len: 5 } enables the TCP MD5 signature
option (RFC 2385). The destination rejects packets with unexpected MD5
options, but the DPI typically ignores them. Toggled via group.actions.md5sig.
Entropy padding
EntropyMode counters DPI entropy analysis. Popcount mode adjusts byte
popcount to bypass GFW-style detection. Shannon mode targets Shannon
entropy levels. Combined does both. Controlled by
entropy_padding_target_permil and shannon_entropy_target_permil.
Fake content generation
build_fake_packet() in fake.rs constructs fake payload bytes:
- Base content from
fake_data(user-provided), HTTP profile, or TLS profile. - If TLS: optionally applies SNI replacement (
fake_sni_list), randomization (FM_RAND), session ID duplication (FM_DUPSID), padding encapsulation (FM_PADENCAP), and size tuning (fake_tls_size). fake_offsetdetermines where in the fake packet to start the overlay.
Delay
DesyncAction::Delay(u16) sleeps for N milliseconds between steps for
timer-based evasion. Used by delayed-split variants (split_delayed_50ms,
split_delayed_150ms) to exploit DPI timeout windows. Capped at 500ms.
Implemented via tokio::time::sleep() in the async execution path.
SeqNum rollback (SeqOverlap)
DesyncAction::WriteSeqOverlap sends overlapping TCP data: a fake prefix
occupies the sequence space, then the real data retransmits over it. The
destination's TCP stack uses the first valid data; the DPI may use the fake.
Gated by seqovl_hard_gate_matches() (round 1 only, small payloads).
PcapHook
Optional PcapHook callback (Arc<dyn Fn(&[u8], bool) + Send + Sync>) invoked
on each packet written during execute_tcp_actions() and execute_tcp_plan()
(the bool is true for outbound packets). The embedding layer owns the sink:
the runtime only forwards raw packet bytes and does no PCAP encoding itself.
No-op when not registered; zero overhead on the hot path.
Adding a new desync technique
End-to-end walkthrough for adding a hypothetical "TcpReorder" step:
1. Add config variant
In the relevant file under native/rust/crates/ripdpi-config/src/model/:
- Add
TcpReorderto theTcpChainStepKindenum. - Implement
as_mode()mapping (or returnNoneif no legacy mode). - Add any new fields to
TcpChainStepif needed. - Update
is_tls_prelude()to returnfalse.
2. Add parsing support
In native/rust/crates/ripdpi-config/src/parse/ (the CLI/config parser):
- Add parsing for the new step kind string (e.g., "tcpreorder").
- Wire it into
TcpChainStepconstruction.
3. Implement planning
In native/rust/crates/ripdpi-desync/src/plan_tcp.rs:
- Add a
TcpChainStepKind::TcpReorder => { ... }arm in thematch step.kindblock insideplan_tcp(). - Generate the appropriate
DesyncActionsequence. If it needs a new action type, add a variant toDesyncActionintypes.rs.
4. Implement execution
In native/rust/crates/ripdpi-desync-runtime/src/tcp.rs and native/rust/crates/ripdpi-desync-runtime/src/tcp_plan/:
- Add handling for any new
DesyncActionvariant in the TCP lowering/execution path. - If the step needs special multi-disorder-style execution, add a handler in the
tcp_planmodule and update the special-plan decision logic. - Update
primary_tcp_strategy_family()in the proxy-runtime state/config layer if the new step needs a user-visible strategy-family label.
5. Add tests
In native/rust/crates/ripdpi-desync/src/tests/ -- add plan-level tests.
In native/rust/crates/ripdpi-desync-runtime/src/tcp/tests/ or the relevant proxy-runtime adapter tests -- add execution tests
if the new action involves socket operations.
6. Update adaptive layer (if applicable)
If the new technique has tunable parameters, add fields to
AdaptivePlannerHints and wire them through the strategy evolution system
in native/rust/crates/ripdpi-proxy-runtime/src/runtime/adaptive/.
Common pitfalls
Activation filter ordering
Activation filters on individual TcpChainStep items are checked
independently from the group-level filter. A step can be skipped even when
the group is active. If a required step is filtered out, the plan may produce
fewer segments than expected, potentially causing DesyncError for
MultiDisorder (which requires 3+ segments).
Offset miss on non-TLS traffic
Host-relative offsets (Host, EndHost, Sld, etc.) require protocol
detection to find the SNI/Host header. If the traffic is neither HTTP nor
TLS (e.g., raw TCP proxy), these offsets return None. Non-adaptive offsets
cause DesyncError on miss; adaptive offsets silently skip. Always pair
host-relative offsets with appropriate detect or proto match settings
on the group.
Fake TTL too low or too high
TTL=1 may be dropped by the first hop (the user's own router). TTL too high
reaches the destination and corrupts the connection. The sweet spot is
typically 4-12, depending on network topology. Use auto_ttl for dynamic
calculation. disorder_ttl() defaults to 1 when unconfigured -- this works
for disorder (where the first chunk is intentionally dropped) but is too
aggressive for fake injection.
Fragment reassembly interference
IpFrag2 (TCP) and IpFrag2Udp (UDP) rely on the DPI not reassembling
IP fragments. Some DPI systems do reassemble, making this technique
ineffective. These steps only activate on round 1 to avoid repeated
fragmentation on retries.
TLS prelude must come first
split_tcp_chain() enforces that all TLS prelude steps (TlsRec,
TlsRandRec) precede all send steps. Mixing returns DesyncError.
Prelude steps modify the TLS record structure before send steps split
the result into TCP segments.
FakeSplit/FakeDisorder boundary degradation
When the offset falls at position 0 or at the payload end, FakeSplit
degrades to Split and FakeDisorder degrades to Disorder. The
fake injection requires a non-empty second segment to copy. This is
intentional graceful degradation, not a bug.
SeqOverlap hard gate
SeqOverlap only activates on round 1, with stream_start >= 0 and
total payload <= 1500 bytes. Outside these bounds it silently falls back
to Split. This prevents sequence overlap on large or resumed streams
where the kernel's TCP state makes overlap unreliable.
MultiDisorder minimum segments
plan_multi_disorder_steps() requires at least 3 resulting segments
(from the resolved offsets + payload boundaries). If fewer offsets
resolve, it returns DesyncError. Configure at least 2 offset points.
QUIC anti-fingerprinting (2026)
New in the 2025-2026 QUIC-evasion research cycle:
quinn pad_to_mtu option
quinn 0.11.x merged PR #2274 (June 2025) adding a pad_to_mtu transport config flag. When enabled, every application-data QUIC packet is padded to the path MTU regardless of payload size. This defeats size-based QUIC flow fingerprinting on censoring middleboxes that profile short application packets as distinct from Initial/Handshake packets.
RIPDPI applicability: the QUIC-carrying crates (ripdpi-hysteria2, ripdpi-masque, ripdpi-tuic, and the optional QUIC probe path in ripdpi-diagnostics-runner) should expose pad_to_mtu as a QuicFakeProfile variant or a per-session toggle. Size normalisation is cheap at the quinn layer — no application changes needed once the flag is set.
USENIX Security 2025: GFW QUIC censorship
Peer-reviewed research at USENIX Security 2025 (see net4people/bbs#505) documented the Great Firewall's current QUIC censorship techniques:
- SNI fingerprinting on the QUIC Initial packet (same attack class as TLS SNI blocking; the QUIC Initial carries an unencrypted ClientHello).
- Initial-packet size/shape profiling (the motivation for
pad_to_mtuabove). - QUIC version negotiation probing.
Implications for DesyncMode extensions:
- New QUIC Initial-packet fragmentation variant (
UdpChainStepKind-level) should mirror the TCP split / fake-TTL approach adapted for QUIC Initial framing. - QUIC SNI scrambling must coordinate with the TLS layer — splitting the ClientHello across multiple QUIC Initial packets requires the TLS record boundary to align with a QUIC frame boundary.
- Version negotiation: do not advertise a QUIC version that the censor flags; RIPDPI's current
QuicFakeProfileenum should gain aVersionPolicyfield.
No uTLS in Rust yet
TLS ClientHello fingerprint mimicry (JA3/JA4 evasion) requires byte-precise control over the TLS ClientHello extension order, GREASE values, and cipher suite ordering. As of April 2026, no Rust crate offers this as a library — the Go ecosystem (tlsmask, httpcloak, mic) still dominates the space.
RIPDPI's answer is ripdpi-tls-profiles wrapping BoringSSL directly (via the boring crate) with profiles for chrome_stable, firefox_stable, safari_stable, edge_stable. The profile set carries an invariant AvoidsBlocked517ByteClientHello which all profile weights respect — a ClientHello with a total length of exactly 517 bytes is known to be flagged by certain DPI implementations. New profiles added to the set MUST maintain this invariant; the desync-engine test suite enforces it.
If a Rust-native uTLS equivalent emerges (a port of utls to rustls or a ClientHello-builder crate over BoringSSL), it replaces ripdpi-tls-profiles' manual boring wrapping. Until then, this is the known gap.
Related skills
ws-tunnel-telegram— the WS-over-TLS tunnel consumes the same TLS fingerprint profiles and shares the 517-byte invariant concern.rust-panic-safety— desync execution paths must handle all panic cases at the JNI boundary;ripdpi-desyncerrors are typed viathiserror.rust-async-internals— UDP desync interacts with the tunnel io_loop; consult its manual-poll bridge guidance before adding UDP fake-packet injection.
TLS ClientHello semantic markers
When implementing offset bases like host, endhost, midsld, sniext, extlen, the parser must locate fields in the ClientHello byte layout per RFC 8446 §4.1.2. The byte structure after the TLS record header (5 bytes) and handshake header (4 bytes) is:
ClientHello {
legacy_version (2)
random (32)
legacy_session_id_len (1)
legacy_session_id (0..32)
cipher_suites_len (2)
cipher_suites (variable)
legacy_compression_len (1)
legacy_compression (variable)
extensions_len (2) // <- `extlen` marker location
extensions (variable)
}
Inside extensions, each entry is ExtensionType (2) + ExtensionData_len (2) + ExtensionData. The server_name extension (ExtensionType=0) wraps a ServerNameList:
server_name_extension {
list_len (2)
ServerNameList [
NameType (1) // 0 for HostName
HostName_len (2)
HostName (variable) // <- `host` marker starts here
// <- `endhost` marker ends here
// <- `midsld` = host_start + sld_offset + sld_len/2
]
}
Specific marker offsets:
host— first byte of theHostNamepayload inside theserver_nameextension.endhost— byte AFTER the last byte ofHostName.midsld— middle byte of the second-level domain (the part to the left of the final dot). Forwww.google.com,sld = "google", somidsldis athost_offset + len("www.") + len("goo") = host_offset + 7.sniext— first byte of theserver_nameextension header (i.e. theExtensionTypebyte = 0x00).extlen— the 2-byteextensions_lengthfield that immediately precedes the extensions array.
Parser edge cases
The parser MUST handle:
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 69
- Forks
- 4
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
desync-engine- Source
- github.com/po4yka/ripdpi