diff --git a/README.md b/README.md index 4a5d9ee..a38b9ce 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ShadowVPN for iOS ================= -[ShadowVPN](https://github.com/clowwindy/ShadowVPN) for iOS 9, using the new Network Extension API. +[ShadowVPN](https://github.com/clowwindy/ShadowVPN) for iOS 10, using the new Network Extension API. Features - Stateless VPN diff --git a/ShadowVPN.xcodeproj/project.pbxproj b/ShadowVPN.xcodeproj/project.pbxproj index ca489b3..6d5634c 100644 --- a/ShadowVPN.xcodeproj/project.pbxproj +++ b/ShadowVPN.xcodeproj/project.pbxproj @@ -324,6 +324,7 @@ 62A4274C1B5A3C9700BB4AD9 = { CreatedOnToolsVersion = 7.0; DevelopmentTeam = LND7PS23X9; + LastSwiftMigration = 0810; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { enabled = 1; @@ -339,6 +340,7 @@ 62A4276C1B5A3ED800BB4AD9 = { CreatedOnToolsVersion = 7.0; DevelopmentTeam = LND7PS23X9; + LastSwiftMigration = 0810; }; }; }; @@ -569,6 +571,7 @@ SKIP_INSTALL = NO; SWIFT_OBJC_BRIDGING_HEADER = "ShadowVPN/ShadowVPN-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -588,6 +591,7 @@ PROVISIONING_PROFILE = ""; SKIP_INSTALL = NO; SWIFT_OBJC_BRIDGING_HEADER = "ShadowVPN/ShadowVPN-Bridging-Header.h"; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -605,6 +609,7 @@ PROVISIONING_PROFILE = ""; SKIP_INSTALL = YES; SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/tunnel/tunnel-Bridging-Header.h"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -622,6 +627,7 @@ PROVISIONING_PROFILE = ""; SKIP_INSTALL = YES; SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/tunnel/tunnel-Bridging-Header.h"; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/ShadowVPN/AppDelegate.swift b/ShadowVPN/AppDelegate.swift index 6bebdef..6951b34 100644 --- a/ShadowVPN/AppDelegate.swift +++ b/ShadowVPN/AppDelegate.swift @@ -14,36 +14,36 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - self.window = UIWindow(frame: UIScreen.mainScreen().bounds) + self.window = UIWindow(frame: UIScreen.main.bounds) if let window = self.window { - let viewController = UINavigationController(rootViewController: MainViewController(style: .Grouped)) + let viewController = UINavigationController(rootViewController: MainViewController(style: .grouped)) window.rootViewController = viewController window.makeKeyAndVisible() } return true } - func applicationWillResignActive(application: UIApplication) { + func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } - func applicationDidEnterBackground(application: UIApplication) { + func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - func applicationWillEnterForeground(application: UIApplication) { + func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } - func applicationDidBecomeActive(application: UIApplication) { + func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - func applicationWillTerminate(application: UIApplication) { + func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } diff --git a/ShadowVPN/ConfigurationTextCell.swift b/ShadowVPN/ConfigurationTextCell.swift index 106065d..77d200c 100644 --- a/ShadowVPN/ConfigurationTextCell.swift +++ b/ShadowVPN/ConfigurationTextCell.swift @@ -14,7 +14,7 @@ class ConfigurationTextCell: UITableViewCell { var textField: UITextField = UITextField() init() { - super.init(style: UITableViewCellStyle.Default, reuseIdentifier: nil) + super.init(style: UITableViewCellStyle.default, reuseIdentifier: nil) self.contentView.addSubview(textField) } diff --git a/ShadowVPN/ConfigurationValidator.swift b/ShadowVPN/ConfigurationValidator.swift index ac2e91d..bd0681d 100644 --- a/ShadowVPN/ConfigurationValidator.swift +++ b/ShadowVPN/ConfigurationValidator.swift @@ -7,12 +7,36 @@ // import UIKit +// FIXME: comparison operators with optionals were removed from the Swift Standard Libary. +// Consider refactoring the code to use the non-optional operators. +fileprivate func < (lhs: T?, rhs: T?) -> Bool { + switch (lhs, rhs) { + case let (l?, r?): + return l < r + case (nil, _?): + return true + default: + return false + } +} + +// FIXME: comparison operators with optionals were removed from the Swift Standard Libary. +// Consider refactoring the code to use the non-optional operators. +fileprivate func > (lhs: T?, rhs: T?) -> Bool { + switch (lhs, rhs) { + case let (l?, r?): + return l > r + default: + return rhs < lhs + } +} + class ConfigurationValidator: NSObject { // return nil if there's no error - class func validateIP(ip: String) -> String? { - let parts = ip.componentsSeparatedByString(".") + class func validateIP(_ ip: String) -> String? { + let parts = ip.components(separatedBy: ".") if parts.count != 4 { return "Invalid IP: " + ip } @@ -26,7 +50,7 @@ class ConfigurationValidator: NSObject { } // return nil if there's no error - class func validate(configuration: [String: AnyObject]) -> String? { + class func validate(_ configuration: [String: AnyObject]) -> String? { // 1. server must be not empty if configuration["server"] == nil || configuration["server"]?.length == 0 { return "Server must not be empty" @@ -46,7 +70,7 @@ class ConfigurationValidator: NSObject { // 4. usertoken must be empty or hex of 8 bytes if configuration["usertoken"] != nil { if let usertoken = configuration["usertoken"] as? String { - if NSData.fromHexString(usertoken).length != 8 && NSData.fromHexString(usertoken).length != 0 { + if Data.fromHexString(usertoken).count != 8 && Data.fromHexString(usertoken).count != 0 { return "Usertoken must be HEX of 8 bytes (example: 7e335d67f1dc2c01)" } } @@ -76,7 +100,7 @@ class ConfigurationValidator: NSObject { return "DNS must not be empty" } if let dns = configuration["dns"] as? String { - let ips = dns.componentsSeparatedByString(",") + let ips = dns.components(separatedBy: ",") if ips.count == 0 { return "DNS must not be empty" } diff --git a/ShadowVPN/ConfigurationViewController.swift b/ShadowVPN/ConfigurationViewController.swift index 7ab2fa1..162ca9c 100644 --- a/ShadowVPN/ConfigurationViewController.swift +++ b/ShadowVPN/ConfigurationViewController.swift @@ -17,16 +17,16 @@ class ConfigurationViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() - self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Save, target: self, action: "save") + self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(ConfigurationViewController.save)) self.title = providerManager?.protocolConfiguration?.serverAddress let conf:NETunnelProviderProtocol = self.providerManager?.protocolConfiguration as! NETunnelProviderProtocol // Dictionary in Swift is a struct. This is a copy - self.configuration = conf.providerConfiguration! + self.configuration = conf.providerConfiguration! as [String : AnyObject] } func updateConfiguration() { for (k, v) in self.bindMap { - self.configuration[k] = v.text + self.configuration[k] = v.text as AnyObject? } // self.configuration["route"] = "chnroutes" } @@ -34,10 +34,10 @@ class ConfigurationViewController: UITableViewController { func save() { updateConfiguration() if let result = ConfigurationValidator.validate(self.configuration) { - let alertController = UIAlertController(title: "Error", message: result, preferredStyle: .Alert) - alertController.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: { (action) -> Void in + let alertController = UIAlertController(title: "Error", message: result, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { (action) -> Void in })) - self.presentViewController(alertController, animated: true, completion: { () -> Void in + self.present(alertController, animated: true, completion: { () -> Void in }) return } @@ -45,24 +45,24 @@ class ConfigurationViewController: UITableViewController { self.providerManager?.protocolConfiguration?.serverAddress = self.configuration["server"] as? String self.providerManager?.localizedDescription = self.configuration["server"] as? String - self.providerManager?.saveToPreferencesWithCompletionHandler { (error) -> Void in - self.navigationController?.popViewControllerAnimated(true) + self.providerManager?.saveToPreferences { (error) -> Void in + self.navigationController?.popViewController(animated: true) } } - func bindData(textField: UITextField, property: String) { + func bindData(_ textField: UITextField, property: String) { let val: AnyObject? = configuration[property] if let val = val { - textField.text = String(val) + textField.text = String(describing: val) } bindMap[property] = textField } - override func numberOfSectionsInTableView(tableView: UITableView) -> Int { + override func numberOfSections(in tableView: UITableView) -> Int { return 2 } - override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch section { case 0: return 10 @@ -73,11 +73,11 @@ class ConfigurationViewController: UITableViewController { } } - override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { switch indexPath.section { case 0: let cell = ConfigurationTextCell() - cell.selectionStyle = .None + cell.selectionStyle = .none switch indexPath.row { case 0: cell.textLabel?.text = "Description" @@ -86,69 +86,69 @@ class ConfigurationViewController: UITableViewController { case 1: cell.textLabel?.text = "Server" cell.textField.placeholder = "Server IP" - cell.textField.autocapitalizationType = .None - cell.textField.autocorrectionType = .No + cell.textField.autocapitalizationType = .none + cell.textField.autocorrectionType = .no bindData(cell.textField, property: "server") case 2: cell.textLabel?.text = "Port" cell.textField.placeholder = "Server Port" cell.textField.text = "1123" - cell.textField.autocapitalizationType = .None - cell.textField.autocorrectionType = .No - cell.textField.keyboardType = .NumberPad + cell.textField.autocapitalizationType = .none + cell.textField.autocorrectionType = .no + cell.textField.keyboardType = .numberPad bindData(cell.textField, property: "port") case 3: cell.textLabel?.text = "Password" cell.textField.placeholder = "Required" cell.textField.text = "" - cell.textField.secureTextEntry = true - cell.textField.autocapitalizationType = .None - cell.textField.autocorrectionType = .No + cell.textField.isSecureTextEntry = true + cell.textField.autocapitalizationType = .none + cell.textField.autocorrectionType = .no bindData(cell.textField, property: "password") case 4: cell.textLabel?.text = "User Token" cell.textField.placeholder = "Optional" cell.textField.text = "" - cell.textField.autocapitalizationType = .None - cell.textField.autocorrectionType = .No + cell.textField.autocapitalizationType = .none + cell.textField.autocorrectionType = .no bindData(cell.textField, property: "usertoken") case 5: cell.textLabel?.text = "IP" cell.textField.placeholder = "Required" cell.textField.text = "10.7.0.2" - cell.textField.autocapitalizationType = .None - cell.textField.autocorrectionType = .No - cell.textField.keyboardType = .DecimalPad + cell.textField.autocapitalizationType = .none + cell.textField.autocorrectionType = .no + cell.textField.keyboardType = .decimalPad bindData(cell.textField, property: "ip") case 6: cell.textLabel?.text = "Subnet" cell.textField.placeholder = "Required" cell.textField.text = "255.255.255.0" - cell.textField.autocapitalizationType = .None - cell.textField.autocorrectionType = .No - cell.textField.keyboardType = .DecimalPad + cell.textField.autocapitalizationType = .none + cell.textField.autocorrectionType = .no + cell.textField.keyboardType = .decimalPad bindData(cell.textField, property: "subnet") case 7: cell.textLabel?.text = "DNS" cell.textField.placeholder = "DNS Server Address" cell.textField.text = "114.114.114.114,223.5.5.5,8.8.8.8,8.8.4.4,208.67.222.222" - cell.textField.autocapitalizationType = .None - cell.textField.autocorrectionType = .No + cell.textField.autocapitalizationType = .none + cell.textField.autocorrectionType = .no bindData(cell.textField, property: "dns") case 8: cell.textLabel?.text = "MTU" cell.textField.placeholder = "MTU" cell.textField.text = "1350" - cell.textField.autocapitalizationType = .None - cell.textField.autocorrectionType = .No - cell.textField.keyboardType = .NumberPad + cell.textField.autocapitalizationType = .none + cell.textField.autocorrectionType = .no + cell.textField.keyboardType = .numberPad bindData(cell.textField, property: "mtu") case 9: cell.textLabel?.text = "Route" cell.textField.text = "chnroutes" - cell.textField.enabled = false - cell.accessoryType = .DisclosureIndicator - cell.selectionStyle = .Default + cell.textField.isEnabled = false + cell.accessoryType = .disclosureIndicator + cell.selectionStyle = .default bindData(cell.textField, property: "route") return cell default: @@ -158,35 +158,35 @@ class ConfigurationViewController: UITableViewController { case 1: let cell = UITableViewCell() cell.textLabel?.text = "Delete This Configuration" - cell.textLabel?.textColor = UIColor.redColor() + cell.textLabel?.textColor = UIColor.red return cell default: return UITableViewCell() } } - override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { - tableView.deselectRowAtIndexPath(indexPath, animated: true) + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) if (indexPath.section == 0) { if (indexPath.row == 9) { - let controller = SimpleTableViewController(labels: ["Default", "CHNRoutes"], values: ["default", "chnroutes"], initialValue: self.configuration["route"] as? String, selectionBlock: { (result) -> Void in + let controller = SimpleTableViewController(labels: ["Default", "CHNRoutes"], values: ["default", "chnroutes"], initialValue: self.configuration["route"] as? String as NSObject!, selectionBlock: { (result) -> Void in // else we'll lost unsaved modifications self.updateConfiguration() self.configuration["route"] = result self.tableView.reloadData() }) - self.navigationController?.pushViewController(controller, animated: true) + self.navigationController?.pushViewController(controller!, animated: true) } } else if (indexPath.section == 1) { - let alertController = UIAlertController(title: nil, message: "Delete this configuration?", preferredStyle: .Alert) - alertController.addAction(UIAlertAction(title: "Delete", style: .Destructive, handler: { (action) -> Void in - self.providerManager?.removeFromPreferencesWithCompletionHandler({ (error) -> Void in - self.navigationController?.popViewControllerAnimated(true) + let alertController = UIAlertController(title: nil, message: "Delete this configuration?", preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "Delete", style: .destructive, handler: { (action) -> Void in + self.providerManager?.removeFromPreferences(completionHandler: { (error) -> Void in + self.navigationController?.popViewController(animated: true) }) })) - alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: { (action) -> Void in + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) -> Void in })) - self.presentViewController(alertController, animated: true, completion: { () -> Void in + self.present(alertController, animated: true, completion: { () -> Void in }) } } diff --git a/ShadowVPN/MainViewController.swift b/ShadowVPN/MainViewController.swift index 382f5ef..3f6b5d3 100644 --- a/ShadowVPN/MainViewController.swift +++ b/ShadowVPN/MainViewController.swift @@ -21,23 +21,23 @@ class MainViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() self.title = "ShadowVPN" - self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "addConfiguration") + self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(MainViewController.addConfiguration)) - NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("VPNStatusDidChange:"), name: NEVPNStatusDidChangeNotification, object: nil) - vpnStatusSwitch.addTarget(self, action: "vpnStatusSwitchValueDidChange:", forControlEvents: .ValueChanged) + NotificationCenter.default.addObserver(self, selector: #selector(MainViewController.VPNStatusDidChange(_:)), name: NSNotification.Name.NEVPNStatusDidChange, object: nil) + vpnStatusSwitch.addTarget(self, action: #selector(MainViewController.vpnStatusSwitchValueDidChange(_:)), for: .valueChanged) // vpnStatusLabel.textAlignment = .Right // vpnStatusLabel.textColor = UIColor.grayColor() } deinit { - NSNotificationCenter.defaultCenter().removeObserver(self, name: NEVPNStatusDidChangeNotification, object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NEVPNStatusDidChange, object: nil) } - func vpnStatusSwitchValueDidChange(sender: UISwitch) { + func vpnStatusSwitchValueDidChange(_ sender: UISwitch) { do { if vpnManagers.count > 0 { if let currentVPNManager = self.currentVPNManager { - if sender.on { + if sender.isOn { try currentVPNManager.connection.startVPNTunnel() } else { currentVPNManager.connection.stopVPNTunnel() @@ -45,32 +45,32 @@ class MainViewController: UITableViewController { } } } catch { - NSLog("%@", String(error)) + NSLog("%@", String(describing: error)) } } - func VPNStatusDidChange(notification: NSNotification?) { + func VPNStatusDidChange(_ notification: Notification?) { var on = false var enabled = false if let currentVPNManager = self.currentVPNManager { let status = currentVPNManager.connection.status switch status { - case .Connecting: + case .connecting: on = true enabled = false vpnStatusLabel.text = "Connecting..." break - case .Connected: + case .connected: on = true enabled = true vpnStatusLabel.text = "Connected" break - case .Disconnecting: + case .disconnecting: on = false enabled = false vpnStatusLabel.text = "Disconnecting..." break - case .Disconnected: + case .disconnected: on = false enabled = true vpnStatusLabel.text = "Not Connected" @@ -80,57 +80,57 @@ class MainViewController: UITableViewController { enabled = true break } - vpnStatusSwitch.on = on - vpnStatusSwitch.enabled = enabled - UIApplication.sharedApplication().networkActivityIndicatorVisible = !enabled + vpnStatusSwitch.isOn = on + vpnStatusSwitch.isEnabled = enabled + UIApplication.shared.isNetworkActivityIndicatorVisible = !enabled } } - override func viewWillAppear(animated: Bool) { + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.loadConfigurationFromSystem() } - override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if indexPath.section == 0 { - let cell = UITableViewCell(style: .Value1, reuseIdentifier: "status") - cell.selectionStyle = .None + let cell = UITableViewCell(style: .value1, reuseIdentifier: "status") + cell.selectionStyle = .none cell.textLabel?.text = "Status" vpnStatusLabel = cell.detailTextLabel! cell.accessoryView = vpnStatusSwitch return cell } else { - let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: "configuration") + let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "configuration") let vpnManager = self.vpnManagers[indexPath.row] cell.textLabel?.text = vpnManager.protocolConfiguration?.serverAddress cell.detailTextLabel?.text = (vpnManager.protocolConfiguration as! NETunnelProviderProtocol).providerConfiguration!["description"] as? String - if vpnManager.enabled { + if vpnManager.isEnabled { cell.imageView?.image = UIImage(named: "checkmark") } else { cell.imageView?.image = UIImage(named: "checkmark_empty") } - cell.accessoryType = .DetailButton + cell.accessoryType = .detailButton return cell } } - override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if indexPath.section == 1 { - tableView.deselectRowAtIndexPath(indexPath, animated: true) + tableView.deselectRow(at: indexPath, animated: true) let vpnManager = self.vpnManagers[indexPath.row] - vpnManager.enabled = true - vpnManager.saveToPreferencesWithCompletionHandler { (error) -> Void in + vpnManager.isEnabled = true + vpnManager.saveToPreferences { (error) -> Void in self.loadConfigurationFromSystem() } } } - override func numberOfSectionsInTableView(tableView: UITableView) -> Int { + override func numberOfSections(in tableView: UITableView) -> Int { return 2 } - override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if section == 0 { return 1 } else { @@ -138,45 +138,45 @@ class MainViewController: UITableViewController { } } - override func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) { - let configurationController = ConfigurationViewController(style:.Grouped) + override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) { + let configurationController = ConfigurationViewController(style:.grouped) configurationController.providerManager = self.vpnManagers[indexPath.row] self.navigationController?.pushViewController(configurationController, animated: true) } func addConfiguration() { let manager = NETunnelProviderManager() - manager.loadFromPreferencesWithCompletionHandler { (error) -> Void in + manager.loadFromPreferences { (error) -> Void in let providerProtocol = NETunnelProviderProtocol() providerProtocol.providerBundleIdentifier = kTunnelProviderBundle providerProtocol.providerConfiguration = [String: AnyObject]() manager.protocolConfiguration = providerProtocol - let configurationController = ConfigurationViewController(style:.Grouped) + let configurationController = ConfigurationViewController(style:.grouped) configurationController.providerManager = manager self.navigationController?.pushViewController(configurationController, animated: true) - manager.saveToPreferencesWithCompletionHandler({ (error) -> Void in + manager.saveToPreferences(completionHandler: { (error) -> Void in print(error) }) } } func loadConfigurationFromSystem() { - NETunnelProviderManager.loadAllFromPreferencesWithCompletionHandler() { newManagers, error in + NETunnelProviderManager.loadAllFromPreferences() { newManagers, error in print(error) guard let vpnManagers = newManagers else { return } self.vpnManagers.removeAll() for vpnManager in vpnManagers { if let providerProtocol = vpnManager.protocolConfiguration as? NETunnelProviderProtocol { if providerProtocol.providerBundleIdentifier == kTunnelProviderBundle { - if vpnManager.enabled { + if vpnManager.isEnabled { self.currentVPNManager = vpnManager } self.vpnManagers.append(vpnManager) } } } - self.vpnStatusSwitch.enabled = vpnManagers.count > 0 + self.vpnStatusSwitch.isEnabled = vpnManagers.count > 0 self.tableView.reloadData() self.VPNStatusDidChange(nil) } diff --git a/tunnel/NSData+Hex.swift b/tunnel/NSData+Hex.swift index 6383816..591292e 100644 --- a/tunnel/NSData+Hex.swift +++ b/tunnel/NSData+Hex.swift @@ -8,23 +8,23 @@ import Foundation -extension NSData { - public class func fromHexString (string: String) -> NSData { +extension Data { + public static func fromHexString (_ string: String) -> Data { let data = NSMutableData() var temp = "" for char in string.characters { temp += String(char) - if temp.lengthOfBytesUsingEncoding(NSUTF8StringEncoding) == 2 { - let scanner = NSScanner(string: temp) + if temp.lengthOfBytes(using: String.Encoding.utf8) == 2 { + let scanner = Scanner(string: temp) var value: CUnsignedInt = 0 - scanner.scanHexInt(&value) - data.appendBytes(&value, length: 1) + scanner.scanHexInt32(&value) + data.append(&value, length: 1) temp = "" } } - return data as NSData + return data as Data } -} \ No newline at end of file +} diff --git a/tunnel/PacketTunnelProvider.swift b/tunnel/PacketTunnelProvider.swift index 99152b5..147ffda 100644 --- a/tunnel/PacketTunnelProvider.swift +++ b/tunnel/PacketTunnelProvider.swift @@ -8,34 +8,41 @@ import NetworkExtension + +let groupBundle = "group.VPNCare.shadowVPN" + + class PacketTunnelProvider: NEPacketTunnelProvider { var session: NWUDPSession? = nil var conf = [String: AnyObject]() - var pendingStartCompletion: (NSError? -> Void)? - var userToken: NSData? + var pendingStartCompletion: ((NSError?) -> Void)? + var userToken: Data? var chinaDNS: ChinaDNSRunner? var routeManager: RouteManager? -// var wifi = ChinaDNSRunner.checkWiFiNetwork() - var queue: dispatch_queue_t? + // var wifi = ChinaDNSRunner.checkWiFiNetwork() + var queue: DispatchQueue? - override func startTunnelWithOptions(options: [String : NSObject]?, completionHandler: (NSError?) -> Void) { - queue = dispatch_queue_create("shadowvpn.queue", DISPATCH_QUEUE_SERIAL) - conf = (self.protocolConfiguration as! NETunnelProviderProtocol).providerConfiguration! + override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { + queue = DispatchQueue(label: "shadowvpn.queue", attributes: []) + conf = (self.protocolConfiguration as! NETunnelProviderProtocol).providerConfiguration! as [String : AnyObject] self.pendingStartCompletion = completionHandler - chinaDNS = ChinaDNSRunner(DNS: conf["dns"] as? String) + chinaDNS = ChinaDNSRunner(dns: conf["dns"] as? String) if let userTokenString = conf["usertoken"] as? String { if userTokenString.characters.count == 16 { - userToken = NSData.fromHexString(userTokenString) + userToken = Data.fromHexString(userTokenString) } } NSLog("setPassword") SVCrypto.setPassword(conf["password"] as! String) self.recreateUDP() let keyPath = "defaultPath" - let options = NSKeyValueObservingOptions([.New, .Old]) + let options = NSKeyValueObservingOptions([.new, .old]) self.addObserver(self, forKeyPath: keyPath, options: options, context: nil) NSLog("readPacketsFromTUN") self.readPacketsFromTUN() + + // shared vpn connect status for today widget + self.shareConnectStateWithNSUserDefaults(vpnState: true) } func recreateUDP() { @@ -43,22 +50,23 @@ class PacketTunnelProvider: NEPacketTunnelProvider { self.reasserting = true self.session = nil } - dispatch_async(queue!) { () -> Void in + queue!.async { () -> Void in if let serverAddress = self.protocolConfiguration.serverAddress { if let port = self.conf["port"] as? String { self.reasserting = false - self.setTunnelNetworkSettings(nil) { (error: NSError?) -> Void in + + self.setTunnelNetworkSettings(nil, completionHandler: {(error) -> Void in if let error = error { - NSLog("%@", error) + // NSLog("%@", error) // simply kill the extension process since it does no harm and ShadowVPN is expected to be always on -// exit(1) + // exit(1) } - dispatch_async(self.queue!) { () -> Void in + self.queue!.async { () -> Void in NSLog("recreateUDP") - self.session = self.createUDPSessionToEndpoint(NWHostEndpoint(hostname: serverAddress, port: port), fromEndpoint: nil) + self.session = self.createUDPSession(to: NWHostEndpoint(hostname: serverAddress, port: port), from: nil) self.updateNetwork() } - } + }); } } } @@ -67,32 +75,34 @@ class PacketTunnelProvider: NEPacketTunnelProvider { func updateNetwork() { NSLog("updateNetwork") let newSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: self.protocolConfiguration.serverAddress!) - newSettings.IPv4Settings = NEIPv4Settings(addresses: [conf["ip"] as! String], subnetMasks: [conf["subnet"] as! String]) - routeManager = RouteManager(route: conf["route"] as? String, IPv4Settings: newSettings.IPv4Settings!) + newSettings.iPv4Settings = NEIPv4Settings(addresses: [conf["ip"] as! String], subnetMasks: [conf["subnet"] as! String]) + routeManager = RouteManager(route: conf["route"] as? String, IPv4Settings: newSettings.iPv4Settings!) if conf["mtu"] != nil { - newSettings.MTU = Int(conf["mtu"] as! String) + let strinb = conf["mtu"] as! String; + newSettings.mtu = Int(strinb) as NSNumber?; } else { - newSettings.MTU = 1432 + newSettings.mtu = 1432 } if "chnroutes" == (conf["route"] as? String) { NSLog("using ChinaDNS") - newSettings.DNSSettings = NEDNSSettings(servers: ["127.0.0.1"]) + newSettings.dnsSettings = NEDNSSettings(servers: ["127.0.0.1"]) } else { NSLog("using DNS") - newSettings.DNSSettings = NEDNSSettings(servers: (conf["dns"] as! String).componentsSeparatedByString(",")) + newSettings.dnsSettings = NEDNSSettings(servers: (conf["dns"] as! String).components(separatedBy: ",")) } NSLog("setTunnelNetworkSettings") - self.setTunnelNetworkSettings(newSettings) { (error: NSError?) -> Void in + self.setTunnelNetworkSettings(newSettings) { (error) -> Void in self.readPacketsFromUDP() NSLog("readPacketsFromUDP") if let completionHandler = self.pendingStartCompletion { // send an packet // self.log("completion") - NSLog("%@", String(error)) + // NSLog("%@", String(error)) NSLog("VPN started") - completionHandler(error) + completionHandler(error as NSError?) if error != nil { // simply kill the extension process since it does no harm and ShadowVPN is expected to be always on + // NSLog("%@", error!) exit(1) } } @@ -100,17 +110,17 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } func readPacketsFromTUN() { - self.packetFlow.readPacketsWithCompletionHandler { + self.packetFlow.readPackets { packets, protocols in for packet in packets { -// NSLog("TUN: %d", packet.length) - self.session?.writeDatagram(SVCrypto.encryptWithData(packet, userToken: self.userToken), completionHandler: { (error: NSError?) -> Void in + // NSLog("TUN: %d", packet.length) + self.session?.writeDatagram(SVCrypto.encrypt(with: packet, userToken: self.userToken), completionHandler: { (error: NSError?) -> Void in if let error = error { NSLog("%@", error) -// self.recreateUDP() -// return + // self.recreateUDP() + // return } - }) + } as! (Error?) -> Void) } self.readPacketsFromTUN() } @@ -118,61 +128,70 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } func readPacketsFromUDP() { - session?.setReadHandler({ (newPackets: [NSData]?, error: NSError?) -> Void in + + session?.setReadHandler({ (newPackets, error) -> Void in // self.log("readPacketsFromUDP") - guard let packets = newPackets else { return } + var packets:[NSData] + if (newPackets.count != 0) { + packets = newPackets as [NSData] + } else { + return; + } var protocols = [NSNumber]() - var decryptedPackets = [NSData]() + var decryptedPackets = [Data]() for packet in packets { -// NSLog("UDP: %d", packet.length) + // NSLog("UDP: %d", packet.length) // currently IPv4 only - let decrypted = SVCrypto.decryptWithData(packet, userToken: self.userToken) -// NSLog("write to TUN: %d", decrypted.length) - decryptedPackets.append(decrypted) + let decrypted = SVCrypto.decrypt(with: packet as Data!, userToken: self.userToken) + // NSLog("write to TUN: %d", decrypted.length) + decryptedPackets.append(decrypted!) protocols.append(2) } self.packetFlow.writePackets(decryptedPackets, withProtocols: protocols) - }, maxDatagrams: NSIntegerMax) + }, maxDatagrams: NSIntegerMax) } - override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer) { + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if let object = object { if object as! NSObject == self { if let keyPath = keyPath { if keyPath == "defaultPath" { // commented out since when switching from 4G to Wi-Fi, this will be called multiple times, only the last time works -// let wifi = ChinaDNSRunner.checkWiFiNetwork() -// if wifi != self.wifi { - NSLog("Wi-Fi status changed") -// self.wifi = wifi - self.recreateUDP() -// return -// } - + // let wifi = ChinaDNSRunner.checkWiFiNetwork() + // if wifi != self.wifi { + NSLog("Wi-Fi status changed") + // self.wifi = wifi + self.recreateUDP() + // return + // } + } } } } } - override func stopTunnelWithReason(reason: NEProviderStopReason, completionHandler: () -> Void) { + override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { // Add code here to start the process of stopping the tunnel NSLog("stopTunnelWithReason") session?.cancel() completionHandler() - super.stopTunnelWithReason(reason, completionHandler: completionHandler) + super.stopTunnel(with: reason, completionHandler: completionHandler) // simply kill the extension process since it does no harm and ShadowVPN is expected to be always on + + // shared vpn connect status for today widget + self.shareConnectStateWithNSUserDefaults(vpnState: false) exit(0) } - override func handleAppMessage(messageData: NSData, completionHandler: ((NSData?) -> Void)?) { + override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { // Add code here to handle the message if let handler = completionHandler { handler(messageData) } } - override func sleepWithCompletionHandler(completionHandler: () -> Void) { + override func sleep(completionHandler: @escaping () -> Void) { // Add code here to get ready to sleep completionHandler() } @@ -180,4 +199,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider { override func wake() { // Add code here to wake up } + + func shareConnectStateWithNSUserDefaults(vpnState state: Bool) { + // shared vpn connect status for today widget + let shared = UserDefaults(suiteName: groupBundle) + shared?.set(state, forKey: "vpnState") + // shared?.synchronize() + } } diff --git a/tunnel/RouteManager.swift b/tunnel/RouteManager.swift index a751074..0063104 100644 --- a/tunnel/RouteManager.swift +++ b/tunnel/RouteManager.swift @@ -52,19 +52,19 @@ class RouteManager: NSObject { } else { NSLog("using default route") // TODO also support https://github.com/ashi009/bestroutetb - IPv4Settings.includedRoutes = [NEIPv4Route.defaultRoute()] + IPv4Settings.includedRoutes = [NEIPv4Route.default()] } } - func parseCHNRoutes(IPv4Settings: NEIPv4Settings) { + func parseCHNRoutes(_ IPv4Settings: NEIPv4Settings) { NSLog("parsing chnroutes") var routes = [NEIPv4Route]() - let chnroutesPath = NSBundle.mainBundle().pathForResource("chnroutes", ofType: "txt") + let chnroutesPath = Bundle.main.path(forResource: "chnroutes", ofType: "txt") do { let content = try String(contentsOfFile: chnroutesPath!) - let lines = content.componentsSeparatedByString("\n") + let lines = content.components(separatedBy: "\n") for line in lines { - let parts = line.componentsSeparatedByString("/") + let parts = line.components(separatedBy: "/") if parts.count == 2 { let address = parts[0] let subnet = self.cidrToSubnetMask[parts[1]] @@ -73,9 +73,9 @@ class RouteManager: NSObject { } } } catch { - NSLog("$@", String(error)) + NSLog("$@", String(describing: error)) } - IPv4Settings.includedRoutes = [NEIPv4Route.defaultRoute()] + IPv4Settings.includedRoutes = [NEIPv4Route.default()] IPv4Settings.excludedRoutes = routes } }