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.sointo the systemVpnServiceprocess; the VPN file descriptor is handed to Leaf via JNI (runLeaf(fd)). - Out-of-process — On desktop the privileged
leaf-ipcbinary (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 retrieval —
update_subs(tls, fragment, client_id, enable_speedtest, enable_try_all). - Offline bundles —
import_offline_subscription(path, passphrase, keyring). - Configuration rendering — subscription text is expanded into a Leaf
.confunderget_current_temp_file(). - Asset updates — downloads
geoip.dat/geosite.datkeyed on your application version. - Preferences — persistent JSON at
get_preferences_path(). - Integrity —
verify_file_integrity()compares checksums before Leaf starts. The SHA-256 constants are regenerated every SDK release — bump your app version passed toupdate_assetsevery 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)¶
- Install / first run — SDK creates the data directories and a default
LeafPreferencesfile. - Assets update — call
updateAssets(major, minor, patch)with your app version so the correct checksum bucket is pulled. - Integrity check —
verifyFileIntegrity()before starting. Fail fast if assets were tampered with. - Provision user — call the panel REST API to create a client, obtain its UUID.
- Fetch subscription —
updateSubscription(clientId)(or offline import) writessubscriptions/default.conf. - Start core — desktop:
startCore(program, daemon)(root elevation is handled by the SDK viapkexec/ RunAs / UAC). Android: theVpnServiceis launched viaIntenton user consent. - Start Leaf —
runLeaf()/startLeaf(); the rendered.confis loaded and the TUN device is attached. - Live operations — use the local Runtime HTTP API to read stats, logs, swap outbound, and trigger failover health checks.
- Reload — after
setPreferencesor selecting a new server group, callreloadLeaf()to re-render the config without dropping the TUN. - Shutdown —
stopLeaf()followed byshutdownCore()(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. |
tlsandfragmentonly affect how the subscription document itself is fetched, not the proxy traffic.enable_speedtest/enable_try_allchange 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_NAMEsplit 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.