From 2ae798462966beee384bf3916b458c864ffbc18a Mon Sep 17 00:00:00 2001 From: Jeroen Leenarts Date: Sat, 4 Aug 2018 21:28:19 +0200 Subject: [PATCH] Add NETunnelProviderManager management to AppCoordinator. Signed-off-by: Jason A. Donenfeld --- WireGuard.xcodeproj/project.pbxproj | 4 + WireGuard/Coordinators/AppCoordinator.swift | 188 +++++++++++++++++- WireGuard/Models/Tunnel+Extension.swift | 16 ++ .../TunnelsTableViewController.swift | 2 +- 4 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 WireGuard/Models/Tunnel+Extension.swift diff --git a/WireGuard.xcodeproj/project.pbxproj b/WireGuard.xcodeproj/project.pbxproj index 694b167..05b5906 100644 --- a/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard.xcodeproj/project.pbxproj @@ -33,6 +33,7 @@ 4A7F6EDD20B674CD00B260B7 /* Address+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A7F6EDB20B674CD00B260B7 /* Address+CoreDataClass.swift */; }; 4A7F6EDE20B674CD00B260B7 /* Address+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A7F6EDC20B674CD00B260B7 /* Address+CoreDataProperties.swift */; }; 4A8AABD820B6A79100B6D8C1 /* UITableView+WireGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A8AABD720B6A79100B6D8C1 /* UITableView+WireGuard.swift */; }; + 4AC5462E2116306F00749D21 /* Tunnel+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC5462D2116306F00749D21 /* Tunnel+Extension.swift */; }; 4AD095C820DC4190000E9CF5 /* libwg-go.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AD0900120DC4171000E9CF5 /* libwg-go.a */; }; 4AD095CC20DC42CD000E9CF5 /* WireGuardGoWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AD095CB20DC42CD000E9CF5 /* WireGuardGoWrapper.m */; }; 4AEAC32920F14B3B007B67AB /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AEAC32820F14B3B007B67AB /* Log.swift */; }; @@ -105,6 +106,7 @@ 4A7F6EDB20B674CD00B260B7 /* Address+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Address+CoreDataClass.swift"; sourceTree = ""; }; 4A7F6EDC20B674CD00B260B7 /* Address+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Address+CoreDataProperties.swift"; sourceTree = ""; }; 4A8AABD720B6A79100B6D8C1 /* UITableView+WireGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+WireGuard.swift"; sourceTree = ""; }; + 4AC5462D2116306F00749D21 /* Tunnel+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tunnel+Extension.swift"; sourceTree = ""; }; 4AD0900120DC4171000E9CF5 /* libwg-go.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libwg-go.a"; sourceTree = ""; }; 4AD0900720DC4171000E9CF5 /* wireguard.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = wireguard.h; sourceTree = ""; }; 4AD095C920DC42CD000E9CF5 /* WireGuardNetworkExtension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireGuardNetworkExtension-Bridging-Header.h"; sourceTree = ""; }; @@ -233,6 +235,7 @@ 4A4BAD1E20B6026900F12B28 /* Interface+CoreDataProperties.swift */, 4A4BAD1820B5F8FF00F12B28 /* Tunnel+CoreDataClass.swift */, 4A4BAD1920B5F8FF00F12B28 /* Tunnel+CoreDataProperties.swift */, + 4AC5462D2116306F00749D21 /* Tunnel+Extension.swift */, 4A4BAD1520B5F8DE00F12B28 /* WireGuard.xcdatamodeld */, ); path = Models; @@ -527,6 +530,7 @@ 4A4BAD1A20B5F8FF00F12B28 /* Tunnel+CoreDataClass.swift in Sources */, 4A4BACE820B5F1BF00F12B28 /* TunnelsTableViewController.swift in Sources */, 4A4BAD1020B5F6EC00F12B28 /* RootCoordinator.swift in Sources */, + 4AC5462E2116306F00749D21 /* Tunnel+Extension.swift in Sources */, 4A4BAD0E20B5F6C300F12B28 /* Coordinator.swift in Sources */, 4A4BA6D820B73CBA00223AB8 /* TunnelConfigurationTableViewController.swift in Sources */, 4A4BAD2020B6026900F12B28 /* Peer+CoreDataProperties.swift in Sources */, diff --git a/WireGuard/Coordinators/AppCoordinator.swift b/WireGuard/Coordinators/AppCoordinator.swift index ce30ae6..2487dad 100644 --- a/WireGuard/Coordinators/AppCoordinator.swift +++ b/WireGuard/Coordinators/AppCoordinator.swift @@ -7,16 +7,23 @@ // import Foundation +import NetworkExtension +import KeychainSwift +import os.log import CoreData import BNRCoreDataStack extension UINavigationController: Identifyable {} +let APPGROUP = "group.com.wireguard.ios.WireGuard" +let VPNBUNDLE = "com.wireguard.ios.WireGuard.WireGuardNetworkExtension" + class AppCoordinator: RootViewCoordinator { let persistentContainer = NSPersistentContainer(name: "WireGuard") let storyboard = UIStoryboard(name: "Main", bundle: nil) + var currentManager: NETunnelProviderManager? // MARK: - Properties @@ -26,6 +33,22 @@ class AppCoordinator: RootViewCoordinator { return self.tunnelsTableViewController } + var status = NEVPNStatus.invalid { + didSet { + switch status { + case .connected: + //TODO: signal connected + os_log("Connected VPN", log: Log.general, type: .info) + case .connecting, .disconnecting, .reasserting: + //TODO: signal connecting + os_log("Connecting VPN", log: Log.general, type: .info) + case .disconnected, .invalid: + //TODO: disconnect / invalid + os_log("Disconnecting VPN", log: Log.general, type: .info) + } + } + } + var tunnelsTableViewController: TunnelsTableViewController! /// Window to manage @@ -42,6 +65,12 @@ class AppCoordinator: RootViewCoordinator { self.window.rootViewController = self.navigationController self.window.makeKeyAndVisible() + + NotificationCenter.default.addObserver(self, + selector: #selector(VPNStatusDidChange(notification:)), + name: .NEVPNStatusDidChange, + object: nil) + reloadCurrentManager(nil) } // MARK: - Functions @@ -74,6 +103,71 @@ class AppCoordinator: RootViewCoordinator { } } + // MARK: - NEVPNManager handling + + func configureVPN(_ configure: @escaping (NETunnelProviderManager) -> NETunnelProviderProtocol?, completionHandler: @escaping (Error?) -> Void) { + reloadCurrentManager { (error) in + if let error = error { + os_log("error reloading preferences: %{public}@", log: Log.general, type: .error, error.localizedDescription) + completionHandler(error) + return + } + + let manager = self.currentManager! + if let protocolConfiguration = configure(manager) { + manager.protocolConfiguration = protocolConfiguration + } + manager.isEnabled = true + + manager.saveToPreferences { (error) in + if let error = error { + os_log("error saving preferences: %{public}@", log: Log.general, type: .error, error.localizedDescription) + completionHandler(error) + return + } + os_log("saved preferences", log: Log.general, type: .info) + self.reloadCurrentManager(completionHandler) + } + } + } + + func reloadCurrentManager(_ completionHandler: ((Error?) -> Void)?) { + NETunnelProviderManager.loadAllFromPreferences { (managers, error) in + if let error = error { + completionHandler?(error) + return + } + + var manager: NETunnelProviderManager? + + for man in managers! { + if let prot = man.protocolConfiguration as? NETunnelProviderProtocol { + if prot.providerBundleIdentifier == VPNBUNDLE { + manager = man + break + } + } + } + + if manager == nil { + manager = NETunnelProviderManager() + } + + self.currentManager = manager + self.status = manager!.connection.status + completionHandler?(nil) + } + } + + @objc private func VPNStatusDidChange(notification: NSNotification) { + guard let status = currentManager?.connection.status else { + os_log("VPNStatusDidChange", log: Log.general, type: .debug) + return + } + os_log("VPNStatusDidChange: %{public}@", log: Log.general, type: .debug, description(for: status)) + self.status = status + } + public func showError(_ error: Error) { showAlert(title: NSLocalizedString("Error", comment: "Error alert title"), message: error.localizedDescription) } @@ -83,6 +177,23 @@ class AppCoordinator: RootViewCoordinator { alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "OK button"), style: .default)) self.navigationController.present(alert, animated: true) } + + private func description(for status: NEVPNStatus) -> String { + switch status { + case .connected: + return "Connected" + case .connecting: + return "Connecting" + case .disconnected: + return "Disconnected" + case .disconnecting: + return "Disconnecting" + case .invalid: + return "Invalid" + case .reasserting: + return "Reasserting" + } + } } extension AppCoordinator: TunnelsTableViewControllerDelegate { @@ -91,9 +202,80 @@ extension AppCoordinator: TunnelsTableViewControllerDelegate { showTunnelConfigurationViewController(tunnel: nil, context: addContext) } - func connect(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) { - // TODO implement - print("connect tunnel \(tunnel)") + func connect(tunnel: Tunnel?, tunnelsTableViewController: TunnelsTableViewController) { + let block = { + switch self.status { + case .invalid, .disconnected: + self.connect(tunnel: tunnel) + + case .connected, .connecting: + // TODO: this needs to check if the passed tunnel is the actual connected tunnel config + self.disconnect() + + default: + break + } + } + + if status == .invalid { + reloadCurrentManager({ (_) in + block() + }) + } else { + block() + } + } + + private func connect(tunnel: Tunnel?) { + // TODO implement NETunnelProviderManager VC showing current connection status, pushing this config into VPN stack + os_log("connect tunnel: %{public}@", log: Log.general, type: .info, tunnel?.description ?? "-none-") + + guard let tunnel = tunnel else { + return + } + + configureVPN({ (_) in + //TODO: decide what to do with on demand + // self.currentManager?.isOnDemandEnabled = true + self.currentManager?.onDemandRules = [NEOnDemandRuleConnect()] + + let protocolConfiguration = NETunnelProviderProtocol() + let keychain = KeychainSwift() + keychain.accessGroup = APPGROUP + //TODO: Set secrets to keychain? + + protocolConfiguration.providerBundleIdentifier = VPNBUNDLE + //TODO obtain endpoint hostname +// protocolConfiguration.serverAddress = endpoint.hostname + //TODO obtain endpoint username +// protocolConfiguration.username = endpoint.username + //TODO: how to obtain this? +// protocolConfiguration.passwordReference = try? keychain.passwordReference(for: endpoint.username) + protocolConfiguration.providerConfiguration = tunnel.generateProviderConfiguration() + + return protocolConfiguration + }, completionHandler: { (error) in + if let error = error { + os_log("configure error: %{public}@", log: Log.general, type: .error, error.localizedDescription) + return + } + let session = self.currentManager?.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() { + configureVPN({ (_) in + //TODO: decide what to do with on demand + // self.currentManager?.isOnDemandEnabled = false + return nil + }, completionHandler: { (_) in + self.currentManager?.connection.stopVPNTunnel() + }) } func configure(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) { diff --git a/WireGuard/Models/Tunnel+Extension.swift b/WireGuard/Models/Tunnel+Extension.swift new file mode 100644 index 0000000..38ccec6 --- /dev/null +++ b/WireGuard/Models/Tunnel+Extension.swift @@ -0,0 +1,16 @@ +// +// Tunnel+Extension.swift +// WireGuard +// +// Created by Jeroen Leenarts on 04-08-18. +// Copyright © 2018 WireGuard. All rights reserved. +// + +import Foundation + +extension Tunnel { + public func generateProviderConfiguration() -> [String: Any] { + //TODO: generate ProviderConfiguration from tunnel with WireGuard config. + return [:] + } +} diff --git a/WireGuard/ViewControllers/TunnelsTableViewController.swift b/WireGuard/ViewControllers/TunnelsTableViewController.swift index 06729c4..b56cffe 100644 --- a/WireGuard/ViewControllers/TunnelsTableViewController.swift +++ b/WireGuard/ViewControllers/TunnelsTableViewController.swift @@ -13,7 +13,7 @@ import BNRCoreDataStack protocol TunnelsTableViewControllerDelegate: class { func addProvider(tunnelsTableViewController: TunnelsTableViewController) - func connect(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) + func connect(tunnel: Tunnel?, tunnelsTableViewController: TunnelsTableViewController) func configure(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) func delete(tunnel: Tunnel, tunnelsTableViewController: TunnelsTableViewController) }