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

View file

@ -55,9 +55,13 @@ extension PacketTunnelProvider {
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
// 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 {
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
let interfaceName = self?.wgAdapter?.interfaceName ?? "unknown"
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
completionHandler(nil)
return
@ -97,12 +101,7 @@ extension PacketTunnelProvider {
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)!
}
wgAdapter?.getRuntimeConfiguration { settings in
let components = settings!.components(separatedBy: "\n")
var settingsDictionary: [String: String] = [:]
@ -125,7 +124,7 @@ extension PacketTunnelProvider {
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
wgAdapter?.getRuntimeConfiguration { settings in
var data: Data?
if let settings {
data = settings.data(using: .utf8)!
@ -143,14 +142,14 @@ extension PacketTunnelProvider {
do {
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
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
self?.wgAdapter?.getRuntimeConfiguration { settings in
var data: Data?
if let settings {
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) {
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)")
wgAdapter.stop { error in
wgAdapter?.stop { error in
ErrorNotifier.removeLastErrorFile()
if let error {

View file

@ -5,7 +5,7 @@ import Darwin
import OpenVPNAdapter
enum TunnelProtoType: String {
case wireguard, openvpn, shadowsocks, none
case wireguard, openvpn
}
struct Constants {
@ -34,29 +34,17 @@ struct Constants {
}
class PacketTunnelProvider: NEPacketTunnelProvider {
lazy var wgAdapter = {
WireGuardAdapter(with: self) { logLevel, message in
wg_log(logLevel.osLogLevel, message: message)
}
}()
var wgAdapter: WireGuardAdapter?
var ovpnAdapter: OpenVPNAdapter?
lazy var ovpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
return adapter
}()
/// Internal queue.
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
var splitTunnelType: Int!
var splitTunnelSites: [String]!
var splitTunnelType: Int?
var splitTunnelSites: [String]?
let vpnReachability = OpenVPNReachability()
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
var protoType: TunnelProtoType = .none
var protoType: TunnelProtoType?
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let message = String(data: messageData, encoding: .utf8) else {
@ -85,85 +73,75 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}
if action == Constants.kActionStatus {
handleStatusAppMessage(messageData, completionHandler: completionHandler)
handleStatusAppMessage(messageData,
completionHandler: completionHandler)
}
}
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
dispatchQueue.async {
override func startTunnel(options: [String : NSObject]? = nil,
completionHandler: @escaping ((any Error)?) -> Void) {
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)
neLog(.info, message: "Start tunnel")
if let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol {
if let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol {
let providerConfiguration = protocolConfiguration.providerConfiguration
if (providerConfiguration?[Constants.ovpnConfigKey] as? Data) != nil {
self.protoType = .openvpn
protoType = .openvpn
} 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:
self.startWireguard(activationAttemptId: activationAttemptId,
startWireguard(activationAttemptId: activationAttemptId,
errorNotifier: errorNotifier,
completionHandler: completionHandler)
case .openvpn:
self.startOpenVPN(completionHandler: completionHandler)
case .shadowsocks:
break
// startShadowSocks(completionHandler: completionHandler)
case .none:
break
}
startOpenVPN(completionHandler: completionHandler)
}
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
dispatchQueue.async {
switch self.protoType {
case .wireguard:
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
guard let protoType else {
completionHandler()
return
}
switch protoType {
case .wireguard:
stopWireguard(with: reason,
completionHandler: completionHandler)
case .openvpn:
stopOpenVPN(with: reason,
completionHandler: completionHandler)
}
}
func handleStatusAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let protoType else {
completionHandler?(nil)
return
}
switch protoType {
case .wireguard:
handleWireguardStatusMessage(messageData, completionHandler: completionHandler)
case .openvpn:
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
case .shadowsocks:
break
// handleShadowSocksAppMessage(messageData, completionHandler: completionHandler)
case .none:
break
}
}
// 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?,
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,