Skip to content
homerouterPublic
forked from cfal/shoes

About

A multi-protocol proxy server written in Rust (HTTP, SOCKS5, Vmess, Vless, Shadowsocks, Trojan, Snell, Hysteria2, TUIC v5, AnyTLS, Naiveproxy, XTLS, XHTTP)

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Repository files navigation

shoes

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.

Quick start

With shoes installed, validate and run the loopback-only SOCKS5 example from this checkout:

shoes check examples/quickstart_socks.yaml
shoes examples/quickstart_socks.yaml

See the first-configuration guide, CLI reference, and copyable example catalog. The tracked YAML files are the source of those example pages.

Supported Protocols

Proxy Protocols

  • 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)

Transport Protocols

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)

TUN/VPN Mode

  • 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

Supported Ciphers

  • 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 are aes-128-gcm only

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 with block-quic=off. It always uses aes-128-gcm. Set udp_enabled: false to turn it off.
  • shoes Snell client: Speaks v4 with aes-128-gcm and 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-poly1305 with v1 or aes-128-gcm with 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.

Features

  • 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.

Installation

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/shoes

On 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 shoes

GitHub 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 . --locked

After 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=1

Mobile Embedding

Android 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 --locked

The 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-sim

Link 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.

Docker

GHCR images are published at ghcr.io/cfal/shoes for linux/amd64 and linux/arm64:

  • master follows 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>-release separately 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.
  • latest is set only by a published release 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:master

Build 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:local

The 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: socks

Mount 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:master

Use 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 state

Set 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/shoes

Publish 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.yaml

The 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.

WireGuard Kernel Interoperability

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=1

Linux Runtime Defaults

Linux 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: 0 in /proc/<pid>/status) so hosts with transparent_hugepage/enabled=always do not back sparse allocator extents or thread stacks with 2 MiB huge pages.
  • Small-VPS jemalloc defaults: Exports _rjem_malloc_conf as background_thread:true,narenas:2,dirty_decay_ms:1000,thp:never. Override at runtime via _RJEM_MALLOC_CONF (MALLOC_CONF is ignored), for example _RJEM_MALLOC_CONF=narenas:0 on high-core servers.
  • TCP user timeout: Inbound and outbound TCP sockets set TCP_USER_TIMEOUT to 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.

Similar Projects

About

A multi-protocol proxy server written in Rust (HTTP, SOCKS5, Vmess, Vless, Shadowsocks, Trojan, Snell, Hysteria2, TUIC v5, AnyTLS, Naiveproxy, XTLS, XHTTP)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages