Skip to content

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:

[lib]
crate-type = ["cdylib", "staticlib"]
name = "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