singbox_ffi 0.1.0
singbox_ffi: ^0.1.0 copied to clipboard
Dart FFI bindings for the singbox-ffi native core.
singbox-ffi #
singbox-ffi is the native core package for apps such as LitheNet. It builds a
small C ABI wrapper around github.com/sagernet/sing-box/experimental/libbox
and publishes native artifacts for GUI apps to download.
LitheNet should not compile this Go code directly. Its Flutter build should
depend on this Flutter FFI plugin, use prebuilt singbox-ffi artifacts, and
build only the Flutter UI.
Outputs #
Windows:
singboxffi.dll
singboxffi.h
Linux:
libsingboxffi.so
singboxffi.h
macOS:
libsingboxffi.dylib
singboxffi.h
Static archive builds for iOS, Android, and advanced desktop integrations use
the same native ABI and should publish platform-specific singboxffi.a
artifacts alongside the generated header.
Flutter FFI Plugin #
The repository root is now a publishable Flutter FFI plugin named
singbox_ffi. The plugin owns platform packaging and linking for the native
artifacts:
- Windows: bundle
windows/artifacts/x64/singboxffi.dll. - Linux: bundle
linux/artifacts/x86_64/libsingboxffi.so. - macOS: link/package
macos/Libraries/libsingboxffi.dylib,macos/Libraries/libsingboxffi.a, or a vendored framework/xcframework. - Android: package ABI-specific
.sofiles fromandroid/src/main/jniLibs/<abi>/libsingboxffi.so. - iOS: link
ios/Libraries/libsingboxffi.aor a vendored framework/xcframework soDynamicLibrary.process()can resolve native symbols.
The pub.dev package is intentionally lightweight and does not include native artifacts. Release builds attach a complete GitHub Release package containing the prebuilt artifacts. Flutter build files intentionally fail fast when a desktop artifact is missing.
Apps that need batteries-included native artifacts should consume the GitHub Release package or copy the matching Release artifacts into the directories listed above.
Build Locally #
Windows with MSYS2 UCRT64 GCC:
$env:CGO_ENABLED = "1"
$env:CC = "C:\msys64\ucrt64\bin\gcc.exe"
$env:PATH = "C:\msys64\ucrt64\bin;$env:PATH"
go build -trimpath -buildmode=c-shared `
-tags "with_gvisor,with_quic,with_wireguard,with_utls,with_naive_outbound,with_purego,with_clash_api,badlinkname,tfogo_checklinkname0" `
-ldflags "-s -w -buildid= -checklinkname=0" `
-o build\singboxffi.dll .
Run The Dart Smoke Proxy #
The Dart example proves the FFI can start a real local mixed proxy on
127.0.0.1:2080.
flutter pub get
cd examples\flutter
flutter pub get
dart run bin\proxy.dart ..\..\build\singboxffi.dll
In another terminal:
curl.exe -x socks5h://127.0.0.1:2080 https://example.com
Press Ctrl+C in the Dart process to stop the proxy.
Dart Exports #
lib/singbox_ffi.dart exports every native symbol in two layers.
Raw C ABI bindings:
SbHandle
SbInitOptions
SbVersionNative / SbVersionDart
SbGoVersionNative / SbGoVersionDart
SbFreeStringNative / SbFreeStringDart
SbInitNative / SbInitDart
SbCheckConfigNative / SbCheckConfigDart
SbStartNative / SbStartDart
SbReloadNative / SbReloadDart
SbStopNative / SbStopDart
SbFreeHandleNative / SbFreeHandleDart
SingboxNativeSymbols
SingboxRawBindings
SingboxRawBindings.openBundled([path])
SingboxRawBindings.openDefault([path])
High-level Dart API:
SingboxFfi.open([path])
SingboxFfi.openBundled([path])
SingboxFfi.openDefault([path])
SingboxFfi.fromLibrary(library)
SingboxFfi.process()
SingboxFfi.defaultLibraryName
SingboxFfi.openBundledLibrary([path])
SingboxFfi.openDefaultLibrary([path])
SingboxFfi.raw
SingboxFfi.version()
SingboxFfi.goVersion()
SingboxFfi.init([options])
SingboxFfi.checkConfig(configJson)
SingboxFfi.start(configJson)
SingboxFfi.reload(handle, configJson)
SingboxFfi.stop(handle)
SingboxFfi.freeHandle(handle)
SingboxFfi.freeString(pointer)
SingboxFfi.takeString(pointer)
SingboxFfi.takeError(errOut)
SingboxInitOptions
SingboxService.handle
SingboxService.reload(configJson)
SingboxService.close()
SingboxException
Static Linking #
Use the bundled plugin default in Flutter apps:
final core = SingboxFfi.openBundled();
SingboxFfi.openBundled([path]) follows the packaging strategy used by the
plugin:
- Use the explicit
path, when provided. - On iOS, use
DynamicLibrary.process()because the plugin links the static archive or framework into the app. - On Android, Windows, Linux, and default macOS builds, use dynamic loading via
SingboxFfi.openDefault().
Dynamic linking is the recommended desktop path:
final core = SingboxFfi.openDefault('singboxffi.dll');
SingboxFfi.openDefault([path]) searches in this order:
- The explicit
path, when provided. - The current working directory.
- The executable directory from
Platform.executable. - The resolved executable directory from
Platform.resolvedExecutable. - The platform loader default via
DynamicLibrary.open(defaultLibraryName).
Static linking is possible, but SingboxFfi.process() is not a normal fallback
mode for the Dart API alone. It only works after the native library has
already been linked into the Flutter runner, app executable, or platform plugin
so that symbols such as sb_version are visible in the process. Then use:
final core = SingboxFfi.process();
Short term: do not expose process() to app users as an ordinary optional
mode unless singbox-ffi also provides the platform project that links the
native symbols into the app.
Build a static C archive instead of a dynamic library:
go build -trimpath -buildmode=c-archive `
-tags "with_gvisor,with_quic,with_wireguard,with_utls,with_naive_outbound,with_purego,with_clash_api,badlinkname,tfogo_checklinkname0" `
-ldflags "-s -w -buildid= -checklinkname=0" `
-o build\singboxffi.a .
Platform notes:
- Windows desktop: prefer
singboxffi.dll; static linking a Go archive into Flutter's MSVC runner is possible only with extra native runner work and toolchain care. - Linux desktop: package
libsingboxffi.sothrough CMake. - macOS desktop: package
libsingboxffi.dylibby default. Static archive/framework builds are supported, but app code should callSingboxFfi.process()only for those builds. - Android: package ABI-specific
.sofiles through the Flutter FFI plugin. - iOS: link a static archive or framework through CocoaPods;
openBundled()usesDynamicLibrary.process()so Dart can resolvesb_version.
Mobile and static mode are handled by the Flutter FFI plugin scaffolding in
this package. It owns Windows/macOS/Linux native library linking or packaging,
Android ABI .so packaging, iOS/macOS static archive or framework linking, and
the platform setup required for DynamicLibrary.process() to find
sb_version.
ABI #
typedef uint64_t sb_handle;
char *sb_version(void);
char *sb_go_version(void);
void sb_free_string(char *ptr);
int32_t sb_init(const sb_init_options *opts, char **err_out);
int32_t sb_check_config(char *config_json, char **err_out);
int32_t sb_start(char *config_json, sb_handle *out, char **err_out);
int32_t sb_reload(sb_handle handle, char *config_json, char **err_out);
int32_t sb_stop(sb_handle handle, char **err_out);
int32_t sb_free_handle(sb_handle handle);
Strings returned by the core must be released with sb_free_string. Handles
returned by sb_start must be stopped and released with sb_stop and
sb_free_handle.
Repository Split #
loafman1120/singbox-ffi: builds and releases native core artifacts.loafman1120/LitheNet: Flutter GUI app; downloadssingbox-ffiartifacts.
Dart Package Layout #
The repository root is the singbox_ffi Flutter FFI plugin. LitheNet should
depend on it directly:
dependencies:
singbox_ffi:
path: ../singbox-ffi
examples/flutter is only a smoke/example package. It depends on the root
plugin with path: ../.. and should not be consumed as the public API.
Release Automation #
GitHub Actions builds native artifacts, validates the lightweight pub.dev package, and assembles a complete GitHub Release package.
On every push, pull request, manual dispatch, and GitHub Release publication,
.github/workflows/build.yml builds:
- Windows x64
singboxffi.dll - Linux x86_64/aarch64
libsingboxffi.so - macOS x86_64/arm64
libsingboxffi.dylib - Android
arm64-v8a,armeabi-v7a,x86_64, andx86libsingboxffi.soshared libraries - iOS device/simulator static archives, assembled into
ios/Frameworks/singboxffi.xcframework
The packaging job copies those artifacts into the Flutter plugin directories,
runs flutter pub publish --dry-run against the lightweight pub.dev package,
then uploads singbox_ffi-<version>.zip and singbox_ffi-<version>.tar.gz as
workflow artifacts. Those archives keep the native artifacts and are intended
for GitHub Release consumption.
The top-level examples/ smoke tests are excluded from the pub package because
Pub only recognizes the singular example/ convention.
When the workflow runs from a v<version> tag or a published GitHub Release,
the package archives are also attached to the GitHub Release. The tag version
must match pubspec.yaml.
When the workflow runs from a v<version> tag, it also attempts
flutter pub publish --force for the lightweight package. Pub.dev automated
publishing must be enabled for loafman1120/singbox-ffi with a matching tag
pattern such as v{{version}}, and the first package version must still be
published manually on pub.dev.
Status #
Implemented:
- config validation
- local mixed/SOCKS/HTTP proxy start, reload, stop
- desktop stub platform interface
- C and Dart smoke examples
Not implemented in this core yet:
- TUN mode
- system proxy toggling
- event/log draining
License Notice #
This project links against github.com/sagernet/sing-box/experimental/libbox.
sing-box is distributed under the GNU General Public License, version 3 or later,
with the additional upstream naming restriction copied in LICENSE.sing-box.
Distributions of this wrapper, linked binaries, and applications embedding the produced native library should carry the corresponding GPL notice and must not use the sing-box name or imply association with the upstream application without prior consent.