#6732·Xray-core

Proposal: user-configurable traffic shaping profiles for XMC / FinalMask (follow-up to #6487)

Author: BolTun-progCreated Sep 7, 2026Updated Sep 14, 2026
LabelsPR welcome

The #6487 summary ends with:

This intentionally does not emit version-specific Play or Chunk Data packets. User-configurable traffic shaping is deferred to a follow-up PR.

In the merge discussion @RPRX also noted that custom traffic-shape configuration would be added later as an incremental feature (流量形状自定义配置下次再补吧,毕竟是增量功能).

This issue proposes a concrete design for that follow-up and asks for design agreement before any implementation (a draft PR would follow if the direction is acceptable). If the original XMC author already has shaping work in flight, happy to stand down and review/help instead.

Problem

The current XMC shaping is a built-in model of one fixed target (uncompressed Minecraft 26.1.2 login/join). Two practical limits:

  • Built-in models decay when the camouflage target's traffic shape evolves; updating them requires core releases.
  • Different deployments may need different camouflage targets, but today the XMC shape is a compiled-in preset and is not configurable (other masks, e.g. fragment #6334 or header-custom #5920, already take declarative config).

The knobs that exist today are scattered across features and do not compose with XMC's directional shaping: freedom fragment maxSplit (#4998), XHTTP upload pacing (minUploadInterval, #3592) and its padding mechanism (#3643), fragment lengths/delays (#6334), and the programmable header-custom templates (#5920).

Proposal

Make the XMC shaping data-driven: a declarative profile loaded from config, interpreted by a small engine in finalmask — no policy logic in core.

Sketch:

jsonc
{
  "schema": "xray.shape@1",
  "name": "xmc-default",
  "version": 1,
  "phases": {
    "login": {
      "c2s": { "lenDist": [ /* sampled length histogram */ ] },
      "s2c": { "lenDist": [ /* ... */ ] },
      "keepAlive": { "iatMs": 1000, "jitterMs": 100 }
    }
    // phase transitions follow the existing protocol state machine
  }
}

Runtime invariants (deliberately conservative):

  • Padding only ever increases record length; shortening is fragmentation, which stays a separate mask.
  • Zero added latency for real data: the profile only rate-limits sends (iatMs as "not faster than") and fills pauses with dummies. Delay-based scheduling is explicitly out of scope here.
  • The turn schedule stays alternating-direction (client first), exactly like the merged XMC padding schedule; a profile only controls lengths and cadence, never the message flow.
  • shape composes with the other configured masks rather than replacing them; the chain order stays whatever the finalmask.tcp array says (noise in particular remains a standalone pre-data layer).
  • Fail-closed: schema/version mismatch between endpoints aborts the connection.
  • Length values the server must reproduce are carried in-band, inside the encrypted channel.

What this is not

  • Not a new protocol and not a wire-format change.
  • Not ML in core: profiles are produced offline elsewhere; core only interprets a table. A separate profile repository and geodata-style distribution can come later — out of scope here.
  • Not delay/jitter scheduling (possible follow-up after measurements).

Questions

  1. Config placement: a profile embedded in streamSettings (per inbound/outbound), or a named registry referenced by id?
  2. Are the invariants above (especially zero-delay + padding-only) the intended direction for the follow-up, or was something broader planned?
  3. If the direction is agreed, would a draft PR with a single built-in preset (reproducing today's XMC 26.1.2 shape as a profile), tests, and the still missing XMC config docs be the right first step?

Prior art for profile-driven shaping engines: Maybenot (Karlstad University, arXiv:2304.09510) — a padding engine driven by parameterized state machines; the offline-profile / online-interpreter split here follows the same reasoning at the proxy layer.

Refs: #6487 #6210 #4998 #3592 #3643 #6334 #5920