134 lines
5.5 KiB
Swift
134 lines
5.5 KiB
Swift
// SPDX-License-Identifier: MIT
|
|
// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved.
|
|
|
|
import NetworkExtension
|
|
|
|
enum ActivateOnDemandOption: Equatable {
|
|
case off
|
|
case wiFiInterfaceOnly(ActivateOnDemandSSIDOption)
|
|
case nonWiFiInterfaceOnly
|
|
case anyInterface(ActivateOnDemandSSIDOption)
|
|
}
|
|
|
|
#if os(iOS)
|
|
private let nonWiFiInterfaceType: NEOnDemandRuleInterfaceType = .cellular
|
|
#elseif os(macOS)
|
|
private let nonWiFiInterfaceType: NEOnDemandRuleInterfaceType = .ethernet
|
|
#else
|
|
#error("Unimplemented")
|
|
#endif
|
|
|
|
enum ActivateOnDemandSSIDOption: Equatable {
|
|
case anySSID
|
|
case onlySpecificSSIDs([String])
|
|
case exceptSpecificSSIDs([String])
|
|
}
|
|
|
|
extension ActivateOnDemandOption {
|
|
func apply(on tunnelProviderManager: NETunnelProviderManager) {
|
|
let rules: [NEOnDemandRule]?
|
|
switch self {
|
|
case .off:
|
|
rules = nil
|
|
case .wiFiInterfaceOnly(let ssidOption):
|
|
rules = ssidOnDemandRules(option: ssidOption) + [NEOnDemandRuleDisconnect(interfaceType: nonWiFiInterfaceType)]
|
|
case .nonWiFiInterfaceOnly:
|
|
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)]
|
|
}
|
|
}
|
|
tunnelProviderManager.onDemandRules = rules
|
|
tunnelProviderManager.isOnDemandEnabled = self != .off
|
|
}
|
|
|
|
init(from tunnelProviderManager: NETunnelProviderManager) {
|
|
if tunnelProviderManager.isOnDemandEnabled, let onDemandRules = tunnelProviderManager.onDemandRules {
|
|
self = ActivateOnDemandOption.create(from: onDemandRules)
|
|
} else {
|
|
self = .off
|
|
}
|
|
}
|
|
|
|
private static func create(from rules: [NEOnDemandRule]) -> ActivateOnDemandOption {
|
|
switch rules.count {
|
|
case 0:
|
|
return .off
|
|
case 1:
|
|
let rule = rules[0]
|
|
guard rule.action == .connect else { return .off }
|
|
return .anyInterface(.anySSID)
|
|
case 2:
|
|
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
|
|
}
|
|
if connectRule.interfaceTypeMatch == .wiFi && disconnectRule.interfaceTypeMatch == nonWiFiInterfaceType {
|
|
return .wiFiInterfaceOnly(.anySSID)
|
|
} else if connectRule.interfaceTypeMatch == nonWiFiInterfaceType && disconnectRule.interfaceTypeMatch == .wiFi {
|
|
return .nonWiFiInterfaceOnly
|
|
} else {
|
|
wg_log(.error, message: "Unexpected onDemandRules set on tunnel provider manager: \(rules.count) rules found but interface types are inconsistent.")
|
|
return .off
|
|
}
|
|
case 3:
|
|
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 }
|
|
let ssids = ssidRule.ssidMatch!
|
|
switch (ssidRule.action, nonWiFiRule.action) {
|
|
case (.connect, .connect):
|
|
return .anyInterface(.onlySpecificSSIDs(ssids))
|
|
case (.connect, .disconnect):
|
|
return .wiFiInterfaceOnly(.onlySpecificSSIDs(ssids))
|
|
case (.disconnect, .connect):
|
|
return .anyInterface(.exceptSpecificSSIDs(ssids))
|
|
case (.disconnect, .disconnect):
|
|
return .wiFiInterfaceOnly(.exceptSpecificSSIDs(ssids))
|
|
default:
|
|
wg_log(.error, message: "Unexpected onDemandRules set on tunnel provider manager: \(rules.count) rules found")
|
|
return .off
|
|
}
|
|
default:
|
|
wg_log(.error, message: "Unexpected number of onDemandRules set on tunnel provider manager: \(rules.count) rules found")
|
|
return .off
|
|
}
|
|
}
|
|
}
|
|
|
|
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)]
|
|
}
|
|
}
|