Creating a VPN app in iOS using swift is pretty simple.
First you will need KeychainService
// // KeychainService.swift // Androvaid VPN // // Created by Shaikh Wasi Sadman on 18/11/21. // Copyright (c) 2021 Androvaid. All rights reserved. // import Foundation import UIKit import Security // Identifiers let serviceIdentifier = "MySerivice" let userAccount = "authenticatedUser" let accessGroup = "MySerivice" // Arguments for the keychain queries var kSecAttrAccessGroupSwift = NSString(format: kSecClass) let kSecClassValue = kSecClass as CFString let kSecAttrAccountValue = kSecAttrAccount as CFString let kSecValueDataValue = kSecValueData as CFString let kSecClassGenericPasswordValue = kSecClassGenericPassword as CFString let kSecAttrServiceValue = kSecAttrService as CFString let kSecMatchLimitValue = kSecMatchLimit as CFString let kSecReturnDataValue = kSecReturnData as CFString let kSecMatchLimitOneValue = kSecMatchLimitOne as CFString let kSecAttrGenericValue = kSecAttrGeneric as CFString let kSecAttrAccessibleValue = kSecAttrAccessible as CFString class KeychainService: NSObject { func save(key:String, value:String) { let keyData: Data = key.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue), allowLossyConversion: false)! let valueData: Data = value.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue), allowLossyConversion: false)! let keychainQuery = NSMutableDictionary(); keychainQuery[kSecClassValue as! NSCopying] = kSecClassGenericPasswordValue keychainQuery[kSecAttrGenericValue as! NSCopying] = keyData keychainQuery[kSecAttrAccountValue as! NSCopying] = keyData keychainQuery[kSecAttrServiceValue as! NSCopying] = "VPN" keychainQuery[kSecAttrAccessibleValue as! NSCopying] = kSecAttrAccessibleAlwaysThisDeviceOnly keychainQuery[kSecValueData as! NSCopying] = valueData; // Delete any existing items SecItemDelete(keychainQuery as CFDictionary) SecItemAdd(keychainQuery as CFDictionary, nil) } func load(key: String)->Data { let keyData: Data = key.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue), allowLossyConversion: false)! let keychainQuery = NSMutableDictionary(); keychainQuery[kSecClassValue as! NSCopying] = kSecClassGenericPasswordValue keychainQuery[kSecAttrGenericValue as! NSCopying] = keyData keychainQuery[kSecAttrAccountValue as! NSCopying] = keyData keychainQuery[kSecAttrServiceValue as! NSCopying] = "VPN" keychainQuery[kSecAttrAccessibleValue as! NSCopying] = kSecAttrAccessibleAlwaysThisDeviceOnly keychainQuery[kSecMatchLimit] = kSecMatchLimitOne keychainQuery[kSecReturnPersistentRef] = kCFBooleanTrue var result: AnyObject? let status = withUnsafeMutablePointer(to: &result) { SecItemCopyMatching(keychainQuery, UnsafeMutablePointer($0)) } if status == errSecSuccess { if let data = result as! NSData? { if let value = NSString(data: data as Data, encoding: String.Encoding.utf8.rawValue) { } return data as Data; } } return "".data(using: .utf8)!; } }
Then use VPNManager
class and you can easily make one.
// // VPNManager.swift // Androvaid VPN // // Created by Shaikh Wasi Sadman on 30/11/21. // Copyright (c) 2021 Androvaid. All rights reserved. // import Foundation import NetworkExtension protocol VpnDelegate: AnyObject { func setStatus(status: Status) func checkNES(status: NEVPNStatus) } class VPNManager { let vpnManager = NEVPNManager.shared(); var server_address : String weak var vpnDelegate: VpnDelegate? /// The static field that controls the access to the singleton instance. /// /// This implementation let you extend the Singleton class while keeping /// just one instance of each subclass around. static var shared: VPNManager = { let instance = VPNManager(serverAddress: "Enter your server address") // ... configure the instance // ... return instance }() private init(serverAddress address : String) { self.server_address = address } private var vpnLoadHandler: (Error?) -> Void { return { (error:Error?) in if ((error) != nil) { self.vpnDelegate?.setStatus(status: Status.errorConnecting) print("vpnLoadHandler Could not load VPN Configurations : \(String(describing: error))") return; } let p = NEVPNProtocolIKEv2() // p.username = "androvaid" p.serverAddress = self.server_address /// Required p.authenticationMethod = NEVPNIKEAuthenticationMethod.sharedSecret let kcs = KeychainService(); kcs.save(key: "SHARED", value: "password") // Enter your password p.sharedSecretReference = kcs.load(key: "SHARED") // Required type is Data p.useExtendedAuthentication = false p.disconnectOnSleep = false p.remoteIdentifier = self.server_address /// Required p.ikeSecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup.group2 p.ikeSecurityAssociationParameters.encryptionAlgorithm = NEVPNIKEv2EncryptionAlgorithm.algorithmAES128 p.ikeSecurityAssociationParameters.integrityAlgorithm = NEVPNIKEv2IntegrityAlgorithm.SHA96 p.ikeSecurityAssociationParameters.lifetimeMinutes = 1140 p.childSecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup.group2 p.childSecurityAssociationParameters.encryptionAlgorithm = NEVPNIKEv2EncryptionAlgorithm.algorithmAES128 p.childSecurityAssociationParameters.integrityAlgorithm = NEVPNIKEv2IntegrityAlgorithm.SHA96 p.childSecurityAssociationParameters.lifetimeMinutes = 1140 self.vpnManager.protocolConfiguration = p self.vpnManager.localizedDescription = "Androvaid VPN" // The VPN Name saved in your setting self.vpnManager.isOnDemandEnabled = false /// You can set rules for On Demand Connection let rule1 : NEOnDemandRule = NEOnDemandRule() rule1.setValuesForKeys(["Action" : NEOnDemandRuleAction.connect, "InterfaceTypeMatch" : NEOnDemandRuleInterfaceType.wiFi]) let rule2 : NEOnDemandRule = NEOnDemandRule() rule2.setValuesForKeys(["Action" : NEOnDemandRuleAction.connect, "InterfaceTypeMatch" : NEOnDemandRuleInterfaceType.cellular]) let connectRule = NEOnDemandRuleConnect() connectRule.interfaceTypeMatch = .any self.vpnManager.onDemandRules = [connectRule] // do { // // // let rule1 : NEOnDemandRuleConnect = NEOnDemandRuleConnect() // // rule1.setValue(NEOnDemandRuleAction.connect, forKeyPath: "Action") // //rule1.setValue(NEOnDemandRuleAction.connect, forKey: "Action") // //rule1.setValue(NEOnDemandRuleAction.connect, forKeyPath:) // //rule1.setValue(NEOnDemandRuleAction.connect, forUndefinedKey: "Action") // // //rule1.setValuesForKeys(["Action" : NEOnDemandRuleAction.connect, "InterfaceTypeMatch" : NEOnDemandRuleInterfaceType.wiFi]) // rule1.setValue(NEOnDemandRuleInterfaceType.wiFi, forKeyPath: "InterfaceTypeMatch") // // // let rule2 : NEOnDemandRule = NEOnDemandRule() // rule2.setValue(NEOnDemandRuleAction.connect, forKeyPath: "action") // rule2.setValue(NEOnDemandRuleInterfaceType.cellular, forKeyPath: "InterfaceTypeMatch") // // self.vpnManager.protocolConfiguration = p // self.vpnManager.onDemandRules = [rule2, rule1] // // } catch { // Log.d(message: "Error info: \(error)") // } // self.vpnManager.isEnabled = true self.vpnManager.saveToPreferences(completionHandler: self.vpnSaveHandler) } } private var vpnSaveHandler: (Error?) -> Void { return { (error:Error?) in if (error != nil) { print("vpnSaveHandler Could not save VPN Configurations \(String(describing: error?.localizedDescription))") return } else { do { print("vpnSaveHandler do") try self.vpnManager.connection.startVPNTunnel() } catch let error { print("Error starting VPN Connection \(error.localizedDescription)"); } } } //self.vpnlock = false } public func connectVPN() { //For no known reason the process of saving/loading the VPN configurations fails.On the 2nd time it works print("connectVPN") self.vpnManager.loadFromPreferences(completionHandler: self.vpnLoadHandler) NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: nil , queue: nil) { notification in let nevpnconn = notification.object as! NEVPNConnection let status = nevpnconn.status self.vpnDelegate?.checkNES(status: status) } } public func disconnectVPN() ->Void { self.vpnManager.loadFromPreferences { error in assert(error == nil, "Failed to load preferences: \(error!.localizedDescription)") self.vpnManager.connection.stopVPNTunnel() NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: nil , queue: nil) { notification in let nevpnconn = notification.object as! NEVPNConnection let status = nevpnconn.status self.vpnDelegate?.checkNES(status: status) } } } }
You can use it like below
Initialize VPNManger => var vpn = VPNManager.shared
Connect VPN => Â self.vpn.connectVPN();
Disconnect VPN => self.vpn.disconnectVPN()
Great post
Thanks