Initial commit (Security Hardened Slate)
This commit is contained in:
commit
157f60aad2
65 files changed
+3735
No files matched your search
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 PT BILLION JAYA GROUP
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,66 @@
|
||||
group 'billion.group.wireguard_flutter'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.8.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.3'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
}
|
||||
|
||||
namespace 'billion.group.wireguard_flutter'
|
||||
|
||||
packaging {
|
||||
resources {
|
||||
excludes += ['META-INF/**']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'com.zaneschepke:amneziawg-android:2.3.7'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.2'
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.2"
|
||||
implementation 'com.beust:klaxon:5.5'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
|
||||
/// to run wireguard on Android 25 or lower
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.3"
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -0,0 +1 @@
|
||||
rootProject.name = 'wireguard_dart'
|
||||
@@ -0,0 +1,2 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
</manifest>
|
||||
+320
@@ -0,0 +1,320 @@
|
||||
package billion.group.wireguard_flutter
|
||||
|
||||
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import io.flutter.plugin.common.MethodChannel.Result
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||
import io.flutter.plugin.common.PluginRegistry
|
||||
|
||||
import android.app.Activity
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import android.content.Intent
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.beust.klaxon.Klaxon
|
||||
import org.amnezia.awg.backend.*
|
||||
import org.amnezia.awg.crypto.Key
|
||||
import org.amnezia.awg.crypto.KeyPair
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.*
|
||||
|
||||
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
/** WireguardFlutterPlugin */
|
||||
|
||||
const val PERMISSIONS_REQUEST_CODE = 10014
|
||||
const val METHOD_CHANNEL_NAME = "billion.group.wireguard_flutter/wgcontrol"
|
||||
const val METHOD_EVENT_NAME = "billion.group.wireguard_flutter/wgstage"
|
||||
|
||||
class WireguardFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
|
||||
PluginRegistry.ActivityResultListener {
|
||||
private lateinit var channel: MethodChannel
|
||||
private lateinit var events: EventChannel
|
||||
private lateinit var tunnelName: String
|
||||
private val futureBackend = CompletableDeferred<Backend>()
|
||||
private var vpnStageSink: EventChannel.EventSink? = null
|
||||
private val scope = CoroutineScope(Job() + Dispatchers.Main.immediate)
|
||||
private var backend: Backend? = null
|
||||
private var havePermission = false
|
||||
private lateinit var context: Context
|
||||
private var activity: Activity? = null
|
||||
private var config: org.amnezia.awg.config.Config? = null
|
||||
private var tunnel: WireGuardTunnel? = null
|
||||
private val TAG = "NVPN"
|
||||
var isVpnChecked = false
|
||||
companion object {
|
||||
private var state: String = "no_connection"
|
||||
|
||||
fun getStatus(): String {
|
||||
return state
|
||||
}
|
||||
}
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
|
||||
this.havePermission =
|
||||
(requestCode == PERMISSIONS_REQUEST_CODE) && (resultCode == Activity.RESULT_OK)
|
||||
return havePermission
|
||||
}
|
||||
|
||||
override fun onAttachedToActivity(activityPluginBinding: ActivityPluginBinding) {
|
||||
this.activity = activityPluginBinding.activity as FlutterActivity
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivityForConfigChanges() {
|
||||
this.activity = null
|
||||
}
|
||||
|
||||
override fun onReattachedToActivityForConfigChanges(activityPluginBinding: ActivityPluginBinding) {
|
||||
this.activity = activityPluginBinding.activity as FlutterActivity
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivity() {
|
||||
this.activity = null
|
||||
}
|
||||
|
||||
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, METHOD_CHANNEL_NAME)
|
||||
events = EventChannel(flutterPluginBinding.binaryMessenger, METHOD_EVENT_NAME)
|
||||
context = flutterPluginBinding.applicationContext
|
||||
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
backend = createBackend()
|
||||
futureBackend.complete(backend!!)
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
}
|
||||
}
|
||||
|
||||
channel.setMethodCallHandler(this)
|
||||
events.setStreamHandler(object : EventChannel.StreamHandler {
|
||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
|
||||
isVpnChecked = false
|
||||
vpnStageSink = events
|
||||
}
|
||||
|
||||
override fun onCancel(arguments: Any?) {
|
||||
isVpnChecked = false
|
||||
vpnStageSink = null
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private fun createBackend(): Backend {
|
||||
if (backend == null) {
|
||||
backend = GoBackend(context, object : TunnelActionHandler {
|
||||
override fun runPreUp(commands: Collection<String>) {}
|
||||
override fun runPostUp(commands: Collection<String>) {}
|
||||
override fun runPreDown(commands: Collection<String>) {}
|
||||
override fun runPostDown(commands: Collection<String>) {}
|
||||
})
|
||||
}
|
||||
return backend as Backend
|
||||
}
|
||||
|
||||
private fun flutterSuccess(result: Result, o: Any) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
result.success(o)
|
||||
}
|
||||
}
|
||||
|
||||
private fun flutterError(result: Result, error: String) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
result.error(error, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun flutterNotImplemented(result: Result) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: Result) {
|
||||
|
||||
when (call.method) {
|
||||
"initialize" -> setupTunnel(call.argument<String>("localizedDescription").toString(), result)
|
||||
"start" -> {
|
||||
connect(call.argument<String>("wgQuickConfig").toString(), result)
|
||||
|
||||
if (!isVpnChecked) {
|
||||
if (isVpnActive()) {
|
||||
state = "connected"
|
||||
isVpnChecked = true
|
||||
println("VPN is active")
|
||||
} else {
|
||||
state = "disconnected"
|
||||
isVpnChecked = true
|
||||
println("VPN is not active")
|
||||
}
|
||||
}
|
||||
}
|
||||
"stop" -> {
|
||||
disconnect(result)
|
||||
}
|
||||
"stage" -> {
|
||||
result.success(getStatus())
|
||||
}
|
||||
"checkPermission" -> {
|
||||
checkPermission()
|
||||
result.success(null)
|
||||
}
|
||||
else -> flutterNotImplemented(result)
|
||||
}
|
||||
}
|
||||
private fun isVpnActive(): Boolean {
|
||||
try {
|
||||
val connectivityManager =
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val activeNetwork = connectivityManager.activeNetwork
|
||||
val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork)
|
||||
return networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "isVpnActive - ERROR - ${e.message}")
|
||||
return false
|
||||
}
|
||||
}
|
||||
private fun updateStage(stage: String?) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
val updatedStage = stage ?: "no_connection"
|
||||
state = updatedStage
|
||||
vpnStageSink?.success(updatedStage.lowercase(Locale.ROOT))
|
||||
}
|
||||
}
|
||||
private fun updateStageFromState(state: Tunnel.State) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
when (state) {
|
||||
Tunnel.State.UP -> updateStage("connected")
|
||||
Tunnel.State.DOWN -> updateStage("disconnected")
|
||||
else -> updateStage("wait_connection")
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun disconnect(result: Result) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
if (futureBackend.await().getRunningTunnelNames().isEmpty()) {
|
||||
updateStage("disconnected")
|
||||
throw Exception("Tunnel is not running")
|
||||
}
|
||||
updateStage("disconnecting")
|
||||
futureBackend.await().setState(
|
||||
tunnel(tunnelName) { state ->
|
||||
scope.launch(Dispatchers.Main) {
|
||||
Log.i(TAG, "onStateChange - $state")
|
||||
updateStageFromState(state)
|
||||
}
|
||||
}, Tunnel.State.DOWN, config
|
||||
)
|
||||
Log.i(TAG, "Disconnect - success!")
|
||||
flutterSuccess(result, "")
|
||||
} catch (e: BackendException) {
|
||||
Log.e(TAG, "Disconnect - BackendException - ERROR - ${e.reason}", e)
|
||||
flutterError(result, e.reason.toString())
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "Disconnect - Can't disconnect from tunnel: ${e.message}")
|
||||
flutterError(result, e.message.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun connect(wgQuickConfig: String, result: Result) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
if (!havePermission) {
|
||||
checkPermission()
|
||||
throw Exception("Permissions are not given")
|
||||
}
|
||||
updateStage("prepare")
|
||||
val inputStream = ByteArrayInputStream(wgQuickConfig.toByteArray())
|
||||
config = org.amnezia.awg.config.Config.parse(inputStream)
|
||||
updateStage("connecting")
|
||||
futureBackend.await().setState(
|
||||
tunnel(tunnelName) { state ->
|
||||
scope.launch(Dispatchers.Main) {
|
||||
Log.i(TAG, "onStateChange - $state")
|
||||
updateStageFromState(state)
|
||||
}
|
||||
}, Tunnel.State.UP, config
|
||||
)
|
||||
Log.i(TAG, "Connect - success!")
|
||||
flutterSuccess(result, "")
|
||||
} catch (e: BackendException) {
|
||||
Log.e(TAG, "Connect - BackendException - ERROR - ${e.reason}", e)
|
||||
flutterError(result, e.reason.toString())
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "Connect - Can't connect to tunnel: $e", e)
|
||||
flutterError(result, e.message.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTunnel(localizedDescription: String, result: Result) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
if (Tunnel.isNameInvalid(localizedDescription)) {
|
||||
flutterError(result, "Invalid Name")
|
||||
return@launch
|
||||
}
|
||||
tunnelName = localizedDescription
|
||||
checkPermission()
|
||||
result.success(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPermission() {
|
||||
val intent = android.net.VpnService.prepare(this.activity)
|
||||
if (intent != null) {
|
||||
havePermission = false
|
||||
this.activity?.startActivityForResult(intent, PERMISSIONS_REQUEST_CODE)
|
||||
} else {
|
||||
havePermission = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel.setMethodCallHandler(null)
|
||||
events.setStreamHandler(null)
|
||||
isVpnChecked = false
|
||||
}
|
||||
|
||||
private fun tunnel(name: String, callback: StateChangeCallback? = null): WireGuardTunnel {
|
||||
if (tunnel == null) {
|
||||
tunnel = WireGuardTunnel(name, callback)
|
||||
}
|
||||
return tunnel as WireGuardTunnel
|
||||
}
|
||||
}
|
||||
|
||||
typealias StateChangeCallback = (Tunnel.State) -> Unit
|
||||
|
||||
class WireGuardTunnel(
|
||||
private val name: String, private val onStateChanged: StateChangeCallback? = null
|
||||
) : Tunnel {
|
||||
|
||||
override fun getName() = name
|
||||
|
||||
override fun onStateChange(newState: Tunnel.State) {
|
||||
onStateChanged?.invoke(newState)
|
||||
}
|
||||
|
||||
override fun isIpv4ResolutionPreferred(): Boolean? = true
|
||||
|
||||
override fun isMetered(): Boolean? = false
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:process_run/shell.dart';
|
||||
|
||||
import '../wireguard_flutter_platform_interface.dart';
|
||||
|
||||
class WireGuardFlutterLinux extends WireGuardFlutterInterface {
|
||||
String? name;
|
||||
File? configFile;
|
||||
|
||||
VpnStage _stage = VpnStage.noConnection;
|
||||
final _stageController = StreamController<VpnStage>.broadcast();
|
||||
void _setStage(VpnStage stage) {
|
||||
_stage = stage;
|
||||
_stageController.add(stage);
|
||||
}
|
||||
|
||||
final shell = Shell(runInShell: true, verbose: kDebugMode);
|
||||
|
||||
@override
|
||||
Future<void> initialize({required String interfaceName}) async {
|
||||
name = interfaceName.replaceAll(' ', '_');
|
||||
await refreshStage();
|
||||
}
|
||||
|
||||
Future<String> get filePath async {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
return '${tempDir.path}${Platform.pathSeparator}$name.conf';
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> startVpn({
|
||||
required String serverAddress,
|
||||
required String wgQuickConfig,
|
||||
required String providerBundleIdentifier,
|
||||
}) async {
|
||||
final isAlreadyConnected = await isConnected();
|
||||
if (!isAlreadyConnected) {
|
||||
_setStage(VpnStage.preparing);
|
||||
} else {
|
||||
debugPrint('Already connected');
|
||||
}
|
||||
|
||||
try {
|
||||
configFile = await File(await filePath).create();
|
||||
await configFile!.writeAsString(wgQuickConfig);
|
||||
} on PathAccessException {
|
||||
debugPrint('Denied to write file. Trying to start interface');
|
||||
if (isAlreadyConnected) {
|
||||
return _setStage(VpnStage.connected);
|
||||
}
|
||||
|
||||
try {
|
||||
await shell.run('sudo wg-quick up $name');
|
||||
} catch (_) {
|
||||
} finally {
|
||||
_setStage(VpnStage.denied);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAlreadyConnected) {
|
||||
_setStage(VpnStage.connecting);
|
||||
await shell.run('sudo wg-quick up ${configFile?.path ?? await filePath}');
|
||||
_setStage(VpnStage.connected);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stopVpn() async {
|
||||
assert(
|
||||
(await isConnected()),
|
||||
'Bad state: vpn has not been started. Call startVpn',
|
||||
);
|
||||
_setStage(VpnStage.disconnecting);
|
||||
try {
|
||||
await shell
|
||||
.run('sudo wg-quick down ${configFile?.path ?? (await filePath)}');
|
||||
} catch (e) {
|
||||
await refreshStage();
|
||||
rethrow;
|
||||
}
|
||||
await refreshStage();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<VpnStage> stage() async => _stage;
|
||||
|
||||
@override
|
||||
Stream<VpnStage> get vpnStageSnapshot => _stageController.stream;
|
||||
|
||||
@override
|
||||
Future<void> refreshStage() async {
|
||||
if (await isConnected()) {
|
||||
_setStage(VpnStage.connected);
|
||||
} else if (name == null) {
|
||||
_setStage(VpnStage.waitingConnection);
|
||||
} else if (configFile == null) {
|
||||
_setStage(VpnStage.noConnection);
|
||||
} else {
|
||||
_setStage(VpnStage.disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isConnected() async {
|
||||
assert(
|
||||
name != null,
|
||||
'Bad state: not initialized. Call "initialize" before calling this command',
|
||||
);
|
||||
final processResultList = await shell.run('sudo wg');
|
||||
final process = processResultList.first;
|
||||
return process.outLines.any((line) => line.trim() == 'interface: $name');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:wireguard_flutter/linux/wireguard_flutter_linux.dart';
|
||||
import 'package:wireguard_flutter/wireguard_flutter_method_channel.dart';
|
||||
|
||||
import 'wireguard_flutter_platform_interface.dart';
|
||||
|
||||
export 'wireguard_flutter_platform_interface.dart' show VpnStage;
|
||||
|
||||
class WireGuardFlutter extends WireGuardFlutterInterface {
|
||||
static WireGuardFlutterInterface? __instance;
|
||||
static WireGuardFlutterInterface get _instance => __instance!;
|
||||
static WireGuardFlutterInterface get instance {
|
||||
registerWith();
|
||||
return _instance;
|
||||
}
|
||||
|
||||
static void registerWith() {
|
||||
if (__instance == null) {
|
||||
if (kIsWeb) {
|
||||
throw UnsupportedError('The web platform is not supported');
|
||||
} else if (Platform.isLinux) {
|
||||
__instance = WireGuardFlutterLinux();
|
||||
} else {
|
||||
__instance = WireGuardFlutterMethodChannel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WireGuardFlutter._();
|
||||
|
||||
@override
|
||||
Stream<VpnStage> get vpnStageSnapshot => _instance.vpnStageSnapshot;
|
||||
|
||||
@override
|
||||
Future<void> initialize({required String interfaceName}) {
|
||||
return _instance.initialize(interfaceName: interfaceName);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> startVpn({
|
||||
required String serverAddress,
|
||||
required String wgQuickConfig,
|
||||
required String providerBundleIdentifier,
|
||||
}) async {
|
||||
return _instance.startVpn(
|
||||
serverAddress: serverAddress,
|
||||
wgQuickConfig: wgQuickConfig,
|
||||
providerBundleIdentifier: providerBundleIdentifier,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stopVpn() => _instance.stopVpn();
|
||||
|
||||
@override
|
||||
Future<void> refreshStage() => _instance.refreshStage();
|
||||
|
||||
@override
|
||||
Future<VpnStage> stage() => _instance.stage();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'wireguard_flutter_platform_interface.dart';
|
||||
|
||||
class WireGuardFlutterMethodChannel extends WireGuardFlutterInterface {
|
||||
static const _methodChannelVpnControl =
|
||||
"billion.group.wireguard_flutter/wgcontrol";
|
||||
static const _methodChannel = MethodChannel(_methodChannelVpnControl);
|
||||
static const _eventChannelVpnStage =
|
||||
'billion.group.wireguard_flutter/wgstage';
|
||||
static const _eventChannel = EventChannel(_eventChannelVpnStage);
|
||||
|
||||
@override
|
||||
Stream<VpnStage> get vpnStageSnapshot =>
|
||||
_eventChannel.receiveBroadcastStream().map(
|
||||
(event) => event == VpnStage.denied.code
|
||||
? VpnStage.disconnected
|
||||
: VpnStage.values.firstWhere(
|
||||
(stage) => stage.code == event,
|
||||
orElse: () => VpnStage.noConnection,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> initialize({required String interfaceName}) {
|
||||
return _methodChannel.invokeMethod("initialize", {
|
||||
"localizedDescription": interfaceName,
|
||||
"win32ServiceName": interfaceName,
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> startVpn({
|
||||
required String serverAddress,
|
||||
required String wgQuickConfig,
|
||||
required String providerBundleIdentifier,
|
||||
}) async {
|
||||
return _methodChannel.invokeMethod("start", {
|
||||
"serverAddress": serverAddress,
|
||||
"wgQuickConfig": wgQuickConfig,
|
||||
"providerBundleIdentifier": providerBundleIdentifier,
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stopVpn() => _methodChannel.invokeMethod('stop');
|
||||
|
||||
@override
|
||||
Future<void> refreshStage() => _methodChannel.invokeMethod("refresh");
|
||||
|
||||
@override
|
||||
Future<VpnStage> stage() => _methodChannel.invokeMethod("stage").then(
|
||||
(value) => value != null
|
||||
? VpnStage.values.firstWhere(
|
||||
(stage) => stage.code == value.toString(),
|
||||
orElse: () => VpnStage.disconnected,
|
||||
)
|
||||
: VpnStage.disconnected,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
abstract class WireGuardFlutterInterface {
|
||||
Stream<VpnStage> get vpnStageSnapshot;
|
||||
|
||||
Future<void> initialize({required String interfaceName});
|
||||
|
||||
Future<void> startVpn({
|
||||
required String serverAddress,
|
||||
required String wgQuickConfig,
|
||||
required String providerBundleIdentifier,
|
||||
});
|
||||
|
||||
Future<void> stopVpn();
|
||||
|
||||
Future<void> refreshStage();
|
||||
Future<VpnStage> stage();
|
||||
Future<bool> isConnected() =>
|
||||
stage().then((stage) => stage == VpnStage.connected);
|
||||
}
|
||||
|
||||
enum VpnStage {
|
||||
connected('connected'),
|
||||
connecting('connecting'),
|
||||
disconnecting('disconnecting'),
|
||||
disconnected('disconnected'),
|
||||
waitingConnection('wait_connection'),
|
||||
authenticating('authenticating'),
|
||||
reconnect('reconnect'),
|
||||
noConnection('no_connection'),
|
||||
preparing('prepare'),
|
||||
denied('denied'),
|
||||
exiting('exiting');
|
||||
|
||||
final String code;
|
||||
|
||||
const VpnStage(this.code);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
name: wireguard_flutter
|
||||
description: Wireguard Flutter SDK
|
||||
version: 0.1.3
|
||||
homepage: https://flutterflux.com
|
||||
repository: https://github.com/Caqil/wireguard_flutter
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
flutter: '>=3.13.7'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
path_provider: ^2.1.2
|
||||
plugin_platform_interface: ^2.0.2
|
||||
process_run: ^0.14.0+1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^2.0.0
|
||||
|
||||
flutter:
|
||||
plugin:
|
||||
platforms:
|
||||
android:
|
||||
package: billion.group.wireguard_flutter
|
||||
pluginClass: WireguardFlutterPlugin
|
||||
Reference in new issue
Block a user