Skip to content

Commit

Permalink
Merge pull request #48 from mysteriumnetwork/feature/check-tunnel-con…
Browse files Browse the repository at this point in the history
…figuration

Implement Tunnel Configuration Check Feature
  • Loading branch information
KristijanMitrik authored Dec 18, 2024
2 parents e1da550 + f7dfc8a commit 77d87c7
Show file tree
Hide file tree
Showing 10 changed files with 374 additions and 84 deletions.
25 changes: 25 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Test example APP (Debug Mode)",
"cwd": "example",
"request": "launch",
"type": "dart"
},
{
"name": "Test example APP (profile mode)",
"cwd": "example",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "Test example APP (release mode)",
"cwd": "example",
"request": "launch",
"type": "dart",
"flutterMode": "release"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ class WireguardDartPlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
call.argument<String>("tunnelName").toString(),
result
)

"checkTunnelConfiguration" -> {
checkTunnelConfiguration(result)
}
"connect" -> connect(call.argument<String>("cfg").toString(), result)
"disconnect" -> disconnect(result)
"status" -> status(result)
Expand All @@ -141,6 +143,12 @@ class WireguardDartPlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
}
}

private fun checkTunnelConfiguration(result: MethodChannel.Result) {
val intent = GoBackend.VpnService.prepare(this.activity)
havePermission = intent == null
return result.success(havePermission)
}

private fun generateKeyPair(result: Result) {
val keyPair = KeyPair()
result.success(
Expand Down
240 changes: 201 additions & 39 deletions darwin/Classes/WireguardDartPlugin.swift
Original file line number Diff line number Diff line change
@@ -1,109 +1,205 @@
import NetworkExtension
import WireGuardKit
import os

#if os(iOS)
import Flutter
import UIKit
import Flutter
import UIKit
#elseif os(macOS)
import Cocoa
import FlutterMacOS
import Cocoa
import FlutterMacOS
#else
#error("Unsupported platform")
#error("Unsupported platform")
#endif

import NetworkExtension
import os
import WireGuardKit

public class WireguardDartPlugin: NSObject, FlutterPlugin {
private var vpnManager: NETunnelProviderManager?

var vpnStatus: NEVPNStatus {
vpnManager?.connection.status ?? NEVPNStatus.invalid
}

public static func register(with registrar: FlutterPluginRegistrar) {
#if os(iOS)
let messenger = registrar.messenger()
let messenger = registrar.messenger()
#else
let messenger = registrar.messenger
let messenger = registrar.messenger
#endif
let channel = FlutterMethodChannel(name: "wireguard_dart", binaryMessenger: messenger)
let channel = FlutterMethodChannel(
name: "wireguard_dart", binaryMessenger: messenger)

let instance = WireguardDartPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)

let statusChannel = FlutterEventChannel(name: "wireguard_dart/status", binaryMessenger: messenger)
let statusChannel = FlutterEventChannel(
name: "wireguard_dart/status", binaryMessenger: messenger)
statusChannel.setStreamHandler(ConnectionStatusObserver())
}

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
public func handle(
_ call: FlutterMethodCall, result: @escaping FlutterResult
) {
switch call.method {
case "nativeInit":
result("")
case "generateKeyPair":
let privateKey = PrivateKey()
let privateKeyResponse: [String: Any] = [
"privateKey": privateKey.base64Key,
"publicKey": privateKey.publicKey.base64Key
"publicKey": privateKey.publicKey.base64Key,
]
result(privateKeyResponse)
case "setupTunnel":
Logger.main.debug("handle setupTunnel")
guard let args = call.arguments as? [String: Any], args["bundleId"] != nil else {
result(nativeFlutterError(message: "required argument: 'bundleId'"))
guard let args = call.arguments as? [String: Any],
args["bundleId"] != nil
else {
result(
nativeFlutterError(message: "required argument: 'bundleId'")
)
return
}
guard let bundleId = args["bundleId"] as? String, !bundleId.isEmpty else {
result(nativeFlutterError(message: "required argument: 'bundleId'"))
guard let bundleId = args["bundleId"] as? String, !bundleId.isEmpty
else {
result(
nativeFlutterError(message: "required argument: 'bundleId'")
)
return
}
guard let tunnelName = args["tunnelName"] as? String, !tunnelName.isEmpty else {
result(nativeFlutterError(message: "required argument: 'tunnelName'"))
guard let tunnelName = args["tunnelName"] as? String,
!tunnelName.isEmpty
else {
result(
nativeFlutterError(
message: "required argument: 'tunnelName'"))
return
}
Logger.main.debug("Tunnel bundle ID: \(bundleId), name: \(tunnelName)")
Logger.main.debug(
"Tunnel bundle ID: \(bundleId), name: \(tunnelName)")
Task {
do {
vpnManager = try await setupProviderManager(bundleId: bundleId, tunnelName: tunnelName)
vpnManager = try await setupProviderManager(
bundleId: bundleId, tunnelName: tunnelName)
Logger.main.debug("Tunnel setup OK")
result("")
} catch {
Logger.main.error("Tunnel setup ERROR: \(error)")
result(nativeFlutterError(message: "could not setup VPN tunnel: \(error)"))
result(
nativeFlutterError(
message: "could not setup VPN tunnel: \(error)"))
return
}
}
case "connect":
Logger.main.debug("handle connect")
let cfg: String
if let args = call.arguments as? [String: Any],
let argCfg = args["cfg"] as? String {
let argCfg = args["cfg"] as? String
{
cfg = argCfg
} else {
Logger.main.error("Required argument 'cfg' not provided")
result(nativeFlutterError(message: "required argument: 'cfg'"))
return
}
guard let mgr = vpnManager else {
Logger.main.error("Tunnel not initialized, missing 'vpnManager'")
result(nativeFlutterError(message: "tunnel not initialized, missing 'vpnManager'"))
Logger.main.error(
"Tunnel not initialized, missing 'vpnManager'")
result(
nativeFlutterError(
message: "tunnel not initialized, missing 'vpnManager'")
)
return
}
Logger.main.debug("Connection configuration: \(cfg)")
Task {
do {
if !mgr.isEnabled {
mgr.isEnabled = true
try await mgr.saveToPreferences()
try await mgr.loadFromPreferences()

}

try mgr.connection.startVPNTunnel(options: [
"cfg": cfg as NSObject
])
Logger.main.debug("Start VPN tunnel OK")
result("")
} catch let error as NEVPNError {
switch error.code {
case .configurationInvalid:
Logger.main.error(
"Start VPN tunnel ERROR: Configuration is invalid")
result(
nativeFlutterError(
message:
"could not start VPN tunnel: Configuration is invalid"
))
case .configurationDisabled:
Logger.main.error(
"Start VPN tunnel ERROR: Configuration is disabled")
result(
nativeFlutterError(
message:
"could not start VPN tunnel: Configuration is disabled"
))
case .connectionFailed:
Logger.main.error(
"Start VPN tunnel ERROR: Connection failed")
result(
nativeFlutterError(
message:
"could not start VPN tunnel: Connection failed"
))
case .configurationStale:
Logger.main.error(
"Start VPN tunnel ERROR: Configuration is stale")
result(
nativeFlutterError(
message:
"could not start VPN tunnel: Configuration is stale"
))
case .configurationReadWriteFailed:
Logger.main.error(
"Start VPN tunnel ERROR: Configuration read/write failed"
)
result(
nativeFlutterError(
message:
"could not start VPN tunnel: Configuration read/write failed"
))
case .configurationUnknown:
Logger.main.error(
"Start VPN tunnel ERROR: Configuration unknown")
result(
nativeFlutterError(
message:
"could not start VPN tunnel: Configuration unknown"
))
@unknown default:
Logger.main.error(
"Start VPN tunnel ERROR: Unknown error")
result(
nativeFlutterError(
message:
"could not start VPN tunnel: Unknown error")
)
}
} catch {
Logger.main.error("Start VPN tunnel ERROR: \(error)")
result(nativeFlutterError(message: "could not start VPN tunnel: \(error)"))
result(
nativeFlutterError(
message: "could not start VPN tunnel: \(error)"))
}
}
case "disconnect":
guard let mgr = vpnManager else {
Logger.main.error("Tunnel not initialized, missing 'vpnManager'")
result(nativeFlutterError(message: "tunnel not initialized, missing 'vpnManager'"))
Logger.main.error(
"Tunnel not initialized, missing 'vpnManager'")
result(
nativeFlutterError(
message: "tunnel not initialized, missing 'vpnManager'")
)
return
}
Task {
Expand All @@ -112,30 +208,65 @@ public class WireguardDartPlugin: NSObject, FlutterPlugin {
result("")
}
case "status":
guard vpnManager != nil else {
Logger.main.error("Tunnel not initialized, missing 'vpnManager'")
result(nativeFlutterError(message: "tunnel not initialized, missing 'vpnManager'"))
if vpnManager != nil {
Task {
result(
ConnectionStatus.fromNEVPNStatus(status: vpnStatus)
.string())
}
} else {
result(ConnectionStatus.unknown.string())
}
case "checkTunnelConfiguration":
guard let args = call.arguments as? [String: Any],
let bundleId = args["bundleId"] as? String, !bundleId.isEmpty
else {
result(
nativeFlutterError(message: "required argument: 'bundleId'")
)
return
}
Task {
result(ConnectionStatus.fromNEVPNStatus(status: vpnStatus).string())
guard let args = call.arguments as? [String: Any],
let tunnelName = args["tunnelName"] as? String,
!tunnelName.isEmpty
else {
result(
nativeFlutterError(
message: "required argument: 'tunnelName'")
)
return
}
checkTunnelConfiguration(bundleId: bundleId, tunnelName: tunnelName)
{ manager in
if let vpnManager = manager {
self.vpnManager = vpnManager
Logger.main.debug("Tunnel is set up and existing")
result(true)
} else {
Logger.main.debug("Tunnel is not set up")
result(false)
}
}

default:
result(FlutterMethodNotImplemented)
}
}

func setupProviderManager(bundleId: String, tunnelName: String) async throws -> NETunnelProviderManager {
func setupProviderManager(bundleId: String, tunnelName: String) async throws
-> NETunnelProviderManager
{
let mgrs = try await NETunnelProviderManager.loadAllFromPreferences()
let existingMgr = mgrs.first(where: {
($0.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == bundleId
($0.protocolConfiguration as? NETunnelProviderProtocol)?
.providerBundleIdentifier == bundleId
})
let mgr = existingMgr ?? NETunnelProviderManager()

mgr.localizedDescription = tunnelName
let proto = NETunnelProviderProtocol()
proto.providerBundleIdentifier = bundleId
proto.serverAddress = "" // must be non-null
proto.serverAddress = "" // must be non-null
mgr.protocolConfiguration = proto
mgr.isEnabled = true

Expand All @@ -144,4 +275,35 @@ public class WireguardDartPlugin: NSObject, FlutterPlugin {

return mgr
}

func isVpnManagerConfigured(bundleId: String, tunnelName: String)
async throws -> NETunnelProviderManager?
{
// Load all managers from preferences
let mgrs = try await NETunnelProviderManager.loadAllFromPreferences()
if let existingMgr = mgrs.first(where: {
($0.protocolConfiguration as? NETunnelProviderProtocol)?
.providerBundleIdentifier == bundleId
}) {
return existingMgr
}
return nil
}

func checkTunnelConfiguration(
bundleId: String, tunnelName: String,
result: @escaping (NETunnelProviderManager?) -> Void
) {
Task {
do {
let mgr = try await isVpnManagerConfigured(
bundleId: bundleId, tunnelName: tunnelName)
result(mgr)
} catch {
Logger.main.error(
"Error checking tunnel configuration: \(error)")
result(nil)
}
}
}
}
Loading

0 comments on commit 77d87c7

Please sign in to comment.