axevpn_flutter

pub package pub points popularity License: GPL v3 Platform

A Flutter plugin that provides OpenVPN and WireGuard VPN connectivity for Android and iOS.
Built with Android 15+ 16 KB page-size compatibility and modern Flutter 3.10+ / Dart 3.0+ support.


Table of Contents


Features

Feature OpenVPN WireGuard
Android support
iOS support
Real-time status monitoring
Connection stats (bytes in/out)
Auto-reconnect handling
Split tunneling (package bypass)
TCP & UDP protocol
Android 15+ 16 KB page-size
Modern cryptography

Platform Support

Platform Minimum Version
Android 7.0 (API 24)
iOS 16.0

Installation

Add to your pubspec.yaml:

dependencies:
  axevpn_flutter: ^2.0.0

Then run:

flutter pub get

Quick Start

OpenVPN

import 'package:axevpn_flutter/openvpn_flutter.dart';

// 1. Instantiate
final vpn = OpenVPN(
  onVpnStatusChanged: (VpnStatus? status) {
    print('Duration: ${status?.duration}');
    print('Bytes in: ${status?.byteIn}  Bytes out: ${status?.byteOut}');
  },
  onVpnStageChanged: (VPNStage stage, String rawStage) {
    print('Stage: $stage');
  },
);

// 2. Initialize (required before connect)
await vpn.initialize(
  // iOS only ↓
  groupIdentifier: 'group.com.example.vpn',
  providerBundleIdentifier: 'com.example.app.VPNExtension',
  localizedDescription: 'My VPN',
);

// 3. Connect
final ovpnConfig = '''
client
dev tun
proto udp
remote vpn.example.com 1194
...
''';

await vpn.connect(ovpnConfig, 'My Server');

// 4. Disconnect
vpn.disconnect();

WireGuard

import 'package:axevpn_flutter/wireguard_flutter.dart';

// 1. Instantiate
final wg = WireGuard(
  onVpnStatusChanged: (WireGuardStatus? status) {
    print('Duration: ${status?.duration}');
    print('Bytes in: ${status?.byteIn}  Bytes out: ${status?.byteOut}');
  },
  onVpnStageChanged: (WGStage stage, String rawStage) {
    print('Stage: $stage');
  },
);

// 2. Initialize
await wg.initialize(
  // iOS only ↓
  groupIdentifier: 'group.com.example.vpn',
  providerBundleIdentifier: 'com.example.app.WGExtension',
  localizedDescription: 'My VPN',
);

// 3. Connect — pass raw WireGuard .conf content
final wgConfig = '''
[Interface]
PrivateKey = <your_private_key>
Address = 10.0.0.2/24
DNS = 1.1.1.1

[Peer]
PublicKey = <server_public_key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0
''';

await wg.connect(wgConfig, 'My WireGuard');

// 4. Disconnect
await wg.disconnect();

Android Setup

1. Handle VPN permission result in MainActivity

Kotlin (MainActivity.kt):

import com.axevpn.flutter.openvpn.AxeVPNFlutterPlugin

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    AxeVPNFlutterPlugin.connectWhileGranted(requestCode == 24 && resultCode == RESULT_OK)
    super.onActivityResult(requestCode, resultCode, data)
}

Java (MainActivity.java):

import com.axevpn.flutter.openvpn.AxeVPNFlutterPlugin;

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    AxeVPNFlutterPlugin.connectWhileGranted(requestCode == 24 && resultCode == RESULT_OK);
    super.onActivityResult(requestCode, resultCode, data);
}

2. app/build.gradle (Kotlin DSL)

android {
    compileSdk = 36
    ndkVersion = "27.0.12077973"   // Required for 16 KB page-size support

    defaultConfig {
        minSdk = 24
        targetSdk = 36
    }

    // Enable Android 15+ 16 KB memory page size
    experimentalProperties["android.experimental.enable16KPageSize"] = true

    packaging {
        jniLibs {
            useLegacyPackaging = true
        }
    }
}

3. android/gradle.properties

android.experimental.enable16KPageSize=true
android.bundle.enableUncompressedNativeLibs=false

4. AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
    android:extractNativeLibs="true"
    ...>

iOS Setup

1. Enable capabilities in Xcode

In your Runner target (and Network Extension target), enable:

  • App Groups – create group.com.yourapp.vpn
  • Network Extensions – enable Packet Tunnel

2. Add Network Extension target

In Xcode → File → New → Target → Network Extension.
Name it (e.g., VPNExtension) and set the same App Group.

OpenVPN Extension

Add to ios/Podfile under the new target:

target 'VPNExtension' do
  use_frameworks!
  pod 'OpenVPNAdapter', :git => 'https://github.com/ss-abramchuk/OpenVPNAdapter.git', :tag => '0.8.0'
end

Copy PacketTunnelProvider.swift to VPNExtension/:

// See example/ios/VPNExtension/ in the repository

WireGuard Extension

target 'WGExtension' do
  use_frameworks!
  pod 'WireGuardKit', :git => 'https://git.zx2c4.com/wireguard-apple', :tag => '1.0.15-26'
end

3. Info.plist — Network Extension target

<key>NSExtension</key>
<dict>
    <key>NSExtensionPointIdentifier</key>
    <string>com.apple.networkextension.packet-tunnel</string>
    <key>NSExtensionPrincipalClass</key>
    <string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>

API Reference

OpenVPN API

OpenVPN constructor

OpenVPN({
  Function(VpnStatus? data)? onVpnStatusChanged,
  Function(VPNStage stage, String rawStage)? onVpnStageChanged,
})
Parameter Type Description
onVpnStatusChanged Function(VpnStatus?) Called when bytes/duration update
onVpnStageChanged Function(VPNStage, String) Called on every stage transition

initialize()

Future<void> initialize({
  String? groupIdentifier,          // iOS: App Group ID
  String? providerBundleIdentifier, // iOS: Extension bundle ID
  String? localizedDescription,     // iOS: Description in Settings
  Function(VpnStatus)? lastStatus,  // Callback with last known status
  Function(VPNStage)? lastStage,    // Callback with last known stage
})

connect()

Future connect(
  String config,    // Raw .ovpn file content
  String name,      // Display name for notification
  {
    String? username,
    String? password,
    List<String>? bypassPackages, // Android: packages to exclude from VPN
    bool certIsRequired = false,
  }
)

disconnect()

void disconnect()

Other methods

Future<VPNStage> stage()
Future<VpnStatus> status()
Future<bool> isConnected()
Future<bool> requestPermissionAndroid()
static Future<String?> filteredConfig(String? config)

VpnStatus

class VpnStatus {
  final DateTime? connectedOn; // Time VPN connected
  final String? duration;      // e.g. "00:05:32"
  final String? byteIn;        // Bytes received (as string)
  final String? byteOut;       // Bytes sent (as string)
  final String? packetsIn;
  final String? packetsOut;
}

WireGuard API

WireGuard constructor

WireGuard({
  Function(WireGuardStatus? data)? onVpnStatusChanged,
  Function(WGStage stage, String rawStage)? onVpnStageChanged,
})

initialize()

Future<void> initialize({
  String? groupIdentifier,
  String? providerBundleIdentifier,
  String? localizedDescription,
  Function(WireGuardStatus)? lastStatus,
  Function(WGStage)? lastStage,
})

connect()

Future connect(
  String config,      // Raw WireGuard .conf content
  String tunnelName,  // Display name
)

Other methods

Future<void> disconnect()
Future<WGStage> stage()
Future<WireGuardStatus> status()

WireGuardStatus

class WireGuardStatus {
  final Duration? duration;         // Connection duration
  final String? lastPacketReceive;  // Last handshake timestamp
  final String? byteIn;             // Bytes received
  final String? byteOut;            // Bytes sent
}

VPN Stages

OpenVPN (VPNStage)

Stage Description
prepare Preparing to connect
authenticating Verifying credentials
connecting Establishing tunnel
connected Tunnel is up
disconnecting Tearing down tunnel
disconnected Tunnel is down
denied VPN permission denied
error Connection error
wait_connection Waiting for network
get_config Fetching configuration
tcp_connect TCP handshake
udp_connect UDP handshake
assign_ip IP assignment
resolve DNS resolution
exiting Process exiting

WireGuard (WGStage)

Stage Description
preparing Preparing to connect
connecting Establishing tunnel
connected Tunnel is up
disconnecting Tearing down tunnel
disconnected Tunnel is down
denied VPN permission denied
error Error occurred

Advanced Usage

Split Tunneling (OpenVPN only)

Exclude specific apps from the VPN tunnel on Android:

await vpn.connect(
  ovpnConfig,
  'My Server',
  bypassPackages: [
    'com.google.android.youtube',
    'com.whatsapp',
  ],
);

Filter Duplicate Remotes

If your .ovpn has many remote entries and causes ANR on some devices:

String? singleRemoteConfig = await OpenVPN.filteredConfig(rawConfig);

Request Android Permission Manually

bool granted = await vpn.requestPermissionAndroid();
if (granted) {
  await vpn.connect(config, 'Server');
}

Toggle Connection

if (await vpn.isConnected()) {
  vpn.disconnect();
} else {
  await vpn.connect(config, 'Server');
}

Troubleshooting

Android

Problem Solution
Build fails with 16 KB error Install NDK 27.0.12077973; set enable16KPageSize=true
VPN permission dialog not shown Call requestPermissionAndroid() before connect()
OpenVPN need to be initialized Call initialize() before connect()
ANR on connect Use OpenVPN.filteredConfig() to reduce remote entries

iOS

Problem Solution
groupIdentifier is required Pass all three iOS params to initialize()
Network Extension not found Verify bundle IDs match providerBundleIdentifier in Xcode
Status not updating after disconnect Register onVpnStageChanged before calling initialize()

Changelog

See CHANGELOG.md for a complete version history.


License

Licensed under the GNU General Public License v3.0 – see LICENSE for details.

OpenVPN® is a registered trademark of OpenVPN Inc.
WireGuard® is a registered trademark of Jason A. Donenfeld.


Forked from openvpn_flutter and extended with WireGuard support and Android 15+ 16 KB page-size compatibility.

Libraries

openvpn_flutter
AxeVPN Flutter Plugin - Advanced VPN Integration
wireguard_flutter
AxeVPN Flutter Plugin - WireGuard Integration