Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Tunnel Configuration Check Feature #48

Merged
merged 8 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading