diff --git a/WireGuard/Base.lproj/Main.storyboard b/WireGuard/Base.lproj/Main.storyboard index 2a6a954..2a5cc2c 100644 --- a/WireGuard/Base.lproj/Main.storyboard +++ b/WireGuard/Base.lproj/Main.storyboard @@ -617,12 +617,36 @@ - + + + + + + + + + + + + + + + + + + + + + @@ -711,9 +735,11 @@ + + diff --git a/WireGuard/Coordinators/AppCoordinator+TunnelInfoTableViewControllerDelegate.swift b/WireGuard/Coordinators/AppCoordinator+TunnelInfoTableViewControllerDelegate.swift index 651cce5..994911a 100644 --- a/WireGuard/Coordinators/AppCoordinator+TunnelInfoTableViewControllerDelegate.swift +++ b/WireGuard/Coordinators/AppCoordinator+TunnelInfoTableViewControllerDelegate.swift @@ -3,8 +3,22 @@ // import Foundation +import NetworkExtension extension AppCoordinator: TunnelInfoTableViewControllerDelegate { + func connect(tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController) { + connect(tunnel: tunnel) + } + + func disconnect(tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController) { + disconnect(tunnel: tunnel) + } + + func status(for tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController) -> NEVPNStatus { + let session = self.providerManager(for: tunnel)?.connection as? NETunnelProviderSession + return session?.status ?? .invalid + } + func configure(tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController) { print("configure tunnel \(tunnel)") let editContext = persistentContainer.newBackgroundContext() diff --git a/WireGuard/Coordinators/AppCoordinator+TunnelsTableViewControllerDelegate.swift b/WireGuard/Coordinators/AppCoordinator+TunnelsTableViewControllerDelegate.swift index 5506641..2e9aebd 100644 --- a/WireGuard/Coordinators/AppCoordinator+TunnelsTableViewControllerDelegate.swift +++ b/WireGuard/Coordinators/AppCoordinator+TunnelsTableViewControllerDelegate.swift @@ -55,77 +55,11 @@ extension AppCoordinator: TunnelsTableViewControllerDelegate { } func connect(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) { - _ = refreshProviderManagers().then { () -> Promise in - let manager = self.providerManager(for: tunnel)! - let block = { - switch manager.connection.status { - case .invalid, .disconnected: - self.connect(tunnel: tunnel) - default: - break - } - } - - if manager.connection.status == .invalid { - manager.loadFromPreferences { (_) in - block() - } - } else { - block() - } - - return Promise.value(()) - } + connect(tunnel: tunnel) } func disconnect(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) { - _ = refreshProviderManagers().then { () -> Promise in - let manager = self.providerManager(for: tunnel)! - let block = { - switch manager.connection.status { - case .connected, .connecting: - self.disconnect(tunnel: tunnel) - default: - break - } - } - - if manager.connection.status == .invalid { - manager.loadFromPreferences { (_) in - block() - } - } else { - block() - } - return Promise.value(()) - } - } - - private func connect(tunnel: Tunnel) { - os_log("connect tunnel: %{public}@", log: Log.general, type: .info, tunnel.description) - // Should the manager be enabled? - - let manager = providerManager(for: tunnel) - manager?.isEnabled = true - manager?.saveToPreferences { (error) in - if let error = error { - os_log("error saving preferences: %{public}@", log: Log.general, type: .error, error.localizedDescription) - return - } - os_log("saved preferences", log: Log.general, type: .info) - - let session = manager?.connection as! NETunnelProviderSession //swiftlint:disable:this force_cast - do { - try session.startTunnel() - } catch let error { - os_log("error starting tunnel: %{public}@", log: Log.general, type: .error, error.localizedDescription) - } - } - } - - func disconnect(tunnel: Tunnel) { - let manager = providerManager(for: tunnel) - manager?.connection.stopVPNTunnel() + disconnect(tunnel: tunnel) } func info(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) { @@ -154,18 +88,6 @@ extension AppCoordinator: TunnelsTableViewControllerDelegate { } } - private func providerManager(for tunnel: Tunnel) -> NETunnelProviderManager? { - return self.providerManagers?.first { - guard let prot = $0.protocolConfiguration as? NETunnelProviderProtocol else { - return false - } - guard let tunnelIdentifier = prot.providerConfiguration?[PCKeys.tunnelIdentifier.rawValue] as? String else { - return false - } - return tunnelIdentifier == tunnel.tunnelIdentifier - } - } - func saveTunnel(_ tunnel: Tunnel) { let manager = providerManager(for: tunnel) ?? NETunnelProviderManager() manager.localizedDescription = tunnel.title diff --git a/WireGuard/Coordinators/AppCoordinator.swift b/WireGuard/Coordinators/AppCoordinator.swift index d94c45e..923e9ae 100644 --- a/WireGuard/Coordinators/AppCoordinator.swift +++ b/WireGuard/Coordinators/AppCoordinator.swift @@ -291,6 +291,73 @@ class AppCoordinator: RootViewCoordinator { showAlert(title: NSLocalizedString("Error", comment: "Error alert title"), message: error.localizedDescription) } + func connect(tunnel: Tunnel) { + _ = refreshProviderManagers().then { () -> Promise in + let manager = self.providerManager(for: tunnel)! + let block = { + switch manager.connection.status { + case .invalid, .disconnected: + os_log("connect tunnel: %{public}@", log: Log.general, type: .info, tunnel.description) + // Should the manager be enabled? + + let manager = self.providerManager(for: tunnel) + manager?.isEnabled = true + manager?.saveToPreferences { (error) in + if let error = error { + os_log("error saving preferences: %{public}@", log: Log.general, type: .error, error.localizedDescription) + return + } + os_log("saved preferences", log: Log.general, type: .info) + + let session = manager?.connection as! NETunnelProviderSession //swiftlint:disable:this force_cast + do { + try session.startTunnel() + } catch let error { + os_log("error starting tunnel: %{public}@", log: Log.general, type: .error, error.localizedDescription) + } + } + + default: + break + } + } + + if manager.connection.status == .invalid { + manager.loadFromPreferences { (_) in + block() + } + } else { + block() + } + + return Promise.value(()) + } + } + + func disconnect(tunnel: Tunnel) { + _ = refreshProviderManagers().then { () -> Promise in + let manager = self.providerManager(for: tunnel)! + let block = { + switch manager.connection.status { + case .connected, .connecting: + let manager = self.providerManager(for: tunnel) + manager?.connection.stopVPNTunnel() + default: + break + } + } + + if manager.connection.status == .invalid { + manager.loadFromPreferences { (_) in + block() + } + } else { + block() + } + return Promise.value(()) + } + } + private func showAlert(title: String, message: String) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "OK button"), style: .default)) @@ -313,6 +380,18 @@ class AppCoordinator: RootViewCoordinator { return "Reasserting" } } + + func providerManager(for tunnel: Tunnel) -> NETunnelProviderManager? { + return self.providerManagers?.first { + guard let prot = $0.protocolConfiguration as? NETunnelProviderProtocol else { + return false + } + guard let tunnelIdentifier = prot.providerConfiguration?[PCKeys.tunnelIdentifier.rawValue] as? String else { + return false + } + return tunnelIdentifier == tunnel.tunnelIdentifier + } + } } class AppDocumentPickerDelegate: NSObject, UIDocumentPickerDelegate { diff --git a/WireGuard/ViewControllers/TunnelInfoTableViewController.swift b/WireGuard/ViewControllers/TunnelInfoTableViewController.swift index f91c41b..d540114 100644 --- a/WireGuard/ViewControllers/TunnelInfoTableViewController.swift +++ b/WireGuard/ViewControllers/TunnelInfoTableViewController.swift @@ -4,12 +4,17 @@ import UIKit import CoreData +import NetworkExtension + import BNRCoreDataStack import PromiseKit protocol TunnelInfoTableViewControllerDelegate: class { + func connect(tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController) + func disconnect(tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController) func configure(tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController) func showSettings() + func status(for tunnel: Tunnel, tunnelInfoTableViewController: TunnelInfoTableViewController) -> NEVPNStatus } class TunnelInfoTableViewController: UITableViewController { @@ -31,6 +36,11 @@ class TunnelInfoTableViewController: UITableViewController { // Get rid of seperator lines in table. tableView.tableFooterView = UIView(frame: CGRect.zero) + + NotificationCenter.default.addObserver(self, + selector: #selector(VPNStatusDidChange(notification:)), + name: .NEVPNStatusDidChange, + object: nil) } override func viewWillAppear(_ animated: Bool) { @@ -56,7 +66,8 @@ class TunnelInfoTableViewController: UITableViewController { switch indexPath.section { case 0: let cell = tableView.dequeueReusableCell(type: InterfaceInfoTableViewCell.self, for: indexPath) - cell.model = tunnel.interface + cell.delegate = self + cell.configure(model: tunnel.interface, status: delegate?.status(for: tunnel, tunnelInfoTableViewController: self) ?? .invalid) return cell default: let cell = tableView.dequeueReusableCell(type: PeerInfoTableViewCell.self, for: indexPath) @@ -78,10 +89,46 @@ class TunnelInfoTableViewController: UITableViewController { @IBAction func editTunnelConfiguration(_ sender: Any) { delegate?.configure(tunnel: self.tunnel, tunnelInfoTableViewController: self) } + + @objc private func VPNStatusDidChange(notification: NSNotification) { + guard let session = notification.object as? NETunnelProviderSession else { + return + } + + guard let prot = session.manager.protocolConfiguration as? NETunnelProviderProtocol else { + return + } + + guard let changedTunnelIdentifier = prot.providerConfiguration?[PCKeys.tunnelIdentifier.rawValue] as? String else { + return + } + + guard tunnel.tunnelIdentifier == changedTunnelIdentifier else { + return + } + + self.tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .none) + } +} + +extension TunnelInfoTableViewController: InterfaceInfoTableViewCellDelegate { + func connect(tunnelIdentifier: String) { + delegate?.connect(tunnel: tunnel, tunnelInfoTableViewController: self) + } + + func disconnect(tunnelIdentifier: String) { + delegate?.disconnect(tunnel: tunnel, tunnelInfoTableViewController: self) + } +} + +protocol InterfaceInfoTableViewCellDelegate: class { + func connect(tunnelIdentifier: String) + func disconnect(tunnelIdentifier: String) } class InterfaceInfoTableViewCell: UITableViewCell { - var model: Interface! { + weak var delegate: InterfaceInfoTableViewCellDelegate? + private var model: Interface! { didSet { nameField.text = model.tunnel?.title addressesField.text = model.addresses @@ -89,9 +136,41 @@ class InterfaceInfoTableViewCell: UITableViewCell { } } + func configure(model: Interface!, status: NEVPNStatus) { + self.model = model + + if status == .connecting || status == .disconnecting || status == .reasserting { + activityIndicator.startAnimating() + tunnelSwitch.isHidden = true + } else { + activityIndicator.stopAnimating() + tunnelSwitch.isHidden = false + } + + tunnelSwitch.isOn = status == .connected + tunnelSwitch.onTintColor = status == .invalid || status == .reasserting ? .gray : .green + tunnelSwitch.isEnabled = true + } + + @IBAction func tunnelSwitchChanged(_ sender: Any) { + tunnelSwitch.isEnabled = false + + guard let tunnelIdentifier = model.tunnel?.tunnelIdentifier else { + return + } + + if tunnelSwitch.isOn { + delegate?.connect(tunnelIdentifier: tunnelIdentifier) + } else { + delegate?.disconnect(tunnelIdentifier: tunnelIdentifier) + } + } + @IBOutlet weak var nameField: UILabel! @IBOutlet weak var addressesField: UILabel! @IBOutlet weak var publicKeyField: UILabel! + @IBOutlet weak var tunnelSwitch: UISwitch! + @IBOutlet weak var activityIndicator: UIActivityIndicatorView! @IBAction func copyPublicKey(_ sender: Any) { if let publicKey = model.publicKey { diff --git a/WireGuard/ViewControllers/TunnelsTableViewController.swift b/WireGuard/ViewControllers/TunnelsTableViewController.swift index 24e5cc9..830cb75 100644 --- a/WireGuard/ViewControllers/TunnelsTableViewController.swift +++ b/WireGuard/ViewControllers/TunnelsTableViewController.swift @@ -44,7 +44,7 @@ class TunnelsTableViewController: UITableViewController { let tunnel = try Tunnel.findFirstInContext(self.viewContext, predicate: NSPredicate(format: "tunnelIdentifier == %@", tunnelIdentifier)) if let tunnel = tunnel { if let indexPath = self.fetchedResultsController.indexPathForObject(tunnel) { - self.tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.none) + self.tableView.reloadRows(at: [indexPath], with: .none) } } } catch {