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 */; };
4A7F6EDB20B674CD00B260B7 /* Address+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Address+CoreDataClass.swift"; sourceTree = "<group>"; };
4A7F6EDC20B674CD00B260B7 /* Address+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Address+CoreDataProperties.swift"; sourceTree = "<group>"; };
4A8AABD720B6A79100B6D8C1 /* UITableView+WireGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+WireGuard.swift"; sourceTree = "<group>"; };
+ 4AC5462D2116306F00749D21 /* Tunnel+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tunnel+Extension.swift"; sourceTree = "<group>"; };
4AD0900120DC4171000E9CF5 /* libwg-go.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libwg-go.a"; sourceTree = "<group>"; };
4AD0900720DC4171000E9CF5 /* wireguard.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = wireguard.h; sourceTree = "<group>"; };
4AD095C920DC42CD000E9CF5 /* WireGuardNetworkExtension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireGuardNetworkExtension-Bridging-Header.h"; sourceTree = "<group>"; };
4A4BAD1E20B6026900F12B28 /* Interface+CoreDataProperties.swift */,
4A4BAD1820B5F8FF00F12B28 /* Tunnel+CoreDataClass.swift */,
4A4BAD1920B5F8FF00F12B28 /* Tunnel+CoreDataProperties.swift */,
+ 4AC5462D2116306F00749D21 /* Tunnel+Extension.swift */,
4A4BAD1520B5F8DE00F12B28 /* WireGuard.xcdatamodeld */,
);
path = Models;
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 */,
//
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
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
self.window.rootViewController = self.navigationController
self.window.makeKeyAndVisible()
+
+ NotificationCenter.default.addObserver(self,
+ selector: #selector(VPNStatusDidChange(notification:)),
+ name: .NEVPNStatusDidChange,
+ object: nil)
+ reloadCurrentManager(nil)
}
// MARK: - Functions
}
}
+ // 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)
}
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 {
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) {