Skip to content
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
2 changes: 1 addition & 1 deletion ios/Runner.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

103 changes: 64 additions & 39 deletions ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,50 +1,75 @@
import Flutter
import UIKit
///VPN
///import Foundation
import NetworkExtension

class VpnConfigurator {
static func setupTunnel() {
let manager = NEVPNManager.shared()
manager.loadFromPreferences { error in
if let error = error {
print("Failed to load VPN preferences: \(error)")
return
}
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)

let proto = NETunnelProviderProtocol()
proto.providerBundleIdentifier = "com.vpnclient.VPNclientTunnel"
proto.serverAddress = "VPNclient"
let controller = window?.rootViewController as! FlutterViewController
let vpnChannel = FlutterMethodChannel(
name: "com.vpnclient/vpn_control",
binaryMessenger: controller.binaryMessenger
)

manager.protocolConfiguration = proto
manager.localizedDescription = "VPNclient"
manager.isEnabled = true
vpnChannel.setMethodCallHandler { [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) in
guard let self = self else { return }
switch call.method {
case "setupVPN":
guard let args = call.arguments as? [String: String],
let tunAddr = args["tunAddr"],
let tunMask = args["tunMask"],
let tunDns = args["tunDns"],
let socks5Proxy = args["socks5Proxy"] else {
result(FlutterError(code: "INVALID_ARGS", message: "Invalid arguments", details: nil))
return
}
print("AppDelegate: Setting up VPN with tunAddr=\(tunAddr), socks5Proxy=\(socks5Proxy)")
let vpnManager = VPNManager.sharedInstance ?? VPNManager()
vpnManager.setupVPNConfiguration(tunAddr: tunAddr, tunMask: tunMask, tunDns: tunDns, socks5Proxy: socks5Proxy) { error in
if let error = error {
print("AppDelegate: Setup VPN failed: \(error.localizedDescription)")
result(FlutterError(code: "SETUP_FAILED", message: error.localizedDescription, details: nil))
} else {
print("AppDelegate: Setup VPN succeeded")
result(nil)
}
}

manager.saveToPreferences { error in
if let error = error {
print("Failed to save VPN configuration: \(error)")
} else {
print("VPN configuration saved successfully.")
case "startVPN":
let vpnManager = VPNManager.sharedInstance ?? VPNManager()
vpnManager.startVPN { error in
if let error = error {
print("AppDelegate: Start VPN failed: \(error.localizedDescription)")
result(FlutterError(code: "START_FAILED", message: error.localizedDescription, details: nil))
} else {
print("AppDelegate: Start VPN succeeded")
result(nil)
}
}
}
}
}
}
///VPN

case "stopVPN":
let vpnManager = VPNManager.sharedInstance ?? VPNManager()
vpnManager.stopVPN {
print("AppDelegate: Stop VPN succeeded")
result(nil)
}

case "getVPNStatus":
let vpnManager = VPNManager.sharedInstance ?? VPNManager()
let status = vpnManager.vpnStatus.description
print("AppDelegate: VPN status: \(status)")
result(status)

@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
///vpn
VpnConfigurator.setupTunnel()
///vpn
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
default:
result(FlutterMethodNotImplemented)
}
}

return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
13 changes: 8 additions & 5 deletions ios/Runner/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
Expand All @@ -14,13 +16,14 @@
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-16" y="-40"/>
</scene>
</scenes>
</document>
9 changes: 9 additions & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand All @@ -50,5 +55,9 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>allow-vpn</string>
</array>
</dict>
</plist>
10 changes: 10 additions & 0 deletions ios/Runner/Runner.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
</dict>
</plist>
15 changes: 1 addition & 14 deletions ios/Runner/RunnerRelease.entitlements
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>dns-settings</string>
<string>packet-tunnel-provider</string>
<string>dns-proxy</string>
<string>app-proxy-provider</string>
<string>content-filter-provider</string>
</array>
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>allow-vpn</string>
</array>
</dict>
<dict/>
</plist>
180 changes: 180 additions & 0 deletions ios/Runner/VPNManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import NetworkExtension
import Foundation

class VPNManager {
private var vpnManager: NETunnelProviderManager?
private let profileName = "Controller"
private var isInitialized = false
private var initializationCompletion: ((Error?) -> Void)?
static var sharedInstance: VPNManager?

init() {
print("VPNManager: Initializing...")
loadVPNManager { error in
if let error = error {
print("VPNManager: Initialization failed with error: \(error.localizedDescription)")
} else {
print("VPNManager: Initialization completed successfully")
}
self.isInitialized = true
self.initializationCompletion?(error)
self.initializationCompletion = nil
}
}

private func loadVPNManager(completion: @escaping (Error?) -> Void) {
print("VPNManager: Loading existing VPN managers...")
NETunnelProviderManager.loadAllFromPreferences { managers, error in
if let error = error {
print("VPNManager: Error loading VPN managers: \(error.localizedDescription)")
self.vpnManager = NETunnelProviderManager()
self.vpnManager?.localizedDescription = self.profileName
print("VPNManager: Created new VPN profile due to error: \(self.profileName)")
completion(nil)
return
}

print("VPNManager: Loaded \(managers?.count ?? 0) VPN managers")
if let existingManager = managers?.first(where: { $0.localizedDescription == self.profileName }) {
self.vpnManager = existingManager
print("VPNManager: Found existing VPN profile: \(self.profileName)")
} else {
self.vpnManager = NETunnelProviderManager()
self.vpnManager?.localizedDescription = self.profileName
print("VPNManager: Created new VPN profile: \(self.profileName)")
}
print("VPNManager: vpnManager is \(self.vpnManager != nil ? "set" : "nil")")
completion(nil)
}

// Таймаут для loadAllFromPreferences
DispatchQueue.global().asyncAfter(deadline: .now() + 5) {
if !self.isInitialized {
print("VPNManager: loadAllFromPreferences timed out")
self.vpnManager = NETunnelProviderManager()
self.vpnManager?.localizedDescription = self.profileName
print("VPNManager: Created new VPN profile due to timeout: \(self.profileName)")
self.isInitialized = true
completion(nil)
}
}
}

private func waitForInitialization(completion: @escaping (Error?) -> Void) {
if isInitialized {
print("VPNManager: Already initialized")
completion(nil)
return
}
print("VPNManager: Waiting for initialization...")
initializationCompletion = completion
}

func setupVPNConfiguration(tunAddr: String, tunMask: String, tunDns: String, socks5Proxy: String, completion: @escaping (Error?) -> Void) {
print("VPNManager: Setting up VPN configuration with tunAddr=\(tunAddr), tunMask=\(tunMask), tunDns=\(tunDns), socks5Proxy=\(socks5Proxy)")
waitForInitialization { error in
if let error = error {
completion(error)
return
}
guard let vpnManager = self.vpnManager else {
print("VPNManager: VPN Manager not initialized")
completion(NSError(domain: "VPNError", code: -1, userInfo: [NSLocalizedDescriptionKey: "VPN Manager not initialized"]))
return
}

vpnManager.loadFromPreferences { error in
if let error = error {
print("VPNManager: Load preferences error: \(error.localizedDescription)")
completion(error)
return
}

let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.providerBundleIdentifier = "click.vpnclient.VPNclientTunnel"
tunnelProtocol.serverAddress = socks5Proxy
tunnelProtocol.providerConfiguration = [
"tunAddr": tunAddr,
"tunMask": tunMask,
"tunDns": tunDns,
"socks5Proxy": socks5Proxy
]

vpnManager.protocolConfiguration = tunnelProtocol
vpnManager.isEnabled = true
vpnManager.isOnDemandEnabled = false

print("VPNManager: Saving VPN configuration...")
vpnManager.saveToPreferences { error in
if let error = error {
print("VPNManager: Save preferences error: \(error.localizedDescription)")
completion(error)
} else {
print("VPNManager: VPN configuration saved successfully")
completion(nil)
}
}
}
}
}

func startVPN(completion: @escaping (Error?) -> Void) {
print("VPNManager: Starting VPN...")
waitForInitialization { error in
if let error = error {
completion(error)
return
}
guard let vpnManager = self.vpnManager else {
print("VPNManager: VPN Manager not initialized")
completion(NSError(domain: "VPNError", code: -1, userInfo: [NSLocalizedDescriptionKey: "VPN Manager not initialized"]))
return
}
vpnManager.loadFromPreferences { error in
if let error = error {
print("VPNManager: Load preferences error before start: \(error.localizedDescription)")
completion(error)
return
}
do {
try vpnManager.connection.startVPNTunnel()
print("VPNManager: VPN tunnel started successfully")
completion(nil)
} catch {
print("VPNManager: Start VPN error: \(error.localizedDescription)")
completion(error)
}
}
}
}

func stopVPN(completion: @escaping () -> Void) {
print("VPNManager: Stopping VPN...")
waitForInitialization { _ in
self.vpnManager?.connection.stopVPNTunnel()
completion()
}
}

var vpnStatus: NEVPNStatus {
let status = vpnManager?.connection.status ?? .invalid
print("VPNManager: Current VPN status: \(status.description)")
return status
}

static func cleanup() {
sharedInstance = nil
}
}

extension NEVPNStatus {
var description: String {
switch self {
case .disconnected: return "Disconnected"
case .connecting: return "Connecting..."
case .connected: return "Connected"
case .disconnecting: return "Disconnecting..."
default: return "Not Added Profile"
}
}
}
Loading
Loading