TunnelsManager: Observe status for all tunnels in one block

Signed-off-by: Roopesh Chander <roop@roopc.net>
This commit is contained in:
Roopesh Chander 2018-12-08 18:43:24 +05:30
parent 6838dc3a74
commit 0dcb285b67
1 changed files with 54 additions and 52 deletions

View File

@ -60,9 +60,11 @@ class TunnelsManager {
private var tunnels: [TunnelContainer]
weak var tunnelsListDelegate: TunnelsManagerListDelegate?
weak var activationDelegate: TunnelsManagerActivationDelegate?
private var statusObservationToken: AnyObject?
init(tunnelProviders: [NETunnelProviderManager]) {
self.tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0) }.sorted { $0.name < $1.name }
self.startObservingTunnelStatuses()
}
static func create(completionHandler: @escaping (WireGuardResult<TunnelsManager>) -> Void) {
@ -268,26 +270,37 @@ class TunnelsManager {
t.refreshStatus()
}
}
private func startObservingTunnelStatuses() {
if (statusObservationToken != nil) { return }
statusObservationToken = NotificationCenter.default.addObserver(
forName: .NEVPNStatusDidChange,
object: nil,
queue: nil) { [weak self] (statusChangeNotification) in
guard let session = statusChangeNotification.object as? NETunnelProviderSession else { return }
guard let tunnelProvider = session.manager as? NETunnelProviderManager else { return }
if let tunnel = self?.tunnels.first(where: { $0.tunnelProvider == tunnelProvider }) {
tunnel.tunnelConnectionStatusDidChange()
} else if let tunnelName = tunnelProvider.localizedDescription, let tunnel = self?.tunnel(named: tunnelName) {
tunnel.tunnelConnectionStatusDidChange()
}
}
}
}
class TunnelContainer: NSObject {
@objc dynamic var name: String
@objc dynamic var status: TunnelStatus
@objc dynamic var isActivateOnDemandEnabled: Bool {
didSet {
if (isActivateOnDemandEnabled) {
startObservingTunnelStatus()
}
}
}
@objc dynamic var isActivateOnDemandEnabled: Bool
var isAttemptingActivation: Bool = false
var onActivationCommitted: ((Bool) -> Void)?
var onDeactivationComplete: (() -> Void)?
fileprivate let tunnelProvider: NETunnelProviderManager
private var statusObservationToken: AnyObject?
private var lastTunnelConnectionStatus: NEVPNStatus?
init(tunnel: NETunnelProviderManager) {
self.name = tunnel.localizedDescription ?? "Unnamed"
@ -296,9 +309,6 @@ class TunnelContainer: NSObject {
self.isActivateOnDemandEnabled = tunnel.isOnDemandEnabled
self.tunnelProvider = tunnel
super.init()
if (status != .inactive || isActivateOnDemandEnabled) {
startObservingTunnelStatus()
}
}
func tunnelConfiguration() -> TunnelConfiguration? {
@ -313,9 +323,6 @@ class TunnelContainer: NSObject {
let status = TunnelStatus(from: self.tunnelProvider.connection.status)
self.status = status
self.isActivateOnDemandEnabled = self.tunnelProvider.isOnDemandEnabled
if (status != .inactive || isActivateOnDemandEnabled) {
startObservingTunnelStatus()
}
}
fileprivate func startActivation(completionHandler: @escaping (TunnelsManagerError?) -> Void) {
@ -359,7 +366,6 @@ class TunnelContainer: NSObject {
}
// Start the tunnel
startObservingTunnelStatus()
let session = (tunnelProvider.connection as! NETunnelProviderSession)
do {
os_log("startActivation: Starting tunnel", log: OSLog.default, type: .debug)
@ -396,55 +402,51 @@ class TunnelContainer: NSObject {
}
fileprivate func startDeactivation() {
assert(status == .active)
assert(statusObservationToken != nil)
assert(status == .active || status == .waiting)
let session = (tunnelProvider.connection as! NETunnelProviderSession)
session.stopTunnel()
}
fileprivate func beginRestart() {
assert(status == .active || status == .activating || status == .reasserting)
assert(statusObservationToken != nil)
status = .restarting
let session = (tunnelProvider.connection as! NETunnelProviderSession)
session.stopTunnel()
}
private func startObservingTunnelStatus() {
if (statusObservationToken != nil) { return }
fileprivate func tunnelConnectionStatusDidChange() {
let connection = tunnelProvider.connection
statusObservationToken = NotificationCenter.default.addObserver(
forName: .NEVPNStatusDidChange,
object: connection,
queue: nil) { [weak self] (_) in
guard let s = self else { return }
if (s.isAttemptingActivation) {
if (connection.status == .connecting || connection.status == .connected) {
// We tried to start the tunnel, and that attempt is on track to become succeessful
s.onActivationCommitted?(true)
s.onActivationCommitted = nil
} else if (connection.status == .disconnecting || connection.status == .disconnected) {
// We tried to start the tunnel, but that attempt didn't succeed
s.onActivationCommitted?(false)
s.onActivationCommitted = nil
}
s.isAttemptingActivation = false
}
if ((s.status == .restarting) && (connection.status == .disconnected || connection.status == .disconnecting)) {
// Don't change s.status when disconnecting for a restart
if (connection.status == .disconnected) {
self?.startActivation(completionHandler: { _ in })
}
return
}
s.status = TunnelStatus(from: connection.status)
if (s.status == .inactive) {
s.onDeactivationComplete?()
s.onDeactivationComplete = nil
if (!s.isActivateOnDemandEnabled) {
s.statusObservationToken = nil
}
}
// Avoid acting on duplicate notifications of status change
if let lastTunnelConnectionStatus = self.lastTunnelConnectionStatus {
if (lastTunnelConnectionStatus == connection.status) {
return
}
}
self.lastTunnelConnectionStatus = connection.status
// Act on the status change
if (self.isAttemptingActivation) {
if (connection.status == .connecting || connection.status == .connected) {
// We tried to start the tunnel, and that attempt is on track to become succeessful
self.onActivationCommitted?(true)
self.onActivationCommitted = nil
} else if (connection.status == .disconnecting || connection.status == .disconnected) {
// We tried to start the tunnel, but that attempt didn't succeed
self.onActivationCommitted?(false)
self.onActivationCommitted = nil
}
self.isAttemptingActivation = false
}
if ((self.status == .restarting) && (connection.status == .disconnected || connection.status == .disconnecting)) {
// Don't change self.status when disconnecting for a restart
if (connection.status == .disconnected) {
self.startActivation(completionHandler: { _ in })
}
return
}
self.status = TunnelStatus(from: connection.status)
if (self.status == .inactive) {
self.onDeactivationComplete?()
self.onDeactivationComplete = nil
}
}
}