PacketTunnelProvider refactoring
- removing unnecessary dispatchQueue - removing lazy initiation for wg and ovpn - fix memory leaks
This commit is contained in:
parent
2254bfc128
commit
9be13ea465
4 changed files with 481 additions and 528 deletions
|
|
@ -2,15 +2,15 @@ import Foundation
|
||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
|
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
|
||||||
neLog(type, title: "WG: \(title)", message: "\(staticMessage)")
|
neLog(type, title: "WG: \(title)", message: "\(staticMessage)")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
|
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
|
||||||
neLog(type, title: "WG: \(title)", message: message)
|
neLog(type, title: "WG: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
|
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
|
||||||
neLog(type, title: "OVPN: \(title)", message: message)
|
neLog(type, title: "OVPN: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func neLog(_ type: OSLogType, title: String = "", message: String) {
|
public func neLog(_ type: OSLogType, title: String = "", message: String) {
|
||||||
|
|
|
||||||
|
|
@ -3,223 +3,232 @@ import NetworkExtension
|
||||||
import OpenVPNAdapter
|
import OpenVPNAdapter
|
||||||
|
|
||||||
struct OpenVPNConfig: Decodable {
|
struct OpenVPNConfig: Decodable {
|
||||||
let config: String
|
let config: String
|
||||||
let splitTunnelType: Int
|
let splitTunnelType: Int
|
||||||
let splitTunnelSites: [String]
|
let splitTunnelSites: [String]
|
||||||
|
|
||||||
var str: String {
|
var str: String {
|
||||||
"splitTunnelType: \(splitTunnelType) splitTunnelSites: \(splitTunnelSites) config: \(config)"
|
"splitTunnelType: \(splitTunnelType) splitTunnelSites: \(splitTunnelSites) config: \(config)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PacketTunnelProvider {
|
extension PacketTunnelProvider {
|
||||||
func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
|
func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
|
||||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||||
let openVPNConfigData = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
|
let openVPNConfigData = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
|
||||||
ovpnLog(.error, message: "Can't start")
|
ovpnLog(.error, message: "Can't start")
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
|
||||||
|
ovpnLog(.info, title: "config: ", message: openVPNConfig.str)
|
||||||
|
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
||||||
|
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||||
|
} catch {
|
||||||
|
ovpnLog(.error, message: "Can't parse config: \(error.localizedDescription)")
|
||||||
|
|
||||||
|
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
|
||||||
|
ovpnLog(.error, message: "Can't parse config: \(underlyingError.localizedDescription)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
|
||||||
// ovpnLog(.info, message: "providerConfiguration: \(String(decoding: openVPNConfigData, as: UTF8.self))")
|
withShadowSocks viaSS: Bool = false,
|
||||||
|
completionHandler: @escaping (Error?) -> Void) {
|
||||||
|
ovpnLog(.info, message: "Setup and launch")
|
||||||
|
|
||||||
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
|
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
||||||
ovpnLog(.info, title: "config: ", message: openVPNConfig.str)
|
|
||||||
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
|
||||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
|
||||||
} catch {
|
|
||||||
ovpnLog(.error, message: "Can't parse config: \(error.localizedDescription)")
|
|
||||||
|
|
||||||
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
|
let configuration = OpenVPNConfiguration()
|
||||||
ovpnLog(.error, message: "Can't parse config: \(underlyingError.localizedDescription)")
|
configuration.fileContent = ovpnConfiguration
|
||||||
}
|
if str.contains("cloak") {
|
||||||
|
configuration.setPTCloak()
|
||||||
|
}
|
||||||
|
|
||||||
return
|
let evaluation: OpenVPNConfigurationEvaluation?
|
||||||
}
|
do {
|
||||||
}
|
ovpnAdapter = OpenVPNAdapter()
|
||||||
|
ovpnAdapter?.delegate = self
|
||||||
|
evaluation = try ovpnAdapter?.apply(configuration: configuration)
|
||||||
|
|
||||||
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
|
} catch {
|
||||||
withShadowSocks viaSS: Bool = false,
|
completionHandler(error)
|
||||||
completionHandler: @escaping (Error?) -> Void) {
|
return
|
||||||
ovpnLog(.info, message: "Setup and launch")
|
}
|
||||||
|
|
||||||
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
if evaluation?.autologin == false {
|
||||||
|
ovpnLog(.info, message: "Implement login with user credentials")
|
||||||
|
}
|
||||||
|
|
||||||
let configuration = OpenVPNConfiguration()
|
vpnReachability.startTracking { [weak self] status in
|
||||||
configuration.fileContent = ovpnConfiguration
|
guard status == .reachableViaWiFi else { return }
|
||||||
if str.contains("cloak") {
|
self?.ovpnAdapter?.reconnect(afterTimeInterval: 5)
|
||||||
configuration.setPTCloak()
|
}
|
||||||
|
|
||||||
|
startHandler = completionHandler
|
||||||
|
ovpnAdapter?.connect(using: packetFlow)
|
||||||
}
|
}
|
||||||
|
|
||||||
let evaluation: OpenVPNConfigurationEvaluation
|
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
do {
|
guard let completionHandler = completionHandler else { return }
|
||||||
evaluation = try ovpnAdapter.apply(configuration: configuration)
|
let bytesin = ovpnAdapter?.transportStatistics.bytesIn
|
||||||
|
let bytesout = ovpnAdapter?.transportStatistics.bytesOut
|
||||||
|
|
||||||
} catch {
|
guard let bytesin, let bytesout else {
|
||||||
completionHandler(error)
|
completionHandler(nil)
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let response: [String: Any] = [
|
||||||
|
"rx_bytes": bytesin,
|
||||||
|
"tx_bytes": bytesout
|
||||||
|
]
|
||||||
|
|
||||||
|
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !evaluation.autologin {
|
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
ovpnLog(.info, message: "Implement login with user credentials")
|
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.description)")
|
||||||
|
|
||||||
|
stopHandler = completionHandler
|
||||||
|
if vpnReachability.isTracking {
|
||||||
|
vpnReachability.stopTracking()
|
||||||
|
}
|
||||||
|
ovpnAdapter?.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
vpnReachability.startTracking { [weak self] status in
|
|
||||||
guard status == .reachableViaWiFi else { return }
|
|
||||||
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
|
|
||||||
}
|
|
||||||
|
|
||||||
startHandler = completionHandler
|
|
||||||
ovpnAdapter.connect(using: packetFlow)
|
|
||||||
|
|
||||||
// let ifaces = Interface.allInterfaces()
|
|
||||||
// .filter { $0.family == .ipv4 }
|
|
||||||
// .map { iface in iface.name }
|
|
||||||
|
|
||||||
// ovpn_log(.error, message: "Available TUN Interfaces: \(ifaces)")
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
|
||||||
guard let completionHandler = completionHandler else { return }
|
|
||||||
let bytesin = ovpnAdapter.transportStatistics.bytesIn
|
|
||||||
let bytesout = ovpnAdapter.transportStatistics.bytesOut
|
|
||||||
|
|
||||||
let response: [String: Any] = [
|
|
||||||
"rx_bytes": bytesin,
|
|
||||||
"tx_bytes": bytesout
|
|
||||||
]
|
|
||||||
|
|
||||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
|
||||||
}
|
|
||||||
|
|
||||||
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
|
||||||
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.description)")
|
|
||||||
|
|
||||||
stopHandler = completionHandler
|
|
||||||
if vpnReachability.isTracking {
|
|
||||||
vpnReachability.stopTracking()
|
|
||||||
}
|
|
||||||
ovpnAdapter.disconnect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||||
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
|
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
|
||||||
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
|
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
|
||||||
// protocol if the tunnel is configured without errors. Otherwise send nil.
|
// protocol if the tunnel is configured without errors. Otherwise send nil.
|
||||||
// `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
|
// `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
|
||||||
// you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
|
// you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
|
||||||
// send `self.packetFlow` to `completionHandler` callback.
|
// send `self.packetFlow` to `completionHandler` callback.
|
||||||
func openVPNAdapter(
|
func openVPNAdapter(
|
||||||
_ openVPNAdapter: OpenVPNAdapter,
|
_ openVPNAdapter: OpenVPNAdapter,
|
||||||
configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?,
|
configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?,
|
||||||
completionHandler: @escaping (Error?) -> Void
|
completionHandler: @escaping (Error?) -> Void
|
||||||
) {
|
) {
|
||||||
// In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
|
// In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
|
||||||
// send empty string to NEDNSSettings.matchDomains
|
// send empty string to NEDNSSettings.matchDomains
|
||||||
networkSettings?.dnsSettings?.matchDomains = [""]
|
networkSettings?.dnsSettings?.matchDomains = [""]
|
||||||
|
|
||||||
if splitTunnelType == 1 {
|
if splitTunnelType == 1 {
|
||||||
var ipv4IncludedRoutes = [NEIPv4Route]()
|
var ipv4IncludedRoutes = [NEIPv4Route]()
|
||||||
|
|
||||||
for allowedIPString in splitTunnelSites {
|
guard let splitTunnelSites else {
|
||||||
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
completionHandler(NSError(domain: "Split tunnel sited not setted up", code: 0))
|
||||||
ipv4IncludedRoutes.append(NEIPv4Route(
|
return
|
||||||
destinationAddress: "\(allowedIP.address)",
|
}
|
||||||
subnetMask: "\(allowedIP.subnetMask())"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
|
for allowedIPString in splitTunnelSites {
|
||||||
} else {
|
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
||||||
if splitTunnelType == 2 {
|
ipv4IncludedRoutes.append(NEIPv4Route(
|
||||||
var ipv4ExcludedRoutes = [NEIPv4Route]()
|
destinationAddress: "\(allowedIP.address)",
|
||||||
var ipv4IncludedRoutes = [NEIPv4Route]()
|
subnetMask: "\(allowedIP.subnetMask())"))
|
||||||
var ipv6IncludedRoutes = [NEIPv6Route]()
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for excludeIPString in splitTunnelSites {
|
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
|
||||||
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
} else {
|
||||||
ipv4ExcludedRoutes.append(NEIPv4Route(
|
if splitTunnelType == 2 {
|
||||||
destinationAddress: "\(excludeIP.address)",
|
var ipv4ExcludedRoutes = [NEIPv4Route]()
|
||||||
subnetMask: "\(excludeIP.subnetMask())"))
|
var ipv4IncludedRoutes = [NEIPv4Route]()
|
||||||
}
|
var ipv6IncludedRoutes = [NEIPv6Route]()
|
||||||
|
|
||||||
|
guard let splitTunnelSites else {
|
||||||
|
completionHandler(NSError(domain: "Split tunnel sited not setted up", code: 0))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for excludeIPString in splitTunnelSites {
|
||||||
|
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
||||||
|
ipv4ExcludedRoutes.append(NEIPv4Route(
|
||||||
|
destinationAddress: "\(excludeIP.address)",
|
||||||
|
subnetMask: "\(excludeIP.subnetMask())"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let allIPv4 = IPAddressRange(from: "0.0.0.0/0") {
|
||||||
|
ipv4IncludedRoutes.append(NEIPv4Route(
|
||||||
|
destinationAddress: "\(allIPv4.address)",
|
||||||
|
subnetMask: "\(allIPv4.subnetMask())"))
|
||||||
|
}
|
||||||
|
if let allIPv6 = IPAddressRange(from: "::/0") {
|
||||||
|
ipv6IncludedRoutes.append(NEIPv6Route(
|
||||||
|
destinationAddress: "\(allIPv6.address)",
|
||||||
|
networkPrefixLength: NSNumber(value: allIPv6.networkPrefixLength)))
|
||||||
|
}
|
||||||
|
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
|
||||||
|
networkSettings?.ipv6Settings?.includedRoutes = ipv6IncludedRoutes
|
||||||
|
networkSettings?.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let allIPv4 = IPAddressRange(from: "0.0.0.0/0") {
|
// Set the network settings for the current tunneling session.
|
||||||
ipv4IncludedRoutes.append(NEIPv4Route(
|
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
|
||||||
destinationAddress: "\(allIPv4.address)",
|
|
||||||
subnetMask: "\(allIPv4.subnetMask())"))
|
|
||||||
}
|
|
||||||
if let allIPv6 = IPAddressRange(from: "::/0") {
|
|
||||||
ipv6IncludedRoutes.append(NEIPv6Route(
|
|
||||||
destinationAddress: "\(allIPv6.address)",
|
|
||||||
networkPrefixLength: NSNumber(value: allIPv6.networkPrefixLength)))
|
|
||||||
}
|
|
||||||
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
|
|
||||||
networkSettings?.ipv6Settings?.includedRoutes = ipv6IncludedRoutes
|
|
||||||
networkSettings?.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the network settings for the current tunneling session.
|
// Process events returned by the OpenVPN library
|
||||||
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
|
func openVPNAdapter(
|
||||||
}
|
_ openVPNAdapter: OpenVPNAdapter,
|
||||||
|
handleEvent event: OpenVPNAdapterEvent,
|
||||||
|
message: String?) {
|
||||||
|
switch event {
|
||||||
|
case .connected:
|
||||||
|
if reasserting {
|
||||||
|
reasserting = false
|
||||||
|
}
|
||||||
|
|
||||||
// Process events returned by the OpenVPN library
|
guard let startHandler = startHandler else { return }
|
||||||
func openVPNAdapter(
|
|
||||||
_ openVPNAdapter: OpenVPNAdapter,
|
startHandler(nil)
|
||||||
handleEvent event: OpenVPNAdapterEvent,
|
self.startHandler = nil
|
||||||
message: String?) {
|
case .disconnected:
|
||||||
switch event {
|
guard let stopHandler = stopHandler else { return }
|
||||||
case .connected:
|
|
||||||
if reasserting {
|
if vpnReachability.isTracking {
|
||||||
reasserting = false
|
vpnReachability.stopTracking()
|
||||||
|
}
|
||||||
|
|
||||||
|
stopHandler()
|
||||||
|
self.stopHandler = nil
|
||||||
|
case .reconnecting:
|
||||||
|
reasserting = true
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let startHandler = startHandler else { return }
|
// Handle errors thrown by the OpenVPN library
|
||||||
|
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
|
||||||
startHandler(nil)
|
// Handle only fatal errors
|
||||||
self.startHandler = nil
|
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
|
||||||
case .disconnected:
|
fatal == true else { return }
|
||||||
guard let stopHandler = stopHandler else { return }
|
|
||||||
|
|
||||||
if vpnReachability.isTracking {
|
if vpnReachability.isTracking {
|
||||||
vpnReachability.stopTracking()
|
vpnReachability.stopTracking()
|
||||||
}
|
}
|
||||||
|
|
||||||
stopHandler()
|
if let startHandler {
|
||||||
self.stopHandler = nil
|
startHandler(error)
|
||||||
case .reconnecting:
|
self.startHandler = nil
|
||||||
reasserting = true
|
} else {
|
||||||
default:
|
cancelTunnelWithError(error)
|
||||||
break
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle errors thrown by the OpenVPN library
|
// Use this method to process any log message returned by OpenVPN library.
|
||||||
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
|
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
|
||||||
// Handle only fatal errors
|
// Handle log messages
|
||||||
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
|
ovpnLog(.info, message: logMessage)
|
||||||
fatal == true else { return }
|
|
||||||
|
|
||||||
if vpnReachability.isTracking {
|
|
||||||
vpnReachability.stopTracking()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let startHandler {
|
|
||||||
startHandler(error)
|
|
||||||
self.startHandler = nil
|
|
||||||
} else {
|
|
||||||
cancelTunnelWithError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use this method to process any log message returned by OpenVPN library.
|
|
||||||
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
|
|
||||||
// Handle log messages
|
|
||||||
ovpnLog(.info, message: logMessage)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
|
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
|
||||||
|
|
|
||||||
|
|
@ -2,220 +2,186 @@ import Foundation
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
|
|
||||||
extension PacketTunnelProvider {
|
extension PacketTunnelProvider {
|
||||||
func startWireguard(activationAttemptId: String?,
|
func startWireguard(activationAttemptId: String?,
|
||||||
errorNotifier: ErrorNotifier,
|
errorNotifier: ErrorNotifier,
|
||||||
completionHandler: @escaping (Error?) -> Void) {
|
completionHandler: @escaping (Error?) -> Void) {
|
||||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||||
let wgConfigData: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
let wgConfigData: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
||||||
wg_log(.error, message: "Can't start, config missing")
|
wg_log(.error, message: "Can't start, config missing")
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let wgConfig = try JSONDecoder().decode(WGConfig.self, from: wgConfigData)
|
let wgConfig = try JSONDecoder().decode(WGConfig.self, from: wgConfigData)
|
||||||
let wgConfigStr = wgConfig.str
|
let wgConfigStr = wgConfig.str
|
||||||
wg_log(.info, title: "config: ", message: wgConfig.redux)
|
wg_log(.info, title: "config: ", message: wgConfig.redux)
|
||||||
|
|
||||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
|
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
|
||||||
|
|
||||||
if tunnelConfiguration.peers.first!.allowedIPs
|
if tunnelConfiguration.peers.first!.allowedIPs
|
||||||
.map({ $0.stringRepresentation })
|
.map({ $0.stringRepresentation })
|
||||||
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
|
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
|
||||||
if wgConfig.splitTunnelType == 1 {
|
if wgConfig.splitTunnelType == 1 {
|
||||||
for index in tunnelConfiguration.peers.indices {
|
for index in tunnelConfiguration.peers.indices {
|
||||||
tunnelConfiguration.peers[index].allowedIPs.removeAll()
|
tunnelConfiguration.peers[index].allowedIPs.removeAll()
|
||||||
var allowedIPs = [IPAddressRange]()
|
var allowedIPs = [IPAddressRange]()
|
||||||
|
|
||||||
for allowedIPString in wgConfig.splitTunnelSites {
|
for allowedIPString in wgConfig.splitTunnelSites {
|
||||||
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
||||||
allowedIPs.append(allowedIP)
|
allowedIPs.append(allowedIP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
|
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
|
||||||
}
|
}
|
||||||
} else if wgConfig.splitTunnelType == 2 {
|
} else if wgConfig.splitTunnelType == 2 {
|
||||||
for index in tunnelConfiguration.peers.indices {
|
for index in tunnelConfiguration.peers.indices {
|
||||||
var excludeIPs = [IPAddressRange]()
|
var excludeIPs = [IPAddressRange]()
|
||||||
|
|
||||||
for excludeIPString in wgConfig.splitTunnelSites {
|
for excludeIPString in wgConfig.splitTunnelSites {
|
||||||
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
||||||
excludeIPs.append(excludeIP)
|
excludeIPs.append(excludeIP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
|
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wg_log(.info, message: "Starting tunnel from the " +
|
wg_log(.info, message: "Starting tunnel from the " +
|
||||||
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
||||||
|
|
||||||
// Start the tunnel
|
// Start the tunnel
|
||||||
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
wgAdapter = WireGuardAdapter(with: self) { logLevel, message in
|
||||||
guard let adapterError else {
|
wg_log(logLevel.osLogLevel, message: message)
|
||||||
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
|
}
|
||||||
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
|
||||||
completionHandler(nil)
|
wgAdapter?.start(tunnelConfiguration: tunnelConfiguration) { [weak self] adapterError in
|
||||||
return
|
guard let adapterError else {
|
||||||
}
|
let interfaceName = self?.wgAdapter?.interfaceName ?? "unknown"
|
||||||
|
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
||||||
switch adapterError {
|
completionHandler(nil)
|
||||||
case .cannotLocateTunnelFileDescriptor:
|
return
|
||||||
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
|
}
|
||||||
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
|
||||||
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
switch adapterError {
|
||||||
case .dnsResolution(let dnsErrors):
|
case .cannotLocateTunnelFileDescriptor:
|
||||||
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
|
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
|
||||||
.joined(separator: ", ")
|
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||||
wg_log(.error, message:
|
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||||
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
|
case .dnsResolution(let dnsErrors):
|
||||||
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
|
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
|
||||||
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
|
.joined(separator: ", ")
|
||||||
case .setNetworkSettings(let error):
|
wg_log(.error, message:
|
||||||
wg_log(.error, message:
|
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
|
||||||
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
|
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
|
||||||
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
|
||||||
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
case .setNetworkSettings(let error):
|
||||||
case .startWireGuardBackend(let errorCode):
|
wg_log(.error, message:
|
||||||
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
|
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
|
||||||
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
|
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||||
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
|
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||||
case .invalidState:
|
case .startWireGuardBackend(let errorCode):
|
||||||
fatalError()
|
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
|
||||||
}
|
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
|
||||||
}
|
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
|
||||||
} catch {
|
case .invalidState:
|
||||||
wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
|
fatalError()
|
||||||
completionHandler(nil)
|
}
|
||||||
return
|
}
|
||||||
}
|
} catch {
|
||||||
}
|
wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
|
||||||
|
|
||||||
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
|
||||||
guard let completionHandler = completionHandler else { return }
|
|
||||||
wgAdapter.getRuntimeConfiguration { settings in
|
|
||||||
var data: Data?
|
|
||||||
if let settings {
|
|
||||||
data = settings.data(using: .utf8)!
|
|
||||||
}
|
|
||||||
|
|
||||||
let components = settings!.components(separatedBy: "\n")
|
|
||||||
|
|
||||||
var settingsDictionary: [String: String] = [:]
|
|
||||||
for component in components {
|
|
||||||
let pair = component.components(separatedBy: "=")
|
|
||||||
if pair.count == 2 {
|
|
||||||
settingsDictionary[pair[0]] = pair[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let response: [String: Any] = [
|
|
||||||
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
|
|
||||||
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
|
|
||||||
]
|
|
||||||
|
|
||||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
|
||||||
guard let completionHandler = completionHandler else { return }
|
|
||||||
if messageData.count == 1 && messageData[0] == 0 {
|
|
||||||
wgAdapter.getRuntimeConfiguration { settings in
|
|
||||||
var data: Data?
|
|
||||||
if let settings {
|
|
||||||
data = settings.data(using: .utf8)!
|
|
||||||
}
|
|
||||||
completionHandler(data)
|
|
||||||
}
|
|
||||||
} else if messageData.count >= 1 {
|
|
||||||
// Updates the tunnel configuration and responds with the active configuration
|
|
||||||
wg_log(.info, message: "Switching tunnel configuration")
|
|
||||||
guard let configString = String(data: messageData, encoding: .utf8)
|
|
||||||
else {
|
|
||||||
completionHandler(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
|
|
||||||
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
|
|
||||||
if let error {
|
|
||||||
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
|
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
self.wgAdapter.getRuntimeConfiguration { settings in
|
|
||||||
var data: Data?
|
|
||||||
if let settings {
|
|
||||||
data = settings.data(using: .utf8)!
|
|
||||||
}
|
|
||||||
completionHandler(data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
completionHandler(nil)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
completionHandler(nil)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
|
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
// dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
guard let completionHandler = completionHandler else { return }
|
||||||
//
|
wgAdapter?.getRuntimeConfiguration { settings in
|
||||||
// let emptyTunnelConfiguration = TunnelConfiguration(
|
let components = settings!.components(separatedBy: "\n")
|
||||||
// name: nil,
|
|
||||||
// interface: InterfaceConfiguration(privateKey: PrivateKey()),
|
|
||||||
// peers: []
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in
|
|
||||||
// self.dispatchQueue.async {
|
|
||||||
// if let error {
|
|
||||||
// wg_log(.error, message: "Failed to start an empty tunnel")
|
|
||||||
// completionHandler(error)
|
|
||||||
// } else {
|
|
||||||
// wg_log(.info, message: "Started an empty tunnel")
|
|
||||||
// self.tunnelAdapterDidStart()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let settings = NETunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
|
|
||||||
//
|
|
||||||
// self.setTunnelNetworkSettings(settings) { error in
|
|
||||||
// completionHandler(error)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private func tunnelAdapterDidStart() {
|
var settingsDictionary: [String: String] = [:]
|
||||||
// dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
for component in components {
|
||||||
// // ...
|
let pair = component.components(separatedBy: "=")
|
||||||
// }
|
if pair.count == 2 {
|
||||||
|
settingsDictionary[pair[0]] = pair[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
let response: [String: Any] = [
|
||||||
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)")
|
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
|
||||||
|
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
|
||||||
|
]
|
||||||
|
|
||||||
wgAdapter.stop { error in
|
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||||
ErrorNotifier.removeLastErrorFile()
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let error {
|
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
|
guard let completionHandler = completionHandler else { return }
|
||||||
}
|
if messageData.count == 1 && messageData[0] == 0 {
|
||||||
completionHandler()
|
wgAdapter?.getRuntimeConfiguration { settings in
|
||||||
|
var data: Data?
|
||||||
|
if let settings {
|
||||||
|
data = settings.data(using: .utf8)!
|
||||||
|
}
|
||||||
|
completionHandler(data)
|
||||||
|
}
|
||||||
|
} else if messageData.count >= 1 {
|
||||||
|
// Updates the tunnel configuration and responds with the active configuration
|
||||||
|
wg_log(.info, message: "Switching tunnel configuration")
|
||||||
|
guard let configString = String(data: messageData, encoding: .utf8)
|
||||||
|
else {
|
||||||
|
completionHandler(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
|
||||||
|
wgAdapter?.update(tunnelConfiguration: tunnelConfiguration) { [weak self] error in
|
||||||
|
if let error {
|
||||||
|
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
|
||||||
|
completionHandler(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self?.wgAdapter?.getRuntimeConfiguration { settings in
|
||||||
|
var data: Data?
|
||||||
|
if let settings {
|
||||||
|
data = settings.data(using: .utf8)!
|
||||||
|
}
|
||||||
|
completionHandler(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
completionHandler(nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
completionHandler(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
|
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)")
|
||||||
|
|
||||||
|
wgAdapter?.stop { error in
|
||||||
|
ErrorNotifier.removeLastErrorFile()
|
||||||
|
|
||||||
|
if let error {
|
||||||
|
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
completionHandler()
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
|
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
|
||||||
// Remove it when they finally fix this upstream and the fix has been rolled out to
|
// Remove it when they finally fix this upstream and the fix has been rolled out to
|
||||||
// sufficient quantities of users.
|
// sufficient quantities of users.
|
||||||
exit(0)
|
exit(0)
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import Darwin
|
||||||
import OpenVPNAdapter
|
import OpenVPNAdapter
|
||||||
|
|
||||||
enum TunnelProtoType: String {
|
enum TunnelProtoType: String {
|
||||||
case wireguard, openvpn, shadowsocks, none
|
case wireguard, openvpn
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Constants {
|
struct Constants {
|
||||||
|
|
@ -34,160 +34,138 @@ struct Constants {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
lazy var wgAdapter = {
|
var wgAdapter: WireGuardAdapter?
|
||||||
WireGuardAdapter(with: self) { logLevel, message in
|
var ovpnAdapter: OpenVPNAdapter?
|
||||||
wg_log(logLevel.osLogLevel, message: message)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var ovpnAdapter: OpenVPNAdapter = {
|
var splitTunnelType: Int?
|
||||||
let adapter = OpenVPNAdapter()
|
var splitTunnelSites: [String]?
|
||||||
adapter.delegate = self
|
|
||||||
return adapter
|
|
||||||
}()
|
|
||||||
|
|
||||||
/// Internal queue.
|
let vpnReachability = OpenVPNReachability()
|
||||||
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
|
|
||||||
|
|
||||||
var splitTunnelType: Int!
|
var startHandler: ((Error?) -> Void)?
|
||||||
var splitTunnelSites: [String]!
|
var stopHandler: (() -> Void)?
|
||||||
|
var protoType: TunnelProtoType?
|
||||||
let vpnReachability = OpenVPNReachability()
|
|
||||||
|
|
||||||
var startHandler: ((Error?) -> Void)?
|
|
||||||
var stopHandler: (() -> Void)?
|
|
||||||
var protoType: TunnelProtoType = .none
|
|
||||||
|
|
||||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
guard let message = String(data: messageData, encoding: .utf8) else {
|
guard let message = String(data: messageData, encoding: .utf8) else {
|
||||||
if let completionHandler {
|
if let completionHandler {
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
neLog(.info, title: "App said: ", message: message)
|
neLog(.info, title: "App said: ", message: message)
|
||||||
|
|
||||||
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
|
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
|
||||||
neLog(.error, message: "Failed to serialize message from app")
|
neLog(.error, message: "Failed to serialize message from app")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let completionHandler else {
|
guard let completionHandler else {
|
||||||
neLog(.error, message: "Missing message completion handler")
|
neLog(.error, message: "Missing message completion handler")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let action = message[Constants.kMessageKeyAction] as? String else {
|
guard let action = message[Constants.kMessageKeyAction] as? String else {
|
||||||
neLog(.error, message: "Missing action key in app message")
|
neLog(.error, message: "Missing action key in app message")
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == Constants.kActionStatus {
|
if action == Constants.kActionStatus {
|
||||||
handleStatusAppMessage(messageData, completionHandler: completionHandler)
|
handleStatusAppMessage(messageData,
|
||||||
}
|
completionHandler: completionHandler)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
override func startTunnel(options: [String : NSObject]? = nil,
|
||||||
dispatchQueue.async {
|
completionHandler: @escaping ((any Error)?) -> Void) {
|
||||||
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
|
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
|
||||||
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)
|
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)
|
||||||
|
|
||||||
neLog(.info, message: "Start tunnel")
|
neLog(.info, message: "Start tunnel")
|
||||||
|
|
||||||
if let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol {
|
if let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol {
|
||||||
let providerConfiguration = protocolConfiguration.providerConfiguration
|
let providerConfiguration = protocolConfiguration.providerConfiguration
|
||||||
if (providerConfiguration?[Constants.ovpnConfigKey] as? Data) != nil {
|
if (providerConfiguration?[Constants.ovpnConfigKey] as? Data) != nil {
|
||||||
self.protoType = .openvpn
|
protoType = .openvpn
|
||||||
} else if (providerConfiguration?[Constants.wireGuardConfigKey] as? Data) != nil {
|
} else if (providerConfiguration?[Constants.wireGuardConfigKey] as? Data) != nil {
|
||||||
self.protoType = .wireguard
|
protoType = .wireguard
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.protoType = .none
|
|
||||||
}
|
|
||||||
|
|
||||||
switch self.protoType {
|
guard let protoType else {
|
||||||
case .wireguard:
|
let error = NSError(domain: "Protocol is not selected", code: 0)
|
||||||
self.startWireguard(activationAttemptId: activationAttemptId,
|
completionHandler(error)
|
||||||
errorNotifier: errorNotifier,
|
return
|
||||||
completionHandler: completionHandler)
|
}
|
||||||
case .openvpn:
|
|
||||||
self.startOpenVPN(completionHandler: completionHandler)
|
switch protoType {
|
||||||
case .shadowsocks:
|
case .wireguard:
|
||||||
break
|
startWireguard(activationAttemptId: activationAttemptId,
|
||||||
// startShadowSocks(completionHandler: completionHandler)
|
errorNotifier: errorNotifier,
|
||||||
case .none:
|
completionHandler: completionHandler)
|
||||||
break
|
case .openvpn:
|
||||||
}
|
startOpenVPN(completionHandler: completionHandler)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
|
||||||
dispatchQueue.async {
|
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
switch self.protoType {
|
guard let protoType else {
|
||||||
case .wireguard:
|
completionHandler()
|
||||||
self.stopWireguard(with: reason, completionHandler: completionHandler)
|
return
|
||||||
case .openvpn:
|
}
|
||||||
self.stopOpenVPN(with: reason, completionHandler: completionHandler)
|
|
||||||
case .shadowsocks:
|
switch protoType {
|
||||||
break
|
case .wireguard:
|
||||||
// stopShadowSocks(with: reason, completionHandler: completionHandler)
|
stopWireguard(with: reason,
|
||||||
case .none:
|
completionHandler: completionHandler)
|
||||||
break
|
case .openvpn:
|
||||||
}
|
stopOpenVPN(with: reason,
|
||||||
|
completionHandler: completionHandler)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func handleStatusAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
func handleStatusAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
switch protoType {
|
guard let protoType else {
|
||||||
case .wireguard:
|
completionHandler?(nil)
|
||||||
handleWireguardStatusMessage(messageData, completionHandler: completionHandler)
|
return
|
||||||
case .openvpn:
|
}
|
||||||
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
|
|
||||||
case .shadowsocks:
|
switch protoType {
|
||||||
break
|
case .wireguard:
|
||||||
// handleShadowSocksAppMessage(messageData, completionHandler: completionHandler)
|
handleWireguardStatusMessage(messageData, completionHandler: completionHandler)
|
||||||
case .none:
|
case .openvpn:
|
||||||
break
|
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Network observing methods
|
// MARK: Network observing methods
|
||||||
|
override func observeValue(forKeyPath keyPath: String?,
|
||||||
private func startListeningForNetworkChanges() {
|
of object: Any?,
|
||||||
stopListeningForNetworkChanges()
|
change: [NSKeyValueChangeKey: Any]?,
|
||||||
addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil)
|
context: UnsafeMutableRawPointer?) {
|
||||||
}
|
guard Constants.kDefaultPathKey != keyPath else { return }
|
||||||
|
// Since iOS 11, we have observed that this KVO event fires repeatedly when connecting over Wifi,
|
||||||
private func stopListeningForNetworkChanges() {
|
// even though the underlying network has not changed (i.e. `isEqualToPath` returns false),
|
||||||
removeObserver(self, forKeyPath: Constants.kDefaultPathKey)
|
// leading to "wakeup crashes" due to excessive network activity. Guard against false positives by
|
||||||
}
|
// comparing the paths' string description, which includes properties not exposed by the class
|
||||||
|
guard let lastPath: NWPath = change?[.oldKey] as? NWPath,
|
||||||
override func observeValue(forKeyPath keyPath: String?,
|
let defPath = defaultPath,
|
||||||
of object: Any?,
|
lastPath != defPath || lastPath.description != defPath.description else {
|
||||||
change: [NSKeyValueChangeKey: Any]?,
|
return
|
||||||
context: UnsafeMutableRawPointer?) {
|
}
|
||||||
guard Constants.kDefaultPathKey != keyPath else { return }
|
DispatchQueue.main.async { [weak self] in
|
||||||
// Since iOS 11, we have observed that this KVO event fires repeatedly when connecting over Wifi,
|
guard let self, self.defaultPath != nil else { return }
|
||||||
// even though the underlying network has not changed (i.e. `isEqualToPath` returns false),
|
self.handle(networkChange: self.defaultPath!) { _ in }
|
||||||
// leading to "wakeup crashes" due to excessive network activity. Guard against false positives by
|
}
|
||||||
// comparing the paths' string description, which includes properties not exposed by the class
|
|
||||||
guard let lastPath: NWPath = change?[.oldKey] as? NWPath,
|
|
||||||
let defPath = defaultPath,
|
|
||||||
lastPath != defPath || lastPath.description != defPath.description else {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
DispatchQueue.main.async { [weak self] in
|
|
||||||
guard let self, self.defaultPath != nil else { return }
|
|
||||||
self.handle(networkChange: self.defaultPath!) { _ in }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
|
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
|
||||||
wg_log(.info, message: "Tunnel restarted.")
|
wg_log(.info, message: "Tunnel restarted.")
|
||||||
startTunnel(options: nil, completionHandler: completion)
|
startTunnel(options: nil, completionHandler: completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension WireGuardLogLevel {
|
extension WireGuardLogLevel {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue