Skip to content

Prizrak-Core

Prizrak-Core is the proxy core that powers Prizrak-Box on every platform. It is based on mihomo (formerly Clash.Meta).

Configurations written for mihomo work in Prizrak-Core unchanged. On top of that, the core adds features that mihomo does not have; they are described below.


Prizrak-Core ships with Prizrak-Box, but it also works on its own. It is a standalone binary compatible with mihomo, so it can be used anywhere mihomo is used.

  • Prizrak-Box. The core is built in, nothing to set up.
  • Standalone. Download the archive for your platform from the releases and run it like regular mihomo: mihomo -d <folder with config.yaml>. The Docker image works too.
  • TrayClash. A very lightweight Windows app, suitable for Windows 7 devices.
  • OpenWrt-exodus. A tool for routers: OpenWrt, Asus, Keenetic, MikroTik. To install Prizrak-Core specifically, set a special variable during installation; see the OpenWrt-exodus documentation for which one.

FeaturemihomoPrizrak-Core
smart group (ML-based node selection)—✅
uTLS librarymetacubex/utls v1.8.8prizrak-utls: up-to-date uTLS, Chrome 155 / Firefox 148 / Safari 26.3
mux.cool multiplexing (Xray Mux, XUDP)—✅ client and server
REALITY with ML-DSA-65, in sync with Xray—✅
XHTTP: HTTP/2 flow governor (h2-flow)—✅ (off by default)
HTTP/2: 32 KiB send buffer instead of 512 KiB—✅
freedom: DIRECT with TLS fragmentation and socket options (Xray-core freedom)—✅

The smart group type comes from vernesong/mihomo. The group picks a node by weights computed from how the nodes actually perform: latency, connection success rate and traffic. Weight prediction by a LightGBM model can be enabled.

Since v1.19.33 the group also learns where each node leads and which sites refuse it:

  • Node exit. The group finds out the country and network (ASN) where a node’s traffic comes out. To do that it sends a request through the node to cloudflare.com/cdn-cgi/trace, api.ip.sb or ipwho.is, the next one if the previous does not answer.
  • Site responses. If a site refuses a node (blocked for its region, an error page, 403, a connection without any answer), the group remembers it and sends this site through other nodes.
  • No hasty verdicts. A region or a network is marked as refused only after several independent nodes from it fail. One refusal decides nothing, and a node that answers again clears the record.

Configuration details are on the Smart Proxy Group page.


uTLS is the library that makes the TLS fingerprint (ClientHello) look like a real browser. It decides which browser a connection looks like for client-fingerprint and REALITY.

mihomo uses metacubex/utls v1.8.8. Prizrak-Core uses its own fork legiz-ru/prizrak-utls (branch v1.9.0-mod-meta-mldsa-chr155). It consists of three parts:

  1. Base: the metacubex v1.9.0-mod-meta branch. It is a newer line than v1.8.8 in mihomo and carries all metacubex changes.
  2. Up-to-date upstream refraction-networking/utls: sync with crypto/tls from Go 1.27.1, the Chrome 155 fingerprint, fixes for compressed certificate decompression, GREASE and PSK fixes, MLKEM1024 key exchange. The fork still builds with Go 1.20.
  3. REALITY ML-DSA-65: see REALITY.
client-fingerprintmihomoPrizrak-Core
chromeChrome 133Chrome 155
firefoxFirefox 120Firefox 148
safariSafari 16.0Safari 26.3
ios, android, edge, 360, qqunchangedunchanged

In Prizrak-Core the firefox and safari fingerprints also carry the post-quantum X25519MLKEM768 key exchange, as real browsers of these versions do. In mihomo only chrome has it.


Prizrak-Core supports mux.cool multiplexing compatible with Xray. It is the same protocol as mux in Xray-core: TCP streams and UDP/XUDP sessions share TCP connections. The implementation comes from MetaCubeX/mihomo#2980; it is more extensive than the one in Xray-core and fully compatible with it.

  • Client. Enabled per proxy, separately from smux. Do not enable both.
  • Server. VLESS and VMess inbounds accept mux.cool from Xray clients.
  • XUDP. Dedicated UDP sessions, with control over their number and a policy for UDP/443.
  • Connection limits. You can limit how many TCP connections are opened in total, how many sessions run on each at once and how many sessions a connection carries over its lifetime, after which it is replaced with a new one.
proxies:
- name: vless-mux
type: vless
# ...
mux.cool:
enabled: true
# max-carriers: 0 # maximum TCP connections, 0 means no limit
# max-concurrency: 8 # active sessions per connection
# max-connections: 128 # sessions over a connection's lifetime before rotation
# xudp-concurrency: 4 # dedicated XUDP sessions per connection, 0 shares the pool
# xudp-proxy-udp443: reject # reject | allow | skip: what to do with UDP/443

To enable mux.cool for Remnawave subscription users, use the Remnawave host mapper; see mux.cool Multiplexing on the Remnawave integration page.


REALITY is aligned with current Xray-core. The implementation comes from MetaCubeX/mihomo#2983.

  • ML-DSA-65. Verification of the post-quantum signature on the REALITY certificate, as in Xray. The client sets the public key mldsa65-verify, the server sets mldsa65-seed.
  • ML-KEM as in Xray. REALITY always keeps the fingerprint of the selected browser, including X25519MLKEM768. The support-x25519mlkem768 option is no longer needed and is ignored. For older servers, set an older fingerprint explicitly, for example chrome120.
  • New client options (named as in Xray):
    • password: alias of public-key;
    • spider-x;
    • show;
    • master-key-log.
  • New server options:
    • target: alias of dest;
    • type;
    • xver;
    • min-client-ver / max-client-ver;
    • mldsa65-seed;
    • show;
    • master-key-log.
proxies:
- name: vless-reality
type: vless
# ...
client-fingerprint: chrome
reality-opts:
public-key: <X25519 public key>
short-id: <short id>
mldsa65-verify: <ML-DSA-65 public key, base64url> # optional

To add mldsa65-verify for Remnawave subscription users, use the Remnawave host mapper; see REALITY ML-DSA-65 on the Remnawave integration page.


Two improvements based on ideas by Medium1992 from mihomo-proxy-ros.

The client side of an HTTP/2 flow governor for XHTTP in h2 mode. The server sends data only as fast as the client reads it. Without the governor the server fills large HTTP/2 windows (4 MiB per stream in Go) and data nobody reads piles up in memory. The server needs no setup: the governor uses standard HTTP/2 flow control and works with any server.

How it works:

  • Window by consumption. The governor shows the server a 64 KiB window, caps the frame size at 16 KiB and measures RTT with its own PING once a second. The server gets credit back only while unread data stays below what the client reads in a couple of round trips.
  • Fast start. A new stream starts with a 256 KiB window or the window the connection recently needed. Until the first RTT is measured, the window opens with every credit.
  • Path model from min_rtt. On Linux and Android the governor reads TCP_INFO from the kernel (rtt, min_rtt, rttvar) and holds a download window to at most 1.25 × highest read rate × min_rtt. On other systems it uses its PING measurements.
  • Works with a server-side governor. The same governor exists on the server side in Jolymmiles/Xray-core (the h2Flow option). A governed client announces the default 65535 window, which tells the server that a governor sits on the other side too, so the server does not hold back what it sends. The governor can therefore be on the client, on the server or on both: downloads do not stall and throughput stays the same.

Off by default, enabled per proxy with an h2-flow block:

xhttp-opts:
h2-flow:
enabled: true
max-stream-receive-window: 16777216 # receive window per stream, bytes
max-connection-receive-window: 33554432 # receive window per connection, bytes
  • enabled turns the governor on.
  • Receive windows:
    • optional, from 65535 bytes to 1 GiB;
    • unset, Go’s default windows apply (4 MiB per stream);
    • with the governor they are a ceiling and the real window follows reading;
    • without it they are fixed windows.
  • h2-flow-control: true still works as a shorthand for h2-flow.enabled: true. When both are set, h2-flow.enabled wins.
  • Downloads through download-settings use the same h2-flow block.

Works only in h2 mode; it has no effect for h3 and http/1.1.

To enable h2-flow for Remnawave subscription users, use the Remnawave host mapper; see XHTTP Flow Control on the Remnawave integration page.

Long-lived HTTP/2 streams keep their send buffer open for the whole connection: XHTTP in stream-one/stream-up modes and gRPC. Its size is reduced from 512 KiB to 32 KiB through the legiz-ru/prizrak-http fork on top of metacubex/http v0.1.8. With many concurrent tunnels this saves noticeable memory at the same speed. Nothing to configure.


freedom: TLS fragmentation and socket options

Section titled “freedom: TLS fragmentation and socket options”

Since v1.19.33-r2 there is a proxy type freedom. It is DIRECT, the connection goes straight to the site, plus the fragment and sockopt settings of the Xray-core freedom outbound. Two fragmentation modes are taken from sing-box (tls_fragment, tls_record_fragment).

Fragmentation splits the TLS ClientHello so that the site name (SNI) never travels in one piece. It helps against DPI that matches the ClientHello as a whole. Use freedom through rules or groups only for the sites that need it.

proxies:
- name: FRAGMENT
type: freedom
fragment:
packets: tlshello
length: "1-1"
interval: "0"
sockopt:
tcp-congestion: bbr
tcp-keep-alive-idle: 300
tcp-keep-alive-interval: 60
tcp-user-timeout: 10000
custom-sockopt:
- { opt: "1", type: int, value: "1" }
rules:
- DOMAIN-SUFFIX,example.com,FRAGMENT

This is the same as the Xray outbound below:

{
"protocol": "freedom",
"settings": { "fragment": { "packets": "tlshello", "length": "1-1", "interval": "0" } },
"streamSettings": {
"sockopt": {
"customSockopt": [{ "opt": "1", "type": "int", "value": "1" }],
"tcpcongestion": "bbr",
"tcpKeepAliveIdle": 300,
"tcpKeepAliveInterval": 60,
"tcpUserTimeout": 10000
}
},
"tag": "FRAGMENT"
}

The usual DIRECT options also work: interface-name, routing-mark, ip-version, tfo, mptcp. UDP goes out as with DIRECT.

ParameterValuesWhat it does
packetstlshello, "N-M" or emptytlshello rewrites the first ClientHello into several TLS records. "1-3" cuts the first three writes of the connection into TCP segments, empty cuts every write
length"N" or "N-M", bytesSize of a piece. Required with split: length
interval"N" or "N-M", msPause between pieces. With tlshello and 0 all records go out in one write
max-split"N" or "N-M"At most this many pieces, the last one takes the rest. Empty or 0 means no limit
splitlength (default) or snisni: one random cut inside every label of the site name but the top-level one, as in sing-box; length is not needed then
waitinterval (default) or ackack: every piece goes in its own TCP segment and the next one waits until the server acknowledged it, as in sing-box
fallback-delayms, 500 by defaultThe pause for wait: ack where the acknowledgement can’t be seen
  • Ranges work as in Xray: the upper bound is not included, so "100-200" gives 100 to 199.
  • A large ClientHello (with post-quantum keys) often comes in several writes. freedom holds it until the record is complete and only then cuts it. Xray in this case sends it uncut.
  • After the fragmented part the connection works at full speed: data is copied between the sockets directly again.
  • With wait: ack the acknowledgement is read on Linux, Android, macOS and iOS. On Windows and other systems fallback-delay is used. An acknowledgement that comes back faster than 20 ms is taken for one from a middlebox, not from the server, and fallback-delay is waited too. For nearby servers lower it, otherwise every piece costs 500 ms.
ParameterValuesWhat it does
tcp-congestionbbr, cubic, …TCP congestion control. Linux and Android only
tcp-user-timeoutmsHow long sent data may stay unacknowledged before the connection is closed. Linux and Android only
tcp-keep-alive-idlesecondsIdle time before the first keep-alive probe. A negative value turns keep-alive off
tcp-keep-alive-intervalsecondsInterval between keep-alive probes
custom-sockoptlistAny socket option, as customSockopt in Xray

custom-sockopt fields:

  • opt: option number;
  • value: its value;
  • type: int (default) or str;
  • level: 6 (TCP) by default;
  • system: linux, windows, darwin or empty for every system; linux also applies on Android;
  • network: tcp, tcp4, tcp6 or empty.

Numbers can be decimal or 0x…. On Windows only int values work.

An option the system or the kernel does not support, for example bbr on many Android phones, is skipped with one warning in the log. The connection works without it.