Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cordova/apple/macos): create new NETunnelProviderManager instances when moving to Catalyst #1822

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2024 The Outline Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation
import NetworkExtension

public enum TunnelProviderKeys {
static let keyVersion = "version"
}

public extension NETunnelProviderManager {
// Checks if the configuration has gone stale, which means clients should discard it.
var isStale: Bool {
#if targetEnvironment(macCatalyst)
// When migrating from macOS to Mac Catalyst, we can't use managers created by the macOS app.
// Instead, we need to create a new one. We track such "stale" managers by a version on the
// provider configuration.
if let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol {
var providerConfig: [String: Any] = protocolConfiguration.providerConfiguration ?? [:]
let version = providerConfig[TunnelProviderKeys.keyVersion, default: 0] as! Int
return version != 1
}
return true
#else
return false
#endif
}

Check warning on line 38 in src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/NETunnelProviderManager+Outline.swift

View check run for this annotation

Codecov / codecov/patch

src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/NETunnelProviderManager+Outline.swift#L24-L38

Added lines #L24 - L38 were not covered by tests

var autoConnect: Bool {
get {
let hasOnDemandRules = !(self.onDemandRules?.isEmpty ?? true)
return self.isEnabled && hasOnDemandRules
}
set {
if newValue {
let connectRule = NEOnDemandRuleConnect()
connectRule.interfaceTypeMatch = .any
self.onDemandRules = [connectRule]
} else {
self.onDemandRules = nil
}
self.isEnabled = newValue
}

Check warning on line 54 in src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/NETunnelProviderManager+Outline.swift

View check run for this annotation

Codecov / codecov/patch

src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/NETunnelProviderManager+Outline.swift#L41-L54

Added lines #L41 - L54 were not covered by tests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
public class OutlineVpn: NSObject {
public static let shared = OutlineVpn()
private static let kVpnExtensionBundleId = "\(Bundle.main.bundleIdentifier!).VpnExtension"
private static let kVpnServerAddress = "Outline"

public typealias Callback = (ErrorCode) -> Void
public typealias VpnStatusObserver = (NEVPNStatus, String) -> Void
Expand Down Expand Up @@ -124,9 +125,9 @@
// MARK: Helpers

private func startVpn(_ tunnelId: String?, configJson: [String: Any]?, isAutoConnect: Bool, _ completion: @escaping(Callback)) {
setupVpn() { error in
if error != nil {
DDLogError("Failed to setup VPN: \(String(describing: error))")
setupVpn() { success in
guard success else {
DDLogError("Failed to setup VPN")

Check warning on line 130 in src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift

View check run for this annotation

Codecov / codecov/patch

src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift#L128-L130

Added lines #L128 - L130 were not covered by tests
return completion(ErrorCode.vpnPermissionNotGranted);
}
let message = [MessageKey.action: Action.start, MessageKey.tunnelId: tunnelId ?? ""];
Expand Down Expand Up @@ -174,45 +175,30 @@

// Adds a VPN configuration to the user preferences if no Outline profile is present. Otherwise
// enables the existing configuration.
private func setupVpn(completion: @escaping(Error?) -> Void) {
NETunnelProviderManager.loadAllFromPreferences() { (managers, error) in
if let error = error {
DDLogError("Failed to load VPN configuration: \(error)")
return completion(error)
private func setupVpn(completion: @escaping(Bool) -> Void) {
getOrCreateTunnelManager() { manager in
guard let manager else {
DDLogError("Failed to setup tunnel manager")
return completion(false)

Check warning on line 182 in src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift

View check run for this annotation

Codecov / codecov/patch

src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift#L178-L182

Added lines #L178 - L182 were not covered by tests
}
var manager: NETunnelProviderManager!
if let managers = managers, managers.count > 0 {
manager = managers.first
let hasOnDemandRules = !(manager.onDemandRules?.isEmpty ?? true)
if manager.isEnabled && hasOnDemandRules {
self.tunnelManager = manager
return completion(nil)
}
} else {
let config = NETunnelProviderProtocol()
config.providerBundleIdentifier = OutlineVpn.kVpnExtensionBundleId
config.serverAddress = "Outline"

manager = NETunnelProviderManager()
manager.protocolConfiguration = config
guard manager.autoConnect else {
manager.autoConnect = true
return self.saveTunnelManager(manager, completion)

Check warning on line 187 in src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift

View check run for this annotation

Codecov / codecov/patch

src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift#L185-L187

Added lines #L185 - L187 were not covered by tests
}
// Set an on-demand rule to connect to any available network to implement auto-connect on boot
let connectRule = NEOnDemandRuleConnect()
connectRule.interfaceTypeMatch = .any
manager.onDemandRules = [connectRule]
manager.isEnabled = true
manager.saveToPreferences() { error in
if let error = error {
DDLogError("Failed to save VPN configuration: \(error)")
return completion(error)
}
self.observeVpnStatusChange(manager!)
self.tunnelManager = manager
NotificationCenter.default.post(name: .NEVPNConfigurationChange, object: nil)
// Workaround for https://forums.developer.apple.com/thread/25928
self.tunnelManager?.loadFromPreferences() { error in
completion(error)
}

self.tunnelManager = manager
return completion(true)
}
}

Check warning on line 193 in src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift

View check run for this annotation

Codecov / codecov/patch

src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift#L189-L193

Added lines #L189 - L193 were not covered by tests

private func getOrCreateTunnelManager(_ completion: @escaping ((NETunnelProviderManager?) -> Void)) {
getTunnelManager() { manager in
if let manager = manager {
return completion(manager)
}
self.createTunnelManager() { newManager in
return completion(newManager)

Check warning on line 201 in src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift

View check run for this annotation

Codecov / codecov/patch

src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift#L195-L201

Added lines #L195 - L201 were not covered by tests
}
}
}
Expand All @@ -226,18 +212,70 @@
}
}

// Creates the application's tunnel provider manager and saves it in the VPN preferences.
private func createTunnelManager(_ completion: @escaping ((NETunnelProviderManager?) -> Void)) {
let config = NETunnelProviderProtocol()
config.providerBundleIdentifier = OutlineVpn.kVpnExtensionBundleId
config.serverAddress = OutlineVpn.kVpnServerAddress
config.providerConfiguration = [TunnelProviderKeys.keyVersion: 1]

let manager = NETunnelProviderManager()
manager.protocolConfiguration = config
manager.autoConnect = true

self.saveTunnelManager(manager) { success in
guard success else {
DDLogError("Failed to create new tunnel manager")
return completion(nil)
}
DDLogInfo("Created new tunnel manager")
return completion(manager)
}
}

Check warning on line 234 in src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift

View check run for this annotation

Codecov / codecov/patch

src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift#L216-L234

Added lines #L216 - L234 were not covered by tests

// Retrieves the application's tunnel provider manager from the VPN preferences.
private func getTunnelManager(_ completion: @escaping ((NETunnelProviderManager?) -> Void)) {
NETunnelProviderManager.loadAllFromPreferences() { (managers, error) in
guard error == nil, managers != nil else {
completion(nil)
return DDLogError("Failed to get tunnel manager: \(String(describing: error))")
}
var manager: NETunnelProviderManager?
if managers!.count > 0 {
manager = managers!.first

DDLogInfo("Loaded \(managers!.count) tunnel managers")
guard managers!.count > 0 else {
return completion(nil)
}
let manager: NETunnelProviderManager = managers!.first!
if manager.isStale {
DDLogInfo("Removing stale tunnel manager")
manager.removeFromPreferences() { _ in
return completion(nil)
}
} else {
return completion(manager)
}
}
}

Check warning on line 258 in src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift

View check run for this annotation

Codecov / codecov/patch

src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift#L243-L258

Added lines #L243 - L258 were not covered by tests

// Updates the application's tunnel provider manager in the VPN preferences.
private func saveTunnelManager(_ manager: NETunnelProviderManager, _ completion: @escaping ((Bool) -> Void)) {
manager.saveToPreferences() { error in
guard error == nil else {
DDLogError("Failed to save VPN configuration: \(error)")
return completion(false)
}
self.tunnelManager = manager
self.observeVpnStatusChange(self.tunnelManager!)
NotificationCenter.default.post(name: .NEVPNConfigurationChange, object: nil)
// Workaround for https://forums.developer.apple.com/thread/25928
self.tunnelManager?.loadFromPreferences() { error in
if let error = error {
DDLogError("Failed to get tunnel manager: \(error)")
return completion(false)
}
DDLogInfo("Saved VPN configuration")
return completion(true)

Check warning on line 277 in src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift

View check run for this annotation

Codecov / codecov/patch

src/cordova/apple/OutlineAppleLib/Sources/OutlineTunnel/OutlineVpn.swift#L261-L277

Added lines #L261 - L277 were not covered by tests
}
completion(manager)
}
}

Expand Down
Loading