Desktop SDK (C / C++ FFI)¶
The ffi crate compiles to a dynamic library with a stable C ABI (libleaf.so / leaf.dll / libleaf.dylib) plus a static libleaf.a. Any language with a C-FFI can drive the VPN: C, C++, Qt, C#, Electron/Node, Python (ctypes), Go (cgo), Swift, Nim, Zig, etc.
The output name is leaf:
1. Build artifacts¶
After downloading the artifacts from the repository, the provided bundle contains:
| Platform | cdylib | staticlib |
|---|---|---|
| Linux / musl | libleaf.so |
libleaf.a |
| macOS | libleaf.dylib |
libleaf.a |
| Windows | leaf.dll + leaf.dll.lib |
leaf.lib |
A leaf.h header is also provided with the release bundle.
You also need the same leaf-ipc sidecar binary distributed alongside your app (see Desktop SDK (Rust) — Bundling the sidecar).
2. Error codes & enums¶
All C functions that can fail return i32. Zero is success; every other constant is declared in leaf.h:
#define ERR_OK 0
#define ERR_PROGRAM_NOT_FOUND 1
#define ERR_TEST_CONFIG_FAILED 2
#define ERR_START_LEAF_FAILED 3
#define ERR_IS_LEAF_RUNNING_FAILED 4
#define ERR_RELOAD_LEAF_FAILED 5
#define ERR_UPDATE_ASSETS_FAILED 6
#define ERR_SETUP_WINTUN_FAILED 7 /* Windows only */
#define ERR_CLIENT_ID_INVALID 8
#define ERR_CONFIG_INVALID 9
#define ERR_SET_PREFERENCES_FAILED 10
#define ERR_START_CORE_FAILED 11
#define ERR_VERIFY_FILE_INTEGRITY_FAILED 12
Lifecycle callbacks deliver integer state + optional error message:
/* core states */
#define CORE_STATE_STARTING 0
#define CORE_STATE_STARTED 1
#define CORE_STATE_STOPPED 2
#define CORE_STATE_ERROR 3
/* leaf states */
#define LEAF_STATE_STARTING 0
#define LEAF_STATE_STARTED 1
#define LEAF_STATE_STOPPED 2
#define LEAF_STATE_RELOADED 3
#define LEAF_STATE_ERROR 4
/* subscription states */
#define SUBSCRIPTION_STATE_UPDATING 0
#define SUBSCRIPTION_STATE_SUCCESS 1
#define SUBSCRIPTION_STATE_ERROR 2
Log levels (enum LogLevel): 0=Trace, 1=Debug, 2=Info, 3=Warn, 4=Error.
Log output target (enum LogOutput): 0=Stdout, 1=Stderr, 2=File.
3. Public functions¶
Version & Logger¶
const char* version(void); /* caller frees via free_c_string */
void init_logger(const LoggerConfig* cfg); /* pass NULL for defaults */
LoggerConfig:
typedef struct {
int32_t level; /* LogLevel */
int32_t output; /* LogOutput */
const char* file_path; /* used if output == File, may be NULL */
bool format_json;
} LoggerConfig;
Core lifecycle¶
typedef void (*CoreCallback)(int32_t state, const char* error);
int32_t start_core(const char* program, bool daemon, CoreCallback cb);
bool is_core_running(void);
void stop_core(bool daemon, CoreCallback cb);
void force_stop_core(CoreCallback cb);
program is the absolute path to the leaf-ipc binary shipped with your app. On Linux / macOS start_core elevates via pkexec / runas. On Windows the SDK launches a UAC prompt.
Leaf lifecycle¶
typedef void (*LeafCallback)(int32_t state, const char* error);
int32_t test_config(void);
int32_t start_leaf(LeafCallback cb);
int32_t is_leaf_running(void); /* 1=running, 0=stopped, <0=err */
int32_t reload_leaf(LeafCallback cb);
void stop_leaf(LeafCallback cb);
Subscription¶
typedef void (*SubscriptionCallback)(int32_t state, const char* error);
void auto_update_subscription(SubscriptionCallback cb);
int32_t update_subscription(int32_t tls,
int32_t fragment,
const char* client_id,
int32_t enable_speedtest,
int32_t enable_try_all,
SubscriptionCallback cb);
int32_t update_custom_config(const char* config, SubscriptionCallback cb);
int32_t import_offline_subscription(const char* path,
const char* passphrase, /* may be NULL */
const char* keyring_json, /* JSON string */
SubscriptionCallback cb);
Pass -1 for any of tls, fragment, enable_speedtest, enable_try_all to let the SDK auto-pick.
Assets & integrity¶
int32_t update_assets(uint64_t major, uint64_t minor, uint64_t patch);
int32_t verify_file_integrity(void);
#ifdef _WIN32
int32_t setup_wintun(const char* path); /* call before start_leaf */
#endif
Preferences¶
typedef struct {
const char* client_id;
int64_t last_update_time;
uint64_t traffic;
uint64_t used_traffic;
const char* expire_time;
bool enable_ipv6;
bool prefer_ipv6;
bool memory_logger;
int32_t log_level; /* LogLevel */
uint16_t api_port;
bool auto_reload;
const char* user_agent;
bool bypass_lan;
bool bypass_lan_in_core;
bool fake_ip;
bool force_resolve_domain;
const char* bypass_geoip_list; /* comma-separated, may be NULL */
const char* bypass_geosite_list;
const char* reject_geoip_list;
const char* reject_geosite_list;
bool internal_dns_server;
} LeafPreferences;
LeafPreferences* get_preferences(void); /* owned — free with free_preferences */
void free_preferences(LeafPreferences*);
int32_t set_preferences(
bool enable_ipv6, bool prefer_ipv6, bool memory_logger,
int32_t log_level, uint16_t api_port, bool auto_reload,
const char* user_agent,
bool bypass_lan, bool bypass_lan_in_core,
bool fake_ip, bool force_resolve_domain,
const char* bypass_geoip_list, /* comma-separated, NULL for none */
const char* bypass_geosite_list,
const char* reject_geoip_list,
const char* reject_geosite_list,
bool internal_dns_server);
Utility¶
const char* ping(void); /* caller frees via free_c_string */
void free_c_string(char*); /* frees strings returned by version()/ping() */
For the full list of fields see Preferences Reference.
4. Minimal C example¶
#include "leaf.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void on_core(int32_t state, const char* err) {
if (state == CORE_STATE_STARTED) puts("core ready");
if (state == CORE_STATE_ERROR) fprintf(stderr, "core error: %s\n", err);
}
static void on_sub(int32_t state, const char* err) {
if (state == SUBSCRIPTION_STATE_SUCCESS) puts("subscription ok");
if (state == SUBSCRIPTION_STATE_ERROR) fprintf(stderr, "sub error: %s\n", err);
}
static void on_leaf(int32_t state, const char* err) {
if (state == LEAF_STATE_STARTED) puts("vpn up");
if (state == LEAF_STATE_ERROR) fprintf(stderr, "leaf error: %s\n", err);
}
int main(int argc, char** argv) {
LoggerConfig lc = { .level = 2, .output = 1, .file_path = NULL, .format_json = false };
init_logger(&lc);
if (start_core("./leaf-ipc", true, on_core) != ERR_OK) return 1;
/* wait until CORE_STATE_STARTED arrives on the callback... */
sleep(2);
if (update_subscription(-1, -1, argv[1], -1, -1, on_sub) != ERR_OK) return 2;
sleep(3);
if (verify_file_integrity() != ERR_OK) return 3;
if (start_leaf(on_leaf) != ERR_OK) return 4;
sleep(30);
stop_leaf(on_leaf);
stop_core(true, on_core);
return 0;
}
Build (Linux example):
gcc example.c -o example \
-I. -L./target/release -lleaf -Wl,-rpath,./target/release
./example YOUR_CLIENT_UUID
5. Qt / C++ example¶
#include "leaf.h"
#include <QCoreApplication>
#include <QtConcurrent>
static void leafCallback(int32_t state, const char* err) {
QMetaObject::invokeMethod(qApp, [state, err = QString::fromUtf8(err ? err : "")] {
emit VpnController::instance().leafStateChanged(state, err);
}, Qt::QueuedConnection);
}
void VpnController::connectClient(const QString& id) {
update_subscription(-1, -1, id.toUtf8().constData(), 1, 1,
[](int32_t state, const char* err) {
if (state == SUBSCRIPTION_STATE_SUCCESS) {
verify_file_integrity();
start_leaf(&leafCallback);
}
});
}
Link with -lleaf (Linux/mac) or leaf.dll.lib (Windows) and ship leaf-ipc (+ wintun.dll on Windows after setup_wintun) next to the executable.
6. C# P/Invoke sketch¶
internal static class LeafNative {
[DllImport("leaf")] public static extern int start_core(string program, bool daemon, CoreCallback cb);
[DllImport("leaf")] public static extern int update_subscription(int tls, int fragment, string clientId,
int enableSpeedtest, int enableTryAll,
SubscriptionCallback cb);
[DllImport("leaf")] public static extern int start_leaf(LeafCallback cb);
[DllImport("leaf")] public static extern int verify_file_integrity();
[DllImport("leaf")] public static extern void stop_leaf(LeafCallback cb);
[DllImport("leaf")] public static extern void stop_core(bool daemon, CoreCallback cb);
public delegate void CoreCallback(int state, IntPtr err);
public delegate void LeafCallback(int state, IntPtr err);
public delegate void SubscriptionCallback(int state, IntPtr err);
}
Keep strong references to every delegate you pass in — the GC will otherwise free them before the Rust side invokes them.
7. Memory management rules¶
| Returned by | Owned by | Free with |
|---|---|---|
version() |
caller | free_c_string |
ping() |
caller | free_c_string |
get_preferences() |
caller | free_preferences |
set_preferences / all update_* / start_* arguments |
caller | caller (Rust copies to owned Strings) |
*_error pointer in callbacks |
Rust (valid only during the callback) | do not free; copy immediately if needed |
8. Next steps¶
- Live stats & log streaming: Runtime HTTP API.
- See the JVM equivalent: Desktop SDK (Java).