PacketTunnelProvider refactoring

- removing unnecessary dispatchQueue
- removing lazy initiation for wg and ovpn
- fix memory leaks
This commit is contained in:
Boris Verbitskii 2024-05-17 18:17:08 +07:00
parent 2254bfc128
commit 9be13ea465
4 changed files with 481 additions and 528 deletions

View file

@ -22,8 +22,6 @@ extension PacketTunnelProvider {
} }
do { do {
// ovpnLog(.info, message: "providerConfiguration: \(String(decoding: openVPNConfigData, as: UTF8.self))")
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData) let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
ovpnLog(.info, title: "config: ", message: openVPNConfig.str) ovpnLog(.info, title: "config: ", message: openVPNConfig.str)
let ovpnConfiguration = Data(openVPNConfig.config.utf8) let ovpnConfiguration = Data(openVPNConfig.config.utf8)
@ -52,38 +50,39 @@ extension PacketTunnelProvider {
configuration.setPTCloak() configuration.setPTCloak()
} }
let evaluation: OpenVPNConfigurationEvaluation let evaluation: OpenVPNConfigurationEvaluation?
do { do {
evaluation = try ovpnAdapter.apply(configuration: configuration) ovpnAdapter = OpenVPNAdapter()
ovpnAdapter?.delegate = self
evaluation = try ovpnAdapter?.apply(configuration: configuration)
} catch { } catch {
completionHandler(error) completionHandler(error)
return return
} }
if !evaluation.autologin { if evaluation?.autologin == false {
ovpnLog(.info, message: "Implement login with user credentials") ovpnLog(.info, message: "Implement login with user credentials")
} }
vpnReachability.startTracking { [weak self] status in vpnReachability.startTracking { [weak self] status in
guard status == .reachableViaWiFi else { return } guard status == .reachableViaWiFi else { return }
self?.ovpnAdapter.reconnect(afterTimeInterval: 5) self?.ovpnAdapter?.reconnect(afterTimeInterval: 5)
} }
startHandler = completionHandler startHandler = completionHandler
ovpnAdapter.connect(using: packetFlow) 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) { func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return } guard let completionHandler = completionHandler else { return }
let bytesin = ovpnAdapter.transportStatistics.bytesIn let bytesin = ovpnAdapter?.transportStatistics.bytesIn
let bytesout = ovpnAdapter.transportStatistics.bytesOut let bytesout = ovpnAdapter?.transportStatistics.bytesOut
guard let bytesin, let bytesout else {
completionHandler(nil)
return
}
let response: [String: Any] = [ let response: [String: Any] = [
"rx_bytes": bytesin, "rx_bytes": bytesin,
@ -100,7 +99,7 @@ extension PacketTunnelProvider {
if vpnReachability.isTracking { if vpnReachability.isTracking {
vpnReachability.stopTracking() vpnReachability.stopTracking()
} }
ovpnAdapter.disconnect() ovpnAdapter?.disconnect()
} }
} }
@ -123,6 +122,11 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
if splitTunnelType == 1 { if splitTunnelType == 1 {
var ipv4IncludedRoutes = [NEIPv4Route]() var ipv4IncludedRoutes = [NEIPv4Route]()
guard let splitTunnelSites else {
completionHandler(NSError(domain: "Split tunnel sited not setted up", code: 0))
return
}
for allowedIPString in splitTunnelSites { for allowedIPString in splitTunnelSites {
if let allowedIP = IPAddressRange(from: allowedIPString) { if let allowedIP = IPAddressRange(from: allowedIPString) {
ipv4IncludedRoutes.append(NEIPv4Route( ipv4IncludedRoutes.append(NEIPv4Route(
@ -138,6 +142,11 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
var ipv4IncludedRoutes = [NEIPv4Route]() var ipv4IncludedRoutes = [NEIPv4Route]()
var ipv6IncludedRoutes = [NEIPv6Route]() var ipv6IncludedRoutes = [NEIPv6Route]()
guard let splitTunnelSites else {
completionHandler(NSError(domain: "Split tunnel sited not setted up", code: 0))
return
}
for excludeIPString in splitTunnelSites { for excludeIPString in splitTunnelSites {
if let excludeIP = IPAddressRange(from: excludeIPString) { if let excludeIP = IPAddressRange(from: excludeIPString) {
ipv4ExcludedRoutes.append(NEIPv4Route( ipv4ExcludedRoutes.append(NEIPv4Route(

View file

@ -55,9 +55,13 @@ extension PacketTunnelProvider {
(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
wg_log(logLevel.osLogLevel, message: message)
}
wgAdapter?.start(tunnelConfiguration: tunnelConfiguration) { [weak self] adapterError in
guard let adapterError else { guard let adapterError else {
let interfaceName = self.wgAdapter.interfaceName ?? "unknown" let interfaceName = self?.wgAdapter?.interfaceName ?? "unknown"
wg_log(.info, message: "Tunnel interface is \(interfaceName)") wg_log(.info, message: "Tunnel interface is \(interfaceName)")
completionHandler(nil) completionHandler(nil)
return return
@ -97,12 +101,7 @@ extension PacketTunnelProvider {
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return } guard let completionHandler = completionHandler else { return }
wgAdapter.getRuntimeConfiguration { settings in wgAdapter?.getRuntimeConfiguration { settings in
var data: Data?
if let settings {
data = settings.data(using: .utf8)!
}
let components = settings!.components(separatedBy: "\n") let components = settings!.components(separatedBy: "\n")
var settingsDictionary: [String: String] = [:] var settingsDictionary: [String: String] = [:]
@ -125,7 +124,7 @@ extension PacketTunnelProvider {
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return } guard let completionHandler = completionHandler else { return }
if messageData.count == 1 && messageData[0] == 0 { if messageData.count == 1 && messageData[0] == 0 {
wgAdapter.getRuntimeConfiguration { settings in wgAdapter?.getRuntimeConfiguration { settings in
var data: Data? var data: Data?
if let settings { if let settings {
data = settings.data(using: .utf8)! data = settings.data(using: .utf8)!
@ -143,14 +142,14 @@ extension PacketTunnelProvider {
do { do {
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString) let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in wgAdapter?.update(tunnelConfiguration: tunnelConfiguration) { [weak self] error in
if let error { if let error {
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)") wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
completionHandler(nil) completionHandler(nil)
return return
} }
self.wgAdapter.getRuntimeConfiguration { settings in self?.wgAdapter?.getRuntimeConfiguration { settings in
var data: Data? var data: Data?
if let settings { if let settings {
data = settings.data(using: .utf8)! data = settings.data(using: .utf8)!
@ -166,43 +165,10 @@ extension PacketTunnelProvider {
} }
} }
// private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
// dispatchPrecondition(condition: .onQueue(dispatchQueue))
//
// let emptyTunnelConfiguration = TunnelConfiguration(
// 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() {
// dispatchPrecondition(condition: .onQueue(dispatchQueue))
// // ...
// }
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)") wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)")
wgAdapter.stop { error in wgAdapter?.stop { error in
ErrorNotifier.removeLastErrorFile() ErrorNotifier.removeLastErrorFile()
if let error { if let error {

View file

@ -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,29 +34,17 @@ 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.
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
var splitTunnelType: Int!
var splitTunnelSites: [String]!
let vpnReachability = OpenVPNReachability() let vpnReachability = OpenVPNReachability()
var startHandler: ((Error?) -> Void)? var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)? var stopHandler: (() -> Void)?
var protoType: TunnelProtoType = .none var protoType: TunnelProtoType?
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 {
@ -85,85 +73,75 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
} }
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 {
let error = NSError(domain: "Protocol is not selected", code: 0)
completionHandler(error)
return
}
switch protoType {
case .wireguard: case .wireguard:
self.startWireguard(activationAttemptId: activationAttemptId, startWireguard(activationAttemptId: activationAttemptId,
errorNotifier: errorNotifier, errorNotifier: errorNotifier,
completionHandler: completionHandler) completionHandler: completionHandler)
case .openvpn: case .openvpn:
self.startOpenVPN(completionHandler: completionHandler) startOpenVPN(completionHandler: completionHandler)
case .shadowsocks:
break
// startShadowSocks(completionHandler: completionHandler)
case .none:
break
}
} }
} }
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
dispatchQueue.async { guard let protoType else {
switch self.protoType { completionHandler()
case .wireguard: return
self.stopWireguard(with: reason, completionHandler: completionHandler)
case .openvpn:
self.stopOpenVPN(with: reason, completionHandler: completionHandler)
case .shadowsocks:
break
// stopShadowSocks(with: reason, completionHandler: completionHandler)
case .none:
break
} }
switch protoType {
case .wireguard:
stopWireguard(with: reason,
completionHandler: completionHandler)
case .openvpn:
stopOpenVPN(with: reason,
completionHandler: completionHandler)
} }
} }
func handleStatusAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { func handleStatusAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let protoType else {
completionHandler?(nil)
return
}
switch protoType { switch protoType {
case .wireguard: case .wireguard:
handleWireguardStatusMessage(messageData, completionHandler: completionHandler) handleWireguardStatusMessage(messageData, completionHandler: completionHandler)
case .openvpn: case .openvpn:
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler) handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
case .shadowsocks:
break
// handleShadowSocksAppMessage(messageData, completionHandler: completionHandler)
case .none:
break
} }
} }
// MARK: Network observing methods // MARK: Network observing methods
private func startListeningForNetworkChanges() {
stopListeningForNetworkChanges()
addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil)
}
private func stopListeningForNetworkChanges() {
removeObserver(self, forKeyPath: Constants.kDefaultPathKey)
}
override func observeValue(forKeyPath keyPath: String?, override func observeValue(forKeyPath keyPath: String?,
of object: Any?, of object: Any?,
change: [NSKeyValueChangeKey: Any]?, change: [NSKeyValueChangeKey: Any]?,