2018-11-12 08:32:09 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2023-02-14 15:10:32 +00:00
|
|
|
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
2018-11-12 08:32:09 +00:00
|
|
|
|
|
|
|
import NetworkExtension
|
|
|
|
|
2019-02-23 08:26:51 +00:00
|
|
|
enum ActivateOnDemandOption: Equatable {
|
2019-03-08 10:42:54 +00:00
|
|
|
case off
|
2019-02-23 08:26:51 +00:00
|
|
|
case wiFiInterfaceOnly(ActivateOnDemandSSIDOption)
|
2019-02-23 08:54:30 +00:00
|
|
|
case nonWiFiInterfaceOnly
|
2019-02-23 08:26:51 +00:00
|
|
|
case anyInterface(ActivateOnDemandSSIDOption)
|
2018-11-12 08:32:09 +00:00
|
|
|
}
|
|
|
|
|
2019-02-23 08:54:30 +00:00
|
|
|
#if os(iOS)
|
|
|
|
private let nonWiFiInterfaceType: NEOnDemandRuleInterfaceType = .cellular
|
|
|
|
#elseif os(macOS)
|
|
|
|
private let nonWiFiInterfaceType: NEOnDemandRuleInterfaceType = .ethernet
|
|
|
|
#else
|
|
|
|
#error("Unimplemented")
|
|
|
|
#endif
|
|
|
|
|
2019-02-23 08:26:51 +00:00
|
|
|
enum ActivateOnDemandSSIDOption: Equatable {
|
|
|
|
case anySSID
|
|
|
|
case onlySpecificSSIDs([String])
|
|
|
|
case exceptSpecificSSIDs([String])
|
|
|
|
}
|
|
|
|
|
2019-03-08 10:42:54 +00:00
|
|
|
extension ActivateOnDemandOption {
|
2018-11-12 08:32:09 +00:00
|
|
|
func apply(on tunnelProviderManager: NETunnelProviderManager) {
|
|
|
|
let rules: [NEOnDemandRule]?
|
2019-03-08 10:42:54 +00:00
|
|
|
switch self {
|
|
|
|
case .off:
|
2018-11-12 08:32:09 +00:00
|
|
|
rules = nil
|
2019-02-23 08:26:51 +00:00
|
|
|
case .wiFiInterfaceOnly(let ssidOption):
|
|
|
|
rules = ssidOnDemandRules(option: ssidOption) + [NEOnDemandRuleDisconnect(interfaceType: nonWiFiInterfaceType)]
|
2019-02-23 08:54:30 +00:00
|
|
|
case .nonWiFiInterfaceOnly:
|
2019-02-23 08:26:51 +00:00
|
|
|
rules = [NEOnDemandRuleConnect(interfaceType: nonWiFiInterfaceType), NEOnDemandRuleDisconnect(interfaceType: .wiFi)]
|
|
|
|
case .anyInterface(let ssidOption):
|
|
|
|
if case .anySSID = ssidOption {
|
|
|
|
rules = [NEOnDemandRuleConnect(interfaceType: .any)]
|
|
|
|
} else {
|
|
|
|
rules = ssidOnDemandRules(option: ssidOption) + [NEOnDemandRuleConnect(interfaceType: nonWiFiInterfaceType)]
|
|
|
|
}
|
2018-11-12 08:32:09 +00:00
|
|
|
}
|
|
|
|
tunnelProviderManager.onDemandRules = rules
|
2021-09-27 10:37:20 +00:00
|
|
|
tunnelProviderManager.isOnDemandEnabled = (rules != nil) && tunnelProviderManager.isOnDemandEnabled
|
2018-11-12 08:32:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
init(from tunnelProviderManager: NETunnelProviderManager) {
|
2021-07-27 20:40:45 +00:00
|
|
|
if let onDemandRules = tunnelProviderManager.onDemandRules {
|
2019-06-09 11:07:20 +00:00
|
|
|
self = ActivateOnDemandOption.create(from: onDemandRules)
|
|
|
|
} else {
|
|
|
|
self = .off
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static func create(from rules: [NEOnDemandRule]) -> ActivateOnDemandOption {
|
2018-12-12 18:28:27 +00:00
|
|
|
switch rules.count {
|
2018-11-12 08:32:09 +00:00
|
|
|
case 0:
|
2019-06-09 11:07:20 +00:00
|
|
|
return .off
|
2018-11-12 08:32:09 +00:00
|
|
|
case 1:
|
|
|
|
let rule = rules[0]
|
2019-06-09 11:07:20 +00:00
|
|
|
guard rule.action == .connect else { return .off }
|
|
|
|
return .anyInterface(.anySSID)
|
2018-11-12 08:32:09 +00:00
|
|
|
case 2:
|
2019-06-09 11:07:20 +00:00
|
|
|
guard let connectRule = rules.first(where: { $0.action == .connect }) else {
|
|
|
|
wg_log(.error, message: "Unexpected onDemandRules set on tunnel provider manager: \(rules.count) rules found but no connect rule.")
|
|
|
|
return .off
|
|
|
|
}
|
|
|
|
guard let disconnectRule = rules.first(where: { $0.action == .disconnect }) else {
|
|
|
|
wg_log(.error, message: "Unexpected onDemandRules set on tunnel provider manager: \(rules.count) rules found but no disconnect rule.")
|
|
|
|
return .off
|
|
|
|
}
|
2019-02-23 08:54:30 +00:00
|
|
|
if connectRule.interfaceTypeMatch == .wiFi && disconnectRule.interfaceTypeMatch == nonWiFiInterfaceType {
|
2019-06-09 11:07:20 +00:00
|
|
|
return .wiFiInterfaceOnly(.anySSID)
|
2019-02-23 08:54:30 +00:00
|
|
|
} else if connectRule.interfaceTypeMatch == nonWiFiInterfaceType && disconnectRule.interfaceTypeMatch == .wiFi {
|
2019-06-09 11:07:20 +00:00
|
|
|
return .nonWiFiInterfaceOnly
|
2018-11-12 08:32:09 +00:00
|
|
|
} else {
|
2019-06-09 11:07:20 +00:00
|
|
|
wg_log(.error, message: "Unexpected onDemandRules set on tunnel provider manager: \(rules.count) rules found but interface types are inconsistent.")
|
|
|
|
return .off
|
2018-11-12 08:32:09 +00:00
|
|
|
}
|
2019-02-23 08:26:51 +00:00
|
|
|
case 3:
|
2019-06-09 11:07:20 +00:00
|
|
|
guard let ssidRule = rules.first(where: { $0.interfaceTypeMatch == .wiFi && $0.ssidMatch != nil }) else { return .off }
|
|
|
|
guard let nonWiFiRule = rules.first(where: { $0.interfaceTypeMatch == nonWiFiInterfaceType }) else { return .off }
|
2019-02-23 08:26:51 +00:00
|
|
|
let ssids = ssidRule.ssidMatch!
|
|
|
|
switch (ssidRule.action, nonWiFiRule.action) {
|
|
|
|
case (.connect, .connect):
|
2019-06-09 11:07:20 +00:00
|
|
|
return .anyInterface(.onlySpecificSSIDs(ssids))
|
2019-02-23 08:26:51 +00:00
|
|
|
case (.connect, .disconnect):
|
2019-06-09 11:07:20 +00:00
|
|
|
return .wiFiInterfaceOnly(.onlySpecificSSIDs(ssids))
|
2019-02-23 08:26:51 +00:00
|
|
|
case (.disconnect, .connect):
|
2019-06-09 11:07:20 +00:00
|
|
|
return .anyInterface(.exceptSpecificSSIDs(ssids))
|
2019-02-23 08:26:51 +00:00
|
|
|
case (.disconnect, .disconnect):
|
2019-06-09 11:07:20 +00:00
|
|
|
return .wiFiInterfaceOnly(.exceptSpecificSSIDs(ssids))
|
2019-02-23 08:26:51 +00:00
|
|
|
default:
|
2019-06-09 11:07:20 +00:00
|
|
|
wg_log(.error, message: "Unexpected onDemandRules set on tunnel provider manager: \(rules.count) rules found")
|
|
|
|
return .off
|
2019-02-23 08:26:51 +00:00
|
|
|
}
|
2018-11-12 08:32:09 +00:00
|
|
|
default:
|
2019-06-09 11:07:20 +00:00
|
|
|
wg_log(.error, message: "Unexpected number of onDemandRules set on tunnel provider manager: \(rules.count) rules found")
|
|
|
|
return .off
|
2018-11-12 08:32:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-23 08:26:51 +00:00
|
|
|
private extension NEOnDemandRuleConnect {
|
|
|
|
convenience init(interfaceType: NEOnDemandRuleInterfaceType, ssids: [String]? = nil) {
|
|
|
|
self.init()
|
|
|
|
interfaceTypeMatch = interfaceType
|
|
|
|
ssidMatch = ssids
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private extension NEOnDemandRuleDisconnect {
|
|
|
|
convenience init(interfaceType: NEOnDemandRuleInterfaceType, ssids: [String]? = nil) {
|
|
|
|
self.init()
|
|
|
|
interfaceTypeMatch = interfaceType
|
|
|
|
ssidMatch = ssids
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func ssidOnDemandRules(option: ActivateOnDemandSSIDOption) -> [NEOnDemandRule] {
|
|
|
|
switch option {
|
|
|
|
case .anySSID:
|
|
|
|
return [NEOnDemandRuleConnect(interfaceType: .wiFi)]
|
|
|
|
case .onlySpecificSSIDs(let ssids):
|
|
|
|
assert(!ssids.isEmpty)
|
|
|
|
return [NEOnDemandRuleConnect(interfaceType: .wiFi, ssids: ssids),
|
|
|
|
NEOnDemandRuleDisconnect(interfaceType: .wiFi)]
|
|
|
|
case .exceptSpecificSSIDs(let ssids):
|
|
|
|
return [NEOnDemandRuleDisconnect(interfaceType: .wiFi, ssids: ssids),
|
|
|
|
NEOnDemandRuleConnect(interfaceType: .wiFi)]
|
|
|
|
}
|
|
|
|
}
|