mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
refactor: extract and simplify OpenVPN reachability and network change handling logic (#2402)
This commit is contained in:
@@ -126,13 +126,7 @@ extension PacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vpnReachability.startTracking { [weak self] status in
|
vpnReachability.startTracking { [weak self] status in
|
||||||
switch status {
|
self?.handleOpenVPNReachabilityChange(status)
|
||||||
case .reachableViaWiFi, .reachableViaWWAN:
|
|
||||||
ovpnLog(.info, message: "Reachability changed, reconnecting OpenVPN session")
|
|
||||||
self?.ovpnAdapter?.reconnect(afterTimeInterval: 1)
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startHandler = completionHandler
|
startHandler = completionHandler
|
||||||
|
|||||||
@@ -46,8 +46,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
private var didReceiveInitialPathUpdate = false
|
private var didReceiveInitialPathUpdate = false
|
||||||
private var currentPath: Network.NWPath?
|
private var currentPath: Network.NWPath?
|
||||||
private var currentPathSignature: String?
|
private var currentPathSignature: String?
|
||||||
|
private var pendingOpenVPNReconnectWorkItem: DispatchWorkItem?
|
||||||
private var pendingNetworkChangeWorkItem: DispatchWorkItem?
|
private var pendingNetworkChangeWorkItem: DispatchWorkItem?
|
||||||
private var isApplyingNetworkChange = false
|
private var isApplyingNetworkChange = false
|
||||||
|
private var lastOpenVPNReachabilityStatus: OpenVPNReachabilityStatus?
|
||||||
|
|
||||||
var splitTunnelType: Int?
|
var splitTunnelType: Int?
|
||||||
var splitTunnelSites: [String]?
|
var splitTunnelSites: [String]?
|
||||||
@@ -81,9 +83,18 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
|
|
||||||
guard hasMeaningfulChange, let proto = self.protoType else { return }
|
guard hasMeaningfulChange, let proto = self.protoType else { return }
|
||||||
|
|
||||||
// OpenVPN and WireGuard/AWG handle network changes internally.
|
// WireGuard/AWG manages network changes internally in its own adapter.
|
||||||
// Restarting them here can race their own reconnect logic and break tunnel setup.
|
if proto == .wireguard {
|
||||||
if proto == .wireguard || proto == .openvpn {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if proto == .openvpn {
|
||||||
|
self.scheduleOpenVPNReconnect(reason: "NWPath changed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.isApplyingNetworkChange || self.reasserting {
|
||||||
|
xrayLog(.debug, message: "Ignoring path change while xray restart is in progress")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,6 +210,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancelPendingOpenVPNReconnect()
|
||||||
|
cancelPendingNetworkChangeHandling()
|
||||||
didReceiveInitialPathUpdate = false
|
didReceiveInitialPathUpdate = false
|
||||||
updateActiveInterfaceIndexForCurrentPath()
|
updateActiveInterfaceIndexForCurrentPath()
|
||||||
|
|
||||||
@@ -217,6 +230,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
|
|
||||||
|
|
||||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
|
cancelPendingOpenVPNReconnect()
|
||||||
|
cancelPendingNetworkChangeHandling()
|
||||||
|
|
||||||
guard let protoType else {
|
guard let protoType else {
|
||||||
completionHandler()
|
completionHandler()
|
||||||
return
|
return
|
||||||
@@ -284,8 +300,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
|
|
||||||
let workItem = DispatchWorkItem { [weak self] in
|
let workItem = DispatchWorkItem { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
self.pendingNetworkChangeWorkItem = nil
|
||||||
|
|
||||||
if self.isApplyingNetworkChange {
|
if self.isApplyingNetworkChange || self.reasserting {
|
||||||
xrayLog(.debug, message: "Skipping network change while restart is already in progress")
|
xrayLog(.debug, message: "Skipping network change while restart is already in progress")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -303,6 +320,69 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
pendingNetworkChangeWorkItem = workItem
|
pendingNetworkChangeWorkItem = workItem
|
||||||
networkChangeQueue.asyncAfter(deadline: .now() + 1.0, execute: workItem)
|
networkChangeQueue.asyncAfter(deadline: .now() + 1.0, execute: workItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func scheduleOpenVPNReconnect(reason: String) {
|
||||||
|
guard protoType == .openvpn else { return }
|
||||||
|
|
||||||
|
pendingOpenVPNReconnectWorkItem?.cancel()
|
||||||
|
|
||||||
|
let workItem = DispatchWorkItem { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
self.pendingOpenVPNReconnectWorkItem = nil
|
||||||
|
|
||||||
|
guard self.protoType == .openvpn else { return }
|
||||||
|
|
||||||
|
if self.reasserting {
|
||||||
|
ovpnLog(.debug, message: "Skipping OpenVPN reconnect while session is already reasserting")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
guard !self.reasserting else {
|
||||||
|
ovpnLog(.debug, message: "Skipping OpenVPN reconnect while session is already reasserting")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ovpnLog(.info, message: "\(reason), reconnecting OpenVPN session")
|
||||||
|
self.ovpnAdapter?.reconnect(afterTimeInterval: 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingOpenVPNReconnectWorkItem = workItem
|
||||||
|
networkChangeQueue.asyncAfter(deadline: .now() + 1.0, execute: workItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleOpenVPNReachabilityChange(_ status: OpenVPNReachabilityStatus) {
|
||||||
|
defer { lastOpenVPNReachabilityStatus = status }
|
||||||
|
|
||||||
|
guard let previousStatus = lastOpenVPNReachabilityStatus else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard previousStatus != status else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch status {
|
||||||
|
case .reachableViaWiFi, .reachableViaWWAN:
|
||||||
|
scheduleOpenVPNReconnect(reason: "Reachability changed")
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func cancelPendingOpenVPNReconnect() {
|
||||||
|
pendingOpenVPNReconnectWorkItem?.cancel()
|
||||||
|
pendingOpenVPNReconnectWorkItem = nil
|
||||||
|
lastOpenVPNReachabilityStatus = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func cancelPendingNetworkChangeHandling() {
|
||||||
|
pendingNetworkChangeWorkItem?.cancel()
|
||||||
|
pendingNetworkChangeWorkItem = nil
|
||||||
|
isApplyingNetworkChange = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension PacketTunnelProvider {
|
private extension PacketTunnelProvider {
|
||||||
@@ -311,8 +391,14 @@ private extension PacketTunnelProvider {
|
|||||||
signatureComponents.append(path.isExpensive ? "exp" : "noexp")
|
signatureComponents.append(path.isExpensive ? "exp" : "noexp")
|
||||||
signatureComponents.append(path.isConstrained ? "con" : "nocon")
|
signatureComponents.append(path.isConstrained ? "con" : "nocon")
|
||||||
|
|
||||||
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular, .loopback, .other]
|
// Ignore loopback and tunnel-style `.other` interfaces so Xray does not
|
||||||
let sortedInterfaces = path.availableInterfaces.sorted { lhs, rhs in
|
// react to its own utun lifecycle as if the physical uplink changed.
|
||||||
|
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular]
|
||||||
|
let externalInterfaces = path.availableInterfaces.filter { interface in
|
||||||
|
interface.type == .wiredEthernet || interface.type == .wifi || interface.type == .cellular
|
||||||
|
}
|
||||||
|
|
||||||
|
let sortedInterfaces = externalInterfaces.sorted { lhs, rhs in
|
||||||
if lhs.type == rhs.type {
|
if lhs.type == rhs.type {
|
||||||
return lhs.index < rhs.index
|
return lhs.index < rhs.index
|
||||||
}
|
}
|
||||||
@@ -333,8 +419,8 @@ private extension PacketTunnelProvider {
|
|||||||
case .wiredEthernet: typeName = "ethernet"
|
case .wiredEthernet: typeName = "ethernet"
|
||||||
case .wifi: typeName = "wifi"
|
case .wifi: typeName = "wifi"
|
||||||
case .cellular: typeName = "cellular"
|
case .cellular: typeName = "cellular"
|
||||||
case .loopback: typeName = "loopback"
|
case .loopback, .other:
|
||||||
case .other: typeName = "other"
|
continue
|
||||||
@unknown default: typeName = "unknown"
|
@unknown default: typeName = "unknown"
|
||||||
}
|
}
|
||||||
signatureComponents.append("\(typeName):\(interface.index)")
|
signatureComponents.append("\(typeName):\(interface.index)")
|
||||||
|
|||||||
Reference in New Issue
Block a user