// // ViewController.swift // BasicTunnel-iOS // // Created by Davide De Rosa on 2/11/17. // Copyright © 2018 Davide De Rosa. All rights reserved. // import UIKit import NetworkExtension import TunnelKit extension ViewController { private static let appGroup = "group.com.algoritmico.ios.demo.BasicTunnel" private static let bundleIdentifier = "com.algoritmico.ios.demo.BasicTunnel.BasicTunnelExtension" private func makeProtocol() -> NETunnelProviderProtocol { let server = textServer.text! let domain = textDomain.text! let hostname = ((domain == "") ? server : [server, domain].joined(separator: ".")) let port = UInt16(textPort.text!)! let username = textUsername.text! let password = textPassword.text! let endpoint = TunnelKitProvider.AuthenticatedEndpoint( hostname: hostname, username: username, password: password ) var builder = TunnelKitProvider.ConfigurationBuilder() let socketType: TunnelKitProvider.SocketType = switchTCP.isOn ? .tcp : .udp builder.endpointProtocols = [TunnelKitProvider.EndpointProtocol(socketType, port)] builder.cipher = .aes128cbc builder.digest = .sha1 builder.mtu = 1350 builder.compressionFraming = .compLZO builder.renegotiatesAfterSeconds = nil builder.shouldDebug = true builder.debugLogKey = "Log" let configuration = builder.build() return try! configuration.generatedTunnelProtocol( withBundleIdentifier: ViewController.bundleIdentifier, appGroup: ViewController.appGroup, endpoint: endpoint ) } } class ViewController: UIViewController, URLSessionDataDelegate { @IBOutlet var textUsername: UITextField! @IBOutlet var textPassword: UITextField! @IBOutlet var textServer: UITextField! @IBOutlet var textDomain: UITextField! @IBOutlet var textPort: UITextField! @IBOutlet var switchTCP: UISwitch! @IBOutlet var buttonConnection: UIButton! @IBOutlet var textLog: UITextView! // var currentManager: NETunnelProviderManager? var status = NEVPNStatus.invalid override func viewDidLoad() { super.viewDidLoad() textServer.text = "germany" textDomain.text = "privateinternetaccess.com" textPort.text = "1198" switchTCP.isOn = false textUsername.text = "myusername" textPassword.text = "mypassword" NotificationCenter.default.addObserver(self, selector: #selector(VPNStatusDidChange(notification:)), name: .NEVPNStatusDidChange, object: nil) reloadCurrentManager(nil) // testFetchRef() } @IBAction func connectionClicked(_ sender: Any) { let block = { switch (self.status) { case .invalid, .disconnected: self.connect() case .connected, .connecting: self.disconnect() default: break } } if (status == .invalid) { reloadCurrentManager({ (error) in block() }) } else { block() } } @IBAction func tcpClicked(_ sender: Any) { if switchTCP.isOn { textPort.text = "502" } else { textPort.text = "1198" } } func connect() { configureVPN({ (manager) in return self.makeProtocol() }, completionHandler: { (error) in if let error = error { print("configure error: \(error)") return } let session = self.currentManager?.connection as! NETunnelProviderSession do { try session.startTunnel() } catch let e { print("error starting tunnel: \(e)") } }) } func disconnect() { configureVPN({ (manager) in return nil }, completionHandler: { (error) in self.currentManager?.connection.stopVPNTunnel() }) } @IBAction func displayLog() { guard let vpn = currentManager?.connection as? NETunnelProviderSession else { return } try? vpn.sendProviderMessage(TunnelKitProvider.Message.requestLog.data) { (data) in guard let log = String(data: data!, encoding: .utf8) else { return } self.textLog.text = log } } func configureVPN(_ configure: @escaping (NETunnelProviderManager) -> NETunnelProviderProtocol?, completionHandler: @escaping (Error?) -> Void) { reloadCurrentManager { (error) in if let error = error { print("error reloading preferences: \(error)") 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 { print("error saving preferences: \(error)") completionHandler(error) return } print("saved preferences") self.reloadCurrentManager(completionHandler) } } } func reloadCurrentManager(_ completionHandler: ((Error?) -> Void)?) { NETunnelProviderManager.loadAllFromPreferences { (managers, error) in if let error = error { completionHandler?(error) return } var manager: NETunnelProviderManager? for m in managers! { if let p = m.protocolConfiguration as? NETunnelProviderProtocol { if (p.providerBundleIdentifier == ViewController.bundleIdentifier) { manager = m break } } } if (manager == nil) { manager = NETunnelProviderManager() } self.currentManager = manager self.status = manager!.connection.status self.updateButton() completionHandler?(nil) } } func updateButton() { switch status { case .connected, .connecting: buttonConnection.setTitle("Disconnect", for: .normal) case .disconnected: buttonConnection.setTitle("Connect", for: .normal) case .disconnecting: buttonConnection.setTitle("Disconnecting", for: .normal) default: break } } @objc private func VPNStatusDidChange(notification: NSNotification) { guard let status = currentManager?.connection.status else { print("VPNStatusDidChange") return } print("VPNStatusDidChange: \(status.rawValue)") self.status = status updateButton() } private func testFetchRef() { // let keychain = Keychain(group: ViewController.APP_GROUP) // let username = "foo" // let password = "bar" // // guard let _ = try? keychain.set(password: password, for: username) else { // print("Couldn't set password") // return // } // guard let passwordReference = try? keychain.passwordReference(for: username) else { // print("Couldn't get password reference") // return // } // guard let fetchedPassword = try? Keychain.password(for: username, reference: passwordReference) else { // print("Couldn't fetch password") // return // } // // print("\(username) -> \(password)") // print("\(username) -> \(fetchedPassword)") } }