22 KiB
BEP-0012 - Mihomo-Compatible Shadowsocks Runtime
Status: Draft
Proposal: BEP-0012
Authors: Codex
Coordinator: Jett Chen
Reviewers: Pending
Constitution Sections: 2, 4, 5
Implementation PRs: Pending
Decision Date: Pending
Summary
Burrow's proxy subscription runtime should move from a narrow hand-written
Shadowsocks AEAD implementation toward Mihomo-compatible Shadowsocks behavior
for imported nodes. Mihomo remains the reference for accepted ciphers, URI
conversion, plugins, UDP-over-TCP, and packet tunnel outbound semantics, while
Burrow keeps the daemon-owned packet runtime and Apple gRPC boundary described
by BEP-0005 and BEP-0010.
This proposal covers the compatibility path for Shadowsocks outbounds. It starts by delegating protocol crypto and stream/datagram framing to a maintained Rust Shadowsocks implementation, adds Burrow-owned adapters for Mihomo cipher gaps where Rust crates are available, and implements Mihomo-compatible UDP-over-TCP. Plugin transports remain staged behind explicit lifecycle and security controls.
Motivation
- Subscriptions that work in Mihomo commonly use more than
aes-128-gcm,aes-256-gcm, orchacha20-ietf-poly1305. - Mihomo supports legacy stream ciphers, AEAD extra ciphers, AEAD 2022 methods, SIP003-style plugins, ShadowTLS/restls/kcptun transports, and UDP-over-TCP.
- Burrow's previous hand-written Shadowsocks runtime made unsupported ciphers appear like permanent product choices rather than implementation gaps.
- Protocol crypto and framing are security-sensitive. Burrow should use a maintained Rust crate where it can do so without shelling out to Mihomo or weakening the daemon packet-runtime boundary.
Detailed Design
- Use the Rust
shadowsockscrate as Burrow's default Shadowsocks protocol adapter for supported cipher families, TCP stream framing, and UDP datagram framing. - Add Burrow-owned AEAD adapters backed by Rust crypto crates for Mihomo methods
missing from the default adapter when their wire behavior is straightforward.
The first such methods are
aes-192-gcm,aes-192-ccm,chacha8-ietf-poly1305,xchacha8-ietf-poly1305,rabbit128-poly1305,aegis-128l,aegis-256,aez-384,deoxys-ii-256-128,ascon128,ascon128a,lea-*-gcm,2022-blake3-aes-128-ccm, and2022-blake3-aes-256-ccm. - Add Burrow-owned legacy stream adapters for Mihomo's
chacha20andxchacha20methods. They use the same Shadowsocks v1 IV-plus-stream shape as Mihomo'ssing-shadowsocks2implementation, backed by Rustchacha20primitives. - Keep Burrow-owned socket creation, route exclusion, and Apple physical interface binding behavior. The Shadowsocks adapter wraps already-connected Burrow sockets where needed instead of taking over tunnel ownership.
- Runtime-supported ciphers include the compiled Rust crate feature set:
legacy stream methods, AEAD methods, AEAD extra methods, and AEAD 2022
methods, plus an explicit Mihomo-compatible
nonetransport, custom ChaCha20/XChaCha20 stream, AES-192 GCM/CCM, ChaCha8-Poly1305, Rabbit128-Poly1305, AEGIS, AEZ-384, Deoxys-II-256-128, Ascon128, Ascon128a, LEA-GCM, and AEAD 2022 AES-CCM adapters.nonestays Burrow-owned because the upstream Rust crate accepts the method name during validation but cannot build a client runtime for it. AEAD 2022 password handling follows Mihomo's base64 key parsing rule: every decoded password segment must be exactly the method key length, with no hashing or truncation fallback for oversized material. Burrow pre-validates the delegated GCM/ChaCha 2022 methods so the upstream Rust adapter cannot accept unpadded key material that Mihomo'sbase64.StdEncodingwould reject. Burrow keeps legacy aliases such asaead_aes_128_gcmandchacha20-poly1305for previously imported payloads. Burrow also preserves Mihomo's top-level Shadowsockssmuxoption during import. The packet runtime supports TCP and UDPprotocol: h2mux,protocol: smux, andprotocol: yamuxpaths for delegated standard ciphers,none, Burrow's custom ciphers, and AEAD 2022 CCM by dialing Mihomo'ssp.mux.sing-box.arpa:444control destination through Shadowsocks, writing the sing-mux protocol preface, and opening mux streams with Mihomo's per-stream SOCKS destination request. Burrow also supports Mihomo's top-level sing-mux padding shape: the version 1 session preface carries a padding flag and 256-767 bytes of skipped padding, then the first 16 mux reads and writes are length-prefixed padded frames before the stream falls back to raw mux bytes. UDP-over-sing-mux uses Mihomo's packet stream shape: UDP stream flags, one SOCKS destination, one status byte, and length-prefixed datagrams. Brutal mode performs Mihomo's_BrutalBwExchangestream handshake and negotiates the same send-rate cap; OS-level TCP Brutal congestion control remains a runtime best-effort because it is Linux-kernel-module dependent in Mihomo and unavailable through every Burrow plugin wrapper. The remaining Mihomo compatibility gaps for Shadowsocks are advanced plugin transports and live interop coverage for additional external permutations, not base Shadowsocks cipher, UDP-over-TCP framing, or top-level sing-mux negotiation. - Honor the Shadowsocks
udpflag at runtime. Clash/Mihomo YAML imports default omittedudpto false like Mihomo'sShadowSocksOptionzero value, whiless://URI conversion keeps Mihomo's converter behavior of settingudpto true. UDP-disabled nodes must reject UDP sessions instead of silently attempting native UDP relay. - Honor
udp-over-tcpfor Shadowsocks nodes by opening a Shadowsocks TCP stream to Mihomo's UOT magic destination. Burrow supports both Mihomo's legacy version 1 destination,sp.udp-over-tcp.arpa, and version 2 destination,sp.v2.udp-over-tcp.arpa; absent or zero versions normalize to legacy v1 to match Mihomo config defaults. Runtime UDP sessions are allowed whenudp-over-tcpis active even if native UDP is disabled, because packets are carried over the selected TCP plugin path instead of a native Shadowsocks UDP relay. - Support Mihomo's
obfsplugin for HTTP and TLS simple-obfs by wrapping the already-connected TCP socket before the Shadowsocks stream handshake. Native UDP remains direct Shadowsocks UDP, whileudp-over-tcpuses the same wrapped TCP path. The plugin mode must be explicit, matching Mihomo'sobfsoption decoder: simple-obfs accepts onlyhttpandtls. Burrow normalizes SIP002 URI plugin names that containobfsinto Mihomo's YAML/runtime plugin nameobfsduring import only when the plugin string has semicolon-delimited SIP002 options; baress://plugin names and non-Mihomo-converted URI plugin names are ignored like Mihomo's converter. Daemon runtime and Apple usability checks intentionally require Mihomo's exact, case-sensitive plugin names. YAMLplugin: obfs-local,plugin: Obfs,plugin: shadowtls, and other unrecognized plugin names therefore fall through to plain Shadowsocks like Mihomo's outbound constructor instead of being treated as supported plugin transports. - Support Mihomo's
v2ray-pluginwebsocket mode, including the lightweight v2ray-plugin mux preface enabled by Mihomo's defaultmux: true, optional TLS, custom path, host, andv2ray-http-upgraderaw-upgrade mode. Burrow also supports Mihomo'sv2ray-http-upgrade-fast-openbehavior by returning the upgraded raw stream after sending the HTTP request and validating the 101 response on first read. Importedss://v2ray-plugin strings preserve Mihomo's SIP002 aliases such asobfs=websocket,obfs-host, and baretlsflags, while also materializing Mihomo's convertedmode,host, and booleantlsoption shape. For websocket paths with Mihomo'sed=query option, Burrow removesedfrom the request path and moves the first payload bytes into theSec-WebSocket-Protocolheader with unpadded URL-safe base64 encoding. When TLS is enabled, Burrow accepts Mihomo'scertificateandprivate-keyplugin options as a PEM client certificate/key pair for mTLS, and enforces Mihomo'sfingerprintcertificate pin option on both Rustls and Apple native TLS paths. Burrow also supports Mihomo'sech-optsfor websocket TLS by passing inline ECH config lists to Rustls, or by resolving HTTPS records through the physical DNS resolver whenech-opts.enableis set without an inline config. - Support Mihomo's
gost-pluginwebsocket mode, including the Go-smux client stream enabled by Mihomo's defaultmux: trueandmux=falseraw websocket mode.v2ray-pluginandgost-pluginrequire explicitmode: websocketor the importedobfs=websocketalias, because Mihomo rejects those plugin nodes when the mode field is omitted. TLS-enabled gost websocket nodes use the same Mihomo-compatible client certificate/key and certificate pin handling, including websocket ECH support. - Support Mihomo's
shadow-tlsplugin for versions 1, 2, and 3 by performing the cover TLS handshake in-process with Rustls, then exposing the post-handshake stream to the Shadowsocks adapter. Version 2 wraps application data in ShadowTLS application-data records and prepends the first client payload with the handshake HMAC, matching Mihomo'ssing-shadowtlsclient behavior. Version 3 uses a ShadowTLS-capable Rustls fork for the ClientHello session ID HMAC, then verifies and emits the four-byte HMAC application-data frames used by Mihomo'ssing-shadowtlsv3 client. Version 1 follows Mihomo's option shape and does not require a plugin password. Burrow also parses and enforces Mihomo's ShadowTLSfingerprintcertificate pin option and supports Mihomo'scertificateandprivate-keymTLS client certificate options.client-fingerprintis preserved from subscriptions. ShadowTLS v1 ignoresclient-fingerprintlike Mihomo because it always uses a normal TLS 1.2 handshake. For ShadowTLS v2/v3, unknown fingerprint names fall back to the normal TLS ClientHello, while names that Mihomo maps to uTLS profiles are rejected by the default runtime and Apple usability filter until Burrow has uTLS-style ClientHello impersonation. Burrow carries an optionalboring-browser-fingerprintsfeature that wires ShadowTLS v2 active browser fingerprints through Rama's BoringSSL-backed embedded browser TLS profiles. ShadowTLS ALPN defaults toh2,http/1.1only when the option is absent; explicitly emptyalpnremains empty like Mihomo's decoded option struct. The same feature also provides the ShadowTLS v3 browser-profile path by signing the first ClientHello record's 32-byte session ID before it reaches the wire, matching Mihomo's uTLSSessionIDGeneratorshape. The BoringSSL browser-profile paths also load Mihomo-compatiblecertificateandprivate-keyclient-auth material, so ShadowTLS v2/v3 browser fingerprints and mTLS can be combined the same way Mihomo passes those options intosing-shadowtls. For ShadowTLSclient-fingerprint: random, Burrow follows Mihomo's initial random selection shape by choosing one process-wide browser profile with the same chrome/safari/ios/firefox weights instead of re-rolling each connection. The ShadowTLS v2 browser-profile path also removes theX25519MLKEM768supported group before building the BoringSSL connector, matching Mihomo's v2-only workaround for servers that fail with that hybrid key-share. These paths are intentionally not default yet because they add a CMake/BoringSSL toolchain requirement, still need CI coverage, and still need live interop coverage. The optional browser-profile path covers Mihomo's commonchrome,firefox,safari,ios,android,edge,random, andsafari16names where Rama has matching embedded profiles; older fixed aliases such aschrome120andfirefox120remain gated until Burrow has exact matching ClientHello profiles instead of approximate fallbacks. - Support Mihomo's
kcptunplugin in-process with Rust KCP, snappy, and smux crates. Burrow parses Mihomo's kcptun option names and defaults, forces UDP-over-TCP for kcptun nodes like Mihomo, and opens Shadowsocks TCP streams over KCP plus optional snappy plus smux. Burrow maintains a Mihomo-style round-robin smux session pool forconnand rotates expired sessions usingautoexpireplus delayedscavengettlcleanup. Burrow mirrors Mihomo's smux keepalive timeout shape: the default timeout remains 30 seconds unless the keepalive interval is at least that value, in which case timeout becomes three times the interval. Burrow also applies Mihomo-style best-effortdscpandsockbufsettings to the underlying UDP socket. Forratelimit, Burrow honors Mihomo's bytes-per-second value with the same64 * 1500byte burst shape at the KCP stream boundary; this is the closest in-process equivalent available through the selected Rust KCP crate, which does not expose kcp-go's exact queued UDP packet transmit hook. - Plugin support landed in stages and must stay covered:
obfs, matching Mihomo URI conversion and option shapes formode/obfs,host/obfs-host, and the defaultbing.comhost.v2ray-pluginwebsocket and its lightweight mux.gost-pluginwebsocket and smux.- TLS client certificate/key support for websocket plugins and
shadow-tlsv1/v2. kcptunwith Rust KCP, snappy, and smux transport code.- ShadowTLS v3 with a Rustls fork that exposes the session ID generator required by the protocol.
- Websocket
ech-optson Rustls-backed TLS paths. v2ray-http-upgrade-fast-openfor v2ray-plugin raw upgrade mode.- Websocket early data via the
ed=path query option. restlsafter its TLS ClientHello, authentication, and traffic-shaping behavior are mapped to audited Rust code. Burrow now parses Mihomo'srestlsoption shape, includinghost,password,version-hint,restls-script, top-levelclient-fingerprintpreservation, Mihomo's case-sensitive Restls client ID lookup with Chrome fallback, Mihomo's TLS 1.3 session ticket disablement rule, and the BLAKE3-derived traffic secret. The default runtime still rejects non-noneuTLS fingerprints, while the optionalboring-browser-fingerprintsfeature can run TLS 1.3 Restls over a BoringSSL-backed browser profile and signs the first ClientHello record's session ID using Mihomo's Restls TLS 1.3 auth material. It also ports and tests Mihomo's BLAKE3-authenticated ClientHello session-ID material for TLS 1.2 and TLS 1.3, extraction of TLS 1.3 key-share and PSK identity labels from the serialized ClientHello shape exposed by Burrow's Rustls session-ID hook, the server-auth record unmasking offsets, a handshake stream shim that captures ServerHello random, unmasks the first server-auth record, and captures the encrypted ClientFinished record for later application-data authentication, the application-data record header, masked length/command bytes, one-shot ClientFinished binding on the first client application record, script-driven padding target, TLS 1.2 GCM explicit counter shape, Mihomo's signed one-byte response-interrupt count behavior, and the post-auth stream state machine for buffering blocked writes, resuming them after server data, and emitting response-interrupt records. The Restls post-handshake state machine is also covered by an async stream wrapper that turns user writes into authenticated TLS application-data records, decodes server records back into plaintext reads, and flushes resumed uploads plus response-interrupt records before surfacing server plaintext. Burrow now wires TLS 1.3 Restls nodes into the Shadowsocks outbound path through the Rustls session-ID hook, the server-auth handshake shim, and the async Restls stream wrapper. Burrow vendors the ShadowTLS Rustls fork and its Tokio adapter so TLS 1.2 Restls can pre-generate the ECDHE keys used in Mihomo's session-ID HMAC material and then reuse the matching private key when emitting ClientKeyExchange. TLS 1.2 Restls nodes are now accepted by runtime support checks, surfaced as selectable Apple subscription nodes, and use the TLS 1.2 GCM Restls application-record codec. Restls still needs end-to-end interop coverage before it can be claimed as full Mihomo parity.
Scripts/burrow-proxy-selftestexercises daemon packet streaming against a controlled local Trojan/Shadowsocks harness and asserts observed TCP, native UDP, legacy UDP-over-TCP, version 2 UDP-over-TCP, simple-obfs HTTP/TLS, v2ray HTTP-upgrade/websocket/mux/early-data, and gost raw websocket shapes.Scripts/burrow-shadowsocks-singmux-interopdrives Burrow's daemon against a temporary Go peer built from Mihomo'sgithub.com/metacubex/sing-muxmodule and asserts top-level Shadowsockssmux,yamux, andh2muxTCP and UDP behavior through the daemon with both unpadded and padded sing-mux sessions, including thesp.mux.sing-box.arpa:444control destination, per-stream TCP destination metadata, UDP packet destination metadata, and UDP packet echo delivery. The interop matrix intentionally leaves the underlying Shadowsocksudpflag false so Burrow matches Mihomo's behavior: top-level sing-mux carries UDP unlessonly-tcpis set. Remaining compatibility evidence still needs live interop against full Mihomo/sing-box deployments for plugin combinations that the local harness does not cover, especially Restls post-handshake behavior and kernel-dependent TCP Brutal socket behavior.- Keep UDP-over-TCP packet framing compatible with Mihomo's
udp-over-tcp-versionhandling, including the version 2 request prefix and per-packet address framing. - UI runtime-support checks must not hard-code stale cipher lists that disagree with the daemon. When local decoding is unavoidable, the local list must be updated with the daemon-supported cipher families or replaced with daemon preview/list-node data.
Security and Operational Considerations
- Shadowsocks passwords and subscription URLs remain secrets. Error messages and logs must not include decoded credentials or bearer tokens.
- Plugin support must stay within Burrow's daemon-owned socket lifecycle unless a later BEP explicitly introduces subprocess management. Shelling out to SIP003 plugins would add lifecycle and supply-chain risk and must include teardown and selected-network scoping if introduced.
noneand legacy stream methods exist for compatibility, not as a security recommendation. UI warnings may be added later for weak ciphers without rejecting Mihomo-compatible imports.- AEAD 2022 passwords may be encoded keys. Validation must report malformed keys without logging the raw password.
- Rollback is a code rollback plus re-importing affected subscriptions if stored payload shape changes.
Contributor Playbook
- Compare behavior against
/Users/jettchen/dev/vpn-ref/mihomoon theAlphabranch before changing Shadowsocks protocol semantics. - Keep Apple UI subscription operations behind daemon gRPC.
- Prefer maintained Rust crates for Shadowsocks crypto/framing rather than hand-rolled protocol code.
- Preserve Burrow socket binding and proxy-server route exclusion behavior.
- Add tests for each parity expansion:
- cipher acceptance and legacy aliases
udpgating- plugin parsing and runtime gating
- UDP-over-TCP version handling and packet framing
- AEAD 2022 TCP/UDP packet framing for custom adapters
- end-to-end local Shadowsocks TCP, native UDP, UDP-over-TCP, and plugin selftests; the proxy selftest must switch the daemon from a Trojan node to Shadowsocks nodes, prove both selected outbounds can carry a TCP probe, prove the selected Shadowsocks outbounds can carry native UDP, legacy UDP-over-TCP, and version 2 UDP-over-TCP echo probes, and prove the simple-obfs HTTP/TLS plugins, v2ray-plugin websocket/default-mux/ HTTP-upgrade/early-data modes, and gost-plugin raw websocket mode wrap selected Shadowsocks TCP probes
- top-level sing-mux Shadowsocks TCP and UDP interop against Mihomo's Go
sing-muxmodule
- Run:
uv run python Scripts/check-bep-metadata.py
cargo fmt -p burrow
cargo test -p burrow proxy_subscription::tests::
cargo test -p burrow proxy_runtime::tests::
cargo check -p burrow
Scripts/burrow-proxy-selftest
Scripts/burrow-shadowsocks-singmux-interop
Alternatives Considered
- Keep the hand-written AEAD-only implementation. Rejected because it locks Burrow into a narrow subset and makes Mihomo parity much slower.
- Shell out to Mihomo. Rejected for the same reasons recorded in
BEP-0010: Burrow would inherit a separate runtime, control plane, and lifecycle surface. - Implement plugins before cipher parity. Rejected because cipher parity is a lower-risk foundation and reduces custom crypto code first.
Impact on Other Work
- Refines the Shadowsocks runtime portion of
BEP-0010. - Complements
BEP-0011, which covers Trojan-specific Mihomo compatibility. - Future plugin work may require a dedicated subprocess lifecycle BEP if the runtime cannot stay within the existing daemon teardown model.
Decision
Pending review.
References
evolution/proposals/BEP-0005-daemon-ipc-and-apple-boundary.mdevolution/proposals/BEP-0010-proxy-subscriptions-as-packet-tunnel-networks.mdevolution/proposals/BEP-0011-mihomo-compatible-trojan-runtime.md/Users/jettchen/dev/vpn-ref/mihomo/adapter/outbound/shadowsocks.go/Users/jettchen/dev/vpn-ref/mihomo/common/convert/converter.go- Rust
shadowsockscrate: https://crates.io/crates/shadowsocks