startVPNTunnel() doesn't work on iOS 14 Swift - ios

Since iOS 14 released startVPNTunnel() stopped working and the app doesn't create VPN connection.
My configuration is the following:
private func _save(_ account: VPNServer, completion: VPNConfigureCompletion?) {
let keychain = Keychain(service: Bundle.main.bundleIdentifier!)
keychain["sharedSecret"] = account.key
let ikev2Protocol = NEVPNProtocolIKEv2()
ikev2Protocol.useExtendedAuthentication = false
ikev2Protocol.authenticationMethod = .sharedSecret
ikev2Protocol.disconnectOnSleep = false
ikev2Protocol.serverAddress = account.ip
ikev2Protocol.sharedSecretReference = keychain[attributes: "sharedSecret"]?.persistentRef
ikev2Protocol.remoteIdentifier = account.ip
if #available(iOS 13.0, *) {
ikev2Protocol.enableFallback = true
}
vpnManager.protocolConfiguration = ikev2Protocol
vpnManager.isEnabled = true
vpnManager.saveToPreferences { error in
if let err = error {
print("Failed to save profile: \(err.localizedDescription)")
self.delegate?.setConnectionStatus(status: .disconnected)
NotificationCenter.default.post(name: NSNotification.Name.NEVPNStatusDidChange, object: "Failed to save profile")
} else {
completion?()
}
}
}
This code saves configuration, but when you try to connect, it throws (even manually in iPhone settings). It worked correctly on iOS 13. We have tried to make server to IPv6 compatible, but without success. I also tried different to add VPN.IKEv2.ChildSecurityAssociationParameters without success.
Anyone has solution?

Related

Unable to attach debugger to Network Extension iOS ("Waiting to Attach")

I'm new to iOS development, and am writing a VPN app, using the OpenVPN framework. I'm making use of the Network Extension target to tunnel network traffic to our OpenVPN server. However, when trying to debug, I can't attach the debugger to the Network Extension - it's stuck at "Waiting to Attach" in the Debug Navigator view.
In this blog post by Alex Grebenyuk he gives some recommendations for troubleshooting when you fail to attach the debugger to the extension. I've meticulously checked all of them, and my app correctly uses everything there: Both the app and the network extension have the Packet Tunnel Network Extension capability and bundle names all work out.
Some more info on the bundle names in case it might be relevant:
Bundle name for the app: com.example.Example-App
Bundle name for the Network Extension: com.example.Example-App.Example-VPN
Group name: group.com.example.Example-App
I'm out of ideas here. Could the problem be in the Network Extension never being started? It doesn't seem to be the case, because the startTunnel() function is called. The code for starting the VPN through the extension:
func configureVPN(serverAddress: String) {
guard let configData = readTestFile() else { return }
// Test file with ovpn configuration seems is read in correctly - contents are shown if printed to console.
self.providerManager?.loadFromPreferences { error in
if error == nil {
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.serverAddress = serverAddress
tunnelProtocol.providerBundleIdentifier = "com.example.Example-App.Example-VPN" // bundle id of the network extension target
tunnelProtocol.providerConfiguration = ["ovpn": configData]
self.providerManager.protocolConfiguration = tunnelProtocol
self.providerManager.localizedDescription = "Example VPN"
self.providerManager.isEnabled = true
self.providerManager.saveToPreferences(completionHandler: { (error) in
if error == nil {
self.providerManager.loadFromPreferences(completionHandler: { (error) in
do {
print("Trying to start connection now") // This line is printed to the Console
try self.providerManager.connection.startVPNTunnel() // start the VPN tunnel.
} catch let error {
print(error.localizedDescription)
}
})
}
})
}
}
}
Not sure if this is relevant information, but I am using the OpenVPNAdapter in the PacketTunnelProvider, following this tutorial:
import NetworkExtension
import OpenVPNAdapter
class PacketTunnelProvider: NEPacketTunnelProvider {
lazy var vpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
return adapter
}()
let vpnReachability = OpenVPNReachability()
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
override func startTunnel(
options: [String : NSObject]?,
completionHandler: #escaping (Error?) -> Void
) {
// There are many ways to provide OpenVPN settings to the tunnel provider. For instance,
// you can use `options` argument of `startTunnel(options:completionHandler:)` method or get
// settings from `protocolConfiguration.providerConfiguration` property of `NEPacketTunnelProvider`
// class. Also you may provide just content of a ovpn file or use key:value pairs
// that may be provided exclusively or in addition to file content.
// In our case we need providerConfiguration dictionary to retrieve content
// of the OpenVPN configuration file. Other options related to the tunnel
// provider also can be stored there.
guard
let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration
else {
fatalError()
}
guard let ovpnFileContent: Data = providerConfiguration["ovpn"] as? Data else {
fatalError()
}
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnFileContent
configuration.settings = [ :
// Additional parameters as key:value pairs may be provided here
]
// Uncomment this line if you want to keep TUN interface active during pauses or reconnections
// configuration.tunPersist = true
// Apply OpenVPN configuration
let evaluation: OpenVPNConfigurationEvaluation
do {
evaluation = try vpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
// Provide credentials if needed
if !evaluation.autologin {
// If your VPN configuration requires user credentials you can provide them by
// `protocolConfiguration.username` and `protocolConfiguration.passwordReference`
// properties. It is recommended to use persistent keychain reference to a keychain
// item containing the password.
guard let username: String = protocolConfiguration.username else {
fatalError()
}
// Retrieve a password from the keychain
let password: String = "Test"
let credentials = OpenVPNCredentials()
credentials.username = username
credentials.password = password
do {
try vpnAdapter.provide(credentials: credentials)
} catch {
completionHandler(error)
return
}
}
// Checking reachability. In some cases after switching from cellular to
// WiFi the adapter still uses cellular data. Changing reachability forces
// reconnection so the adapter will use actual connection.
vpnReachability.startTracking { [weak self] status in
guard status == .reachableViaWiFi else { return }
self?.vpnAdapter.reconnect(afterTimeInterval: 5)
}
// Establish connection and wait for .connected event
startHandler = completionHandler
vpnAdapter.connect(using: packetFlow as! OpenVPNAdapterPacketFlow)
}
override func stopTunnel(
with reason: NEProviderStopReason,
completionHandler: #escaping () -> Void
) {
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
vpnAdapter.disconnect()
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
// protocol if the tunnel is configured without errors. Otherwise send nil.
// `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
// you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
// send `self.packetFlow` to `completionHandler` callback.
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?,
completionHandler: #escaping (Error?) -> Void
) {
// In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
// send empty string to NEDNSSettings.matchDomains
networkSettings?.dnsSettings?.matchDomains = [""]
// Set the network settings for the current tunneling session.
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
}
// Process events returned by the OpenVPN library
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
handleEvent event:
OpenVPNAdapterEvent, message: String?
) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
// Handle only fatal errors
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
fatal == true else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler = startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
}
Any help with this would be hugely appreciated! Thanks.
You need to attache the bundle name and not the bundle identifier and the bundle name should not be an identifier. The PRODUCT_BUNDLE_IDENTIFIER of your network extension would be something like com.example.my-network-extension-id (it cannot be com.example.Example-App.Example-VPN, as a third period is not allowed for network extension IDs) and the PRODUCT_NAME would be something like My Network Extension, and you would then attach to My Network Extension (yes, with spaces and no extension).

Flutter iOS, native screen is displayed in the background

I'm creating a flutter plugin to make WebRTC calls using the Twilio API. On the iOS side I use CXProvider and CallKit to make/receive calls. My problem is, the native call screen UI is always launched in the background and my Flutter app stay on the front.
Here a demo video :
I really don't understand this behavior.
This is how I display the incoming notification
func reportIncomingCall(from: String, uuid: UUID) {
let callHandle = CXHandle(type: .generic, value: from)
let callUpdate = CXCallUpdate()
callUpdate.remoteHandle = callHandle
callUpdate.localizedCallerName = from
callUpdate.supportsDTMF = true
callUpdate.supportsHolding = true
callUpdate.supportsGrouping = false
callUpdate.supportsUngrouping = false
callUpdate.hasVideo = false
// this display the callInvite UI
self.callKitProvider.reportNewIncomingCall(with: uuid, update: callUpdate) { error in
if let error = error {
print("error", error as Any)
}
}
}
This is how I answer a call from the native side
func performAnswerVoiceCall(uuid: UUID, completionHandler: #escaping (Bool) -> Swift.Void) {
if let ci = self.twilioVoiceDelegate!.callInvite {
let acceptOptions: AcceptOptions = AcceptOptions(callInvite: ci) { (builder) in
builder.uuid = ci.uuid
}
let theCall = ci.accept(options: acceptOptions, delegate: self.twilioVoiceDelegate!)
self.twilioVoiceDelegate!.call = theCall
self.twilioVoiceDelegate!.callCompletionCallback = completionHandler
self.twilioVoiceDelegate!.callInvite = nil
}
}
If anyone has a suggestion, It will be a pleasure
That's is how CallKIt works. Try to receive a call using WhatsApp on iOS. You get the same behavior

Unsubscribe all subscriptions on logout #Quickblox APNS & APNS.VOIP

I am trying to Unsubscribe subscriptions APNS & APNS.VOIP on logout in iOS swift Quickblox project. It unsubscribes only one of them can anyone please guide me.
Here's my code for logout.
#objc func didTapLogout(_ sender: UIBarButtonItem) {
if QBChat.instance.isConnected == false {
SVProgressHUD.showError(withStatus: "Error")
return
}
SVProgressHUD.show(withStatus: "SA_STR_LOGOUTING".localized)
SVProgressHUD.setDefaultMaskType(.clear)
guard let identifierForVendor = UIDevice.current.identifierForVendor else {
return
}
let uuidString = identifierForVendor.uuidString
#if targetEnvironment(simulator)
disconnectUser()
#else
QBRequest.subscriptions(successBlock: { (response, subscriptions) in
if let subscriptions = subscriptions {
for subscription in subscriptions {
if let subscriptionsUIUD = subscriptions.first?.deviceUDID,
subscriptionsUIUD == uuidString,
subscription.notificationChannel == .APNS {
self.unregisterSubscription(forUniqueDeviceIdentifier: uuidString)
return
}
}
}
self.disconnectUser()
}) { response in
if response.status.rawValue == 404 {
self.disconnectUser()
}
}
#endif
}
private func unregisterSubscription(forUniqueDeviceIdentifier uuidString: String) {
QBRequest.unregisterSubscription(forUniqueDeviceIdentifier: uuidString, successBlock: { response in
self.disconnectUser()
}, errorBlock: { error in
if let error = error.error {
SVProgressHUD.showError(withStatus: error.localizedDescription)
return
}
SVProgressHUD.dismiss()
})
}
Environment details
Info Value
iOS Version 13.0
Quickblox iOS SDK version 2.17.4
QuickbloxWebRTC SDK version 2.7.4
Xcode Version e.g. Xcode 12.0
Make sure you are connected to chat when removing subscriptions and logout only when all unsubscribe completions are called. You can also simply unsubscribe from remote notifications and PushKit in the app.

iOS 12 - NSFaceIDUsageDescription all of a sudden not working

I was using NSFaceIDUsageDescription in my app and it was working. I deleted my app from my device and and re-uploaded (plugging my device into my mac and running from xcode) it and now I don't get the alert that my app would like to use FaceID, how come the alert is not appearing anymore? This is preventing me from using FaceID in my app.
class TouchIDAuth {
let context = LAContext()
func canEvaluatePolicy() -> Bool {
return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
}
func authenticateUser(completion: #escaping (NSNumber?) -> Void) {
guard canEvaluatePolicy() else {
completion(0)
return
}
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Logging in with Touch ID") { (success, evaluateError) in
if success {
DispatchQueue.main.async {
completion(nil)
}
} else {
let response: NSNumber
switch evaluateError?._code {
case Int(kLAErrorAuthenticationFailed):
response = 2
case Int(kLAErrorUserCancel):
response = 3
case Int(kLAErrorUserFallback):
response = 4
default:
response = 1
}
completion(response)
}
}
}
}
And when I do this:
let touchMe = TouchIDAuth()
print(touchMe.canEvaluatePolicy())
The print returns false.
Is this an issue with my device? Or with NSFaceIDUsageDescription?
When your device exceeds the limit of incorrect attempts it usually returns false.
Try locking your device, then unlocking with face/touch ID and it starts working again in your app.
It should also return an error code why it is failling for evaluateError
Hope this was your case and solves the issue.

GKLocalPlayer.authenticated always returns false

I'm using Game Center in my app and I'm having some problem with the GKLocalPlayer.authenticated attribute. Regardless if the authentication process is successful or not, localPlayer.authenticated always returns false. This also happens if my device is already logged in to Game Center.
I get this both on actual device (iPhone 6s) and simulator (tried several).
The only information I've found about this suggests that there is a problem with the time settings but they seem to be fine.
Is this a bug or am I doing something wrong?
private static let localPlayer = GKLocalPlayer()
static func authenticateLocalPlayer() {
localPlayer.authenticateHandler = { (viewController, error) -> Void in
if let viewController = viewController {
if let rootViewController = UIApplication.sharedApplication().keyWindow?.rootViewController {
rootViewController.presentViewController(viewController, animated: true, completion: nil)
}
} else if localPlayer.authenticated {
gameCenterEnabled = true
let defaultCenter = NSNotificationCenter.defaultCenter()
defaultCenter.postNotificationName("local_player_authenticated", object: nil)
} else {
gameCenterEnabled = false
}
if let error = error {
print(error)
}
}
}
static func isAuthenticated() -> Bool {
return localPlayer.authenticated
}
My bad, looks like I made a little mistake when translating this code from Objective-C. It's supposed to be
GKLocalPlayer.localPlayer()
not
GKLocalPlayer()

Resources