Skip to content

Architecture

This page explains how the pieces of the Surf Shield ecosystem fit together so you can decide which SDK to embed and where the boundaries live.


High-level Diagram

                 ┌──────────────────────────────┐
                 │     Surf Shield Panel        │
                 │  (multi-tenant, billing,     │
                 │   subscriptions, telemetry)  │
                 └──────────────┬───────────────┘
                                │  HTTPS (subscription URL,
                                │         REST "webapi")
                  ┌─────────────┴─────────────┐
                  │                           │
      ┌───────────▼──────────┐    ┌───────────▼──────────┐
      │  Your Mobile App     │    │  Your Desktop App    │
      │  (uses leaf-sdk-     │    │  (uses leaf_sdk_     │
      │   android)           │    │   desktop / FFI /    │
      │                      │    │   leaf-sdk-java)     │
      └──────────┬───────────┘    └──────────┬───────────┘
                 │                           │
                 │  JNI / AIDL                │  IPC (UDS / Named Pipe)
                 │                           │
      ┌──────────▼───────────┐    ┌──────────▼───────────┐
      │   LeafVPNService     │    │     leaf-ipc         │
      │   (Android VpnService│    │  (privileged daemon, │
      │    + Rust JNI)       │    │   manages TUN)       │
      └──────────┬───────────┘    └──────────┬───────────┘
                 │                           │
                 ▼                           ▼
      ┌────────────────────────────────────────────────┐
      │                Leaf Core (Rust)                │
      │  inbounds (TUN / SOCKS / HTTP) → routing →     │
      │  outbounds (VMess, Trojan, Shadowsocks, MPTP,  │
      │  Stealth, Fragment, TLS, QUIC, VLESS, WS…)     │
      └────────────────────────────────────────────────┘
                    Remote Surf Shield Node(s)

Components

Panel

  • Web interface for operators, resellers, customers.
  • Exposes two API surfaces:
  • /webapi/application/... — machine-to-machine REST (see REST API).
  • Subscription URL per client — returned as a signed bundle consumed by the SDK.
  • Stores client UUIDs, traffic quotas, expiry dates, and online IPs.

Node Agent

  • Accepts user traffic through the Leaf outbound of choice.
  • Reports telemetry (bandwidth, CPU, RAM, connected IPs) back to the panel.
  • Operated as a trusted fleet member and not directly addressed by the SDK.

Leaf Core

The Rust proxy engine. Runs in one of two topologies:

  • In-process — Android loads libleaf.so into the system VpnService process; the VPN file descriptor is handed to Leaf via JNI (runLeaf(fd)).
  • Out-of-process — On desktop the privileged leaf-ipc binary (installed as a sidecar) runs as root/Administrator and manages the TUN device. Your user-space process talks to it over a Unix Domain Socket (UDS) on Linux/macOS or a Named Pipe on Windows.

leaf-util (shared back-end)

Used by every SDK, handles:

  • Subscription retrievalupdate_subs(tls, fragment, client_id, enable_speedtest, enable_try_all).
  • Offline bundlesimport_offline_subscription(path, passphrase, keyring).
  • Configuration rendering — subscription text is expanded into a Leaf .conf under get_current_temp_file().
  • Asset updates — downloads geoip.dat / geosite.dat keyed on your application version.
  • Preferences — persistent JSON at get_preferences_path().
  • Integrityverify_file_integrity() compares checksums before Leaf starts. The SHA-256 constants are regenerated every SDK release — bump your app version passed to update_assets every time you upgrade the SDK or the integrity check will fail.

Data Paths

leaf-util resolves data paths from either the LEAF_DATA_PATH environment variable or the platform default (dirs::config_dir() on desktop; app filesDir on Android, which is pinned via Os.setenv("LEAF_DATA_PATH", ...) inside LeafVPNService.onCreate).

Path Purpose
<data>/leaf-api/preferences/default.json LeafPreferences file
<data>/leaf-api/subscriptions/default.conf raw subscription document
<data>/leaf-api/assets/ geoip.dat, geosite.dat
<data>/leaf-api/cache/current_temp.conf rendered Leaf config used by the core
<data>/leaf-api/cache/logs/ disk logs
<data>/leaf-api/cache/wintun.dll Windows Wintun driver (desktop only)

Lifecycle (typical)

  1. Install / first run — SDK creates the data directories and a default LeafPreferences file.
  2. Assets update — call updateAssets(major, minor, patch) with your app version so the correct checksum bucket is pulled.
  3. Integrity checkverifyFileIntegrity() before starting. Fail fast if assets were tampered with.
  4. Provision user — call the panel REST API to create a client, obtain its UUID.
  5. Fetch subscriptionupdateSubscription(clientId) (or offline import) writes subscriptions/default.conf.
  6. Start core — desktop: startCore(program, daemon) (root elevation is handled by the SDK via pkexec / RunAs / UAC). Android: the VpnService is launched via Intent on user consent.
  7. Start LeafrunLeaf() / startLeaf(); the rendered .conf is loaded and the TUN device is attached.
  8. Live operations — use the local Runtime HTTP API to read stats, logs, swap outbound, and trigger failover health checks.
  9. Reload — after setPreferences or selecting a new server group, call reloadLeaf() to re-render the config without dropping the TUN.
  10. ShutdownstopLeaf() followed by shutdownCore() (desktop) or unbinding the service (Android).

Subscription Flags

Every SDK surfaces five arguments on updateSubscription. Passing -1 (Android/FFI/Java) or None (Rust) asks leaf-util to pick the best value based on the last successful connection.

Flag Effect when enabled
tls Fetch the subscription document over HTTPS (domain fronting / external domain).
fragment Fragment the TLS Client Hello to bypass SNI-based filters when fetching.
enable_speedtest Core runs periodic speed tests; the fastest endpoint is elected.
enable_try_all Core connects to every candidate outbound in parallel; first successful TCP handshake wins and the rest are dropped.

tls and fragment only affect how the subscription document itself is fetched, not the proxy traffic. enable_speedtest / enable_try_all change how outbounds are chosen at runtime.


Asset Versioning Rule

The SDKs ship a compile-time checksum table. Whenever you upgrade an SDK, bump the version you pass to updateAssets:

  • Android: BuildConfig.VERSION_NAME split into major/minor/patch.
  • Desktop Rust: app.package_info().version.{major,minor,patch}.
  • FFI / Java: whatever semver triple matches the SDK release you linked against.

If the checksum bucket is missing verify_file_integrity() will throw. Always ship a release that re-runs updateAssets on first launch.