From d89cf5479c55b3c5d8afb1fb4d97f42e88968e79 Mon Sep 17 00:00:00 2001 From: Ox Cart Date: Tue, 3 Oct 2023 16:27:26 -0500 Subject: [PATCH] Formatted swift code with swift-format --- ios/LanternTests/LanternTests.swift | 20 +- ios/Runner/AppDelegate.swift | 129 ++-- .../Lantern/Core/Db/SubscriberRequest.swift | 235 ++++---- .../Lantern/Core/Extension/Result.swift | 10 +- .../Lantern/Core/Network/DnsDetector.swift | 8 +- .../Lantern/Core/Vpn/FlashlightManager.swift | 44 +- .../Core/Vpn/FlashlightManagerExtension.swift | 69 ++- .../Lantern/Core/Vpn/MockVPNManager.swift | 50 +- ios/Runner/Lantern/Core/Vpn/VPNBase.swift | 10 +- ios/Runner/Lantern/Core/Vpn/VPNManager.swift | 393 ++++++------ ios/Runner/Lantern/Core/Vpn/VpnHelper.swift | 564 +++++++++--------- ios/Runner/Lantern/Models/BaseModel.swift | 382 ++++++------ ios/Runner/Lantern/Models/LanternModel.swift | 78 +-- .../Lantern/Models/MessagingModel.swift | 16 +- .../Lantern/Models/NavigationModel.swift | 57 +- ios/Runner/Lantern/Models/SessionModel.swift | 14 +- ios/Runner/Lantern/Models/VpnModel.swift | 92 +-- ios/Runner/Lantern/Utils/Constants.swift | 232 +++---- ios/Runner/Lantern/Utils/Event.swift | 68 ++- ios/Runner/Lantern/Utils/Events.swift | 4 +- ios/Runner/Lantern/Utils/FileUtils.swift | 52 +- ios/Runner/Lantern/Utils/JsonUtils.swift | 16 +- .../Utils/LoadingIndicatorManager.swift | 44 +- ios/Runner/Lantern/Utils/Logger.swift | 30 +- ios/Runner/Lantern/Utils/Process.swift | 20 +- ios/Runner/Lantern/Utils/RunningEnv.swift | 62 +- .../Utils/UserNotificationsManager.swift | 172 +++--- ios/Tunnel/NEProviderStopReason.swift | 78 +-- ios/Tunnel/PacketTunnelProvider.swift | 501 ++++++++-------- ios/Tunnel/UDPConn.swift | 98 +-- 30 files changed, 1821 insertions(+), 1727 deletions(-) diff --git a/ios/LanternTests/LanternTests.swift b/ios/LanternTests/LanternTests.swift index c8635f133..145dbe993 100644 --- a/ios/LanternTests/LanternTests.swift +++ b/ios/LanternTests/LanternTests.swift @@ -7,18 +7,18 @@ import Internalsdk import SQLite -@testable import Runner import XCTest +@testable import Runner final class LanternTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + } diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index cb8129340..07653967c 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,8 +1,8 @@ -import UIKit -import SQLite import Flutter import Internalsdk +import SQLite import Toast_Swift +import UIKit // Before Commit Run linter // swiftlint autocorrect --format @@ -11,77 +11,78 @@ import Toast_Swift @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { - // Flutter Properties - var flutterViewController: FlutterViewController! - var flutterbinaryMessenger: FlutterBinaryMessenger! - // Model Properties - var sessionModel: SessionModel! - var lanternModel: LanternModel! - var navigationModel: NavigationModel! - var vpnModel: VpnModel! - var messagingModel: MessagingModel! - - // IOS - var loadingManager: LoadingIndicatorManager? + // Flutter Properties + var flutterViewController: FlutterViewController! + var flutterbinaryMessenger: FlutterBinaryMessenger! + // Model Properties + var sessionModel: SessionModel! + var lanternModel: LanternModel! + var navigationModel: NavigationModel! + var vpnModel: VpnModel! + var messagingModel: MessagingModel! + + // IOS + var loadingManager: LoadingIndicatorManager? - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - initializeFlutterComponents() - do { - try setupAppComponents() - } catch { - logger.error("Unexpected error setting up app components: \(error)") - exit(1) - } - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + initializeFlutterComponents() + do { + try setupAppComponents() + } catch { + logger.error("Unexpected error setting up app components: \(error)") + exit(1) } + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } - // Flutter related stuff - private func initializeFlutterComponents() { - flutterViewController = window?.rootViewController as! FlutterViewController - flutterbinaryMessenger = flutterViewController.binaryMessenger} + // Flutter related stuff + private func initializeFlutterComponents() { + flutterViewController = window?.rootViewController as! FlutterViewController + flutterbinaryMessenger = flutterViewController.binaryMessenger + } - // Intlize this GO model and callback - private func setupAppComponents() throws { - try setupModels() - startUpSequency() - setupLoadingBar() - } + // Intlize this GO model and callback + private func setupAppComponents() throws { + try setupModels() + startUpSequency() + setupLoadingBar() + } - // Init all the models - private func setupModels() throws { - logger.log("setupModels method called") - sessionModel=try SessionModel(flutterBinary: flutterbinaryMessenger) - lanternModel=LanternModel(flutterBinary: flutterbinaryMessenger) - vpnModel=try VpnModel(flutterBinary: flutterbinaryMessenger, vpnBase: VPNManager.appDefault) - navigationModel=NavigationModel(flutterBinary: flutterbinaryMessenger) - messagingModel=try MessagingModel(flutterBinary: flutterbinaryMessenger) - } + // Init all the models + private func setupModels() throws { + logger.log("setupModels method called") + sessionModel = try SessionModel(flutterBinary: flutterbinaryMessenger) + lanternModel = LanternModel(flutterBinary: flutterbinaryMessenger) + vpnModel = try VpnModel(flutterBinary: flutterbinaryMessenger, vpnBase: VPNManager.appDefault) + navigationModel = NavigationModel(flutterBinary: flutterbinaryMessenger) + messagingModel = try MessagingModel(flutterBinary: flutterbinaryMessenger) + } - // Post start up - // Init all method needed for user - func startUpSequency() { - // setupLocal() - // createUser() - askNotificationPermssion() + // Post start up + // Init all method needed for user + func startUpSequency() { + // setupLocal() + // createUser() + askNotificationPermssion() - } + } - func askNotificationPermssion() { - UserNotificationsManager.shared.requestNotificationPermission { granted in - if granted { - logger.debug("Notification Permssion is granted") - } else { - logger.debug("Notification Permssion is denied") - } - } + func askNotificationPermssion() { + UserNotificationsManager.shared.requestNotificationPermission { granted in + if granted { + logger.debug("Notification Permssion is granted") + } else { + logger.debug("Notification Permssion is denied") + } } + } - func setupLoadingBar() { - loadingManager = LoadingIndicatorManager(parentView: flutterViewController.view) - } + func setupLoadingBar() { + loadingManager = LoadingIndicatorManager(parentView: flutterViewController.view) + } } diff --git a/ios/Runner/Lantern/Core/Db/SubscriberRequest.swift b/ios/Runner/Lantern/Core/Db/SubscriberRequest.swift index 9ce6ce504..7adc63247 100644 --- a/ios/Runner/Lantern/Core/Db/SubscriberRequest.swift +++ b/ios/Runner/Lantern/Core/Db/SubscriberRequest.swift @@ -5,144 +5,149 @@ // Created by jigar fumakiya on 25/08/23. // +import DBModule import Foundation import Internalsdk -import DBModule class DetailsSubscriber: InternalsdkSubscriptionRequest { - var subscriberID: String - var path: String - var updaterDelegate: DetailsSubscriberUpdater - - init(subscriberID: String, path: String, onChanges: @escaping ([String: Any], [String]) -> Void) { - self.subscriberID = subscriberID - self.path = path - self.updaterDelegate = DetailsSubscriberUpdater() - super.init() - self.id_ = subscriberID - self.pathPrefixes = path - self.receiveInitial = true - self.updater = updaterDelegate - updaterDelegate.onChangesCallback = onChanges - } + var subscriberID: String + var path: String + var updaterDelegate: DetailsSubscriberUpdater + + init(subscriberID: String, path: String, onChanges: @escaping ([String: Any], [String]) -> Void) { + self.subscriberID = subscriberID + self.path = path + self.updaterDelegate = DetailsSubscriberUpdater() + super.init() + self.id_ = subscriberID + self.pathPrefixes = path + self.receiveInitial = true + self.updater = updaterDelegate + updaterDelegate.onChangesCallback = onChanges + } } class DetailsSubscriberUpdater: NSObject, InternalsdkUpdaterModelProtocol { - var onChangesCallback: (([String: Any], [String]) -> Void)? - typealias DynamicKeysData = [String: ItemDetail] + var onChangesCallback: (([String: Any], [String]) -> Void)? + typealias DynamicKeysData = [String: ItemDetail] + + func onChanges(_ cs: InternalsdkChangeSetInterface?) throws { + guard let cs = cs else { + throw NSError( + domain: "onChangesError", code: 1, userInfo: [NSLocalizedDescriptionKey: "ChangeSet is nil"] + ) + } + let decoder = JSONDecoder() + var updatesDictionary: [String: Any] = [:] + var deletesList: [String] = [] + + // Deserialize updates + if let updatesData = cs.updatesSerialized.data(using: .utf8), !updatesData.isEmpty { + do { + let updates = try decoder.decode(DynamicKeysData.self, from: updatesData) + + updatesDictionary = Dictionary( + uniqueKeysWithValues: updates.map { (path, detail) -> (String, Any) in + return (path, detail.value.value) + }) + + } catch let jsonError { + logger.log("Error deserializing updates: \(jsonError.localizedDescription)") + throw jsonError + } + } else { + logger.log("No updatesSerialized data to parse or it's empty.") + } - func onChanges(_ cs: InternalsdkChangeSetInterface?) throws { - guard let cs = cs else { - throw NSError(domain: "onChangesError", code: 1, userInfo: [NSLocalizedDescriptionKey: "ChangeSet is nil"]) - } - let decoder = JSONDecoder() - var updatesDictionary: [String: Any] = [:] - var deletesList: [String] = [] - - // Deserialize updates - if let updatesData = cs.updatesSerialized.data(using: .utf8), !updatesData.isEmpty { - do { - let updates = try decoder.decode(DynamicKeysData.self, from: updatesData) - - updatesDictionary = Dictionary(uniqueKeysWithValues: updates.map { (path, detail) -> (String, Any) in - return (path, detail.value.value) - }) - - } catch let jsonError { - logger.log("Error deserializing updates: \(jsonError.localizedDescription)") - throw jsonError - } - } else { - logger.log("No updatesSerialized data to parse or it's empty.") - } + // Deserialize deletes + if cs.deletesSerialized.lowercased() != "null" { + if let deletesData = cs.deletesSerialized.data(using: .utf8), !deletesData.isEmpty { + do { - // Deserialize deletes - if cs.deletesSerialized.lowercased() != "null" { - if let deletesData = cs.deletesSerialized.data(using: .utf8), !deletesData.isEmpty { - do { - - deletesList = try decoder.decode([String].self, from: deletesData) - } catch let jsonError { - logger.log("Error deserializing deletes: \(jsonError.localizedDescription)") - throw jsonError - } - } else { - logger.log("No deletesSerialized data to parse or it's empty.") - } + deletesList = try decoder.decode([String].self, from: deletesData) + } catch let jsonError { + logger.log("Error deserializing deletes: \(jsonError.localizedDescription)") + throw jsonError } + } else { + logger.log("No deletesSerialized data to parse or it's empty.") + } + } - onChangesCallback?(updatesDictionary, deletesList) + onChangesCallback?(updatesDictionary, deletesList) - } + } } // Json Decode struct ItemDetail: Codable { - let path: String - let detailPath: String - let value: ValueStruct - - enum CodingKeys: String, CodingKey { - case path = "Path" - case detailPath = "DetailPath" - case value = "Value" - } + let path: String + let detailPath: String + let value: ValueStruct + + enum CodingKeys: String, CodingKey { + case path = "Path" + case detailPath = "DetailPath" + case value = "Value" + } } struct ValueStruct: Codable { - let type: Int - var value: Any? - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - type = try container.decode(Int.self, forKey: .type) - - switch type { - case ValueUtil.TYPE_BYTES: // Assuming this corresponds to bytes - // Handle bytes - let stringValue = try container.decode(String.self, forKey: .value) - value = Data(base64Encoded: stringValue) - case ValueUtil.TYPE_STRING: - value = try container.decode(String.self, forKey: .value) - case ValueUtil.TYPE_INT: - value = try container.decode(Int.self, forKey: .value) - case ValueUtil.TYPE_BOOL: - value = try container.decode(Bool.self, forKey: .value) - default: - throw DecodingError.dataCorruptedError( - forKey: .value, - in: container, - debugDescription: "Invalid type" - ) - } + let type: Int + var value: Any? + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + type = try container.decode(Int.self, forKey: .type) + + switch type { + case ValueUtil.TYPE_BYTES: // Assuming this corresponds to bytes + // Handle bytes + let stringValue = try container.decode(String.self, forKey: .value) + value = Data(base64Encoded: stringValue) + case ValueUtil.TYPE_STRING: + value = try container.decode(String.self, forKey: .value) + case ValueUtil.TYPE_INT: + value = try container.decode(Int.self, forKey: .value) + case ValueUtil.TYPE_BOOL: + value = try container.decode(Bool.self, forKey: .value) + default: + throw DecodingError.dataCorruptedError( + forKey: .value, + in: container, + debugDescription: "Invalid type" + ) } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(type, forKey: .type) - - switch type { - case ValueUtil.TYPE_BYTES: - // Handle bytes - if let dataValue = value as? Data { - try container.encode(dataValue.base64EncodedString(), forKey: .value) - } - case ValueUtil.TYPE_STRING: - try container.encode(value as? String, forKey: .value) - case ValueUtil.TYPE_INT: - try container.encode(value as? Int, forKey: .value) - case ValueUtil.TYPE_BOOL: - try container.encode(value as? Bool, forKey: .value) - default: - throw EncodingError.invalidValue(value as Any, EncodingError.Context(codingPath: [CodingKeys.value], debugDescription: "Invalid type")) - } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(type, forKey: .type) + + switch type { + case ValueUtil.TYPE_BYTES: + // Handle bytes + if let dataValue = value as? Data { + try container.encode(dataValue.base64EncodedString(), forKey: .value) + } + case ValueUtil.TYPE_STRING: + try container.encode(value as? String, forKey: .value) + case ValueUtil.TYPE_INT: + try container.encode(value as? Int, forKey: .value) + case ValueUtil.TYPE_BOOL: + try container.encode(value as? Bool, forKey: .value) + default: + throw EncodingError.invalidValue( + value as Any, + EncodingError.Context(codingPath: [CodingKeys.value], debugDescription: "Invalid type")) } + } - enum CodingKeys: String, CodingKey { - case type = "Type" - case value = "Value" - } + enum CodingKeys: String, CodingKey { + case type = "Type" + case value = "Value" + } } diff --git a/ios/Runner/Lantern/Core/Extension/Result.swift b/ios/Runner/Lantern/Core/Extension/Result.swift index bd3668c50..6e0184666 100644 --- a/ios/Runner/Lantern/Core/Extension/Result.swift +++ b/ios/Runner/Lantern/Core/Extension/Result.swift @@ -6,10 +6,10 @@ import Foundation extension Result { - var isSuccess: Bool { - switch self { - case .success: return true - case .failure: return false - } + var isSuccess: Bool { + switch self { + case .success: return true + case .failure: return false } + } } diff --git a/ios/Runner/Lantern/Core/Network/DnsDetector.swift b/ios/Runner/Lantern/Core/Network/DnsDetector.swift index 9fbee34f0..6dd9d0245 100644 --- a/ios/Runner/Lantern/Core/Network/DnsDetector.swift +++ b/ios/Runner/Lantern/Core/Network/DnsDetector.swift @@ -5,14 +5,14 @@ // Created by jigar fumakiya on 05/09/23. // +import Darwin import Foundation import NetworkExtension -import Darwin class DnsDetector { - // Since IOS does allow to read dns server from Setting - // So while creating setting we will use this as our Ip so this will become dns server IP - static let DEFAULT_DNS_SERVER = "8.8.8.8" + // Since IOS does allow to read dns server from Setting + // So while creating setting we will use this as our Ip so this will become dns server IP + static let DEFAULT_DNS_SERVER = "8.8.8.8" } diff --git a/ios/Runner/Lantern/Core/Vpn/FlashlightManager.swift b/ios/Runner/Lantern/Core/Vpn/FlashlightManager.swift index 02fe940dc..cf05d8bac 100644 --- a/ios/Runner/Lantern/Core/Vpn/FlashlightManager.swift +++ b/ios/Runner/Lantern/Core/Vpn/FlashlightManager.swift @@ -4,36 +4,36 @@ // import Foundation -import os.log import Internalsdk import UIKit +import os.log class FlashlightManager { - static let `appDefault` = FlashlightManager(constants: .appDefault) - static let `netExDefault` = FlashlightManager(constants: .netExDefault) + static let `appDefault` = FlashlightManager(constants: .appDefault) + static let `netExDefault` = FlashlightManager(constants: .netExDefault) - // Designated queue for backgrounding go-specific calls. - // Keep it as clear as possible to prevent blocking network activity. - // .userInitiated: "..for tasks that prevent the user from actively using your app." - let queue = DispatchQueue(label: "FlashlightManager", qos: .userInitiated) - let backgroundQueue = DispatchQueue(label: "FlashlightManager-background", qos: .background) - let constants: Constants + // Designated queue for backgrounding go-specific calls. + // Keep it as clear as possible to prevent blocking network activity. + // .userInitiated: "..for tasks that prevent the user from actively using your app." + let queue = DispatchQueue(label: "FlashlightManager", qos: .userInitiated) + let backgroundQueue = DispatchQueue(label: "FlashlightManager-background", qos: .background) + let constants: Constants - // MARK: Init + // MARK: Init - init(constants: Constants) { - self.constants = constants - } + init(constants: Constants) { + self.constants = constants + } - var hasCheckedForUpdate = false + var hasCheckedForUpdate = false - // MARK: Go Logging - func configureGoLoggerReturningError() -> Error? { - var error: NSError? - IosConfigureFileLogging(constants.goLogBaseURL.path, constants.targetDirectoryURL.path, &error) - return error - } + // MARK: Go Logging + func configureGoLoggerReturningError() -> Error? { + var error: NSError? + IosConfigureFileLogging(constants.goLogBaseURL.path, constants.targetDirectoryURL.path, &error) + return error + } - // set the below to a proxies.yaml with whatever proxies you want to test, will override - static let hardcodedProxies = "" + // set the below to a proxies.yaml with whatever proxies you want to test, will override + static let hardcodedProxies = "" } diff --git a/ios/Runner/Lantern/Core/Vpn/FlashlightManagerExtension.swift b/ios/Runner/Lantern/Core/Vpn/FlashlightManagerExtension.swift index 47683a7e8..c86ad34ca 100644 --- a/ios/Runner/Lantern/Core/Vpn/FlashlightManagerExtension.swift +++ b/ios/Runner/Lantern/Core/Vpn/FlashlightManagerExtension.swift @@ -4,42 +4,47 @@ // import Foundation -import UIKit import Internalsdk +import UIKit extension FlashlightManager { - // MARK: Config - func fetchConfig(userID: Int, proToken: String?, excludedIPsURL: URL? = nil, refreshProxies: Bool, _ completion: ((Result) -> Void)? = nil) { - var configError: NSError? - let configDirectory = constants.configDirectoryURL.path - let deviceID = UIDevice.current.identifierForVendor!.uuidString - - let workItem = DispatchWorkItem { - logger.debug("Calling IosConfigure") - let configResult = IosConfigure(configDirectory, userID, proToken, deviceID, refreshProxies, FlashlightManager.hardcodedProxies, &configError) - logger.debug("Called IosConfigure") - - guard configError == nil else { - assertionFailure("fetch config failed:" + configError!.localizedDescription) - completion?(.failure(configError!)) - return - } - - guard let result = configResult else { - completion?(.failure(VpnHelper.Error.unknown)) - return - } - - if let url = excludedIPsURL { - if result.vpnNeedsReconfiguring { - try! result.ipsToExcludeFromVPN.write(to: url, atomically: false, encoding: .utf8) - } - } - - completion?(.success(result.vpnNeedsReconfiguring)) + // MARK: Config + func fetchConfig( + userID: Int, proToken: String?, excludedIPsURL: URL? = nil, refreshProxies: Bool, + _ completion: ((Result) -> Void)? = nil + ) { + var configError: NSError? + let configDirectory = constants.configDirectoryURL.path + let deviceID = UIDevice.current.identifierForVendor!.uuidString + + let workItem = DispatchWorkItem { + logger.debug("Calling IosConfigure") + let configResult = IosConfigure( + configDirectory, userID, proToken, deviceID, refreshProxies, + FlashlightManager.hardcodedProxies, &configError) + logger.debug("Called IosConfigure") + + guard configError == nil else { + assertionFailure("fetch config failed:" + configError!.localizedDescription) + completion?(.failure(configError!)) + return + } + + guard let result = configResult else { + completion?(.failure(VpnHelper.Error.unknown)) + return + } + + if let url = excludedIPsURL { + if result.vpnNeedsReconfiguring { + try! result.ipsToExcludeFromVPN.write(to: url, atomically: false, encoding: .utf8) } + } - // TODO: investigate: does this have to happen on goQueue? - queue.async(execute: workItem) + completion?(.success(result.vpnNeedsReconfiguring)) } + + // TODO: investigate: does this have to happen on goQueue? + queue.async(execute: workItem) + } } diff --git a/ios/Runner/Lantern/Core/Vpn/MockVPNManager.swift b/ios/Runner/Lantern/Core/Vpn/MockVPNManager.swift index 76acab0c9..ee8c87c42 100644 --- a/ios/Runner/Lantern/Core/Vpn/MockVPNManager.swift +++ b/ios/Runner/Lantern/Core/Vpn/MockVPNManager.swift @@ -10,38 +10,38 @@ import NetworkExtension // Calls within actual VPNManager will crash on simulator. class MockVPNManager: VPNBase { - var connectionStatus: NEVPNStatus = .disconnected { - didSet { - guard oldValue != connectionStatus else { return } - didUpdateConnectionStatusCallback?(connectionStatus) - } + var connectionStatus: NEVPNStatus = .disconnected { + didSet { + guard oldValue != connectionStatus else { return } + didUpdateConnectionStatusCallback?(connectionStatus) } - var didUpdateConnectionStatusCallback: ((NEVPNStatus) -> Void)? + } + var didUpdateConnectionStatusCallback: ((NEVPNStatus) -> Void)? - // MARK: Start/Stop Tunnel + // MARK: Start/Stop Tunnel - var startTunnelError: VPNManagerError? + var startTunnelError: VPNManagerError? - func startTunnel(completion: (Result) -> Void) { - connectionStatus = .connected - completion(startTunnelError == nil ? .success(()) : .failure(startTunnelError!)) - } + func startTunnel(completion: (Result) -> Void) { + connectionStatus = .connected + completion(startTunnelError == nil ? .success(()) : .failure(startTunnelError!)) + } - func stopTunnel() { - connectionStatus = .disconnected - } + func stopTunnel() { + connectionStatus = .disconnected + } - // MARK: Message NetEx + // MARK: Message NetEx - var messageNetExError: VPNManagerError? - var messageNetExResponseData: Data? + var messageNetExError: VPNManagerError? + var messageNetExResponseData: Data? - func messageNetEx(messageData: Data, responseHandler: ((Data?) -> Void)?) throws { - if let error = messageNetExError { - throw error - } else { - let data = messageNetExResponseData - DispatchQueue.global().async { responseHandler?(data) } - } + func messageNetEx(messageData: Data, responseHandler: ((Data?) -> Void)?) throws { + if let error = messageNetExError { + throw error + } else { + let data = messageNetExResponseData + DispatchQueue.global().async { responseHandler?(data) } } + } } diff --git a/ios/Runner/Lantern/Core/Vpn/VPNBase.swift b/ios/Runner/Lantern/Core/Vpn/VPNBase.swift index 6fa9a76c7..85183e61a 100644 --- a/ios/Runner/Lantern/Core/Vpn/VPNBase.swift +++ b/ios/Runner/Lantern/Core/Vpn/VPNBase.swift @@ -6,9 +6,9 @@ import NetworkExtension protocol VPNBase: AnyObject { - var connectionStatus: NEVPNStatus { get } - var didUpdateConnectionStatusCallback: ((NEVPNStatus) -> Void)? { get set } - func startTunnel(completion: @escaping (Result) -> Void) - func stopTunnel() - func messageNetEx(messageData: Data, responseHandler: ((Data?) -> Void)?) throws + var connectionStatus: NEVPNStatus { get } + var didUpdateConnectionStatusCallback: ((NEVPNStatus) -> Void)? { get set } + func startTunnel(completion: @escaping (Result) -> Void) + func stopTunnel() + func messageNetEx(messageData: Data, responseHandler: ((Data?) -> Void)?) throws } diff --git a/ios/Runner/Lantern/Core/Vpn/VPNManager.swift b/ios/Runner/Lantern/Core/Vpn/VPNManager.swift index 42b23ccc4..f806af644 100644 --- a/ios/Runner/Lantern/Core/Vpn/VPNManager.swift +++ b/ios/Runner/Lantern/Core/Vpn/VPNManager.swift @@ -3,227 +3,236 @@ // Lantern // -import NetworkExtension import Network +import NetworkExtension enum VPNManagerError: Swift.Error { - case userDisallowedVPNConfigurations - case loadingProviderFailed - case savingProviderFailed - case unknown + case userDisallowedVPNConfigurations + case loadingProviderFailed + case savingProviderFailed + case unknown } class VPNManager: VPNBase { - static let appDefault = VPNManager(userDefaults: .standard) + static let appDefault = VPNManager(userDefaults: .standard) - // This is used to determine if we have successfully set up a VPN configuration - // If user deletes VPN config in device Settings, status becomes "Invalid" and we clear this flag. - private static let expectsSavedProviderDefaultsKey = "VPNManager.savedProvider" + // This is used to determine if we have successfully set up a VPN configuration + // If user deletes VPN config in device Settings, status becomes "Invalid" and we clear this flag. + private static let expectsSavedProviderDefaultsKey = "VPNManager.savedProvider" - private let queue: DispatchQueue = .global(qos: .userInitiated) - private var providerManager: NETunnelProviderManager? + private let queue: DispatchQueue = .global(qos: .userInitiated) + private var providerManager: NETunnelProviderManager? - var connectionStatus: NEVPNStatus = .disconnected { - didSet { - guard oldValue != connectionStatus else { return } - didUpdateConnectionStatusCallback?(connectionStatus) - } + var connectionStatus: NEVPNStatus = .disconnected { + didSet { + guard oldValue != connectionStatus else { return } + didUpdateConnectionStatusCallback?(connectionStatus) } - var didUpdateConnectionStatusCallback: ((NEVPNStatus) -> Void)? - - private let userDefaults: UserDefaults - private var expectsSavedProvider: Bool { - get { return userDefaults.bool(forKey: VPNManager.expectsSavedProviderDefaultsKey) } - set { userDefaults.set(newValue, forKey: VPNManager.expectsSavedProviderDefaultsKey) } + } + var didUpdateConnectionStatusCallback: ((NEVPNStatus) -> Void)? + + private let userDefaults: UserDefaults + private var expectsSavedProvider: Bool { + get { return userDefaults.bool(forKey: VPNManager.expectsSavedProviderDefaultsKey) } + set { userDefaults.set(newValue, forKey: VPNManager.expectsSavedProviderDefaultsKey) } + } + + // MARK: Init + private init(userDefaults: UserDefaults) { + self.userDefaults = userDefaults + subscribeToVPNStatusNotifications() + if expectsSavedProvider { + // setUpProvider() will prompt VPN Configuration permission if not granted, + // so only allow this when user has already set up, and we want VPN Status ASAP. + setUpProvider(completion: { _ in }) } - - // MARK: Init - private init(userDefaults: UserDefaults) { - self.userDefaults = userDefaults - subscribeToVPNStatusNotifications() - if expectsSavedProvider { - // setUpProvider() will prompt VPN Configuration permission if not granted, - // so only allow this when user has already set up, and we want VPN Status ASAP. - setUpProvider(completion: { _ in }) + } + + // MARK: NETunnelProviderManager Set Up + private func setUpProvider(completion: @escaping (Result) -> Void) { + queue.async { + VPNManager.loadSavedProvider { [weak self] result in + switch result { + case .success(let savedProvider): + guard let saved = savedProvider else { + self?.createAndSaveNewProvider(completion) + return + } + self?.providerManager = saved + self?.expectsSavedProvider = true + completion(.success(())) + case .failure(let error): + self?.expectsSavedProvider = false + completion(.failure(error)) } + } } - - // MARK: NETunnelProviderManager Set Up - private func setUpProvider(completion: @escaping (Result) -> Void) { - queue.async { - VPNManager.loadSavedProvider { [weak self] result in - switch result { - case .success(let savedProvider): - guard let saved = savedProvider else { - self?.createAndSaveNewProvider(completion) - return - } - self?.providerManager = saved - self?.expectsSavedProvider = true - completion(.success(())) - case .failure(let error): - self?.expectsSavedProvider = false - completion(.failure(error)) - } - } - } + } + + private func createAndSaveNewProvider( + _ completion: @escaping (Result) -> Void + ) { + let newManager = VPNManager.newProvider() + VPNManager.saveThenLoadProvider(newManager) { [weak self] result in + if result.isSuccess { + self?.providerManager = newManager + self?.expectsSavedProvider = true + } + completion(result) } - - private func createAndSaveNewProvider(_ completion: @escaping (Result) -> Void) { - let newManager = VPNManager.newProvider() - VPNManager.saveThenLoadProvider(newManager) { [weak self] result in - if result.isSuccess { - self?.providerManager = newManager - self?.expectsSavedProvider = true - } - completion(result) + } + + // MARK: Start VPN / Tunnel + func startTunnel(completion: @escaping (Result) -> Void) { + guard let provider = self.providerManager else { + setUpProvider { [weak self] result in + switch result { + case .success: // re-call + self?.startTunnel(completion: completion) + case .failure(let error): // complete + completion(.failure(error as! VPNManagerError)) } + } + return } - // MARK: Start VPN / Tunnel - func startTunnel(completion: @escaping (Result) -> Void) { - guard let provider = self.providerManager else { - setUpProvider { [weak self] result in - switch result { - case .success: // re-call - self?.startTunnel(completion: completion) - case .failure(let error): // complete - completion(.failure(error as! VPNManagerError)) - } - } - return - } - - do { - provider.isEnabled = true // calling start when !isEnabled will crash - let options = [Constants.netExStartReasonKey: NSString("User Initiated")] - try provider.connection.startVPNTunnel(options: options) - - // set onDemand enabled AFTER starting to avoid potential race-condition - provider.isOnDemandEnabled = true - - VPNManager.saveThenLoadProvider(provider, { _ in }) // necessary to persist isOnDemandEnabled - completion(.success(())) - } catch { - // re-mark needs set up - self.providerManager = nil - self.expectsSavedProvider = false - completion(.failure(.userDisallowedVPNConfigurations)) - } + do { + provider.isEnabled = true // calling start when !isEnabled will crash + let options = [Constants.netExStartReasonKey: NSString("User Initiated")] + try provider.connection.startVPNTunnel(options: options) + + // set onDemand enabled AFTER starting to avoid potential race-condition + provider.isOnDemandEnabled = true + + VPNManager.saveThenLoadProvider(provider, { _ in }) // necessary to persist isOnDemandEnabled + completion(.success(())) + } catch { + // re-mark needs set up + self.providerManager = nil + self.expectsSavedProvider = false + completion(.failure(.userDisallowedVPNConfigurations)) } - - // MARK: Stop VPN / Tunnel - func stopTunnel() { - guard let provider = self.providerManager else { - // this should never happen but _just in case_ - self.expectsSavedProvider = false - return - } - provider.isOnDemandEnabled = false - VPNManager.saveThenLoadProvider(provider) { _ in - provider.connection.stopVPNTunnel() // no need to pass reason, system passes UserInitiated on its own - } + } + + // MARK: Stop VPN / Tunnel + func stopTunnel() { + guard let provider = self.providerManager else { + // this should never happen but _just in case_ + self.expectsSavedProvider = false + return } - - // MARK: VPN Status Update - private func subscribeToVPNStatusNotifications() { - NotificationCenter.default.addObserver( - self, - selector: #selector(VPNManager.handleVPNStatusDidChangeNotification(_:)), - name: NSNotification.Name.NEVPNStatusDidChange, - object: nil - ) + provider.isOnDemandEnabled = false + VPNManager.saveThenLoadProvider(provider) { _ in + provider.connection.stopVPNTunnel() // no need to pass reason, system passes UserInitiated on its own } - - @objc private func handleVPNStatusDidChangeNotification(_ notification: Notification) { - // We can assume this session is the same as ours because: - // 1. Apps only have access to providers/VPNStatus that they have installed - // 2. This app specifically only installs/uses _one_ provider - guard let session = notification.object as? NETunnelProviderSession else { - assertionFailure("VPNStatus notification fired but unable to grab session") - return - } - - connectionStatus = session.status // potentially triggers update callback - - if session.status == .invalid { - // this indicates the user has deleted the VPN config in device Settings - // reset flag and nil provider so we can perform a clean config install - expectsSavedProvider = false - providerManager = nil - } + } + + // MARK: VPN Status Update + private func subscribeToVPNStatusNotifications() { + NotificationCenter.default.addObserver( + self, + selector: #selector(VPNManager.handleVPNStatusDidChangeNotification(_:)), + name: NSNotification.Name.NEVPNStatusDidChange, + object: nil + ) + } + + @objc private func handleVPNStatusDidChangeNotification(_ notification: Notification) { + // We can assume this session is the same as ours because: + // 1. Apps only have access to providers/VPNStatus that they have installed + // 2. This app specifically only installs/uses _one_ provider + guard let session = notification.object as? NETunnelProviderSession else { + assertionFailure("VPNStatus notification fired but unable to grab session") + return } - // MARK: Messaging Net Ex + connectionStatus = session.status // potentially triggers update callback - func messageNetEx(messageData: Data, responseHandler: ((Data?) -> Void)?) throws { - guard let session = self.providerManager?.connection as? NETunnelProviderSession, session.status == .connected else { - return - } - try session.sendProviderMessage(messageData, responseHandler: responseHandler) + if session.status == .invalid { + // this indicates the user has deleted the VPN config in device Settings + // reset flag and nil provider so we can perform a clean config install + expectsSavedProvider = false + providerManager = nil } + } - // MARK: Provider Management + // MARK: Messaging Net Ex - static private func loadSavedProvider(_ completion: @escaping (Result) -> Void) { - // loadAllFromPreferences triggers VPNConfiguration permission prompt if not yet granted - NETunnelProviderManager.loadAllFromPreferences { (savedManagers, error) in - if error == nil { - completion(.success(savedManagers?.first)) // may be nil, but thats ok - } else { - completion(.failure(.loadingProviderFailed)) - } - } + func messageNetEx(messageData: Data, responseHandler: ((Data?) -> Void)?) throws { + guard let session = self.providerManager?.connection as? NETunnelProviderSession, + session.status == .connected + else { + return } - - // This function exists because of this thread: https://forums.developer.apple.com/thread/25928 - static private func saveThenLoadProvider(_ provider: NETunnelProviderManager, _ completion: @escaping (Result) -> Void) { - provider.saveToPreferences { saveError in - if let _ = saveError { - completion(.failure(.savingProviderFailed)) - } else { - provider.loadFromPreferences { loadError in - if let _ = loadError { - completion(.failure(.loadingProviderFailed)) - } else { - completion(.success(())) - } - } - } - } + try session.sendProviderMessage(messageData, responseHandler: responseHandler) + } + + // MARK: Provider Management + + static private func loadSavedProvider( + _ completion: @escaping (Result) -> Void + ) { + // loadAllFromPreferences triggers VPNConfiguration permission prompt if not yet granted + NETunnelProviderManager.loadAllFromPreferences { (savedManagers, error) in + if error == nil { + completion(.success(savedManagers?.first)) // may be nil, but thats ok + } else { + completion(.failure(.loadingProviderFailed)) + } } - - static private func newProvider() -> NETunnelProviderManager { - let provider = NETunnelProviderManager() - let config = NETunnelProviderProtocol() - config.providerBundleIdentifier = Constants.netExBundleId - config.serverAddress = "0.0.0.0" // needs to be set but purely 8.8.8.8 - provider.protocolConfiguration = config - provider.isEnabled = true // calling start when disabled crashes - // Set rules for onDemand... - let alwaysConnectRule = NEOnDemandRuleConnect() - provider.onDemandRules = [alwaysConnectRule] - // BUT set to false for now— set to true RIGHT BEFORE calling start - // otherwise it will continually try to turn itself on BEFORE the user even hits the switch - provider.isOnDemandEnabled = false - - return provider - } - - // For debug use only— allows you to wipe all created providerManagers - static private func clearOldProviderManagers(_ completion: @escaping () -> Void) { - NETunnelProviderManager.loadAllFromPreferences { (savedManagers, _: Swift.Error?) in - guard let saved = savedManagers, !saved.isEmpty else { - completion() - return - } - let group = DispatchGroup() - saved.forEach({ old in - group.enter() - old.removeFromPreferences(completionHandler: { _ in group.leave() }) - }) - group.notify(queue: .main, execute: completion) + } + + // This function exists because of this thread: https://forums.developer.apple.com/thread/25928 + static private func saveThenLoadProvider( + _ provider: NETunnelProviderManager, + _ completion: @escaping (Result) -> Void + ) { + provider.saveToPreferences { saveError in + if let _ = saveError { + completion(.failure(.savingProviderFailed)) + } else { + provider.loadFromPreferences { loadError in + if let _ = loadError { + completion(.failure(.loadingProviderFailed)) + } else { + completion(.success(())) + } } + } + } + } + + static private func newProvider() -> NETunnelProviderManager { + let provider = NETunnelProviderManager() + let config = NETunnelProviderProtocol() + config.providerBundleIdentifier = Constants.netExBundleId + config.serverAddress = "0.0.0.0" // needs to be set but purely 8.8.8.8 + provider.protocolConfiguration = config + provider.isEnabled = true // calling start when disabled crashes + // Set rules for onDemand... + let alwaysConnectRule = NEOnDemandRuleConnect() + provider.onDemandRules = [alwaysConnectRule] + // BUT set to false for now— set to true RIGHT BEFORE calling start + // otherwise it will continually try to turn itself on BEFORE the user even hits the switch + provider.isOnDemandEnabled = false + + return provider + } + + // For debug use only— allows you to wipe all created providerManagers + static private func clearOldProviderManagers(_ completion: @escaping () -> Void) { + NETunnelProviderManager.loadAllFromPreferences { (savedManagers, _: Swift.Error?) in + guard let saved = savedManagers, !saved.isEmpty else { + completion() + return + } + let group = DispatchGroup() + saved.forEach({ old in + group.enter() + old.removeFromPreferences(completionHandler: { _ in group.leave() }) + }) + group.notify(queue: .main, execute: completion) } + } } diff --git a/ios/Runner/Lantern/Core/Vpn/VpnHelper.swift b/ios/Runner/Lantern/Core/Vpn/VpnHelper.swift index 1f523b44c..098b2ed80 100644 --- a/ios/Runner/Lantern/Core/Vpn/VpnHelper.swift +++ b/ios/Runner/Lantern/Core/Vpn/VpnHelper.swift @@ -4,335 +4,349 @@ // import Foundation +import Internalsdk +import NetworkExtension import UIKit import UserNotifications -import NetworkExtension -import Internalsdk class VpnHelper: NSObject { - static let shared = VpnHelper(constants: Constants(process: .app), - fileManager: .default, - userDefaults: Constants.appGroupDefaults, - notificationCenter: .default, - flashlightManager: FlashlightManager.appDefault, - vpnManager: (isSimulator() ? MockVPNManager() : VPNManager.appDefault)) - // MARK: State - static let didUpdateStateNotification = Notification.Name("Lantern.didUpdateState") - enum VPNState: Equatable { - case configuring - case idle(Error?) - case connecting - case connected - case disconnecting - var isIdle: Bool { - if case .idle = self { return true } - return false - } + static let shared = VpnHelper( + constants: Constants(process: .app), + fileManager: .default, + userDefaults: Constants.appGroupDefaults, + notificationCenter: .default, + flashlightManager: FlashlightManager.appDefault, + vpnManager: (isSimulator() ? MockVPNManager() : VPNManager.appDefault)) + // MARK: State + static let didUpdateStateNotification = Notification.Name("Lantern.didUpdateState") + enum VPNState: Equatable { + case configuring + case idle(Error?) + case connecting + case connected + case disconnecting + var isIdle: Bool { + if case .idle = self { return true } + return false } - var configuring: Bool { - didSet { - guard oldValue != configuring else { return } - notificationCenter.post(name: VpnHelper.didUpdateStateNotification, object: nil) - } + } + var configuring: Bool { + didSet { + guard oldValue != configuring else { return } + notificationCenter.post(name: VpnHelper.didUpdateStateNotification, object: nil) } + } - private let stateLock = NSLock() - private var _state: VPNState - var state: VPNState { - get { - stateLock.lock() - defer { stateLock.unlock() } - if configuring { - return .configuring - } - return _state - } - - set(newState) { - stateLock.lock() - guard _state != newState else { - stateLock.unlock() - return - } - _state = newState - notificationCenter.post(name: VpnHelper.didUpdateStateNotification, object: nil) - stateLock.unlock() - } + private let stateLock = NSLock() + private var _state: VPNState + var state: VPNState { + get { + stateLock.lock() + defer { stateLock.unlock() } + if configuring { + return .configuring + } + return _state } - static let hasFetchedConfigDefaultsKey = "Lantern.hasConfig" - // Apple - let fileManager: FileManager - let userDefaults: UserDefaults - let notificationCenter: NotificationCenter - // Custom - let constants: Constants - let flashlightManager: FlashlightManager - let vpnManager: VPNBase - var configFetchTimer: Timer! - var hasConfiguredThisSession = false - var hasFetchedConfigOnce: Bool { - return (userDefaults.value(forKey: VpnHelper.hasFetchedConfigDefaultsKey) as? Bool) ?? false + set(newState) { + stateLock.lock() + guard _state != newState else { + stateLock.unlock() + return + } + _state = newState + notificationCenter.post(name: VpnHelper.didUpdateStateNotification, object: nil) + stateLock.unlock() } + } - init(constants: Constants, - fileManager: FileManager, - userDefaults: UserDefaults, - notificationCenter: NotificationCenter, - flashlightManager: FlashlightManager, - vpnManager: VPNBase, - userNotificationsManager: UserNotificationsManager? = nil - ) { - self.constants = constants - self.fileManager = fileManager - self.userDefaults = userDefaults - self.notificationCenter = notificationCenter - self.flashlightManager = flashlightManager - self.vpnManager = vpnManager - configuring = true - _state = .idle(nil) - super.init() - if self.hasFetchedConfigOnce { - self.configuring = false - } - performAppSetUp() + static let hasFetchedConfigDefaultsKey = "Lantern.hasConfig" + // Apple + let fileManager: FileManager + let userDefaults: UserDefaults + let notificationCenter: NotificationCenter + // Custom + let constants: Constants + let flashlightManager: FlashlightManager + let vpnManager: VPNBase + var configFetchTimer: Timer! + var hasConfiguredThisSession = false + var hasFetchedConfigOnce: Bool { + return (userDefaults.value(forKey: VpnHelper.hasFetchedConfigDefaultsKey) as? Bool) ?? false + } + + init( + constants: Constants, + fileManager: FileManager, + userDefaults: UserDefaults, + notificationCenter: NotificationCenter, + flashlightManager: FlashlightManager, + vpnManager: VPNBase, + userNotificationsManager: UserNotificationsManager? = nil + ) { + self.constants = constants + self.fileManager = fileManager + self.userDefaults = userDefaults + self.notificationCenter = notificationCenter + self.flashlightManager = flashlightManager + self.vpnManager = vpnManager + configuring = true + _state = .idle(nil) + super.init() + if self.hasFetchedConfigOnce { + self.configuring = false } + performAppSetUp() + } - // MARK: Set Up - func performAppSetUp() { - // STARTUP OVERVIEW + // MARK: Set Up + func performAppSetUp() { + // STARTUP OVERVIEW - // 1. set up files for flashlight - createFilesForAppGoPackage() - // Todo Use new method we are using in Android - // 2. set up data usage monitor - // dataUsageMonitor.startObservingDataUsageChanges(callback: handleDataUsageUpdated) + // 1. set up files for flashlight + createFilesForAppGoPackage() + // Todo Use new method we are using in Android + // 2. set up data usage monitor + // dataUsageMonitor.startObservingDataUsageChanges(callback: handleDataUsageUpdated) - // 3. set up VPN manager - vpnManager.didUpdateConnectionStatusCallback = handleVPNStatusUpdated + // 3. set up VPN manager + vpnManager.didUpdateConnectionStatusCallback = handleVPNStatusUpdated - logger.debug("Setting Go Log path to:\(constants.goLogBaseURL.path)") - if let error = flashlightManager.configureGoLoggerReturningError() { - logger.error("IosConfigureLogger FAILED: " + error.localizedDescription) - } - // 5 Fetch config - fetchConfigIfNecessary() + logger.debug("Setting Go Log path to:\(constants.goLogBaseURL.path)") + if let error = flashlightManager.configureGoLoggerReturningError() { + logger.error("IosConfigureLogger FAILED: " + error.localizedDescription) } + // 5 Fetch config + fetchConfigIfNecessary() + } - private func createFilesForAppGoPackage() { - // where "~" is the shared app group container... - // create process-specific directory @ ~/app + private func createFilesForAppGoPackage() { + // where "~" is the shared app group container... + // create process-specific directory @ ~/app - do { - try fileManager.ensureDirectoryExists(at: constants.targetDirectoryURL) - } catch { - logger.error("Failed to create directory @ \(constants.targetDirectoryURL.path)") - } - // create process-shared directory @ ~/config - do { - try fileManager.ensureDirectoryExists(at: constants.configDirectoryURL) - } catch { - logger.error("Failed to create directory @ \(constants.configDirectoryURL.path)") - } + do { + try fileManager.ensureDirectoryExists(at: constants.targetDirectoryURL) + } catch { + logger.error("Failed to create directory @ \(constants.targetDirectoryURL.path)") + } + // create process-shared directory @ ~/config + do { + try fileManager.ensureDirectoryExists(at: constants.configDirectoryURL) + } catch { + logger.error("Failed to create directory @ \(constants.configDirectoryURL.path)") + } - // create shared config files (eg- config.yaml, masquerade_cache, etc) @ ~/config/<_> - let configSuccess = fileManager.ensureFilesExist(at: constants.allConfigURLs) - if !configSuccess { - logger.error("Failed to create config files") - } - // create process-specific log files @ ~/app/lantern.log.# - var logURLs = fileManager.generateLogRotationURLs(count: Constants.defaultLogRotationFileCount, from: constants.goLogBaseURL) - logURLs.append(constants.heapProfileURL) - logURLs.append(constants.heapProfileTempURL) - logURLs.append(constants.goroutineProfileURL) - logURLs.append(constants.goroutineProfileTempURL) - let success = fileManager.ensureFilesExist(at: logURLs) - if !success { - logger.error("Failed to create log URLs") - } + // create shared config files (eg- config.yaml, masquerade_cache, etc) @ ~/config/<_> + let configSuccess = fileManager.ensureFilesExist(at: constants.allConfigURLs) + if !configSuccess { + logger.error("Failed to create config files") } + // create process-specific log files @ ~/app/lantern.log.# + var logURLs = fileManager.generateLogRotationURLs( + count: Constants.defaultLogRotationFileCount, from: constants.goLogBaseURL) + logURLs.append(constants.heapProfileURL) + logURLs.append(constants.heapProfileTempURL) + logURLs.append(constants.goroutineProfileURL) + logURLs.append(constants.goroutineProfileTempURL) + let success = fileManager.ensureFilesExist(at: logURLs) + if !success { + logger.error("Failed to create log URLs") + } + } - func startVPN( - onError: ((Error) -> Void)? = nil, - onSuccess: (() -> Void)? = nil - ) { - guard state.isIdle else { return } - if !hasFetchedConfigOnce { - initiateConfigFetching(onError: onError, onSuccess: onSuccess) - } else { - initiateVPNStart(onError: onError, onSuccess: onSuccess) - } + func startVPN( + onError: ((Error) -> Void)? = nil, + onSuccess: (() -> Void)? = nil + ) { + guard state.isIdle else { return } + if !hasFetchedConfigOnce { + initiateConfigFetching(onError: onError, onSuccess: onSuccess) + } else { + initiateVPNStart(onError: onError, onSuccess: onSuccess) } + } - private func initiateConfigFetching(onError: ((Error) -> Void)? = nil, onSuccess: (() -> Void)? = nil) { - configuring = true - fetchConfig { [weak self] result in - DispatchQueue.main.async { - self?.configuring = false - guard let state = self?.state, state.isIdle else { return } - if result.isSuccess { - self?.startVPN(onError: onError, onSuccess: onSuccess) - } else { - self?.state = .idle(.unableToFetchConfig) - onError?(.unableToFetchConfig) - } - } + private func initiateConfigFetching( + onError: ((Error) -> Void)? = nil, onSuccess: (() -> Void)? = nil + ) { + configuring = true + fetchConfig { [weak self] result in + DispatchQueue.main.async { + self?.configuring = false + guard let state = self?.state, state.isIdle else { return } + if result.isSuccess { + self?.startVPN(onError: onError, onSuccess: onSuccess) + } else { + self?.state = .idle(.unableToFetchConfig) + onError?(.unableToFetchConfig) } + } } + } - private func initiateVPNStart(onError: ((Error) -> Void)? = nil, onSuccess: (() -> Void)? = nil) { - vpnManager.startTunnel { result in - switch result { - case .success: - logger.debug("VPN successfully started") - onSuccess?() - case .failure(let error): - logger.error("VPN start failed: \(error.localizedDescription)") - onError?(.userDisallowedVPNConfig) - } - } + private func initiateVPNStart(onError: ((Error) -> Void)? = nil, onSuccess: (() -> Void)? = nil) { + vpnManager.startTunnel { result in + switch result { + case .success: + logger.debug("VPN successfully started") + onSuccess?() + case .failure(let error): + logger.error("VPN start failed: \(error.localizedDescription)") + onError?(.userDisallowedVPNConfig) + } } + } - func stopVPN() { - vpnManager.stopTunnel() + func stopVPN() { + vpnManager.stopTunnel() + } + + // Internal method for VPN status + func handleVPNStatusUpdated(_ status: NEVPNStatus) { + let newState = translateVPNStatusToLanternState(status) + logger.debug("VPN status updated while \(state): \(newState)") + guard status != .reasserting && status != .invalid else { + state = .idle(.invalidVPNState) + stopVPN() + return } + state = newState + } - // Internal method for VPN status - func handleVPNStatusUpdated(_ status: NEVPNStatus) { - let newState = translateVPNStatusToLanternState(status) - logger.debug("VPN status updated while \(state): \(newState)") - guard status != .reasserting && status != .invalid else { - state = .idle(.invalidVPNState) - stopVPN() - return - } - state = newState + func translateVPNStatusToLanternState(_ status: NEVPNStatus) -> VpnHelper.VPNState { + switch status { + case .disconnected, .invalid, .reasserting: + return .idle(nil) + case .connecting: + return .connecting + case .connected: + return .connected + case .disconnecting: + return .disconnecting + @unknown default: + assertionFailure("Unhandled VPN state") + return .idle(.unknown) } + } - func translateVPNStatusToLanternState(_ status: NEVPNStatus) -> VpnHelper.VPNState { - switch status { - case .disconnected, .invalid, .reasserting: - return .idle(nil) - case .connecting: - return .connecting - case .connected: - return .connected - case .disconnecting: - return .disconnecting - @unknown default: - assertionFailure("Unhandled VPN state") - return .idle(.unknown) + func fetchConfigIfNecessary() { + logger.debug("Checking if config fetch is needed") + guard !self.hasConfiguredThisSession else { return } + logger.debug("Will fetch config") + self.hasConfiguredThisSession = true + let hasFetchedConfigOnce = self.hasFetchedConfigOnce + fetchConfig { [weak self] result in + self?.setUpConfigFetchTimer() + DispatchQueue.main.async { + self?.configuring = false + if let state = self?.state { + guard state.isIdle else { return } + } + if !result.isSuccess && !hasFetchedConfigOnce { + self?.state = .idle(.unableToFetchConfig) } + } } + } - func fetchConfigIfNecessary() { - logger.debug("Checking if config fetch is needed") - guard !self.hasConfiguredThisSession else { return } - logger.debug("Will fetch config") - self.hasConfiguredThisSession = true - let hasFetchedConfigOnce = self.hasFetchedConfigOnce - fetchConfig { [weak self] result in - self?.setUpConfigFetchTimer() - DispatchQueue.main.async { - self?.configuring = false - if let state = self?.state { - guard state.isIdle else { return } - } - if !result.isSuccess && !hasFetchedConfigOnce { - self?.state = .idle(.unableToFetchConfig) - } - } + func fetchConfig( + refreshProxies: Bool = true, _ completion: @escaping (Result) -> Void + ) { + flashlightManager.fetchConfig( + userID: self.userID, proToken: self.proToken, excludedIPsURL: constants.excludedIPsURL, + refreshProxies: refreshProxies + ) { [weak self] result in + switch result { + case .success(let vpnNeedsReconfiguring): + if vpnNeedsReconfiguring { + self?.messageNetExToUpdateExcludedIPs() } + self?.userDefaults.set(refreshProxies, forKey: VpnHelper.hasFetchedConfigDefaultsKey) + logger.debug("Successfully fetched new config with \(result)") + completion(.success(())) + case .failure(let error): + // TODO: convert this error to a Lantern.Error + logger.error("Fetch config failed:" + error.localizedDescription) + completion(.failure(error)) + } } + } - func fetchConfig(refreshProxies: Bool = true, _ completion: @escaping (Result) -> Void) { - flashlightManager.fetchConfig(userID: self.userID, proToken: self.proToken, excludedIPsURL: constants.excludedIPsURL, refreshProxies: refreshProxies) { [weak self] result in + func setUpConfigFetchTimer() { + // set up timer on Main queue's runloop + // FlashlightManager will automatically use its designated goQueue when fetching + DispatchQueue.main.async { [weak self] in + let time: Double = 60 + self?.configFetchTimer = Timer.scheduledTimer( + withTimeInterval: time, repeats: true, + block: { [weak self] _ in + // Only auto-fetch new config when VPN is on + guard self?.state == .connected else { return } + logger.debug("Config Fetch timer fired after \(time), fetching...") + self?.fetchConfig { result in switch result { - case .success(let vpnNeedsReconfiguring): - if vpnNeedsReconfiguring { - self?.messageNetExToUpdateExcludedIPs() - } - self?.userDefaults.set(refreshProxies, forKey: VpnHelper.hasFetchedConfigDefaultsKey) - logger.debug("Successfully fetched new config with \(result)") - completion(.success(())) + case .success: + logger.debug("Auto-config fetch success") case .failure(let error): - // TODO: convert this error to a Lantern.Error - logger.error("Fetch config failed:" + error.localizedDescription) - completion(.failure(error)) + logger.error("Auto-config fetch failed: \(error.localizedDescription)") } - } + } + }) } + } - func setUpConfigFetchTimer() { - // set up timer on Main queue's runloop - // FlashlightManager will automatically use its designated goQueue when fetching - DispatchQueue.main.async { [weak self] in - let time: Double = 60 - self?.configFetchTimer = Timer.scheduledTimer(withTimeInterval: time, repeats: true, block: { [weak self] _ in - // Only auto-fetch new config when VPN is on - guard self?.state == .connected else { return } - logger.debug("Config Fetch timer fired after \(time), fetching...") - self?.fetchConfig { result in - switch result { - case .success: - logger.debug("Auto-config fetch success") - case .failure(let error): - logger.error("Auto-config fetch failed: \(error.localizedDescription)") - } - } - }) - } - } - - private func messageNetExToUpdateExcludedIPs() { - logger.debug("Notifying network extension of updated config") - do { - let msg = Constants.configUpdatedMessageData - try vpnManager.messageNetEx(messageData: msg) { data in - let success = (data == Constants.configUpdatedACKData) - let logMsg = (success ? "Successfully ACKd config update message" - : "Did not return expected ACK for config update message.") - logger.error("NetEx \(logMsg)") - } - } catch { - logger.error("Failed to message Provider from App— \(error.localizedDescription)") - } + private func messageNetExToUpdateExcludedIPs() { + logger.debug("Notifying network extension of updated config") + do { + let msg = Constants.configUpdatedMessageData + try vpnManager.messageNetEx(messageData: msg) { data in + let success = (data == Constants.configUpdatedACKData) + let logMsg = + (success + ? "Successfully ACKd config update message" + : "Did not return expected ACK for config update message.") + logger.error("NetEx \(logMsg)") + } + } catch { + logger.error("Failed to message Provider from App— \(error.localizedDescription)") } + } } extension VpnHelper { - enum Error: Swift.Error { - case unknown - case userDisallowedVPNConfig - case unableToFetchConfig - case invalidVPNState - } + enum Error: Swift.Error { + case unknown + case userDisallowedVPNConfig + case unableToFetchConfig + case invalidVPNState + } } extension VpnHelper { - // MARK: Pro - var userID: Int { - return self.userDefaults.integer(forKey: Constants.userID) - } - - var proToken: String? { - return self.userDefaults.string(forKey: Constants.proToken) - } + // MARK: Pro + var userID: Int { + return self.userDefaults.integer(forKey: Constants.userID) + } - var isPro: Bool { - if !self.userDefaults.bool(forKey: Constants.isPro) { - return false - } + var proToken: String? { + return self.userDefaults.string(forKey: Constants.proToken) + } - if self.userID == 0 { - return false - } + var isPro: Bool { + if !self.userDefaults.bool(forKey: Constants.isPro) { + return false + } - let pt = self.proToken - if pt == nil || pt?.isEmpty == true { - return false - } + if self.userID == 0 { + return false + } - return true + let pt = self.proToken + if pt == nil || pt?.isEmpty == true { + return false } + + return true + } } diff --git a/ios/Runner/Lantern/Models/BaseModel.swift b/ios/Runner/Lantern/Models/BaseModel.swift index f3b6e5c4d..7f755cfb1 100644 --- a/ios/Runner/Lantern/Models/BaseModel.swift +++ b/ios/Runner/Lantern/Models/BaseModel.swift @@ -5,213 +5,223 @@ // Created by jigar fumakiya on 31/07/23. // +import DBModule +import Flutter import Foundation import Internalsdk import SQLite -import Flutter -import DBModule internal var swiftDB: MinisqlDBProtocol? open class BaseModel: NSObject, FlutterStreamHandler { - var model: InternalsdkModelProtocol - var eventChannel: FlutterEventChannel! - var methodChannel: FlutterMethodChannel! - var binaryMessenger: FlutterBinaryMessenger! - let activeSinks = AtomicReference(nil) - var activeSubscribers: Set = [] - private let mainHandler = DispatchQueue.main - private let asyncHandler = DispatchQueue(label: "BaseModel-AsyncHandler") - - init(_ flutterBinary: FlutterBinaryMessenger, _ model: InternalsdkModelProtocol) throws { - self.model = model - self.binaryMessenger = flutterBinary - super.init() - setupFlutterChannels() - } - - internal static func getDB() throws -> MinisqlDBProtocol { - if let db = swiftDB { - return db - } else { - swiftDB = try DatabaseFactory.getDbManager(databasePath: getDatabasePath()) - return swiftDB! - } - } - - internal static func getDatabasePath() -> String { - let fileManager = FileManager.default - let dbDir = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("masterDBv2") - do { - try fileManager.createDirectory(at: dbDir, withIntermediateDirectories: true, attributes: nil) - let dbLocation = dbDir.appendingPathComponent("db").path - logger.log("DB location \(dbLocation)") - return dbLocation - } catch { - print("Error creating directory: \(error)") - return "" // Return an empty string or handle the error accordingly. - } - } - - private func setupFlutterChannels() { - eventChannel = FlutterEventChannel(name: "\(model.name())_event_channel", binaryMessenger: binaryMessenger) - eventChannel.setStreamHandler(self) - - methodChannel = FlutterMethodChannel(name: "\(model.name())_method_channel", binaryMessenger: binaryMessenger) - methodChannel.setMethodCallHandler(handleMethodCall) - } - - public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { - logger.log("onListen initiated with arguments: \(String(describing: arguments))") - guard let args = arguments as? [String: Any] else { - let errorMessage = "Failed to cast arguments \(String(describing: arguments)) to dictionary. Exiting..." - return createFlutterError(code: "INVALID_ARGUMENTS", message: errorMessage) - } - activeSinks.set(events) - guard let subscriberID = args["subscriberID"] as? String, - let path = args["path"] as? String else { - let errorMessage = "Required parameters subscriberID or path missing in arguments. Exiting..." - return createFlutterError(code: "MISSING_PARAMETERS", message: errorMessage) - } - - let details = args["details"] as? Bool ?? false - // Mark the subscriber as active - activeSubscribers.insert(subscriberID) - - // Closure to send events back to the Flutter side asynchronously - let notifyActiveSink = { (data: [String: Any]) in - self.mainHandler.async { - self.activeSinks.get()?(data) - } - } - // Initializing the subscriber with callback for updates - let subscriber = DetailsSubscriber(subscriberID: subscriberID, path: path) { updates, deletes in - self.mainHandler.async { - let data: [String: Any] = [ - "s": subscriberID, - "u": updates, - "d": deletes - ] - notifyActiveSink(data) - } - } - - do { - try model.subscribe(subscriber) - } catch let error { - let errorMessage = "An error occurred while subscribing: \(error.localizedDescription)" - return createFlutterError(code: "SUBSCRIBE_ERROR", message: errorMessage) - } - return nil - } - - public func onCancel(withArguments arguments: Any?) -> FlutterError? { - if arguments==nil { - return nil - } - guard let args = arguments as? [String: Any] else { - let errorMessage = "onCancel Failed to cast arguments \(String(describing: arguments)) to dictionary. Exiting..." - return createFlutterError(code: "INVALID_ARGUMENTS", message: errorMessage) - } - - guard let subscriberID = args["subscriberID"] as? String else { - let errorMessage = "Required parameters subscriberID missing in arguments. Exiting..." - return createFlutterError(code: "MISSING_PARAMETERS", message: errorMessage) - } - - do { - try model.unsubscribe(subscriberID) - activeSubscribers.remove(subscriberID) - } catch let error { - let errorMessage = "An error occurred while unsubscribing: \(error.localizedDescription)" - return createFlutterError(code: "UNSUBSCRIBE_ERROR", message: errorMessage) - } - return nil - } - - // Method channels - func handleMethodCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - // Handle your method calls here - // The 'call' contains the method name and arguments - // The 'result' can be used to send back the data to Flutter - asyncHandler.async { - self.doOnMethodCall(call: call, result: result) - } - } - - internal func doOnMethodCall(call: FlutterMethodCall, result: @escaping FlutterResult) { - // Convert the entire arguments to a single MinisqlValue - guard let minisqlValue = ValueUtil.convertToMinisqlValue(call.arguments) else { - result( - FlutterError( - code: "ARGUMENTS_ERROR", message: "Failed to convert arguments to MinisqlValue", - details: nil)) - return + var model: InternalsdkModelProtocol + var eventChannel: FlutterEventChannel! + var methodChannel: FlutterMethodChannel! + var binaryMessenger: FlutterBinaryMessenger! + let activeSinks = AtomicReference(nil) + var activeSubscribers: Set = [] + private let mainHandler = DispatchQueue.main + private let asyncHandler = DispatchQueue(label: "BaseModel-AsyncHandler") + + init(_ flutterBinary: FlutterBinaryMessenger, _ model: InternalsdkModelProtocol) throws { + self.model = model + self.binaryMessenger = flutterBinary + super.init() + setupFlutterChannels() + } + + internal static func getDB() throws -> MinisqlDBProtocol { + if let db = swiftDB { + return db + } else { + swiftDB = try DatabaseFactory.getDbManager(databasePath: getDatabasePath()) + return swiftDB! + } + } + + internal static func getDatabasePath() -> String { + let fileManager = FileManager.default + let dbDir = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! + .appendingPathComponent("masterDBv2") + do { + try fileManager.createDirectory(at: dbDir, withIntermediateDirectories: true, attributes: nil) + let dbLocation = dbDir.appendingPathComponent("db").path + logger.log("DB location \(dbLocation)") + return dbLocation + } catch { + print("Error creating directory: \(error)") + return "" // Return an empty string or handle the error accordingly. + } + } + + private func setupFlutterChannels() { + eventChannel = FlutterEventChannel( + name: "\(model.name())_event_channel", binaryMessenger: binaryMessenger) + eventChannel.setStreamHandler(self) + + methodChannel = FlutterMethodChannel( + name: "\(model.name())_method_channel", binaryMessenger: binaryMessenger) + methodChannel.setMethodCallHandler(handleMethodCall) + } + + public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) + -> FlutterError? + { + logger.log("onListen initiated with arguments: \(String(describing: arguments))") + guard let args = arguments as? [String: Any] else { + let errorMessage = + "Failed to cast arguments \(String(describing: arguments)) to dictionary. Exiting..." + return createFlutterError(code: "INVALID_ARGUMENTS", message: errorMessage) + } + activeSinks.set(events) + guard let subscriberID = args["subscriberID"] as? String, + let path = args["path"] as? String + else { + let errorMessage = "Required parameters subscriberID or path missing in arguments. Exiting..." + return createFlutterError(code: "MISSING_PARAMETERS", message: errorMessage) + } + + let details = args["details"] as? Bool ?? false + // Mark the subscriber as active + activeSubscribers.insert(subscriberID) + + // Closure to send events back to the Flutter side asynchronously + let notifyActiveSink = { (data: [String: Any]) in + self.mainHandler.async { + self.activeSinks.get()?(data) } - do { - let invocationResult = try invokeMethodOnGo(call.method, minisqlValue) - - if let originalValue = ValueUtil.convertFromMinisqlValue( - from: invocationResult as! MinisqlValue) - { - result(originalValue) - } else { - result( - FlutterError( - code: "CONVERSION_ERROR", - message: "Failed to convert MinisqlValue back to original value", details: nil)) - } - } catch let error as NSError { - - // Check for the specific "method not implemented" error - if error.localizedDescription.contains("method not implemented") { - result(FlutterMethodNotImplemented) - } - // Check for another specific error (e.g., "database error") - else if error.localizedDescription.contains("database error") { - result( - FlutterError(code: "DATABASE_ERROR", message: "A database error occurred.", details: nil)) - } - // Handle all other errors - else { - result( - FlutterError(code: "UNKNOWN_ERROR", message: error.localizedDescription, details: nil)) - } + } + // Initializing the subscriber with callback for updates + let subscriber = DetailsSubscriber(subscriberID: subscriberID, path: path) { updates, deletes in + self.mainHandler.async { + let data: [String: Any] = [ + "s": subscriberID, + "u": updates, + "d": deletes, + ] + notifyActiveSink(data) } } - - internal func invokeMethodOnGo(_ name: String, _ argument: MinisqlValue) throws -> MinisqlValue { - // Convert any argument to Minisql values - let goResult = try model.invokeMethod( - name, arguments: ValueArrayFactory.createValueArrayHandler(values: [argument])) - return goResult + + do { + try model.subscribe(subscriber) + } catch let error { + let errorMessage = "An error occurred while subscribing: \(error.localizedDescription)" + return createFlutterError(code: "SUBSCRIBE_ERROR", message: errorMessage) + } + return nil + } + + public func onCancel(withArguments arguments: Any?) -> FlutterError? { + if arguments == nil { + return nil + } + guard let args = arguments as? [String: Any] else { + let errorMessage = + "onCancel Failed to cast arguments \(String(describing: arguments)) to dictionary. Exiting..." + return createFlutterError(code: "INVALID_ARGUMENTS", message: errorMessage) + } + + guard let subscriberID = args["subscriberID"] as? String else { + let errorMessage = "Required parameters subscriberID missing in arguments. Exiting..." + return createFlutterError(code: "MISSING_PARAMETERS", message: errorMessage) + } + + do { + try model.unsubscribe(subscriberID) + activeSubscribers.remove(subscriberID) + } catch let error { + let errorMessage = "An error occurred while unsubscribing: \(error.localizedDescription)" + return createFlutterError(code: "UNSUBSCRIBE_ERROR", message: errorMessage) + } + return nil + } + + // Method channels + func handleMethodCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + // Handle your method calls here + // The 'call' contains the method name and arguments + // The 'result' can be used to send back the data to Flutter + asyncHandler.async { + self.doOnMethodCall(call: call, result: result) } + } + + internal func doOnMethodCall(call: FlutterMethodCall, result: @escaping FlutterResult) { + // Convert the entire arguments to a single MinisqlValue + guard let minisqlValue = ValueUtil.convertToMinisqlValue(call.arguments) else { + result( + FlutterError( + code: "ARGUMENTS_ERROR", message: "Failed to convert arguments to MinisqlValue", + details: nil)) + return + } + do { + let invocationResult = try invokeMethodOnGo(call.method, minisqlValue) + + if let originalValue = ValueUtil.convertFromMinisqlValue( + from: invocationResult as! MinisqlValue) + { + result(originalValue) + } else { + result( + FlutterError( + code: "CONVERSION_ERROR", + message: "Failed to convert MinisqlValue back to original value", details: nil)) + } + } catch let error as NSError { - private func createFlutterError(code: String, message: String, details: Any? = nil) -> FlutterError { - logger.log(message) - return FlutterError(code: code, message: message, details: details) + // Check for the specific "method not implemented" error + if error.localizedDescription.contains("method not implemented") { + result(FlutterMethodNotImplemented) + } + // Check for another specific error (e.g., "database error") + else if error.localizedDescription.contains("database error") { + result( + FlutterError(code: "DATABASE_ERROR", message: "A database error occurred.", details: nil)) + } + // Handle all other errors + else { + result( + FlutterError(code: "UNKNOWN_ERROR", message: error.localizedDescription, details: nil)) + } } + } + + internal func invokeMethodOnGo(_ name: String, _ argument: MinisqlValue) throws -> MinisqlValue { + // Convert any argument to Minisql values + let goResult = try model.invokeMethod( + name, arguments: ValueArrayFactory.createValueArrayHandler(values: [argument])) + return goResult + } + + private func createFlutterError(code: String, message: String, details: Any? = nil) + -> FlutterError + { + logger.log(message) + return FlutterError(code: code, message: message, details: details) + } } /// A simple thread-safe wrapper for atomic property access. class AtomicReference { - private var value: Value - private let queue = DispatchQueue(label: "com.atomic.reference", attributes: .concurrent) + private var value: Value + private let queue = DispatchQueue(label: "com.atomic.reference", attributes: .concurrent) - init(_ value: Value) { - self.value = value - } + init(_ value: Value) { + self.value = value + } - func set(_ newValue: Value) { - queue.async(flags: .barrier) { - self.value = newValue - } + func set(_ newValue: Value) { + queue.async(flags: .barrier) { + self.value = newValue } + } - func get() -> Value { - return queue.sync { - value - } + func get() -> Value { + return queue.sync { + value } + } } diff --git a/ios/Runner/Lantern/Models/LanternModel.swift b/ios/Runner/Lantern/Models/LanternModel.swift index aa9ffe13a..15953d3ae 100644 --- a/ios/Runner/Lantern/Models/LanternModel.swift +++ b/ios/Runner/Lantern/Models/LanternModel.swift @@ -5,50 +5,54 @@ // Created by jigar fumakiya on 26/07/23. // +import Flutter import Foundation import Internalsdk -import Flutter class LanternModel: NSObject, FlutterStreamHandler { - let LANTERN_EVENT_CHANNEL="lantern_event_channel" - let LANTERN_METHOED_CHANNEL="lantern_method_channel" - - var lanternMethodChannel: FlutterMethodChannel! - var lanternEventChannel: FlutterEventChannel! - - var flutterbinaryMessenger: FlutterBinaryMessenger - - init(flutterBinary: FlutterBinaryMessenger) { - self.flutterbinaryMessenger=flutterBinary - super.init() - lanternMethodChannel = FlutterMethodChannel(name: LANTERN_METHOED_CHANNEL, binaryMessenger: flutterBinary) - lanternMethodChannel.setMethodCallHandler(handleMethodCall) - lanternEventChannel = FlutterEventChannel(name: LANTERN_EVENT_CHANNEL, binaryMessenger: flutterBinary) - lanternEventChannel.setStreamHandler(self) - + let LANTERN_EVENT_CHANNEL = "lantern_event_channel" + let LANTERN_METHOED_CHANNEL = "lantern_method_channel" + + var lanternMethodChannel: FlutterMethodChannel! + var lanternEventChannel: FlutterEventChannel! + + var flutterbinaryMessenger: FlutterBinaryMessenger + + init(flutterBinary: FlutterBinaryMessenger) { + self.flutterbinaryMessenger = flutterBinary + super.init() + lanternMethodChannel = FlutterMethodChannel( + name: LANTERN_METHOED_CHANNEL, binaryMessenger: flutterBinary) + lanternMethodChannel.setMethodCallHandler(handleMethodCall) + lanternEventChannel = FlutterEventChannel( + name: LANTERN_EVENT_CHANNEL, binaryMessenger: flutterBinary) + lanternEventChannel.setStreamHandler(self) + + } + + func handleMethodCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + // Handle your method calls here + // The 'call' contains the method name and arguments + // The 'result' can be used to send back the data to Flutter + + switch call.method { + case "yourMethod": + // handle yourMethod + break + default: + result(FlutterMethodNotImplemented) } + } - func handleMethodCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - // Handle your method calls here - // The 'call' contains the method name and arguments - // The 'result' can be used to send back the data to Flutter - - switch call.method { - case "yourMethod": - // handle yourMethod - break - default: - result(FlutterMethodNotImplemented) - } - } + func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) + -> FlutterError? + { + return nil + } - func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { - return nil - } - - func onCancel(withArguments arguments: Any?) -> FlutterError? { - return nil - } + func onCancel(withArguments arguments: Any?) -> FlutterError? { + return nil + } } diff --git a/ios/Runner/Lantern/Models/MessagingModel.swift b/ios/Runner/Lantern/Models/MessagingModel.swift index c549cba07..727670331 100644 --- a/ios/Runner/Lantern/Models/MessagingModel.swift +++ b/ios/Runner/Lantern/Models/MessagingModel.swift @@ -5,17 +5,17 @@ // Created by jigar fumakiya on 21/08/23. // +import DBModule +import Flutter import Foundation import Internalsdk -import Flutter -import DBModule class MessagingModel: BaseModel { - init(flutterBinary: FlutterBinaryMessenger) throws { - var error: NSError? - guard let model = InternalsdkNewMessagingModel(try BaseModel.getDB(), &error) else { - throw error! - } - try super.init(flutterBinary, model) + init(flutterBinary: FlutterBinaryMessenger) throws { + var error: NSError? + guard let model = InternalsdkNewMessagingModel(try BaseModel.getDB(), &error) else { + throw error! } + try super.init(flutterBinary, model) + } } diff --git a/ios/Runner/Lantern/Models/NavigationModel.swift b/ios/Runner/Lantern/Models/NavigationModel.swift index 2babc9828..856102bf1 100644 --- a/ios/Runner/Lantern/Models/NavigationModel.swift +++ b/ios/Runner/Lantern/Models/NavigationModel.swift @@ -5,37 +5,38 @@ // Created by jigar fumakiya on 12/09/23. // -import Foundation import Flutter +import Foundation class NavigationModel { - let navigationMethodChannel = "lantern_method_channel" - - var flutterbinaryMessenger: FlutterBinaryMessenger - - init(flutterBinary: FlutterBinaryMessenger) { - self.flutterbinaryMessenger=flutterBinary - prepareNavigationChannel() - } - - private func prepareNavigationChannel () { - - // Navigation Channel - let navigationChannel=FlutterMethodChannel(name: navigationMethodChannel, binaryMessenger: flutterbinaryMessenger) - navigationChannel.setMethodCallHandler(handleNavigationethodCall) - } - - func handleNavigationethodCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - // Handle your method calls here - // The 'call' contains the method name and arguments - // The 'result' can be used to send back the data to Flutter - switch call.method { - case "yourMethod": - // handle yourMethod - break - default: - result(FlutterMethodNotImplemented) - } + let navigationMethodChannel = "lantern_method_channel" + + var flutterbinaryMessenger: FlutterBinaryMessenger + + init(flutterBinary: FlutterBinaryMessenger) { + self.flutterbinaryMessenger = flutterBinary + prepareNavigationChannel() + } + + private func prepareNavigationChannel() { + + // Navigation Channel + let navigationChannel = FlutterMethodChannel( + name: navigationMethodChannel, binaryMessenger: flutterbinaryMessenger) + navigationChannel.setMethodCallHandler(handleNavigationethodCall) + } + + func handleNavigationethodCall(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + // Handle your method calls here + // The 'call' contains the method name and arguments + // The 'result' can be used to send back the data to Flutter + switch call.method { + case "yourMethod": + // handle yourMethod + break + default: + result(FlutterMethodNotImplemented) } + } } diff --git a/ios/Runner/Lantern/Models/SessionModel.swift b/ios/Runner/Lantern/Models/SessionModel.swift index 4bf9a1a90..798beb2dd 100644 --- a/ios/Runner/Lantern/Models/SessionModel.swift +++ b/ios/Runner/Lantern/Models/SessionModel.swift @@ -12,16 +12,16 @@ import Internalsdk import UIKit class SessionModel: BaseModel { -lazy var notificationsManager: UserNotificationsManager = { + lazy var notificationsManager: UserNotificationsManager = { return UserNotificationsManager() }() init(flutterBinary: FlutterBinaryMessenger) throws { - var error: NSError? - guard let model = InternalsdkNewSessionModel(try BaseModel.getDB(), &error) else { - throw error! - } - try super.init(flutterBinary, model) + var error: NSError? + guard let model = InternalsdkNewSessionModel(try BaseModel.getDB(), &error) else { + throw error! + } + try super.init(flutterBinary, model) initializeAppSettings() } @@ -158,7 +158,7 @@ lazy var notificationsManager: UserNotificationsManager = { } private func setDNS() { - // TODO: why are we setting timezone in setDNS()? + // TODO: why are we setting timezone in setDNS()? let timeZoneId = TimeZone.current.identifier let miniSqlValue = ValueUtil.convertToMinisqlValue(DnsDetector.DEFAULT_DNS_SERVER) if miniSqlValue != nil { diff --git a/ios/Runner/Lantern/Models/VpnModel.swift b/ios/Runner/Lantern/Models/VpnModel.swift index cdd4bd058..7f46db7e1 100644 --- a/ios/Runner/Lantern/Models/VpnModel.swift +++ b/ios/Runner/Lantern/Models/VpnModel.swift @@ -2,60 +2,62 @@ // Runner // Created by jigar fumakiya on 01/09/23. +import DBModule +import Flutter import Foundation import Internalsdk -import Flutter -import DBModule class VpnModel: BaseModel { - let vpnManager: VPNBase - let vpnHelper = VpnHelper.shared - - init(flutterBinary: FlutterBinaryMessenger, vpnBase: VPNBase) throws { - self.vpnManager = vpnBase - var error: NSError? - guard let model = InternalsdkNewVpnModel(try BaseModel.getDB(), &error) else { - throw error! - } - try super.init(flutterBinary, model) - } - - private func saveVPNStatus(status: String) { - let miniSqlValue = ValueUtil.convertToMinisqlValue(status) - if miniSqlValue != nil { - do { - let result = try invokeMethodOnGo("saveVpnStatus", miniSqlValue!) - logger.log("Sucessfully set VPN status with \(status)") - } catch { - logger.log("Error while setting VPN status") - } - } + let vpnManager: VPNBase + let vpnHelper = VpnHelper.shared + + init(flutterBinary: FlutterBinaryMessenger, vpnBase: VPNBase) throws { + self.vpnManager = vpnBase + var error: NSError? + guard let model = InternalsdkNewVpnModel(try BaseModel.getDB(), &error) else { + throw error! } - - func switchVPN(status: Bool) { - if status { - startVPN() - } else { - stopVPN() - } + try super.init(flutterBinary, model) + } + + private func saveVPNStatus(status: String) { + let miniSqlValue = ValueUtil.convertToMinisqlValue(status) + if miniSqlValue != nil { + do { + let result = try invokeMethodOnGo("saveVpnStatus", miniSqlValue!) + logger.log("Sucessfully set VPN status with \(status)") + } catch { + logger.log("Error while setting VPN status") + } } + } - func startVPN() { - self.saveVPNStatus(status: "connected") - vpnHelper.startVPN( onError: { error in - // in case of error, reset switch position - self.saveVPNStatus(status: "disconnected") - logger.debug("VPN not started \(error)") - }, onSuccess: { - logger.debug("VPN started") - self.saveVPNStatus(status: "connected") - }) + func switchVPN(status: Bool) { + if status { + startVPN() + } else { + stopVPN() } + } - func stopVPN() { - vpnHelper.stopVPN() - logger.debug("VPN Successfully stoped") + func startVPN() { + self.saveVPNStatus(status: "connected") + vpnHelper.startVPN( + onError: { error in + // in case of error, reset switch position self.saveVPNStatus(status: "disconnected") - } + logger.debug("VPN not started \(error)") + }, + onSuccess: { + logger.debug("VPN started") + self.saveVPNStatus(status: "connected") + }) + } + + func stopVPN() { + vpnHelper.stopVPN() + logger.debug("VPN Successfully stoped") + self.saveVPNStatus(status: "disconnected") + } } diff --git a/ios/Runner/Lantern/Utils/Constants.swift b/ios/Runner/Lantern/Utils/Constants.swift index e1dd0464f..6a980c4fe 100644 --- a/ios/Runner/Lantern/Utils/Constants.swift +++ b/ios/Runner/Lantern/Utils/Constants.swift @@ -5,114 +5,132 @@ import Foundation import Internalsdk -struct Constants { - // MARK: Convenience Inits - static let appDefault = Constants(process: .app) - static let netExDefault = Constants(process: .netEx) - // MARK: Project Constants - static let appBundleId = "org.getlantern.lantern" - static let netExBundleId = "org.getlantern.lantern.Tunnel" - static let appGroupName = "group.getlantern.lantern" - // MARK: App Group - static let appGroupDefaults = UserDefaults(suiteName: appGroupName)! - static var appGroupContainerURL: URL { - // implicitly unwrapped because the app cant work without this, might as well crash. - return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)! - } - - // MARK: App/NetEx Message Data - static let configUpdatedMessageData = "Flashlight.ConfigUpdated".data(using: .utf8)! - static let configUpdatedACKData = "Flashlight.TunnelUpdated".data(using: .utf8)! - static let requestReadWriteCountMessageData = "Flashlight.ReadWriteCount".data(using: .utf8)! - - // Key used for passing reason from app->netEx when startTunnel is called - static let netExStartReasonKey: String = "netEx.StartReason" - - // Key used in shared UserDefaults for caching user-selected locale - static let localeSharedDefaultsKey = "Localization.SelectedLocale" - - static let defaultLogRotationFileCount = 5 - - // MARK: Pro - static let isPro = "is_pro" - - // MARK: User IDs - static let userID = "user_id" - static let proToken = "pro_token" - - // MARK: Privacy Policy - static let acceptedPrivacyPolicyVersion = "accepted_privacy_policy_version" - static let currentPrivacyPolicyVersion = 1 - - // MARK: Tunnel Settings - static let capturedDNSHost = "8.8.8.8" - static let realDNSHost = "8.8.4.4" - - // MARK: Init - - init(process: Process, - sharedContainerURL: URL = Constants.appGroupContainerURL) { - self.sharedContainerURL = sharedContainerURL - self.configDirectoryURL = sharedContainerURL.appendingPathComponent("config", isDirectory: true) - // Need refs for both on app-side for Log Submission - self.appDirectoryURL = sharedContainerURL.appendingPathComponent("app", isDirectory: true) - self.netExDirectoryURL = sharedContainerURL.appendingPathComponent("netEx", isDirectory: true) - print("app config path: \(configDirectoryURL)") - - let current: URL - switch process { - case .app: current = appDirectoryURL - case .netEx: current = netExDirectoryURL - } - self.targetDirectoryURL = current - } - - // MARK: Directory URLs - - let sharedContainerURL: URL - let configDirectoryURL: URL - let appDirectoryURL: URL - let netExDirectoryURL: URL - - let targetDirectoryURL: URL // convenience - - // MARK: Log Base URLs - - var goLogBaseURL: URL { return targetDirectoryURL.appendingPathComponent("lantern.log") } - var heapProfileURL: URL { return targetDirectoryURL.appendingPathComponent("heap.profile") } - var heapProfileTempURL: URL { return targetDirectoryURL.appendingPathComponent("heap.profile.tmp") } - var goroutineProfileURL: URL { return targetDirectoryURL.appendingPathComponent("goroutine_profile.txt") } - var goroutineProfileTempURL: URL { return targetDirectoryURL.appendingPathComponent("goroutine_profile.txt.tmp") } - - // MARK: Config URLs - - var configURL: URL { return configDirectoryURL.appendingPathComponent("global.yaml") } - var configEtagURL: URL { return configDirectoryURL.appendingPathComponent("global.yaml.etag") } - var proxiesURL: URL { return configDirectoryURL.appendingPathComponent("proxies.yaml") } - var proxyStatsURL: URL { return configDirectoryURL.appendingPathComponent("proxystats.csv")} - var proxyStatsTempURL: URL { return configDirectoryURL.appendingPathComponent("proxystats.csv.tmp")} - var proxiesEtagURL: URL { return configDirectoryURL.appendingPathComponent("proxies.yaml.etag") } - var tlsSessionStatesURL: URL { return configDirectoryURL.appendingPathComponent("tls_session_states")} - var masqueradeCacheURL: URL { return configDirectoryURL.appendingPathComponent("masquerade_cache") } - var userConfigURL: URL { return configDirectoryURL.appendingPathComponent("userconfig.yaml") } - var quotaURL: URL { return configDirectoryURL.appendingPathComponent("quota.txt") } - var dnsgrabCacheURL: URL { return configDirectoryURL.appendingPathComponent("dnsgrab.cache") } - - var allConfigURLs: [URL] { - return [configURL, configEtagURL, proxiesURL, proxyStatsURL, proxyStatsTempURL, proxiesEtagURL, tlsSessionStatesURL, masqueradeCacheURL, userConfigURL, quotaURL, dnsgrabCacheURL] +struct Constants { + // MARK: Convenience Inits + static let appDefault = Constants(process: .app) + static let netExDefault = Constants(process: .netEx) + // MARK: Project Constants + static let appBundleId = "org.getlantern.lantern" + static let netExBundleId = "org.getlantern.lantern.Tunnel" + static let appGroupName = "group.getlantern.lantern" + // MARK: App Group + static let appGroupDefaults = UserDefaults(suiteName: appGroupName)! + static var appGroupContainerURL: URL { + // implicitly unwrapped because the app cant work without this, might as well crash. + return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)! + } + + // MARK: App/NetEx Message Data + static let configUpdatedMessageData = "Flashlight.ConfigUpdated".data(using: .utf8)! + static let configUpdatedACKData = "Flashlight.TunnelUpdated".data(using: .utf8)! + static let requestReadWriteCountMessageData = "Flashlight.ReadWriteCount".data(using: .utf8)! + + // Key used for passing reason from app->netEx when startTunnel is called + static let netExStartReasonKey: String = "netEx.StartReason" + + // Key used in shared UserDefaults for caching user-selected locale + static let localeSharedDefaultsKey = "Localization.SelectedLocale" + + static let defaultLogRotationFileCount = 5 + + // MARK: Pro + static let isPro = "is_pro" + + // MARK: User IDs + static let userID = "user_id" + static let proToken = "pro_token" + + // MARK: Privacy Policy + static let acceptedPrivacyPolicyVersion = "accepted_privacy_policy_version" + static let currentPrivacyPolicyVersion = 1 + + // MARK: Tunnel Settings + static let capturedDNSHost = "8.8.8.8" + static let realDNSHost = "8.8.4.4" + + // MARK: Init + + init( + process: Process, + sharedContainerURL: URL = Constants.appGroupContainerURL + ) { + self.sharedContainerURL = sharedContainerURL + self.configDirectoryURL = sharedContainerURL.appendingPathComponent("config", isDirectory: true) + + // Need refs for both on app-side for Log Submission + self.appDirectoryURL = sharedContainerURL.appendingPathComponent("app", isDirectory: true) + self.netExDirectoryURL = sharedContainerURL.appendingPathComponent("netEx", isDirectory: true) + print("app config path: \(configDirectoryURL)") + + let current: URL + switch process { + case .app: current = appDirectoryURL + case .netEx: current = netExDirectoryURL } - - // URL where app stores "excluded IPs" string from `IosConfigure`. - // NetEx loads them to generate excluded routes on the TUN device. - var excludedIPsURL: URL { return configDirectoryURL.appendingPathComponent("ips") } - - // MARK: Const for database keys - // All values comes from go even if values chaegs it will reflact here - static var developmentMode = InternalsdkDEVELOPMNET_MODE - static var prouser = InternalsdkPRO_USER - static var deviceid = InternalsdkDEVICE_ID - static var playVersion = InternalsdkIS_PLAY_VERSION - static var lang = InternalsdkLANG + self.targetDirectoryURL = current + } + + // MARK: Directory URLs + + let sharedContainerURL: URL + let configDirectoryURL: URL + let appDirectoryURL: URL + let netExDirectoryURL: URL + + let targetDirectoryURL: URL // convenience + + // MARK: Log Base URLs + + var goLogBaseURL: URL { return targetDirectoryURL.appendingPathComponent("lantern.log") } + var heapProfileURL: URL { return targetDirectoryURL.appendingPathComponent("heap.profile") } + var heapProfileTempURL: URL { + return targetDirectoryURL.appendingPathComponent("heap.profile.tmp") + } + var goroutineProfileURL: URL { + return targetDirectoryURL.appendingPathComponent("goroutine_profile.txt") + } + var goroutineProfileTempURL: URL { + return targetDirectoryURL.appendingPathComponent("goroutine_profile.txt.tmp") + } + + // MARK: Config URLs + + var configURL: URL { return configDirectoryURL.appendingPathComponent("global.yaml") } + var configEtagURL: URL { return configDirectoryURL.appendingPathComponent("global.yaml.etag") } + var proxiesURL: URL { return configDirectoryURL.appendingPathComponent("proxies.yaml") } + var proxyStatsURL: URL { return configDirectoryURL.appendingPathComponent("proxystats.csv") } + var proxyStatsTempURL: URL { + return configDirectoryURL.appendingPathComponent("proxystats.csv.tmp") + } + var proxiesEtagURL: URL { return configDirectoryURL.appendingPathComponent("proxies.yaml.etag") } + var tlsSessionStatesURL: URL { + return configDirectoryURL.appendingPathComponent("tls_session_states") + } + var masqueradeCacheURL: URL { + return configDirectoryURL.appendingPathComponent("masquerade_cache") + } + var userConfigURL: URL { return configDirectoryURL.appendingPathComponent("userconfig.yaml") } + var quotaURL: URL { return configDirectoryURL.appendingPathComponent("quota.txt") } + var dnsgrabCacheURL: URL { return configDirectoryURL.appendingPathComponent("dnsgrab.cache") } + + var allConfigURLs: [URL] { + return [ + configURL, configEtagURL, proxiesURL, proxyStatsURL, proxyStatsTempURL, proxiesEtagURL, + tlsSessionStatesURL, masqueradeCacheURL, userConfigURL, quotaURL, dnsgrabCacheURL, + ] + } + + // URL where app stores "excluded IPs" string from `IosConfigure`. + // NetEx loads them to generate excluded routes on the TUN device. + var excludedIPsURL: URL { return configDirectoryURL.appendingPathComponent("ips") } + + // MARK: Const for database keys + // All values comes from go even if values chaegs it will reflact here + static var developmentMode = InternalsdkDEVELOPMNET_MODE + static var prouser = InternalsdkPRO_USER + static var deviceid = InternalsdkDEVICE_ID + static var playVersion = InternalsdkIS_PLAY_VERSION + static var lang = InternalsdkLANG } diff --git a/ios/Runner/Lantern/Utils/Event.swift b/ios/Runner/Lantern/Utils/Event.swift index b8d4c8e42..9123b5289 100644 --- a/ios/Runner/Lantern/Utils/Event.swift +++ b/ios/Runner/Lantern/Utils/Event.swift @@ -12,53 +12,55 @@ import Foundation public class Event { - public typealias EventHandler = (T) -> Void + public typealias EventHandler = (T) -> Void - fileprivate var eventHandlers = [Invocable]() + fileprivate var eventHandlers = [Invocable]() - public func raise(_ data: T) { - for handler in self.eventHandlers { - handler.invoke(data) - } + public func raise(_ data: T) { + for handler in self.eventHandlers { + handler.invoke(data) } + } - public func addHandler(target: U, - handler: @escaping (U) -> EventHandler) -> Disposable { - let wrapper = EventHandlerWrapper(target: target, - handler: handler, event: self) - eventHandlers.append(wrapper) - return wrapper - } + public func addHandler( + target: U, + handler: @escaping (U) -> EventHandler + ) -> Disposable { + let wrapper = EventHandlerWrapper( + target: target, + handler: handler, event: self) + eventHandlers.append(wrapper) + return wrapper + } } private protocol Invocable: class { - func invoke(_ data: Any) + func invoke(_ data: Any) } -private class EventHandlerWrapper -: Invocable, Disposable { - weak var target: T? - let handler: (T) -> (U) -> Void - let event: Event +private class EventHandlerWrapper: Invocable, Disposable { + weak var target: T? + let handler: (T) -> (U) -> Void + let event: Event - init(target: T?, handler: @escaping (T) -> (U) -> Void, event: Event) { - self.target = target - self.handler = handler - self.event = event - } + init(target: T?, handler: @escaping (T) -> (U) -> Void, event: Event) { + self.target = target + self.handler = handler + self.event = event + } - func invoke(_ data: Any) { - if let t = target { - handler(t)(data as! U) - } + func invoke(_ data: Any) { + if let t = target { + handler(t)(data as! U) } + } - func dispose() { - event.eventHandlers = - event.eventHandlers.filter { $0 !== self } - } + func dispose() { + event.eventHandlers = + event.eventHandlers.filter { $0 !== self } + } } public protocol Disposable { - func dispose() + func dispose() } diff --git a/ios/Runner/Lantern/Utils/Events.swift b/ios/Runner/Lantern/Utils/Events.swift index 26cc563c2..dd0aed8aa 100644 --- a/ios/Runner/Lantern/Utils/Events.swift +++ b/ios/Runner/Lantern/Utils/Events.swift @@ -9,7 +9,7 @@ import Foundation struct Events { - static let configFetchTakingLongTime = Event() + static let configFetchTakingLongTime = Event() - static let updateAvailable = Event() + static let updateAvailable = Event() } diff --git a/ios/Runner/Lantern/Utils/FileUtils.swift b/ios/Runner/Lantern/Utils/FileUtils.swift index 39786139f..7a85e9bab 100644 --- a/ios/Runner/Lantern/Utils/FileUtils.swift +++ b/ios/Runner/Lantern/Utils/FileUtils.swift @@ -12,33 +12,35 @@ import Foundation // Function signatures match the core functionality of FileManager. extension FileManager { - func ensureDirectoryExists(at url: URL) throws { - if !fileExists(atPath: url.path, isDirectory: nil) { - try createDirectory(at: url, - withIntermediateDirectories: false, - attributes: nil) - } + func ensureDirectoryExists(at url: URL) throws { + if !fileExists(atPath: url.path, isDirectory: nil) { + try createDirectory( + at: url, + withIntermediateDirectories: false, + attributes: nil) } + } - @discardableResult func ensureFilesExist(at urls: [URL]) -> Bool { - var overallSuccess = true - urls.forEach { url in - let path = url.path - if !fileExists(atPath: path) { - // posix permission 666 is `rw-rw-rw` aka read/write for all - let rwAllPermission = 0o666 as Int16 - let success = createFile(atPath: path, contents: nil, attributes: [.posixPermissions: rwAllPermission]) - if !success { overallSuccess = false } - } - } - return overallSuccess + @discardableResult func ensureFilesExist(at urls: [URL]) -> Bool { + var overallSuccess = true + urls.forEach { url in + let path = url.path + if !fileExists(atPath: path) { + // posix permission 666 is `rw-rw-rw` aka read/write for all + let rwAllPermission = 0o666 as Int16 + let success = createFile( + atPath: path, contents: nil, attributes: [.posixPermissions: rwAllPermission]) + if !success { overallSuccess = false } + } } + return overallSuccess + } - func generateLogRotationURLs(count: Int, from baseURL: URL) -> [URL] { - var urls = [baseURL] - guard count > 1 else { return urls } - // make `.log.1` up to X - for i in 1...count { urls.append(baseURL.appendingPathExtension("\(i)")) } - return urls - } + func generateLogRotationURLs(count: Int, from baseURL: URL) -> [URL] { + var urls = [baseURL] + guard count > 1 else { return urls } + // make `.log.1` up to X + for i in 1...count { urls.append(baseURL.appendingPathExtension("\(i)")) } + return urls + } } diff --git a/ios/Runner/Lantern/Utils/JsonUtils.swift b/ios/Runner/Lantern/Utils/JsonUtils.swift index c86c5728e..6c62d13ba 100644 --- a/ios/Runner/Lantern/Utils/JsonUtils.swift +++ b/ios/Runner/Lantern/Utils/JsonUtils.swift @@ -8,13 +8,13 @@ import Foundation class JsonUtil { - static func convertToJSONString(_ initData: [String: [String: Any]]) -> String? { - do { - let jsonData = try JSONSerialization.data(withJSONObject: initData) - return String(data: jsonData, encoding: .utf8) - } catch { - print("Error converting initData to JSON: \(error)") - return nil - } + static func convertToJSONString(_ initData: [String: [String: Any]]) -> String? { + do { + let jsonData = try JSONSerialization.data(withJSONObject: initData) + return String(data: jsonData, encoding: .utf8) + } catch { + print("Error converting initData to JSON: \(error)") + return nil } + } } diff --git a/ios/Runner/Lantern/Utils/LoadingIndicatorManager.swift b/ios/Runner/Lantern/Utils/LoadingIndicatorManager.swift index 3f8594681..0efff8d68 100644 --- a/ios/Runner/Lantern/Utils/LoadingIndicatorManager.swift +++ b/ios/Runner/Lantern/Utils/LoadingIndicatorManager.swift @@ -9,31 +9,31 @@ import Foundation import UIKit class LoadingIndicatorManager { - private var loadingIndicator: UIActivityIndicatorView? - private weak var parentView: UIView? + private var loadingIndicator: UIActivityIndicatorView? + private weak var parentView: UIView? - init(parentView: UIView) { - self.parentView = parentView - setupLoadingIndicator() - } + init(parentView: UIView) { + self.parentView = parentView + setupLoadingIndicator() + } - private func setupLoadingIndicator() { - loadingIndicator = UIActivityIndicatorView(style: .gray) - guard let loadingIndicator = loadingIndicator, let parentView = parentView else { - return - } - loadingIndicator.center = parentView.center - loadingIndicator.hidesWhenStopped = true - parentView.addSubview(loadingIndicator) + private func setupLoadingIndicator() { + loadingIndicator = UIActivityIndicatorView(style: .gray) + guard let loadingIndicator = loadingIndicator, let parentView = parentView else { + return } + loadingIndicator.center = parentView.center + loadingIndicator.hidesWhenStopped = true + parentView.addSubview(loadingIndicator) + } - func show() { - loadingIndicator?.startAnimating() - } + func show() { + loadingIndicator?.startAnimating() + } - func hide() { - loadingIndicator?.stopAnimating() - loadingIndicator?.removeFromSuperview() - loadingIndicator = nil - } + func hide() { + loadingIndicator?.stopAnimating() + loadingIndicator?.removeFromSuperview() + loadingIndicator = nil + } } diff --git a/ios/Runner/Lantern/Utils/Logger.swift b/ios/Runner/Lantern/Utils/Logger.swift index 0d6a7eb19..d74fe47d5 100644 --- a/ios/Runner/Lantern/Utils/Logger.swift +++ b/ios/Runner/Lantern/Utils/Logger.swift @@ -5,27 +5,27 @@ // Created by jigar fumakiya on 20/07/23. // -import os import Internalsdk +import os let logger = LanternLogger() class LanternLogger { - private let logger = OSLog(subsystem: "", category: "") - private let prefix: String = "[LANTERN-IOS]" + private let logger = OSLog(subsystem: "", category: "") + private let prefix: String = "[LANTERN-IOS]" - func log(_ message: String) { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" - let date = dateFormatter.string(from: Date()) - os_log("%{public}@", log: logger, type: .default, "\(prefix) - \(date): \(message)") - } + func log(_ message: String) { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" + let date = dateFormatter.string(from: Date()) + os_log("%{public}@", log: logger, type: .default, "\(prefix) - \(date): \(message)") + } - func error(_ msg: String) { - IosLogError(msg) - } + func error(_ msg: String) { + IosLogError(msg) + } - func debug(_ msg: String) { - IosLogDebug(msg) - } + func debug(_ msg: String) { + IosLogDebug(msg) + } } diff --git a/ios/Runner/Lantern/Utils/Process.swift b/ios/Runner/Lantern/Utils/Process.swift index 3ba6082fa..1c72893d0 100644 --- a/ios/Runner/Lantern/Utils/Process.swift +++ b/ios/Runner/Lantern/Utils/Process.swift @@ -7,17 +7,17 @@ import Foundation import os.log var currentProcess: Process = { - // lazy instead of computed cause this gets called _a lot_ - switch Bundle.main.bundleIdentifier! { - case Constants.appBundleId: return .app - case Constants.netExBundleId: return .netEx - default: - // TODO: see if you can use this to your advantage in the Test target - fatalError() // this shouldn't even be possible - } + // lazy instead of computed cause this gets called _a lot_ + switch Bundle.main.bundleIdentifier! { + case Constants.appBundleId: return .app + case Constants.netExBundleId: return .netEx + default: + // TODO: see if you can use this to your advantage in the Test target + fatalError() // this shouldn't even be possible + } }() enum Process { - case app - case netEx + case app + case netEx } diff --git a/ios/Runner/Lantern/Utils/RunningEnv.swift b/ios/Runner/Lantern/Utils/RunningEnv.swift index e3f76456e..430ec2156 100644 --- a/ios/Runner/Lantern/Utils/RunningEnv.swift +++ b/ios/Runner/Lantern/Utils/RunningEnv.swift @@ -7,52 +7,52 @@ import Foundation func isRunningFromAppStore() -> Bool { - let file = "\(NSHomeDirectory())/iTunesMetadata.plist" - if FileManager.default.fileExists(atPath: file) { - // The app is running from the App Store - return true - } else { - // The app is not running from the App Store - return false - } + let file = "\(NSHomeDirectory())/iTunesMetadata.plist" + if FileManager.default.fileExists(atPath: file) { + // The app is running from the App Store + return true + } else { + // The app is not running from the App Store + return false + } } func isRunningInTestFlightEnvironment() -> Bool { - if isSimulator() { - return false + if isSimulator() { + return false + } else { + if isAppStoreReceiptSandbox() && !hasEmbeddedMobileProvision() { + return true } else { - if isAppStoreReceiptSandbox() && !hasEmbeddedMobileProvision() { - return true - } else { - return false - } + return false } + } } private func hasEmbeddedMobileProvision() -> Bool { - if let _ = Bundle.main.path(forResource: "embedded", ofType: "mobileprovision") { - return true - } - return false + if let _ = Bundle.main.path(forResource: "embedded", ofType: "mobileprovision") { + return true + } + return false } func isAppStoreReceiptSandbox() -> Bool { - if isSimulator() { - return false - } else { - if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL { - if appStoreReceiptURL.lastPathComponent == "sandboxReceipt" { - return true - } - } - return false + if isSimulator() { + return false + } else { + if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL { + if appStoreReceiptURL.lastPathComponent == "sandboxReceipt" { + return true + } } + return false + } } func isSimulator() -> Bool { - #if arch(i386) || arch(x86_64) + #if arch(i386) || arch(x86_64) return true - #else + #else return false - #endif + #endif } diff --git a/ios/Runner/Lantern/Utils/UserNotificationsManager.swift b/ios/Runner/Lantern/Utils/UserNotificationsManager.swift index a62645be2..c08bfa083 100644 --- a/ios/Runner/Lantern/Utils/UserNotificationsManager.swift +++ b/ios/Runner/Lantern/Utils/UserNotificationsManager.swift @@ -7,101 +7,107 @@ import Foundation import UserNotifications class UserNotificationsManager: NSObject, UNUserNotificationCenterDelegate { - static let shared = UserNotificationsManager() + static let shared = UserNotificationsManager() - private static let lastDataCapNotificationDefaultsKey = "Lantern.NotifyDataCapDate" - let dataUsageUpdatedNotification = Notification.Name("Lantern.dataUsageUpdated") - let center: UNUserNotificationCenter - let userDefaults: UserDefaults - // Check if user has alerady notification permssion or not - var notificationsEnabled: Bool { - get { - var isAuthorized = false - // This will ensure synchronous access to notification permission - let semaphore = DispatchSemaphore(value: 0) - UNUserNotificationCenter.current().getNotificationSettings { settings in - if settings.authorizationStatus == .authorized || settings.authorizationStatus == .provisional { - isAuthorized = true - } - semaphore.signal() - } - // Waiting for the completion handler of getNotificationSettings to complete - _ = semaphore.wait(timeout: .distantFuture) - return isAuthorized - } + private static let lastDataCapNotificationDefaultsKey = "Lantern.NotifyDataCapDate" + let dataUsageUpdatedNotification = Notification.Name("Lantern.dataUsageUpdated") + let center: UNUserNotificationCenter + let userDefaults: UserDefaults + // Check if user has alerady notification permssion or not + var notificationsEnabled: Bool { + var isAuthorized = false + // This will ensure synchronous access to notification permission + let semaphore = DispatchSemaphore(value: 0) + UNUserNotificationCenter.current().getNotificationSettings { settings in + if settings.authorizationStatus == .authorized || settings.authorizationStatus == .provisional + { + isAuthorized = true + } + semaphore.signal() } + // Waiting for the completion handler of getNotificationSettings to complete + _ = semaphore.wait(timeout: .distantFuture) + return isAuthorized + } - private var notifiedDataCapThisMonth: Bool { - guard let value = userDefaults.value(forKey: UserNotificationsManager.lastDataCapNotificationDefaultsKey), - let lastDate = (value as? Date) else { return false } - return Calendar.current.isDate(lastDate, equalTo: Date(), toGranularity: .month) - } + private var notifiedDataCapThisMonth: Bool { + guard + let value = userDefaults.value( + forKey: UserNotificationsManager.lastDataCapNotificationDefaultsKey), + let lastDate = (value as? Date) + else { return false } + return Calendar.current.isDate(lastDate, equalTo: Date(), toGranularity: .month) + } - // MARK: Init + // MARK: Init - init(center: UNUserNotificationCenter = .current(), - userDefaults: UserDefaults = Constants.appGroupDefaults) { - self.center = center - self.userDefaults = userDefaults - } + init( + center: UNUserNotificationCenter = .current(), + userDefaults: UserDefaults = Constants.appGroupDefaults + ) { + self.center = center + self.userDefaults = userDefaults + } - func requestNotificationPermission(completion: @escaping (Bool) -> Void) { - center.getNotificationSettings { settings in - switch settings.authorizationStatus { - case .notDetermined: - self.center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in - DispatchQueue.main.async { - completion(granted) - } - } - case .denied: - DispatchQueue.main.async { - completion(false) - } - case .authorized, .provisional: - DispatchQueue.main.async { - completion(true) - } - default: - DispatchQueue.main.async { - completion(false) - } - } + func requestNotificationPermission(completion: @escaping (Bool) -> Void) { + center.getNotificationSettings { settings in + switch settings.authorizationStatus { + case .notDetermined: + self.center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in + DispatchQueue.main.async { + completion(granted) + } + } + case .denied: + DispatchQueue.main.async { + completion(false) } + case .authorized, .provisional: + DispatchQueue.main.async { + completion(true) + } + default: + DispatchQueue.main.async { + completion(false) + } + } } + } - // MARK: Posting - func scheduleDataCapLocalNotification(withDataLimit limit: Int) { - guard notificationsEnabled, !notifiedDataCapThisMonth else { return } - let content = localizedNotificationContent(withDataLimit: limit) - let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) - let request = UNNotificationRequest(identifier: "Lantern.DataCap", - content: content, - trigger: trigger) - center.add(request) { [weak self] error in - if error == nil { - let key = UserNotificationsManager.lastDataCapNotificationDefaultsKey - self?.userDefaults.set(Date(), forKey: key) - } - } + // MARK: Posting + func scheduleDataCapLocalNotification(withDataLimit limit: Int) { + guard notificationsEnabled, !notifiedDataCapThisMonth else { return } + let content = localizedNotificationContent(withDataLimit: limit) + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) + let request = UNNotificationRequest( + identifier: "Lantern.DataCap", + content: content, + trigger: trigger) + center.add(request) { [weak self] error in + if error == nil { + let key = UserNotificationsManager.lastDataCapNotificationDefaultsKey + self?.userDefaults.set(Date(), forKey: key) + } } + } - // Todo:- Implement generic way to support multiple lang - // Same as Android - func localizedNotificationContent(withDataLimit limit: Int) -> UNMutableNotificationContent { - let content = UNMutableNotificationContent() + // Todo:- Implement generic way to support multiple lang + // Same as Android + func localizedNotificationContent(withDataLimit limit: Int) -> UNMutableNotificationContent { + let content = UNMutableNotificationContent() - let localeString = userDefaults.string(forKey: Constants.localeSharedDefaultsKey) ?? "en-US" - // ^default to English, only other supported language right now + let localeString = userDefaults.string(forKey: Constants.localeSharedDefaultsKey) ?? "en-US" + // ^default to English, only other supported language right now - // localizing "raw" to avoid adding Text.swift + strings files to netEx - if localeString == "zh_CN" { - content.title = "你已经超过每月\(limit)MB高速流量限制" - content.body = "您能继续使用蓝灯,但会被限速。您的高速流量月初会重置。" - } else { - content.title = "You have hit your \(limit)MB monthly data cap." - content.body = "You can continue to use Lantern, but speeds will be reduced. Your data cap will be reset at the beginning of the month." - } - return content + // localizing "raw" to avoid adding Text.swift + strings files to netEx + if localeString == "zh_CN" { + content.title = "你已经超过每月\(limit)MB高速流量限制" + content.body = "您能继续使用蓝灯,但会被限速。您的高速流量月初会重置。" + } else { + content.title = "You have hit your \(limit)MB monthly data cap." + content.body = + "You can continue to use Lantern, but speeds will be reduced. Your data cap will be reset at the beginning of the month." } + return content + } } diff --git a/ios/Tunnel/NEProviderStopReason.swift b/ios/Tunnel/NEProviderStopReason.swift index bc843aeb9..3b9954cb7 100644 --- a/ios/Tunnel/NEProviderStopReason.swift +++ b/ios/Tunnel/NEProviderStopReason.swift @@ -7,44 +7,44 @@ import NetworkExtension // Just for logging purposes, Status taken from NEProviderStopReason extension NEProviderStopReason { - var debugString: String { - switch self { - case .none: - return "NEProviderStopReasonNone" - case .userInitiated: - return "NEProviderStopReasonUserInitiated" - case .providerFailed: - return "NEProviderStopReasonProviderFailed" - case .noNetworkAvailable: - return "NEProviderStopReasonNoNetworkAvailable" - case .unrecoverableNetworkChange: - return "NEProviderStopReasonUnrecoverableNetworkChange" - case .providerDisabled: - return "NEProviderStopReasonProviderDisabled" - case .authenticationCanceled: - return "NEProviderStopReasonAuthenticationCanceled" - case .configurationFailed: - return "NEProviderStopReasonConfigurationFailed" - case .idleTimeout: - return "NEProviderStopReasonIdleTimeout" - case .configurationDisabled: - return "NEProviderStopReasonConfigurationDisabled" - case .configurationRemoved: - return "NEProviderStopReasonConfigurationRemoved" - case .superceded: - return "NEProviderStopReasonSuperceded" - case .userLogout: - return "NEProviderStopReasonUserLogout" - case .userSwitch: - return "NEProviderStopReasonUserSwitch" - case .connectionFailed: - return "NEProviderStopReasonConnectionFailed" - case .sleep: - return "NEProviderStopReasonSleep" - case .appUpdate: - return "NEProviderStopReasonAppUpdate" - @unknown default: - return "Unsupported NEProviderStopReason" - } + var debugString: String { + switch self { + case .none: + return "NEProviderStopReasonNone" + case .userInitiated: + return "NEProviderStopReasonUserInitiated" + case .providerFailed: + return "NEProviderStopReasonProviderFailed" + case .noNetworkAvailable: + return "NEProviderStopReasonNoNetworkAvailable" + case .unrecoverableNetworkChange: + return "NEProviderStopReasonUnrecoverableNetworkChange" + case .providerDisabled: + return "NEProviderStopReasonProviderDisabled" + case .authenticationCanceled: + return "NEProviderStopReasonAuthenticationCanceled" + case .configurationFailed: + return "NEProviderStopReasonConfigurationFailed" + case .idleTimeout: + return "NEProviderStopReasonIdleTimeout" + case .configurationDisabled: + return "NEProviderStopReasonConfigurationDisabled" + case .configurationRemoved: + return "NEProviderStopReasonConfigurationRemoved" + case .superceded: + return "NEProviderStopReasonSuperceded" + case .userLogout: + return "NEProviderStopReasonUserLogout" + case .userSwitch: + return "NEProviderStopReasonUserSwitch" + case .connectionFailed: + return "NEProviderStopReasonConnectionFailed" + case .sleep: + return "NEProviderStopReasonSleep" + case .appUpdate: + return "NEProviderStopReasonAppUpdate" + @unknown default: + return "Unsupported NEProviderStopReason" } + } } diff --git a/ios/Tunnel/PacketTunnelProvider.swift b/ios/Tunnel/PacketTunnelProvider.swift index 2286adf8e..30d3bb369 100644 --- a/ios/Tunnel/PacketTunnelProvider.swift +++ b/ios/Tunnel/PacketTunnelProvider.swift @@ -3,144 +3,150 @@ // tunnel // -import NetworkExtension import Internalsdk +import NetworkExtension import os.log class PacketTunnelProvider: NEPacketTunnelProvider { - // MARK: Dependencies - let fileManager = FileManager.default - let constants = Constants(process: .netEx) - lazy var notificationsManager: UserNotificationsManager = { - return UserNotificationsManager() - }() - let flashlightManager: FlashlightManager = .netExDefault - - // MARK: PacketFlow - let tunIP = "10.66.66.1" - let mtu = 1500 - var client: IosClientWriterProtocol? - var bytesRead: Int = 0 - var bytesWritten: Int = 0 - - // MARK: NEPacketTunnelProvider - - override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { - // this is our first life-cycle event; perform set up - logMemoryUsage(tag: "Before starting flashlight") - increaseFileLimit() - createFilesForNetExGoPackage() - let logError = flashlightManager.configureGoLoggerReturningError() - if let error = logError { - logger.error(error.localizedDescription) - // crash the tunnel? - } - // we have no way to discern if the User toggled VPN on in Settings :( - let reason = (options?[Constants.netExStartReasonKey] as? NSString) ?? "'On Demand'/Settings" - logger.debug("startTunnel with reason: \(reason)") - - let settings = generateTunnelNetworkSettings() - setTunnelNetworkSettings(settings) { error in - guard error == nil else { - logger.error(error!.localizedDescription) - completionHandler(error) - return - } - self.startFlashlight(completionHandler) - } + // MARK: Dependencies + let fileManager = FileManager.default + let constants = Constants(process: .netEx) + lazy var notificationsManager: UserNotificationsManager = { + return UserNotificationsManager() + }() + let flashlightManager: FlashlightManager = .netExDefault + + // MARK: PacketFlow + let tunIP = "10.66.66.1" + let mtu = 1500 + var client: IosClientWriterProtocol? + var bytesRead: Int = 0 + var bytesWritten: Int = 0 + + // MARK: NEPacketTunnelProvider + + override func startTunnel( + options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void + ) { + // this is our first life-cycle event; perform set up + logMemoryUsage(tag: "Before starting flashlight") + increaseFileLimit() + createFilesForNetExGoPackage() + let logError = flashlightManager.configureGoLoggerReturningError() + if let error = logError { + logger.error(error.localizedDescription) + // crash the tunnel? } - - override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - logger.debug("stopTunnel with reason: \(reason.debugString)") - stopFlashlight() - completionHandler() + // we have no way to discern if the User toggled VPN on in Settings :( + let reason = (options?[Constants.netExStartReasonKey] as? NSString) ?? "'On Demand'/Settings" + logger.debug("startTunnel with reason: \(reason)") + + let settings = generateTunnelNetworkSettings() + setTunnelNetworkSettings(settings) { error in + guard error == nil else { + logger.error(error!.localizedDescription) + completionHandler(error) + return + } + self.startFlashlight(completionHandler) } - - // MARK: NETunnelProvider - - override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { - switch messageData { - - case Constants.configUpdatedMessageData: - logger.debug("Received app message that config has updated. Generating new tunnel settings.") - // Regenerate and set network settings so TUN device has correct excluded routes - let newSettings = generateTunnelNetworkSettings() - setTunnelNetworkSettings(newSettings) { error in - if let err = error { - logger.error("Updating TUN excluded routes - \(err.localizedDescription)") - } else { - logger.debug("Updating TUN excluded routes - Success!") - } - completionHandler?((error == nil) ? Constants.configUpdatedACKData : nil) - } - - self.client?.reconfigure() - - case Constants.requestReadWriteCountMessageData: - logger.debug("Received app message requesting count of bytes read/written.") - // report back with bytes written/read - let string = "\(bytesWritten)/\(bytesRead)" - let data = string.data(using: .utf8)! - completionHandler?(data) - - default: - // unrecognized message, log error - let string = String(bytes: messageData, encoding: .utf8) ?? "" - logger.error("\(#function) called with unknown data: \(string)") - completionHandler?(nil) + } + + override func stopTunnel( + with reason: NEProviderStopReason, completionHandler: @escaping () -> Void + ) { + logger.debug("stopTunnel with reason: \(reason.debugString)") + stopFlashlight() + completionHandler() + } + + // MARK: NETunnelProvider + + override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { + switch messageData { + + case Constants.configUpdatedMessageData: + logger.debug("Received app message that config has updated. Generating new tunnel settings.") + // Regenerate and set network settings so TUN device has correct excluded routes + let newSettings = generateTunnelNetworkSettings() + setTunnelNetworkSettings(newSettings) { error in + if let err = error { + logger.error("Updating TUN excluded routes - \(err.localizedDescription)") + } else { + logger.debug("Updating TUN excluded routes - Success!") } + completionHandler?((error == nil) ? Constants.configUpdatedACKData : nil) + } + + self.client?.reconfigure() + + case Constants.requestReadWriteCountMessageData: + logger.debug("Received app message requesting count of bytes read/written.") + // report back with bytes written/read + let string = "\(bytesWritten)/\(bytesRead)" + let data = string.data(using: .utf8)! + completionHandler?(data) + + default: + // unrecognized message, log error + let string = String(bytes: messageData, encoding: .utf8) ?? "" + logger.error("\(#function) called with unknown data: \(string)") + completionHandler?(nil) } + } - // MARK: NEProvider + // MARK: NEProvider - override func sleep(completionHandler: @escaping () -> Void) { - logger.debug("Device going to sleep") - completionHandler() - } + override func sleep(completionHandler: @escaping () -> Void) { + logger.debug("Device going to sleep") + completionHandler() + } - override func wake() { - logger.debug("Device woke") - } + override func wake() { + logger.debug("Device woke") + } } extension PacketTunnelProvider { - // MARK: Start/Stop Flashlight + // MARK: Start/Stop Flashlight - func startFlashlight(_ completionHandler: @escaping (Error?) -> Void) { - flashlightManager.queue.async { [weak self] in - guard let welf = self else { return } + func startFlashlight(_ completionHandler: @escaping (Error?) -> Void) { + flashlightManager.queue.async { [weak self] in + guard let welf = self else { return } - // init IosClient, which is just a Swift abstraction for Flashlight - var error: NSError? - welf.client = IosClient(welf, UDPDialer(), MemChecker(), welf.constants.configDirectoryURL.path, welf.mtu, Constants.capturedDNSHost, Constants.realDNSHost, &error) + // init IosClient, which is just a Swift abstraction for Flashlight + var error: NSError? + welf.client = IosClient( + welf, UDPDialer(), MemChecker(), welf.constants.configDirectoryURL.path, welf.mtu, + Constants.capturedDNSHost, Constants.realDNSHost, &error) - if let err = error { - logger.error(err.localizedDescription) - } else { - logger.debug("Flashlight started without error.") - } + if let err = error { + logger.error(err.localizedDescription) + } else { + logger.debug("Flashlight started without error.") + } - logMemoryUsage(tag: "After starting flashlight") + logMemoryUsage(tag: "After starting flashlight") - // complete to signal to system we are g2g or have failed - completionHandler(error) + // complete to signal to system we are g2g or have failed + completionHandler(error) - if error == nil { - // if we're all set, kick off the reading and writing - welf.packetFlow.readPacketObjects(completionHandler: welf.onPacket) - } - } + if error == nil { + // if we're all set, kick off the reading and writing + welf.packetFlow.readPacketObjects(completionHandler: welf.onPacket) + } } + } - func stopFlashlight() { - do { - try client?.close() - } catch let error { - logger.error(#function + error.localizedDescription) - } + func stopFlashlight() { + do { + try client?.close() + } catch let error { + logger.error(#function + error.localizedDescription) } + } } // IosWriterProtocol is a Swift abstraction for Flashlight, made by gomobile @@ -149,174 +155,181 @@ extension PacketTunnelProvider { // github.com/getlantern/flashlight/ios/ios.go:Writer interface extension PacketTunnelProvider: IosWriterProtocol { - // MARK: PacketFlow Read/Write Callbacks + // MARK: PacketFlow Read/Write Callbacks - func write(_ p0: Data?) -> Bool { - bytesWritten += p0?.count ?? 0 - return self.packetFlow.writePackets([p0!], withProtocols: [AF_INET as NSNumber]) - } + func write(_ p0: Data?) -> Bool { + bytesWritten += p0?.count ?? 0 + return self.packetFlow.writePackets([p0!], withProtocols: [AF_INET as NSNumber]) + } - func onPacket(packets: [NEPacket]) { - flashlightManager.queue.async { [weak self] in - var dataCap = 0 - var readTotal = packets.reduce(0, { $0 + $1.data.count }) // get packet byte total - - packets.forEach { packet in - do { - try self?.client?.write(packet.data, ret0_:&dataCap) - } catch let error { - logger.error(#function + error.localizedDescription) - readTotal -= packet.data.count // remove bytes from read count - } - } - if let welf = self { - welf.packetFlow.readPacketObjects(completionHandler: welf.onPacket) - } - - self?.bytesRead += readTotal - if dataCap > 0 { - self?.notificationsManager.scheduleDataCapLocalNotification(withDataLimit: dataCap) - //^ internally handles permission, "once a month" notification limit - } + func onPacket(packets: [NEPacket]) { + flashlightManager.queue.async { [weak self] in + var dataCap = 0 + var readTotal = packets.reduce(0, { $0 + $1.data.count }) // get packet byte total + + packets.forEach { packet in + do { + try self?.client?.write(packet.data, ret0_: &dataCap) + } catch let error { + logger.error(#function + error.localizedDescription) + readTotal -= packet.data.count // remove bytes from read count } + } + if let welf = self { + welf.packetFlow.readPacketObjects(completionHandler: welf.onPacket) + } + + self?.bytesRead += readTotal + if dataCap > 0 { + self?.notificationsManager.scheduleDataCapLocalNotification(withDataLimit: dataCap) + //^ internally handles permission, "once a month" notification limit + } } + } } extension PacketTunnelProvider { - // MARK: Tunnel Settings - func generateTunnelNetworkSettings() -> NETunnelNetworkSettings { - let remoteAddress = "0.0.0.0" // display use only - let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: remoteAddress) - - let dnsServerStrings = [Constants.capturedDNSHost] - let dnsSettings = NEDNSSettings(servers: dnsServerStrings) - dnsSettings.matchDomains = [""] // makes all DNS queries first go through the tunnel's DNS - networkSettings.dnsSettings = dnsSettings - - networkSettings.mtu = NSNumber(value: mtu) - - let ipv4Settings = NEIPv4Settings(addresses: [tunIP], subnetMasks: ["255.255.224.0"]) - ipv4Settings.includedRoutes = [NEIPv4Route(destinationAddress: "0.0.0.0", subnetMask: "0.0.0.0")] - networkSettings.ipv4Settings = ipv4Settings - networkSettings.ipv4Settings?.excludedRoutes = loadExcludedRoutes() - - return networkSettings + // MARK: Tunnel Settings + func generateTunnelNetworkSettings() -> NETunnelNetworkSettings { + let remoteAddress = "0.0.0.0" // display use only + let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: remoteAddress) + + let dnsServerStrings = [Constants.capturedDNSHost] + let dnsSettings = NEDNSSettings(servers: dnsServerStrings) + dnsSettings.matchDomains = [""] // makes all DNS queries first go through the tunnel's DNS + networkSettings.dnsSettings = dnsSettings + + networkSettings.mtu = NSNumber(value: mtu) + + let ipv4Settings = NEIPv4Settings(addresses: [tunIP], subnetMasks: ["255.255.224.0"]) + ipv4Settings.includedRoutes = [ + NEIPv4Route(destinationAddress: "0.0.0.0", subnetMask: "0.0.0.0") + ] + networkSettings.ipv4Settings = ipv4Settings + networkSettings.ipv4Settings?.excludedRoutes = loadExcludedRoutes() + + return networkSettings + } + + private func loadExcludedRoutes() -> [NEIPv4Route] { + // Loads excluded routes from disk, written by app side + var routes = [ + NEIPv4Route(destinationAddress: Constants.realDNSHost, subnetMask: "255.255.255.255") + ] // dns + routes.append(NEIPv4Route(destinationAddress: "127.0.0.1", subnetMask: "255.255.255.255")) // local servers like dnsgrab + + // see FlashlightManager+FetchConfig.swift for how String is being saved + do { + let ipsString = try String(contentsOf: constants.excludedIPsURL, encoding: .utf8) + logger.debug("Excluding \(ipsString)") + let ips = ipsString.components(separatedBy: ",") + + ips.forEach { address in + routes.append(NEIPv4Route(destinationAddress: address, subnetMask: "255.255.255.255")) + } + } catch { + logger.error( + "Loading excluded routes failed, killing tunnel process: \(error.localizedDescription)") + stopTunnel(with: .configurationFailed, completionHandler: {}) } - private func loadExcludedRoutes() -> [NEIPv4Route] { - // Loads excluded routes from disk, written by app side - var routes = [NEIPv4Route(destinationAddress: Constants.realDNSHost, subnetMask: "255.255.255.255")] // dns - routes.append(NEIPv4Route(destinationAddress: "127.0.0.1", subnetMask: "255.255.255.255")) // local servers like dnsgrab - - // see FlashlightManager+FetchConfig.swift for how String is being saved - do { - let ipsString = try String(contentsOf: constants.excludedIPsURL, encoding: .utf8) - logger.debug("Excluding \(ipsString)") - let ips = ipsString.components(separatedBy: ",") - - ips.forEach { address in - routes.append(NEIPv4Route(destinationAddress: address, subnetMask: "255.255.255.255")) - } - } catch { - logger.error("Loading excluded routes failed, killing tunnel process: \(error.localizedDescription)") - stopTunnel(with: .configurationFailed, completionHandler: { }) - } - - return routes - } + return routes + } } extension PacketTunnelProvider { - // MARK: File Management + // MARK: File Management - private func createFilesForNetExGoPackage() { - // where "~" is the shared app group container... + private func createFilesForNetExGoPackage() { + // where "~" is the shared app group container... - // create process-specific directory @ ~/netEx - do { - try fileManager.ensureDirectoryExists(at: constants.targetDirectoryURL) - } catch { - logger.error("failed to create directory @ \(constants.targetDirectoryURL.path)") - } + // create process-specific directory @ ~/netEx + do { + try fileManager.ensureDirectoryExists(at: constants.targetDirectoryURL) + } catch { + logger.error("failed to create directory @ \(constants.targetDirectoryURL.path)") + } - // create process-specific log files @ ~/netEx/lantern.log.# - let logURLs = fileManager.generateLogRotationURLs(count: Constants.defaultLogRotationFileCount, from: constants.goLogBaseURL) - let success = fileManager.ensureFilesExist(at: logURLs) - if !success { - logger.error("Failed to create log URLs") - } + // create process-specific log files @ ~/netEx/lantern.log.# + let logURLs = fileManager.generateLogRotationURLs( + count: Constants.defaultLogRotationFileCount, from: constants.goLogBaseURL) + let success = fileManager.ensureFilesExist(at: logURLs) + if !success { + logger.error("Failed to create log URLs") } + } } // MARK: File Limits extension PacketTunnelProvider { - // I've noticed occassional dial errors saying "unable to assign requested address" followed by a crash, which might be due to running out of file descriptors - func increaseFileLimit() { - var limits = rlimit() + // I've noticed occassional dial errors saying "unable to assign requested address" followed by a crash, which might be due to running out of file descriptors + func increaseFileLimit() { + var limits = rlimit() - guard getrlimit(RLIMIT_NOFILE, &limits) != -1 else { - logger.error("Problem with rlimit") - return - } + guard getrlimit(RLIMIT_NOFILE, &limits) != -1 else { + logger.error("Problem with rlimit") + return + } - logger.debug("File descriptor limit current: \(limits.rlim_cur) max: \(limits.rlim_max)") + logger.debug("File descriptor limit current: \(limits.rlim_cur) max: \(limits.rlim_max)") - limits.rlim_cur = 16384 - if setrlimit(RLIMIT_NOFILE, &limits) == -1 { - logger.error("Problem updating rlimit") - } + limits.rlim_cur = 16384 + if setrlimit(RLIMIT_NOFILE, &limits) == -1 { + logger.error("Problem updating rlimit") } + } } // MARK: Logging Memory Usage -class MemChecker : NSObject, IosMemCheckerProtocol { - func bytesRemain() -> Int { - return getBytesRemain() - } +class MemChecker: NSObject, IosMemCheckerProtocol { + func bytesRemain() -> Int { + return getBytesRemain() + } } func getBytesRemain() -> Int { - var info = task_vm_info_data_t() - var infoCount = TASK_VM_INFO_COUNT + var info = task_vm_info_data_t() + var infoCount = TASK_VM_INFO_COUNT - let kerr = withUnsafeMutablePointer(to: &info) { - $0.withMemoryRebound(to: integer_t.self, capacity: 1) { - task_info(mach_task_self_, thread_flavor_t(TASK_VM_INFO), $0, &infoCount) - } + let kerr = withUnsafeMutablePointer(to: &info) { + $0.withMemoryRebound(to: integer_t.self, capacity: 1) { + task_info(mach_task_self_, thread_flavor_t(TASK_VM_INFO), $0, &infoCount) } - guard kerr == KERN_SUCCESS else { - logger.error("\(#function) - Attempt to read memory usage unsuccessful") - return 0 - } - - // It's important that this value aligns closely to what iOS monitors to enforce the 15MB memory limit for network extensions, - // because we use this number to tell if we're getting close to the limit and start taking remedial action. - // Per the below discussion, phys_footprint is a good value to use, and from our own observations it looks like - // limit_bytes_remaining is a good indicator of how close we are to hitting the limit. - // - // https://developer.apple.com/forums/thread/97636 - // - // Note that the way that Go frees memory to the OS doesn't always play well with memory usage reporting, but per the below discussion - // and from our testing, using task_vm_info should work okay. - // - // https://github.com/golang/go/issues/29844 - // - return Int(info.limit_bytes_remaining) + } + guard kerr == KERN_SUCCESS else { + logger.error("\(#function) - Attempt to read memory usage unsuccessful") + return 0 + } + + // It's important that this value aligns closely to what iOS monitors to enforce the 15MB memory limit for network extensions, + // because we use this number to tell if we're getting close to the limit and start taking remedial action. + // Per the below discussion, phys_footprint is a good value to use, and from our own observations it looks like + // limit_bytes_remaining is a good indicator of how close we are to hitting the limit. + // + // https://developer.apple.com/forums/thread/97636 + // + // Note that the way that Go frees memory to the OS doesn't always play well with memory usage reporting, but per the below discussion + // and from our testing, using task_vm_info should work okay. + // + // https://github.com/golang/go/issues/29844 + // + return Int(info.limit_bytes_remaining) } func logMemoryUsage(tag: String? = nil, bytesRemain: Int? = nil) { - let remain = bytesRemain ?? getBytesRemain() - - var format = "stats Memory Remaining" - if let actualTag = tag { - format = format + ": " + actualTag - } - format = format + ": %d bytes" - let string = String(format: format, remain) - logger.debug(string) + let remain = bytesRemain ?? getBytesRemain() + + var format = "stats Memory Remaining" + if let actualTag = tag { + format = format + ": " + actualTag + } + format = format + ": %d bytes" + let string = String(format: format, remain) + logger.debug(string) } // used for logging memory usage // based on https://gist.github.com/nh7a/70832fa2658b591dca22e4606388a07f -private let TASK_VM_INFO_COUNT = mach_msg_type_number_t(MemoryLayout.size / MemoryLayout.size) +private let TASK_VM_INFO_COUNT = mach_msg_type_number_t( + MemoryLayout.size / MemoryLayout.size) diff --git a/ios/Tunnel/UDPConn.swift b/ios/Tunnel/UDPConn.swift index 5670215f7..c6c6ac395 100644 --- a/ios/Tunnel/UDPConn.swift +++ b/ios/Tunnel/UDPConn.swift @@ -11,65 +11,67 @@ import Internalsdk import Network /// UDPDialer provides a mechanism for dialing outbound UDP connections that bypass the VPN. -class UDPDialer : NSObject, IosUDPDialerProtocol { - /// Dials the given host and port. In practice, nothing actually happens until IosUDPCallbacks get registered on the returned UDPConn. - func dial(_ host: String?, port: Int) -> IosUDPConnProtocol? { - return UDPConn(host: NWEndpoint.Host(host!), port: NWEndpoint.Port(rawValue: UInt16(port))!) - } +class UDPDialer: NSObject, IosUDPDialerProtocol { + /// Dials the given host and port. In practice, nothing actually happens until IosUDPCallbacks get registered on the returned UDPConn. + func dial(_ host: String?, port: Int) -> IosUDPConnProtocol? { + return UDPConn(host: NWEndpoint.Host(host!), port: NWEndpoint.Port(rawValue: UInt16(port))!) + } } /// Encapsulates a UDP connection to a specific host and port. -class UDPConn : NSObject, IosUDPConnProtocol { - var connection: NWConnection - var cb: IosUDPCallbacks? +class UDPConn: NSObject, IosUDPConnProtocol { + var connection: NWConnection + var cb: IosUDPCallbacks? - init(host: NWEndpoint.Host, port: NWEndpoint.Port) { - self.connection = NWConnection(host: host, port: port, using: .udp) - super.init() - } + init(host: NWEndpoint.Host, port: NWEndpoint.Port) { + self.connection = NWConnection(host: host, port: port, using: .udp) + super.init() + } - func register(_ cb: IosUDPCallbacks?) { - self.cb = cb + func register(_ cb: IosUDPCallbacks?) { + self.cb = cb - self.connection.stateUpdateHandler = { (newState) in - switch (newState) { - case .ready: - cb?.onDialSucceeded() - case .failed(let error): - cb?.onError(error) - case .cancelled: - cb?.onClose() - default: - break - } - } - - connection.start(queue: .global()) + self.connection.stateUpdateHandler = { (newState) in + switch newState { + case .ready: + cb?.onDialSucceeded() + case .failed(let error): + cb?.onError(error) + case .cancelled: + cb?.onClose() + default: + break + } } - func writeDatagram(_ data: Data?) { - self.connection.send(content: data, completion: NWConnection.SendCompletion.contentProcessed({ [weak self] error in - if let error = error { - self?.cb?.onError(error) - return - } + connection.start(queue: .global()) + } - self?.cb?.onWritten() - })) - } + func writeDatagram(_ data: Data?) { + self.connection.send( + content: data, + completion: NWConnection.SendCompletion.contentProcessed({ [weak self] error in + if let error = error { + self?.cb?.onError(error) + return + } - func receiveDatagram() { - self.connection.receiveMessage { [weak self] (data, _, _, error) in - if let error = error { - self?.cb?.onError(error) - return - } + self?.cb?.onWritten() + })) + } - self?.cb?.onReceive(data) - } - } + func receiveDatagram() { + self.connection.receiveMessage { [weak self] (data, _, _, error) in + if let error = error { + self?.cb?.onError(error) + return + } - func close() { - self.connection.cancel() + self?.cb?.onReceive(data) } + } + + func close() { + self.connection.cancel() + } }