Proxy Core is a powerful Flutter plugin that brings modern proxy tools to your mobile applications. With implementations of cutting-edge cores like XRay, Outline and SingBox (coming soon), Proxy Core enhances your app's networking capabilities.
- π Persistent notifications for proxy core connection status (Android only)
- π‘οΈ Native VPN/proxy tunnel support
- π§ Unified core interface
- π Tun2Socks-based VPN integration on Android/iOS
- XRay: A powerful proxy core with advanced features and high performance.
- Outline: A user-friendly proxy core designed for easy setup and use.
- SingBox: (Soon)
- β Android (Proxy and Vpn Mode)
- β iOS (VPN Mode)
- π οΈ macOS (Proxy Mode)
- π οΈ Windows (Proxy Mode)
ProxyCore uses different architectures depending on the platform to provide optimal performance and native integration:
- Uses Method Channels for communication between Dart and Swift
- VPN status updates via EventChannel (event-based, real-time)
- Runs proxy cores inside Network Extension sandbox (PacketTunnelProvider)
- No polling required - native OS notifications for status changes
- More battery efficient due to event-driven architecture
- Uses gRPC for communication with native core
- FFI bindings for direct native library calls
- Timer-based polling (1-second intervals) for VPN status monitoring in VPN mode
- Persistent notifications for user visibility (Android)
- Direct memory access for better performance
The onCoreStateChanged callback behaves differently per platform:
- iOS: Triggered by OS-level VPN status events (connecting, connected, disconnected, reasserting, etc.) via EventChannel
- Other platforms: Called after start/stop operations and during periodic VPN status checks (every 1 second when in VPN mode)
| Function | Description |
|---|---|
initialize(onCoreStateChanged) |
Initialize the proxy core and takes an optional Future callback to receive core state changes. iOS: Sets up method channel communication and event listeners Other platforms: Initializes gRPC server, FFI bindings, and notification service |
start(config) |
Start the proxy/VPN service with the given configuration. iOS: Checks if tunnel is connected; if yes, calls simpleStart. Otherwise prepares VPN manager and starts Network Extension tunnelOther platforms: Requests permissions, stops existing connections, starts core via gRPC, shows notifications, and begins VPN status polling |
simpleStart(config) |
Start or restart the core with new configuration without stopping VPN tunnel β useful for quick server switching and testing. iOS: Updates configuration in running tunnel without full VPN reconnection Android: Disconnects any running VPN first, skips notifications and permission checks Desktop: Skips notification handling |
stop |
Gracefully stop the proxy/VPN service. iOS: Sends stop command to VPN tunnel via method channel Other platforms: Stops VPN, stops core via gRPC, cancels notifications, and stops status polling |
measurePing(List<String> urls) |
Measure delay to the provided URLs. iOS: Sends comma-separated URLs to tunnel, receives comma-separated results, parses into PingResult objects Other platforms: Uses gRPC with structured request/response |
isRunning |
Check if the proxy core is currently active. iOS: Queries tunnel status via method channel Other platforms: Checks via gRPC call |
version |
Get the current version of the core. iOS: Queries via method channel Other platforms: Retrieves via gRPC (Xray only for now) |
setIosTunnelInfo |
iOS Only: Set the app name and PacketTunnelProvider bundle ID for VPN mode. Must be called before initialize(). |
fetchLogs |
Fetch core logs to receive real-time updates. iOS: Retrieves logs from tunnel provider Other platforms: Fetches via gRPC |
clearLogs |
Clear stored core logs to free up memory. iOS: Sends clear command to tunnel provider Other platforms: Clears via gRPC |
The ProxyCoreConfig class is used to configure the core with required parameters. Here's an overview of its parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
core |
ProxyCoresTypes | xray | Which core to start (ProxyCoresTypes.xray, ProxyCoresTypes.outline) |
dir |
String | - | The directory where the application temporary files saved |
config |
String | - | The actual configuration, either a JSON string or a path to a configuration file. |
isString |
bool | true | If true, treats config as a JSON string; if false, treats config as a file path. |
memory |
int | 128 | Amount of memory (in MB) to allocate for the proxy core. |
proxyPort |
int | 2080 | Port on which the proxy core will listen for incoming connections (inProxyMode) |
To use this plugin, add proxy_core as a dependency in your pubspec.yaml file:
dependencies:
proxy_core: ^latestProxyCore uses flutter_local_notifications to display persistent notifications for proxy core connection status (on Android only) so you need to do "Gradle Setup" section first. You can find it here: Flutter Local Notifications Gradle Setup
The VPN notification icon:
- Place your notification icon in the
android/app/src/main/res/drawable/directory - Name the icon file
ic_vpn_notif.png(or appropriate format) - Follow Android's notification icon guidelines:
- Use only white (foreground) and transparent (background) colors
- Design should be simple and recognizable at small sizes (24dp Γ 24dp)
For more information on creating notification icons that follow Material Design guidelines, see the Android developer documentation.
Important: As Android strips and deletes unused resources in release mode, you should create a
keep.xmlfile in theandroid/app/src/main/res/raw/directory with the following content to preserve your notification icons:<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@drawable/*" />
Due to Apple's limitations for running tunnels in separate sandboxes, we need to start the proxy cores from within the PacketTunnelProvider. The proxy core can only interact with the tunnel if it's placed within the PacketTunnelProvider sandbox. Because of this restriction, we need to include another version of the cores as a Swift kit (called ProxyCoreKit) into the iOS project of the Flutter app and perform these extra steps for iOS VPN mode:
- Add an "App Group" for the Runner target in Xcode with the same Bundle Identifier but with a "group" prefix. For example, if your app's bundle identifier is " com.example.YourApp", create an App Group named "group.com.example.YourApp"
- Add "Network Extension" capability with the "Packet Tunnel" option selected/enabled for the Runner target in Xcode
- Add "Personal VPN" capability for the Runner target in Xcode
- Create a new target in Xcode:
- Select "File" > "New" > "Target..."
- Choose "Network Extension" and select "Packet Tunnel"
- Name the target "PacketTunnel"
- Ensure the target's bundle identifier is set to "com.example.YourApp.PacketTunnelProvider" (or similar, matching your app's bundle identifier)
- Add a package dependency from this URL: https://github.com/EbrahimTahernejad/Tun2SocksKit.git to the entire project
- Add a package dependency from this URL: https://github.com/mahsanet/ProxyCoreKit.git
- Add both newly added dependencies to the created PacketTunnel target (from step 2) as requirements in the "Frameworks and Libraries" section
- Add "libresolv.9.tbd" also to the "Frameworks and Libraries" section of the PacketTunnel target
- Create a PacketTunnelProvider.swift file (if it doesn't exist already) in the newly created PacketTunnel directory (in the root of the iOS directory) and copy the contents from the PacketTunnelProvider.sample.swift file located in the repository root
After completing the Xcode setup, you must set the app name and PacketTunnelProvider bundle identifier before initializing the core in your Flutter app. This step is mandatory and should be done once at the start of your app:
void main() async {
// For iOS VPN mode, set tunnel info before initializing
if (Platform.isIOS) {
ProxyCore.ins.setIosTunnelInfo(
"YourAppName",
"com.example.YourApp.PacketTunnelProvider"
);
}
// Then continue with initialization
ProxyCore.ins.initialize();
// Rest of your code...
}- Make sure to replace "YourAppName" with your actual application name and " com.example.YourApp.PacketTunnelProvider" with your actual PacketTunnelProvider bundle identifier.
- The App Group ID must be the same for both the main Runner target and the PacketTunnel target in xcode
- The PacketTunnel bundle identifier in xcode must match exactly what you pass to the
setIosTunnelInfo()method
import 'dart:io';
import 'package:proxy_core/proxy_core.dart';
import 'package:proxy_core/models/proxy_core_config.dart';
void main() async {
// For iOS VPN mode, set tunnel info before initializing
if (Platform.isIOS) {
ProxyCore.ins.setIosTunnelInfo(
"YourAppName",
"com.example.YourApp.PacketTunnelProvider"
);
}
// Initialize the gRPC server before using other methods
ProxyCore.ins.initialize();
// Define your core configuration
final config = ProxyCoreConfig.inVpnMode(
core: ProxyCoresTypes.xray // or ProxyCoresTypes.outline
dir: "/path/to/application_temp_dir",
config: "config string or file path",
);
// Start the proxy core with the configuration
await ProxyCore.ins.start(config);
// Other logics
// Stop the proxy core
await ProxyCore.ins.stop();
}Note: Calling
start()with a new configuration or mode will act as a restart. Even if the core is already running, invokingstart()will automatically callstop()to gracefully terminate the existing instance before applying the new configuration. This ensures a clean start with the updated settings.
VPN Not Starting:
- Ensure
setIosTunnelInfo()is called beforeinitialize() - Verify App Group ID matches exactly between Runner and PacketTunnel targets in Xcode
- Check that the PacketTunnelProvider bundle identifier matches exactly what you pass to
setIosTunnelInfo() - Make sure all required capabilities are enabled (Network Extension, Personal VPN)
Configuration Not Updating:
- Use
simpleStart()to update configuration without reconnecting the VPN tunnel - Check that the tunnel is in "connected" state before calling
simpleStart()
Notification Not Showing:
- Ensure notification permissions are granted (required for Android 13+)
- Check that
ic_vpn_notif.pngexists inandroid/app/src/main/res/drawable/ - Verify
keep.xmlis present inandroid/app/src/main/res/raw/to prevent icon removal in release builds
VPN Permission Denied:
- The app will request VPN permission on first start
- If denied, user must grant permission manually in system settings
Core Not Stopping:
- Check logs using
fetchLogs()to identify any errors - On Android/Desktop: VPN status polling will auto-stop the core if VPN disconnects
- On iOS: EventChannel will notify of disconnection immediately
Memory Issues:
- Adjust the
memoryparameter inProxyCoreConfig(default is 128 MB) - Use
clearLogs()periodically to free up log storage
- iOS: Uses event-driven architecture with native OS notifications - minimal battery impact
- Android/Desktop (VPN mode): Polls VPN status every 1 second - slight battery impact during VPN session
- Android/Desktop (Proxy mode): No polling - minimal battery impact
- iOS: Use
simpleStart()for fast server switching without VPN reconnection overhead - Android/Desktop:
simpleStart()andstart()have similar performance as they both restart the core
- Persistent notifications (Android/Linux/macOS) have negligible performance impact
- Can be avoided in testing scenarios by using
simpleStart()
Use the pre_run_clean.sh script to prepare your environment before running the app, helps to make
sure that new builds are replaced. The script
removes .lock files, clears build directories, and uninstalls the app on connected devices (
optional). To customize:
-v,--verbose: Enable verbose mode--skip-adb: Skip ADB uninstall-p,--package: Specify package name for ADB uninstall
./pre_run_clean.sh -vOr add it as a launch configuration in your IDE/Editor to run it automatically before starting the app.
Check out our example project to see Proxy Core in action!
Add your desired changes to Go files in src/ directory and then update the flutter-related functions in lib/ (if
necessary) then build the plugin for your target platforms like this:
- Install Android Studio and the Android NDK through the SDK Manager
- Set the NDK_HOME environment variable to point to your latest NDK installation:
Replace
# For macOS/Linux export NDK_HOME=$HOME/Library/Android/sdk/ndk/[latest_version] # For Windows set NDK_HOME=C:\Users\YourUsername\AppData\Local\Android\Sdk\ndk\[latest_version]
[latest_version]with your installed NDK version (e.g.,25.2.9519653)
The plugin will handle the building process automatically when you run your Flutter application. No manual build steps are required.
To build the plugin for ios:
- Run the provided
build_ios_kit.shscript. - Make a fork from current
ProxyCoreKitswift plugin repo into your github account. - The script will generate
ProxyCoreKit.xcframeworkin thescripts/buildfolder. - Make a zip archive from the
ProxyCoreKit.xcframework& make a release github of it (on your personal repo). - Replace the
checksumvariable inPackage.swiftwith your own createdProxyCoreKit.xcframework.zipfile, using:sha256 /path/to/file.zip. - Now you can add the forked repo as a swift package dependency, in your Xcode project ( see iOS setup instructions step 5).
- Done!
To build the plugin for macOS:
- Run the provided
build_mac.shscript. - The script will generate
libproxy_core.xcframework.zipand in thescriptsfolder. - Copy the
libproxy_core.xcframework.zipinto themacos/frameworks/directory. - Done!
To build the plugin for Windows, you'll need to meet the following requirements on a Windows machine:
- Visual Studio: Required for Flutter.
- LLVM: Install via
winget install -e --id LLVM.LLVM - MinGW/MSYS: Download and install from the official site
- Toolchain: Install the required toolchain by running
pacman -Sy mingw-w64-cross-toolchain mingw-w64-crossin the MSYS terminal - System Path Variables: Add
C:\msys64\opt\binandC:\msys64\usr\binto your system's Path under Environment Variables.
Steps:
- Run the provided
build_windows.batscript. - The script will generate
libproxy_core.zipandlibproxy_core.hin thescriptsfolder. - Copy the
libproxy_core.zipinto thewindows/directory. - Done!
After building the native libraries, you need to generate the Dart FFI bindings:
- Make sure the header file
libproxy_core.his in thescriptsfolder. - Run
dart run ffigen --config ffigen.yamlto generate the bindings. - The bindings will be generated in
lib/gen/proxy_core_bindings_generated.dart.
Contributions are welcome! Please feel free to submit a Pull Request.