shoes is a high-performance multi-protocol proxy server written in Rust.
Documentation: shoes-rs.github.io for installation, configuration, protocol reference, CLI usage, and complete examples. Available in English and Simplified Chinese.
With shoes installed, validate and run the loopback-only SOCKS5 example from this checkout:
shoes check examples/quickstart_socks.yaml
shoes examples/quickstart_socks.yamlSee the first-configuration guide, CLI reference, and copyable example catalog. The tracked YAML files are the source of those example pages.
- HTTP/HTTPS
- SOCKS5 (with UDP ASSOCIATE)
- Mixed (auto-detect HTTP/SOCKS5)
- VMess AEAD
- VLESS (native encryption, resumption, and fallback for unencrypted VLESS)
- Shadowsocks
- Trojan (with UDP-over-TLS client and server support)
- Snell (server: v3, v4, v5, and v6 with automatic version detection, plus QUIC proxy mode over UDP; client: v3 and v4)
- Hysteria2
- TUIC v5
- AnyTLS
- NaiveProxy
- H2MUX (supported with VMess, VLESS, Trojan, Shadowsocks, Snell)
All server protocols plus:
- SagerNet UDP over TCP (for Shadowsocks, SOCKS5, AnyTLS, NaiveProxy)
- ShadowTLS v3
- TLS
- WebSocket (Shadowsocks SIP003)
- HTTPUpgrade (HTTP/1.1 Upgrade transport compatible with sing-box)
- gRPC (Gun transport compatible with sing-box)
- XHTTP / SplitHTTP (packet-up, stream-up, stream-one over HTTP/1.1, HTTP/2, HTTP/3)
- XTLS Reality
- Hybrid-first X25519MLKEM768 key exchange and optional ML-DSA-65 server authentication
- XTLS Vision (for VLESS)
- Sudoku TCP mask (Xray FinalMask-compatible, composable client/server wrapper; configuration)
- WireGuard - Userspace TCP/UDP server and client protocols using GotaTun, without root, an OS TUN, or host route changes (Unix only). See WireGuard configuration.
- TUN device support - Layer 3 VPN for transparent proxying
- Supported platforms: Linux, Android, iOS
- VMess:
aes-128-gcm,chacha20-poly1305,none - Shadowsocks:
aes-128-gcm,aes-256-gcm,chacha20-ietf-poly1305,2022-blake3-aes-128-gcm,2022-blake3-aes-256-gcm,2022-blake3-chacha20-ietf-poly1305 - Snell: v3 supports
aes-128-gcm,aes-256-gcm,chacha20-ietf-poly1305; v4, v5, and v6 areaes-128-gcmonly
For Snell client compatibility:
- Snell v4, v5, and v6 (server): Connection reuse (Connect v2), UDP-over-TCP, multi-user authentication (userkey or per-user PSK), replay protection, with tunnel and error replies matching sing-snell / snell-server. Auto-detection only considers v4+ when the cipher is
aes-128-gcm; other ciphers are served as v3. - Snell QUIC proxy mode: Enabled automatically on every Snell server with
udp_enabled(the default), as a UDP listener on the same port for Surge clients withblock-quic=off. It always usesaes-128-gcm. Setudp_enabled: falseto turn it off. - shoes Snell client: Speaks v4 with
aes-128-gcmand v3 with other ciphers; the version cannot be chosen and connections are not pooled. A v4 client cannot reach shoes servers older than v0.3.8 or real Snell v3 servers, so use another cipher there. - Clash.Meta/Mihomo legacy clients: Use
chacha20-ietf-poly1305with v1 oraes-128-gcmwith v3. Run shoes v0.3.3 or newer for the corrected 16-byte Snell salts. These clients do not offer an AES-256 option. Do not use v2: shoes does not implement Snell v2 connection reuse, and older Clash.Meta clients fail requests on reused v2 connections. - shoes-to-shoes: Can also use
aes-256-gcm(v3); both ends must use the same cipher and run v0.3.3 or newer for AES-256 or ChaCha20.
- Multi-transport: TCP or QUIC for all protocols
- TLS with SNI routing: Select the outer TLS handler by Server Name Indication
- Opt-in TCP hostname sniffing: Match inner TLS SNI or cleartext HTTP/1 Host without changing the connection destination (TLS example, HTTP example, semantics)
- Upstream proxy chaining: Multi-hop chains with load balancing
- Rule-based routing: Route by IP/CIDR or hostname masks
- Named PEM certificates: Define once, reference everywhere
- Live certificate sources: Shared TLS/QUIC file rotation and built-in ACME HTTP-01/TLS-ALPN-01 (configuration)
- TLS fingerprint authentication: Certificate pinning for TLS/QUIC
- Hot reloading: Apply config changes without restart
- Unix socket support: Bind to Unix domain sockets
- Upstream groups and optional Clash controller: Round-robin, manual selectors, and automatic URL tests (without an API listener); connection monitoring/cancellation and optional selector persistence (configuration, example, URL-test example)
For advanced access control (IP allowlist/blocklists), see tobaru.
Building shoes requires Rust 1.99.1 or newer. Unix WireGuard builds include MPL-2.0-covered GotaTun code; binary and library distributors must retain the dependency notices and corresponding source information. See dependency maintenance for protected forks, update constraints and required integration checks.
Precompiled binaries are available on GitHub Releases
for Linux x86_64/aarch64 and macOS Intel/Apple Silicon. Since v0.3.7, releases also include
Linux ARMv7 hard-float (armv7-unknown-linux-gnueabihf and
armv7-unknown-linux-musleabihf), for CPUs that support 32-bit ARMv7 hard-float
execution, not ARMv6 devices. GNU builds require a matching glibc userspace;
musl builds are statically linked. ARMv7 release smoke checks run under QEMU,
not on physical boards.
Since v0.3.7, releases also include mips-unknown-linux-musl: a static,
big-endian MIPS32r2, o32, soft-float CLI executable. It is not for little-endian
MIPS, MIPS64, pre-R2 or R6 CPUs, and requires 4 KiB kernel pages. The executable
is approximately 32 MiB, so check available storage. Validation uses QEMU user
mode with a 24Kc CPU, not physical
routers or an OpenWrt kernel; MIPS TUN and independent-peer WireGuard operation
have not been validated. No MIPS shared library or container image is provided.
The Tier 3 build uses Rust 1.99.1 with matching rust-src, RUSTC_BOOTSTRAP=1
and -Z build-std, plus the pinned OpenWrt 24.10.4 GCC 13.3.0/musl 1.2.5
toolchain and GCC unwinder adapter in the release workflow.
Since v0.3.7, releases provide a separate SHA256SUMS asset
and GitHub build-provenance attestations. From the directory containing the
downloaded archives and checksum file:
sha256sum --check --ignore-missing SHA256SUMS
gh attestation verify shoes-armv7-unknown-linux-musleabihf.tar.gz --repo cfal/shoesOn macOS, use shasum -a 256 --check SHA256SUMS (download all listed archives or
check the selected archive's entry). Attestations verify build provenance, not
Apple Developer ID signing or notarization; macOS signing is not yet provided.
For the previously published release:
cargo install shoesGitHub release builds pin h3 and h3-quinn to the
cfal/h3 fork,
including the empty-DATA fix.
Empty HTTP/3 DATA frames must not be treated as body EOF; XHTTP uses them for
idle cancellation probes. The fork also backports
bounded frame buffering: oversized
buffered frames are rejected from their declared lengths, and unknown-frame
payloads are skipped incrementally. The fork defaults the local decoded-header
limit to 64 KiB; callers can set their own limit. DATA payloads remain streamed.
The workspace patches cover direct and transitive
dependencies, including DNS over HTTP/3. The experimental zero-copy change in
h3 #368 is not included.
The blake3 patch pins our binary-context fork,
rebased onto upstream buffer-alignment and AVX-512 soundness fixes. VLESS Encryption
requires derive_key_bytes for contexts that are not valid UTF-8. Retain the patch
until a registry release provides equivalent binary-context derivation; validate
updates against the fork's tests and shoes' Go-derived VLESS vectors.
The nix patch pins an upstream commit
containing nix #2821 for iOS socket
compatibility. Retain it until adopting a registry release containing that fix.
The smoltcp patch pins our rebased TCP recovery fork
to preserve TCP ACK sequencing during partial retransmission in TUN and WireGuard.
It tracks unreleased upstream main after 0.14.0, including keepalive cancellation;
see fork maintenance notes for provenance and invariants.
Retain it until adopting a registry release with equivalent loss-recovery fixes.
When updating the pin, run the fork's socket::tcp::test:: regressions with Reno
and CUBIC enabled, plus shoes' rootless TUN and WireGuard suites.
GitHub releases and checkout builds retain these patches. Crates.io publishing remains disabled because Cargo removes workspace patches from published packages. Remove the H3 patches only after adopting fixed registry versions that pass the H3 empty-DATA and idle-stream regressions. Remove the publishing guard only when all workspace patches have been replaced with registry dependencies. To install this revision from its checkout:
cargo install --path . --lockedAfter updating the H3 pins, run the empty-DATA and idle-stream regressions:
CARGO_BUILD_JOBS=1 cargo test --locked --lib --features ffi h3_ -- --test-threads=1Android and iOS embedding remains supported through the Rust JNI/C interfaces.
This repository does not ship Kotlin wrappers, AARs, or XCFrameworks. Applications
own their platform glue and packaging, including Android VpnService or iOS
Network Extension setup.
For Android, install cargo-ndk 4.1.2, set ANDROID_NDK_HOME to an installed Android
NDK, and build the native libraries:
rustup target add aarch64-linux-android armv7-linux-androideabi
CARGO_BUILD_JOBS=1 cargo ndk -t arm64-v8a -t armeabi-v7a -P 21 -- build --release --lib --lockedThe resulting target/<target>/release/libshoes.so files expose the JNI methods
in src/ffi/android.rs. The app supplies the matching
com.shoesproxy.ShoesNative declarations and socket-protection callback.
The callback object must expose protect(fd: Int): Boolean, delegating to
VpnService.protect; a plain Kotlin lambda does not satisfy this JNI contract.
For iOS, build on macOS with Xcode installed. These commands match CI's deployment target; set it to match the consuming app when integrating:
rustup target add aarch64-apple-ios aarch64-apple-ios-sim
export IPHONEOS_DEPLOYMENT_TARGET=16.0
CARGO_BUILD_JOBS=1 cargo build --release --lib --locked --target aarch64-apple-ios
CARGO_BUILD_JOBS=1 cargo build --release --lib --locked --target aarch64-apple-ios-simLink target/<target>/release/libshoes.a using include/shoes.h through the app's
bridging header or module map. After changing the C interface, use cbindgen 0.29.4
to regenerate the header with
cbindgen --config cbindgen.toml --output include/shoes.h. CI verifies the header
matches the Rust interface.
On both platforms, the app supplies device_fd in the TUN configuration and
retains ownership of that descriptor until the native service has stopped.
Distribute the dependency notices and source information
with the app. CI cross-compiles the native libraries without packaging or
publishing a mobile SDK.
GHCR images are published at ghcr.io/cfal/shoes for linux/amd64 and
linux/arm64:
masterfollows the current branch head after both architectures pass the container smoke test.sha-<full commit SHA>is published before smoke verification and pins a development commit image; retries reuse it.sha-<full commit SHA>-releaseseparately pins the official release build, so publishing a release never reuses a development binary's version metadata.- A published GitHub release adds its exact tag, such as
v0.3.4. latestis set only by apublishedrelease event when that stable release is GitHub's latest at promotion time.
Only master, release tags, and latest wait for successful smoke tests.
Converting a prerelease to stable or changing GitHub's latest release later
does not update the image tags.
Draft releases and tag pushes alone do not publish release image tags. Release
tags must match v followed by the package version. master, latest,
buildcache*, and sha-* are reserved. Publishing uses the repository's GITHUB_TOKEN with package
write permission. After the first publication, make the GHCR package public
in its package settings to allow anonymous pulls. Forks publish under their
own repository name.
docker pull ghcr.io/cfal/shoes:masterBuild an optimized local binary and its runtime image from the checkout:
docker build -t shoes:local \
--build-arg SHOES_BUILD_REVISION="$(git describe --always --exclude='*' --abbrev=12 --dirty=.dirty)" .The build argument preserves the checkout revision without copying .git into
the image. Omitting it produces a +unknown version. See
build metadata and
CLI build information.
Verify the image on Linux with Python 3.11 or newer:
python3 scripts/smoke-docker.py shoes:localThe image runs as UID/GID 65532:65532 and reads /etc/shoes/config.yaml by
default. Create a config directory containing config.yaml, for example:
- address: "0.0.0.0:1080"
protocol:
type: socksMount the whole directory so configuration reloads also see files replaced by
an editor. Configuration, certificates, and keys must be readable by the
container user; relative paths resolve from /etc/shoes.
docker run --rm --name shoes \
--publish 127.0.0.1:1080:1080 \
--mount type=bind,src="$(pwd)/config",dst=/etc/shoes,readonly \
ghcr.io/cfal/shoes:masterUse shoes:local instead to run the checkout build.
Files shoes writes cannot live in the read-only /etc/shoes mount. Give
cache_file_path, fake DNS state_file, Unix socket listeners, and --log-file
targets absolute paths on a separate writable mount owned by the container
user. For example, create a private state directory:
mkdir -p state
sudo chown 65532:65532 state
sudo chmod 700 stateSet cache_file_path: /var/lib/shoes/cache.db in the configuration and add this
option to docker run:
--mount type=bind,src="$(pwd)/state",dst=/var/lib/shoesPublish UDP ports separately for UDP listeners. Bind listeners to 0.0.0.0
inside the container; host port bindings control which clients can reach them.
Configure authentication before exposing a proxy beyond the host.
Arguments after the image name replace the default configuration argument:
docker run --rm shoes:local --version
docker run --rm \
--mount type=bind,src="$(pwd)/config",dst=/etc/shoes,readonly \
shoes:local check /etc/shoes/config.yamlThe runtime includes CA certificates and the license/source notices under
/usr/share/doc/shoes. No configuration or credentials are baked into it.
Ordinary proxy and userspace WireGuard listeners need no extra capabilities.
Linux TUN configurations require --user 0:0, --cap-add NET_ADMIN, and
--device /dev/net/tun; their network changes remain in the container's network
namespace unless host networking is explicitly selected.
Linux kernel peer tests require kernel WireGuard support, network/PID namespaces,
passwordless sudo, ip, wg, unshare, and timeout. They run both shoes roles
inside isolated namespaces without changing host routes. Coverage includes
inner IPv4/IPv6 TCP and UDP, TCP half-close, PSK/no-PSK, MTUs 1280/1408, and VLESS
chaining in both directions. Kernel handshake and byte counters verify that
payloads actually cross WireGuard. Outer-chain cases require SOCKS -> VLESS before
WireGuard, optionally with VLESS after it, and use an unreachable destination that
only the outer proxy can translate, including IPv6 outer destinations. The 64-case
matrix is supplemented by carrier outage/recovery, a real two-minute rekey with
the same TCP flow, concurrent kernel peers, cross-peer source spoofing rejection,
and wrong-PSK rejection/recovery. Missing prerequisites fail rather than skip.
Set SHOES_TEST_WG_BIN to an absolute path to override wg.
CARGO_BUILD_JOBS=1 cargo test --locked --features privileged-tests --test privileged wireguard_kernel:: -- --test-threads=1Linux builds configure conservative memory and socket defaults at startup without changing host sysctl or sysfs settings:
- Process THP disabled: A startup constructor calls
prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0)(THP_enabled: 0in/proc/<pid>/status) so hosts withtransparent_hugepage/enabled=alwaysdo not back sparse allocator extents or thread stacks with 2 MiB huge pages. - Small-VPS jemalloc defaults: Exports
_rjem_malloc_confasbackground_thread:true,narenas:2,dirty_decay_ms:1000,thp:never. Override at runtime via_RJEM_MALLOC_CONF(MALLOC_CONFis ignored), for example_RJEM_MALLOC_CONF=narenas:0on high-core servers. - TCP user timeout: Inbound and outbound TCP sockets set
TCP_USER_TIMEOUTto 120 seconds on Linux and Android so half-open sockets with unacknowledged data close promptly.
For low-memory deployments, see the small-VPS Snell example and Linux memory and connection tuning.