From ddecfcad2612322ce10dadd0b5333ef2e9760872 Mon Sep 17 00:00:00 2001 From: yyy-amnezia Date: Fri, 20 Mar 2026 14:51:36 +0200 Subject: [PATCH] fix: apple platform network switch fix (#2359) * Apple platform network switch fix * macos_ne exclusion fixed --- .../ios/PacketTunnelProvider+OpenVPN.swift | 9 +++- .../platforms/ios/PacketTunnelProvider.swift | 54 ++++++++++++++++--- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift b/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift index 118545c2c..6f534e8a8 100644 --- a/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift +++ b/client/platforms/ios/PacketTunnelProvider+OpenVPN.swift @@ -126,8 +126,13 @@ extension PacketTunnelProvider { } vpnReachability.startTracking { [weak self] status in - guard status == .reachableViaWiFi else { return } - self?.ovpnAdapter?.reconnect(afterTimeInterval: 5) + switch status { + case .reachableViaWiFi, .reachableViaWWAN: + ovpnLog(.info, message: "Reachability changed, reconnecting OpenVPN session") + self?.ovpnAdapter?.reconnect(afterTimeInterval: 1) + default: + break + } } startHandler = completionHandler diff --git a/client/platforms/ios/PacketTunnelProvider.swift b/client/platforms/ios/PacketTunnelProvider.swift index 8a6784133..1825b816c 100644 --- a/client/platforms/ios/PacketTunnelProvider.swift +++ b/client/platforms/ios/PacketTunnelProvider.swift @@ -41,10 +41,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider { var ovpnAdapter: OpenVPNAdapter? private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow) private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor") + private let networkChangeQueue = DispatchQueue(label: Constants.processQueueName + ".network-change") private let pathMonitor = NWPathMonitor() private var didReceiveInitialPathUpdate = false private var currentPath: Network.NWPath? private var currentPathSignature: String? + private var pendingNetworkChangeWorkItem: DispatchWorkItem? + private var isApplyingNetworkChange = false var splitTunnelType: Int? var splitTunnelSites: [String]? @@ -78,14 +81,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider { guard hasMeaningfulChange, let proto = self.protoType else { return } - // WireGuard/AWG manages network changes internally; avoid restarting the tunnel here. - if proto == .wireguard { + // OpenVPN and WireGuard/AWG handle network changes internally. + // Restarting them here can race their own reconnect logic and break tunnel setup. + if proto == .wireguard || proto == .openvpn { return } - DispatchQueue.main.async { - self.handle(networkChange: path) { _ in } - } + self.scheduleNetworkChangeHandling(for: proto, path: path) } pathMonitor.start(queue: pathMonitorQueue) @@ -259,9 +261,47 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) { + guard protoType == .xray else { + updateActiveInterfaceIndex(for: changePath) + completion(nil) + return + } + updateActiveInterfaceIndex(for: changePath) - wg_log(.info, message: "Tunnel restarted.") - startTunnel(options: nil, completionHandler: completion) + reasserting = true + xrayLog(.info, message: "Applying network change to xray tunnel") + stopXray { } + startXray { [weak self] error in + self?.reasserting = false + completion(error) + } + } + + private func scheduleNetworkChangeHandling(for proto: TunnelProtoType, path: Network.NWPath) { + guard proto == .xray else { return } + + pendingNetworkChangeWorkItem?.cancel() + + let workItem = DispatchWorkItem { [weak self] in + guard let self else { return } + + if self.isApplyingNetworkChange { + xrayLog(.debug, message: "Skipping network change while restart is already in progress") + return + } + + self.isApplyingNetworkChange = true + DispatchQueue.main.async { + self.handle(networkChange: path) { [weak self] _ in + self?.networkChangeQueue.async { + self?.isApplyingNetworkChange = false + } + } + } + } + + pendingNetworkChangeWorkItem = workItem + networkChangeQueue.asyncAfter(deadline: .now() + 1.0, execute: workItem) } }