2018-10-24 01:37:28 +00:00
// S P D X - L i c e n s e - I d e n t i f i e r : M I T
2020-12-04 11:15:29 +00:00
// C o p y r i g h t © 2 0 1 8 - 2 0 2 0 W i r e G u a r d L L C . A l l R i g h t s R e s e r v e d .
2018-10-15 08:05:24 +00:00
import Foundation
2018-10-25 10:20:27 +00:00
import NetworkExtension
import os . log
2018-10-15 08:05:24 +00:00
2018-12-03 13:21:51 +00:00
protocol TunnelsManagerListDelegate : class {
2018-12-12 18:28:27 +00:00
func tunnelAdded ( at index : Int )
func tunnelModified ( at index : Int )
func tunnelMoved ( from oldIndex : Int , to newIndex : Int )
2019-01-23 10:15:24 +00:00
func tunnelRemoved ( at index : Int , tunnel : TunnelContainer )
2018-10-23 12:11:37 +00:00
}
2018-12-03 13:21:51 +00:00
protocol TunnelsManagerActivationDelegate : class {
2018-12-13 13:25:20 +00:00
func tunnelActivationAttemptFailed ( tunnel : TunnelContainer , error : TunnelsManagerActivationAttemptError ) // s t a r t T u n n e l w a s n ' t c a l l e d o r f a i l e d
func tunnelActivationAttemptSucceeded ( tunnel : TunnelContainer ) // s t a r t T u n n e l s u c c e e d e d
func tunnelActivationFailed ( tunnel : TunnelContainer , error : TunnelsManagerActivationError ) // s t a t u s d i d n ' t c h a n g e t o c o n n e c t e d
func tunnelActivationSucceeded ( tunnel : TunnelContainer ) // s t a t u s c h a n g e d t o c o n n e c t e d
}
2018-10-15 08:05:24 +00:00
class TunnelsManager {
2020-12-11 11:50:31 +00:00
private var tunnels : [ TunnelContainer ]
2018-12-03 13:21:51 +00:00
weak var tunnelsListDelegate : TunnelsManagerListDelegate ?
weak var activationDelegate : TunnelsManagerActivationDelegate ?
2020-12-22 10:09:18 +00:00
private var statusObservationToken : NotificationToken ?
private var waiteeObservationToken : NSKeyValueObservation ?
private var configurationsObservationToken : NotificationToken ?
2018-10-15 08:05:24 +00:00
2018-10-25 10:20:27 +00:00
init ( tunnelProviders : [ NETunnelProviderManager ] ) {
2019-03-17 09:28:27 +00:00
tunnels = tunnelProviders . map { TunnelContainer ( tunnel : $0 ) } . sorted { TunnelsManager . tunnelNameIsLessThan ( $0 . name , $1 . name ) }
2018-12-14 23:12:59 +00:00
startObservingTunnelStatuses ( )
2019-01-22 13:41:51 +00:00
startObservingTunnelConfigurations ( )
2018-10-15 08:05:24 +00:00
}
2019-04-08 07:52:06 +00:00
static func create ( completionHandler : @ escaping ( Result < TunnelsManager , TunnelsManagerError > ) -> Void ) {
2018-11-07 12:43:50 +00:00
#if targetEnvironment ( simulator )
2018-12-15 19:41:23 +00:00
completionHandler ( . success ( TunnelsManager ( tunnelProviders : MockTunnels . createMockTunnels ( ) ) ) )
2018-11-07 12:43:50 +00:00
#else
2018-12-12 21:33:14 +00:00
NETunnelProviderManager . loadAllFromPreferences { managers , error in
2018-10-25 10:20:27 +00:00
if let error = error {
2018-12-13 12:00:02 +00:00
wg_log ( . error , message : " Failed to load tunnel provider managers: \( error ) " )
2018-12-17 13:26:26 +00:00
completionHandler ( . failure ( TunnelsManagerError . systemErrorOnListingTunnels ( systemError : error ) ) )
2018-10-25 10:20:27 +00:00
return
}
2018-12-21 22:34:56 +00:00
2019-02-04 06:37:26 +00:00
var tunnelManagers = managers ? ? [ ]
var refs : Set < Data > = [ ]
2019-04-30 10:08:38 +00:00
var tunnelNames : Set < String > = [ ]
2019-02-04 06:37:26 +00:00
for ( index , tunnelManager ) in tunnelManagers . enumerated ( ) . reversed ( ) {
2019-04-30 10:08:38 +00:00
if let tunnelName = tunnelManager . localizedDescription {
tunnelNames . insert ( tunnelName )
}
2019-04-03 11:02:12 +00:00
guard let proto = tunnelManager . protocolConfiguration as ? NETunnelProviderProtocol else { continue }
if proto . migrateConfigurationIfNeeded ( called : tunnelManager . localizedDescription ? ? " unknown " ) {
2018-12-21 21:16:09 +00:00
tunnelManager . saveToPreferences { _ in }
2018-12-21 04:52:45 +00:00
}
2019-04-03 11:02:12 +00:00
#if os ( iOS )
let passwordRef = proto . verifyConfigurationReference ( ) ? proto . passwordReference : nil
#elseif os ( macOS )
2019-06-11 00:03:11 +00:00
let passwordRef : Data ?
if proto . providerConfiguration ? [ " UID " ] as ? uid_t = = getuid ( ) {
passwordRef = proto . verifyConfigurationReference ( ) ? proto . passwordReference : nil
} else {
passwordRef = proto . passwordReference // T o h a n d l e m u l t i p l e u s e r s i n m a c O S , w e s k i p v e r i f y i n g
}
2019-04-03 11:02:12 +00:00
#else
# error ( " Unimplemented " )
#endif
if let ref = passwordRef {
2019-02-04 06:37:26 +00:00
refs . insert ( ref )
} else {
2019-10-11 19:52:55 +00:00
wg_log ( . info , message : " Removing orphaned tunnel with non-verifying keychain entry: \( tunnelManager . localizedDescription ? ? " <unknown> " ) " )
2019-02-04 06:37:26 +00:00
tunnelManager . removeFromPreferences { _ in }
tunnelManagers . remove ( at : index )
}
2018-12-21 04:52:45 +00:00
}
2019-02-04 06:37:26 +00:00
Keychain . deleteReferences ( except : refs )
2019-05-25 16:08:09 +00:00
#if os ( iOS )
2019-04-30 10:08:38 +00:00
RecentTunnelsTracker . cleanupTunnels ( except : tunnelNames )
2019-05-25 16:08:09 +00:00
#endif
2018-12-21 04:52:45 +00:00
completionHandler ( . success ( TunnelsManager ( tunnelProviders : tunnelManagers ) ) )
2018-10-25 10:20:27 +00:00
}
2018-11-07 12:43:50 +00:00
#endif
2018-10-15 08:05:24 +00:00
}
2018-12-22 04:30:35 +00:00
2019-01-22 13:41:51 +00:00
func reload ( ) {
NETunnelProviderManager . loadAllFromPreferences { [ weak self ] managers , _ in
guard let self = self else { return }
2018-12-22 04:30:35 +00:00
2019-01-22 13:41:51 +00:00
let loadedTunnelProviders = managers ? ? [ ]
2019-01-22 14:27:35 +00:00
for ( index , currentTunnel ) in self . tunnels . enumerated ( ) . reversed ( ) {
2019-04-05 08:13:05 +00:00
if ! loadedTunnelProviders . contains ( where : { $0 . isEquivalentTo ( currentTunnel ) } ) {
2019-01-22 13:41:51 +00:00
// T u n n e l w a s d e l e t e d o u t s i d e t h e a p p
2019-01-22 14:27:35 +00:00
self . tunnels . remove ( at : index )
2019-01-23 10:15:24 +00:00
self . tunnelsListDelegate ? . tunnelRemoved ( at : index , tunnel : currentTunnel )
2019-01-22 13:41:51 +00:00
}
}
for loadedTunnelProvider in loadedTunnelProviders {
2019-04-05 08:13:05 +00:00
if let matchingTunnel = self . tunnels . first ( where : { loadedTunnelProvider . isEquivalentTo ( $0 ) } ) {
2019-01-22 13:41:51 +00:00
matchingTunnel . tunnelProvider = loadedTunnelProvider
2019-01-22 14:35:14 +00:00
matchingTunnel . refreshStatus ( )
2019-01-22 13:41:51 +00:00
} else {
// T u n n e l w a s a d d e d o u t s i d e t h e a p p
2019-02-12 12:13:40 +00:00
if let proto = loadedTunnelProvider . protocolConfiguration as ? NETunnelProviderProtocol {
if proto . migrateConfigurationIfNeeded ( called : loadedTunnelProvider . localizedDescription ? ? " unknown " ) {
loadedTunnelProvider . saveToPreferences { _ in }
}
}
2019-01-22 13:41:51 +00:00
let tunnel = TunnelContainer ( tunnel : loadedTunnelProvider )
self . tunnels . append ( tunnel )
2019-03-17 09:28:27 +00:00
self . tunnels . sort { TunnelsManager . tunnelNameIsLessThan ( $0 . name , $1 . name ) }
2019-01-22 13:41:51 +00:00
self . tunnelsListDelegate ? . tunnelAdded ( at : self . tunnels . firstIndex ( of : tunnel ) ! )
}
2018-12-22 03:59:43 +00:00
}
}
}
2018-10-15 08:05:24 +00:00
2019-04-08 07:52:06 +00:00
func add ( tunnelConfiguration : TunnelConfiguration , onDemandOption : ActivateOnDemandOption = . off , completionHandler : @ escaping ( Result < TunnelContainer , TunnelsManagerError > ) -> Void ) {
2018-12-21 23:28:18 +00:00
let tunnelName = tunnelConfiguration . name ? ? " "
2018-11-03 02:40:23 +00:00
if tunnelName . isEmpty {
2018-12-06 10:28:27 +00:00
completionHandler ( . failure ( TunnelsManagerError . tunnelNameEmpty ) )
2018-11-03 02:40:23 +00:00
return
}
2018-11-03 18:35:25 +00:00
2018-12-14 23:12:59 +00:00
if tunnels . contains ( where : { $0 . name = = tunnelName } ) {
2018-12-06 10:28:27 +00:00
completionHandler ( . failure ( TunnelsManagerError . tunnelAlreadyExistsWithThatName ) )
2018-11-01 06:12:32 +00:00
return
}
2018-10-25 10:20:27 +00:00
let tunnelProviderManager = NETunnelProviderManager ( )
2019-02-07 12:48:04 +00:00
tunnelProviderManager . setTunnelConfiguration ( tunnelConfiguration )
2018-10-25 10:20:27 +00:00
tunnelProviderManager . isEnabled = true
2019-03-08 10:42:54 +00:00
onDemandOption . apply ( on : tunnelProviderManager )
2018-11-10 20:01:38 +00:00
2019-02-24 14:00:14 +00:00
let activeTunnel = tunnels . first { $0 . status = = . active || $0 . status = = . activating }
2018-12-12 21:33:14 +00:00
tunnelProviderManager . saveToPreferences { [ weak self ] error in
2018-12-12 18:28:27 +00:00
guard error = = nil else {
2018-12-13 12:00:02 +00:00
wg_log ( . error , message : " Add: Saving configuration failed: \( error ! ) " )
2019-02-04 06:37:26 +00:00
( tunnelProviderManager . protocolConfiguration as ? NETunnelProviderProtocol ) ? . destroyConfigurationReference ( )
2018-12-17 13:26:26 +00:00
completionHandler ( . failure ( TunnelsManagerError . systemErrorOnAddTunnel ( systemError : error ! ) ) )
2018-10-25 10:20:27 +00:00
return
}
2018-12-21 22:34:56 +00:00
2018-12-13 18:58:50 +00:00
guard let self = self else { return }
2018-12-21 22:34:56 +00:00
2019-02-24 14:00:14 +00:00
#if os ( iOS )
// H A C K : I n i O S , a d d i n g a t u n n e l c a u s e s d e a c t i v a t i o n o f a n y c u r r e n t l y a c t i v e t u n n e l .
// T h i s i s a n u g l y h a c k t o r e a c t i v a t e t h e t u n n e l t h a t h a s b e e n d e a c t i v a t e d l i k e t h a t .
if let activeTunnel = activeTunnel {
if activeTunnel . status = = . inactive || activeTunnel . status = = . deactivating {
self . startActivation ( of : activeTunnel )
}
if activeTunnel . status = = . active || activeTunnel . status = = . activating {
activeTunnel . status = . restarting
}
}
#endif
2018-12-13 18:58:50 +00:00
let tunnel = TunnelContainer ( tunnel : tunnelProviderManager )
self . tunnels . append ( tunnel )
2019-03-17 09:28:27 +00:00
self . tunnels . sort { TunnelsManager . tunnelNameIsLessThan ( $0 . name , $1 . name ) }
2018-12-13 18:58:50 +00:00
self . tunnelsListDelegate ? . tunnelAdded ( at : self . tunnels . firstIndex ( of : tunnel ) ! )
completionHandler ( . success ( tunnel ) )
2018-10-15 08:05:24 +00:00
}
}
2019-03-05 09:54:08 +00:00
func addMultiple ( tunnelConfigurations : [ TunnelConfiguration ] , completionHandler : @ escaping ( UInt , TunnelsManagerError ? ) -> Void ) {
2020-12-21 12:46:55 +00:00
// T e m p o r a r i l y p a u s e o b s e r v a t i o n o f c h a n g e s t o V P N c o n f i g u r a t i o n s t o p r e v e n t t h e f e e d b a c k
// l o o p t h a t c a u s e s ` r e l o a d ( ) ` t o b e c a l l e d o n e a c h n e w l y a d d e d t u n n e l , w h i c h s i g n i f i c a n t l y
// i m p a c t s p e r f o r m a n c e .
configurationsObservationToken = nil
self . addMultiple ( tunnelConfigurations : ArraySlice ( tunnelConfigurations ) , numberSuccessful : 0 , lastError : nil ) { [ weak self ] numSucceeded , error in
completionHandler ( numSucceeded , error )
// R e s t a r t o b s e r v a t i o n o f c h a n g e s t o V P N c o n f i g r a t i o n s .
self ? . startObservingTunnelConfigurations ( )
// F o r c e r e l o a d a l l c o n f i g u r a t i o n s t o m a k e s u r e t h a t a l l t u n n e l s a r e u p t o d a t e .
self ? . reload ( )
}
2018-10-31 08:59:54 +00:00
}
2019-03-05 09:54:08 +00:00
private func addMultiple ( tunnelConfigurations : ArraySlice < TunnelConfiguration > , numberSuccessful : UInt , lastError : TunnelsManagerError ? , completionHandler : @ escaping ( UInt , TunnelsManagerError ? ) -> Void ) {
2018-11-03 03:37:56 +00:00
guard let head = tunnelConfigurations . first else {
2019-03-05 09:54:08 +00:00
completionHandler ( numberSuccessful , lastError )
2018-11-03 01:51:32 +00:00
return
}
2018-11-03 03:37:56 +00:00
let tail = tunnelConfigurations . dropFirst ( )
2018-12-14 23:12:59 +00:00
add ( tunnelConfiguration : head ) { [ weak self , tail ] result in
2018-11-03 01:51:32 +00:00
DispatchQueue . main . async {
2019-04-08 07:52:06 +00:00
var numberSuccessfulCount = numberSuccessful
var lastError : TunnelsManagerError ?
switch result {
case . failure ( let error ) :
lastError = error
case . success :
numberSuccessfulCount = numberSuccessful + 1
}
self ? . addMultiple ( tunnelConfigurations : tail , numberSuccessful : numberSuccessfulCount , lastError : lastError , completionHandler : completionHandler )
2018-10-31 08:59:54 +00:00
}
}
}
2019-03-08 10:42:54 +00:00
func modify ( tunnel : TunnelContainer , tunnelConfiguration : TunnelConfiguration , onDemandOption : ActivateOnDemandOption , completionHandler : @ escaping ( TunnelsManagerError ? ) -> Void ) {
2018-12-21 23:28:18 +00:00
let tunnelName = tunnelConfiguration . name ? ? " "
2018-11-03 02:40:23 +00:00
if tunnelName . isEmpty {
2018-12-06 10:28:27 +00:00
completionHandler ( TunnelsManagerError . tunnelNameEmpty )
2018-11-03 02:40:23 +00:00
return
}
2018-10-25 10:20:27 +00:00
let tunnelProviderManager = tunnel . tunnelProvider
2019-04-30 10:08:38 +00:00
let oldName = tunnelProviderManager . localizedDescription ? ? " "
let isNameChanged = tunnelName != oldName
2018-12-12 18:28:27 +00:00
if isNameChanged {
2018-12-17 05:51:25 +00:00
guard ! tunnels . contains ( where : { $0 . name = = tunnelName } ) else {
2018-12-06 10:28:27 +00:00
completionHandler ( TunnelsManagerError . tunnelAlreadyExistsWithThatName )
2018-11-01 06:12:32 +00:00
return
}
2018-10-28 09:25:24 +00:00
tunnel . name = tunnelName
}
2018-12-19 10:32:48 +00:00
2019-03-11 07:50:21 +00:00
var isTunnelConfigurationChanged = false
if tunnelProviderManager . tunnelConfiguration != tunnelConfiguration {
tunnelProviderManager . setTunnelConfiguration ( tunnelConfiguration )
isTunnelConfigurationChanged = true
}
2018-10-25 10:20:27 +00:00
tunnelProviderManager . isEnabled = true
2018-12-21 22:34:56 +00:00
2019-03-08 10:42:54 +00:00
let isActivatingOnDemand = ! tunnelProviderManager . isOnDemandEnabled && onDemandOption != . off
onDemandOption . apply ( on : tunnelProviderManager )
2018-11-10 20:01:38 +00:00
2018-12-12 21:33:14 +00:00
tunnelProviderManager . saveToPreferences { [ weak self ] error in
2018-12-12 18:28:27 +00:00
guard error = = nil else {
2019-02-04 06:37:26 +00:00
// TODO: t h e p a s s w o r d R e f e r e n c e f o r t h e o l d o n e h a s a l r e a d y b e e n r e m o v e d a t t h i s p o i n t a n d w e c a n ' t e a s i l y r o l l b a c k !
2018-12-13 12:00:02 +00:00
wg_log ( . error , message : " Modify: Saving configuration failed: \( error ! ) " )
2018-12-17 13:26:26 +00:00
completionHandler ( TunnelsManagerError . systemErrorOnModifyTunnel ( systemError : error ! ) )
2018-10-25 10:20:27 +00:00
return
}
2018-12-13 18:58:50 +00:00
guard let self = self else { return }
if isNameChanged {
let oldIndex = self . tunnels . firstIndex ( of : tunnel ) !
2019-03-17 09:28:27 +00:00
self . tunnels . sort { TunnelsManager . tunnelNameIsLessThan ( $0 . name , $1 . name ) }
2018-12-13 18:58:50 +00:00
let newIndex = self . tunnels . firstIndex ( of : tunnel ) !
self . tunnelsListDelegate ? . tunnelMoved ( from : oldIndex , to : newIndex )
2019-04-30 10:08:38 +00:00
#if os ( iOS )
RecentTunnelsTracker . handleTunnelRenamed ( oldName : oldName , newName : tunnelName )
#endif
2018-12-13 18:58:50 +00:00
}
self . tunnelsListDelegate ? . tunnelModified ( at : self . tunnels . firstIndex ( of : tunnel ) ! )
2018-12-19 10:32:48 +00:00
2019-03-11 07:50:21 +00:00
if isTunnelConfigurationChanged {
if tunnel . status = = . active || tunnel . status = = . activating || tunnel . status = = . reasserting {
// T u r n o f f t h e t u n n e l , a n d t h e n t u r n i t b a c k o n , s o t h e c h a n g e s a r e m a d e e f f e c t i v e
tunnel . status = . restarting
( tunnel . tunnelProvider . connection as ? NETunnelProviderSession ) ? . stopTunnel ( )
}
2018-12-13 18:58:50 +00:00
}
2018-12-19 10:32:48 +00:00
2018-12-13 18:58:50 +00:00
if isActivatingOnDemand {
// R e l o a d t u n n e l a f t e r s a v i n g .
// W i t h o u t t h i s , t h e t u n n e l s t o p e s g e t t i n g u p d a t e s o n t h e t u n n e l s t a t u s f r o m i O S .
tunnelProviderManager . loadFromPreferences { error in
tunnel . isActivateOnDemandEnabled = tunnelProviderManager . isOnDemandEnabled
guard error = = nil else {
wg_log ( . error , message : " Modify: Re-loading after saving configuration failed: \( error ! ) " )
2018-12-17 13:26:26 +00:00
completionHandler ( TunnelsManagerError . systemErrorOnModifyTunnel ( systemError : error ! ) )
2018-12-13 18:58:50 +00:00
return
2018-11-14 07:30:52 +00:00
}
completionHandler ( nil )
}
2018-12-13 18:58:50 +00:00
} else {
completionHandler ( nil )
2018-10-25 10:20:27 +00:00
}
}
2018-10-15 08:05:24 +00:00
}
2018-12-06 10:28:27 +00:00
func remove ( tunnel : TunnelContainer , completionHandler : @ escaping ( TunnelsManagerError ? ) -> Void ) {
2018-10-25 10:20:27 +00:00
let tunnelProviderManager = tunnel . tunnelProvider
2019-06-11 00:03:11 +00:00
#if os ( macOS )
if tunnel . isTunnelAvailableToUser {
2019-04-04 09:59:25 +00:00
( tunnelProviderManager . protocolConfiguration as ? NETunnelProviderProtocol ) ? . destroyConfigurationReference ( )
}
2019-06-11 00:03:11 +00:00
#elseif os ( iOS )
( tunnelProviderManager . protocolConfiguration as ? NETunnelProviderProtocol ) ? . destroyConfigurationReference ( )
#else
# error ( " Unimplemented " )
#endif
2018-12-12 21:33:14 +00:00
tunnelProviderManager . removeFromPreferences { [ weak self ] error in
2018-12-12 18:28:27 +00:00
guard error = = nil else {
2018-12-13 12:00:02 +00:00
wg_log ( . error , message : " Remove: Saving configuration failed: \( error ! ) " )
2018-12-17 13:26:26 +00:00
completionHandler ( TunnelsManagerError . systemErrorOnRemoveTunnel ( systemError : error ! ) )
2018-10-25 10:20:27 +00:00
return
}
2019-03-10 14:13:27 +00:00
if let self = self , let index = self . tunnels . firstIndex ( of : tunnel ) {
2018-12-12 17:40:57 +00:00
self . tunnels . remove ( at : index )
2019-01-23 10:15:24 +00:00
self . tunnelsListDelegate ? . tunnelRemoved ( at : index , tunnel : tunnel )
2018-10-25 10:20:27 +00:00
}
2018-11-03 16:23:03 +00:00
completionHandler ( nil )
2019-04-30 10:08:38 +00:00
#if os ( iOS )
RecentTunnelsTracker . handleTunnelRemoved ( tunnelName : tunnel . name )
#endif
2018-10-15 08:05:24 +00:00
}
}
2019-03-10 14:13:27 +00:00
func removeMultiple ( tunnels : [ TunnelContainer ] , completionHandler : @ escaping ( TunnelsManagerError ? ) -> Void ) {
2020-12-21 12:46:55 +00:00
// T e m p o r a r i l y p a u s e o b s e r v a t i o n o f c h a n g e s t o V P N c o n f i g u r a t i o n s t o p r e v e n t t h e f e e d b a c k
// l o o p t h a t c a u s e s ` r e l o a d ( ) ` t o b e c a l l e d f o r e a c h r e m o v e d t u n n e l , w h i c h s i g n i f i c a n t l y
// i m p a c t s p e r f o r m a n c e .
configurationsObservationToken = nil
removeMultiple ( tunnels : ArraySlice ( tunnels ) ) { [ weak self ] error in
completionHandler ( error )
// R e s t a r t o b s e r v a t i o n o f c h a n g e s t o V P N c o n f i g r a t i o n s .
self ? . startObservingTunnelConfigurations ( )
// F o r c e r e l o a d a l l c o n f i g u r a t i o n s t o m a k e s u r e t h a t a l l t u n n e l s a r e u p t o d a t e .
self ? . reload ( )
}
2019-03-10 14:13:27 +00:00
}
private func removeMultiple ( tunnels : ArraySlice < TunnelContainer > , completionHandler : @ escaping ( TunnelsManagerError ? ) -> Void ) {
guard let head = tunnels . first else {
completionHandler ( nil )
return
}
let tail = tunnels . dropFirst ( )
remove ( tunnel : head ) { [ weak self , tail ] error in
DispatchQueue . main . async {
if let error = error {
completionHandler ( error )
} else {
self ? . removeMultiple ( tunnels : tail , completionHandler : completionHandler )
}
}
}
}
2018-10-15 08:05:24 +00:00
func numberOfTunnels ( ) -> Int {
return tunnels . count
}
func tunnel ( at index : Int ) -> TunnelContainer {
return tunnels [ index ]
}
2018-10-26 09:25:20 +00:00
2019-04-30 12:58:06 +00:00
func mapTunnels < T > ( transform : ( TunnelContainer ) throws -> T ) rethrows -> [ T ] {
return try tunnels . map ( transform )
}
2019-01-24 13:05:07 +00:00
func index ( of tunnel : TunnelContainer ) -> Int ? {
return tunnels . firstIndex ( of : tunnel )
}
2018-12-07 13:35:04 +00:00
func tunnel ( named tunnelName : String ) -> TunnelContainer ? {
2018-12-14 23:12:59 +00:00
return tunnels . first { $0 . name = = tunnelName }
2018-12-07 13:35:04 +00:00
}
2019-01-15 19:30:42 +00:00
func waitingTunnel ( ) -> TunnelContainer ? {
return tunnels . first { $0 . status = = . waiting }
}
2019-01-24 12:41:26 +00:00
func tunnelInOperation ( ) -> TunnelContainer ? {
if let waitingTunnelObject = waitingTunnel ( ) {
return waitingTunnelObject
}
return tunnels . first { $0 . status != . inactive }
}
2018-12-13 13:25:20 +00:00
func startActivation ( of tunnel : TunnelContainer ) {
2018-12-10 11:34:24 +00:00
guard tunnels . contains ( tunnel ) else { return } // E n s u r e i t ' s n o t d e l e t e d
2018-12-12 18:28:27 +00:00
guard tunnel . status = = . inactive else {
2018-12-14 23:12:59 +00:00
activationDelegate ? . tunnelActivationAttemptFailed ( tunnel : tunnel , error : . tunnelIsNotInactive )
2018-10-26 09:25:20 +00:00
return
}
2018-12-03 10:04:58 +00:00
2018-12-13 17:43:54 +00:00
if let alreadyWaitingTunnel = tunnels . first ( where : { $0 . status = = . waiting } ) {
alreadyWaitingTunnel . status = . inactive
}
2018-12-10 21:54:28 +00:00
if let tunnelInOperation = tunnels . first ( where : { $0 . status != . inactive } ) {
2018-12-13 17:43:54 +00:00
wg_log ( . info , message : " Tunnel ' \( tunnel . name ) ' waiting for deactivation of ' \( tunnelInOperation . name ) ' " )
tunnel . status = . waiting
2018-12-18 14:15:00 +00:00
activateWaitingTunnelOnDeactivation ( of : tunnelInOperation )
2018-12-13 17:43:54 +00:00
if tunnelInOperation . status != . deactivating {
startDeactivation ( of : tunnelInOperation )
}
2018-12-10 21:54:28 +00:00
return
2018-12-03 10:04:58 +00:00
}
2018-12-15 19:41:23 +00:00
#if targetEnvironment ( simulator )
tunnel . status = . active
#else
2018-12-14 23:12:59 +00:00
tunnel . startActivation ( activationDelegate : activationDelegate )
2018-12-15 19:41:23 +00:00
#endif
2019-04-30 10:08:38 +00:00
#if os ( iOS )
RecentTunnelsTracker . handleTunnelActivated ( tunnelName : tunnel . name )
#endif
2018-10-26 09:25:20 +00:00
}
2018-11-10 06:55:17 +00:00
func startDeactivation ( of tunnel : TunnelContainer ) {
2018-12-13 17:43:54 +00:00
tunnel . isAttemptingActivation = false
2018-12-13 18:58:50 +00:00
guard tunnel . status != . inactive && tunnel . status != . deactivating else { return }
2018-12-15 19:41:23 +00:00
#if targetEnvironment ( simulator )
tunnel . status = . inactive
#else
2018-10-27 13:00:07 +00:00
tunnel . startDeactivation ( )
2018-12-15 19:41:23 +00:00
#endif
2018-10-26 09:25:20 +00:00
}
2018-11-09 13:49:32 +00:00
2018-11-12 10:34:03 +00:00
func refreshStatuses ( ) {
2018-12-12 17:40:57 +00:00
tunnels . forEach { $0 . refreshStatus ( ) }
2018-11-09 13:49:32 +00:00
}
2018-12-08 13:13:24 +00:00
2018-12-18 14:15:00 +00:00
private func activateWaitingTunnelOnDeactivation ( of tunnel : TunnelContainer ) {
waiteeObservationToken = tunnel . observe ( \ . status ) { [ weak self ] tunnel , _ in
guard let self = self else { return }
if tunnel . status = = . inactive {
if let waitingTunnel = self . tunnels . first ( where : { $0 . status = = . waiting } ) {
waitingTunnel . startActivation ( activationDelegate : self . activationDelegate )
}
self . waiteeObservationToken = nil
}
}
}
2018-12-08 13:13:24 +00:00
private func startObservingTunnelStatuses ( ) {
2020-12-22 10:09:18 +00:00
statusObservationToken = NotificationCenter . default . observe ( name : . NEVPNStatusDidChange , object : nil , queue : OperationQueue . main ) { [ weak self ] statusChangeNotification in
2018-12-13 18:58:50 +00:00
guard let self = self ,
let session = statusChangeNotification . object as ? NETunnelProviderSession ,
let tunnelProvider = session . manager as ? NETunnelProviderManager ,
2019-02-09 13:37:17 +00:00
let tunnel = self . tunnels . first ( where : { $0 . tunnelProvider = = tunnelProvider } ) else { return }
2018-12-13 04:26:04 +00:00
2018-12-13 12:00:02 +00:00
wg_log ( . debug , message : " Tunnel ' \( tunnel . name ) ' connection status changed to ' \( tunnel . tunnelProvider . connection . status ) ' " )
2018-12-13 04:26:04 +00:00
2018-12-13 13:25:20 +00:00
if tunnel . isAttemptingActivation {
if session . status = = . connected {
tunnel . isAttemptingActivation = false
self . activationDelegate ? . tunnelActivationSucceeded ( tunnel : tunnel )
} else if session . status = = . disconnected {
tunnel . isAttemptingActivation = false
2018-12-22 03:59:43 +00:00
if let ( title , message ) = lastErrorTextFromNetworkExtension ( for : tunnel ) {
2018-12-21 17:50:32 +00:00
self . activationDelegate ? . tunnelActivationFailed ( tunnel : tunnel , error : . activationFailedWithExtensionError ( title : title , message : message , wasOnDemandEnabled : tunnelProvider . isOnDemandEnabled ) )
2018-12-13 20:54:53 +00:00
} else {
2018-12-21 17:50:32 +00:00
self . activationDelegate ? . tunnelActivationFailed ( tunnel : tunnel , error : . activationFailed ( wasOnDemandEnabled : tunnelProvider . isOnDemandEnabled ) )
2018-12-13 20:54:53 +00:00
}
2018-12-10 11:34:24 +00:00
}
2018-12-13 03:09:52 +00:00
}
2018-12-13 04:26:04 +00:00
2019-02-07 18:47:05 +00:00
if tunnel . status = = . restarting && session . status = = . disconnected {
tunnel . startActivation ( activationDelegate : self . activationDelegate )
2018-12-13 03:09:52 +00:00
return
}
2018-12-13 04:26:04 +00:00
2018-12-13 03:09:52 +00:00
tunnel . refreshStatus ( )
2018-12-08 13:13:24 +00:00
}
}
2019-01-22 13:41:51 +00:00
func startObservingTunnelConfigurations ( ) {
2020-12-22 10:09:18 +00:00
configurationsObservationToken = NotificationCenter . default . observe ( name : . NEVPNConfigurationChange , object : nil , queue : OperationQueue . main ) { [ weak self ] _ in
2019-01-26 09:01:38 +00:00
DispatchQueue . main . async { [ weak self ] in
// W e s c h e d u l e r e l o a d ( ) i n a s u b s e q u e n t r u n l o o p t o e n s u r e t h a t t h e c o m p l e t i o n h a n d l e r o f l o a d A l l F r o m P r e f e r e n c e s
// ( r e l o a d ( ) c a l l s l o a d A l l F r o m P r e f e r e n c e s ) i s c a l l e d a f t e r t h e c o m p l e t i o n h a n d l e r o f t h e s a v e T o P r e f e r e n c e s o r
// r e m o v e F r o m P r e f e r e n c e s c a l l , i f a n y , t h a t c a u s e d t h i s n o t i f i c a t i o n t o f i r e . T h i s n o t i f i c a t i o n c a n a l s o f i r e
// a s a r e s u l t o f a t u n n e l g e t t i n g a d d e d o r r e m o v e d o u t s i d e o f t h e a p p .
self ? . reload ( )
}
2019-01-22 13:41:51 +00:00
}
}
2020-12-02 14:08:45 +00:00
static func tunnelNameIsLessThan ( _ lhs : String , _ rhs : String ) -> Bool {
return lhs . compare ( rhs , options : [ . caseInsensitive , . diacriticInsensitive , . widthInsensitive , . numeric ] ) = = . orderedAscending
2019-03-17 09:28:27 +00:00
}
2018-12-22 03:59:43 +00:00
}
2018-12-13 20:54:53 +00:00
2018-12-22 03:59:43 +00:00
private func lastErrorTextFromNetworkExtension ( for tunnel : TunnelContainer ) -> ( title : String , message : String ) ? {
guard let lastErrorFileURL = FileManager . networkExtensionLastErrorFileURL else { return nil }
guard let lastErrorData = try ? Data ( contentsOf : lastErrorFileURL ) else { return nil }
guard let lastErrorStrings = String ( data : lastErrorData , encoding : . utf8 ) ? . splitToArray ( separator : " \n " ) else { return nil }
guard lastErrorStrings . count = = 2 && tunnel . activationAttemptId = = lastErrorStrings [ 0 ] else { return nil }
2018-12-22 04:30:35 +00:00
2018-12-22 10:35:35 +00:00
if let extensionError = PacketTunnelProviderError ( rawValue : lastErrorStrings [ 1 ] ) {
return extensionError . alertText
2018-12-10 11:34:24 +00:00
}
2018-12-22 10:35:35 +00:00
return ( tr ( " alertTunnelActivationFailureTitle " ) , tr ( " alertTunnelActivationFailureMessage " ) )
2018-10-15 08:05:24 +00:00
}
2018-10-25 10:20:27 +00:00
2018-10-26 09:25:20 +00:00
class TunnelContainer : NSObject {
@objc dynamic var name : String
@objc dynamic var status : TunnelStatus
2018-11-12 12:46:44 +00:00
2018-12-08 13:13:24 +00:00
@objc dynamic var isActivateOnDemandEnabled : Bool
2018-10-26 09:25:20 +00:00
2018-12-14 12:03:52 +00:00
var isAttemptingActivation = false {
didSet {
if isAttemptingActivation {
2018-12-18 14:15:00 +00:00
self . activationTimer ? . invalidate ( )
2018-12-14 12:03:52 +00:00
let activationTimer = Timer ( timeInterval : 5 /* s e c o n d s */ , repeats : true ) { [ weak self ] _ in
guard let self = self else { return }
2018-12-18 14:15:00 +00:00
wg_log ( . debug , message : " Status update notification timeout for tunnel ' \( self . name ) '. Tunnel status is now ' \( self . tunnelProvider . connection . status ) '. " )
switch self . tunnelProvider . connection . status {
case . connected , . disconnected , . invalid :
self . activationTimer ? . invalidate ( )
self . activationTimer = nil
default :
break
2018-12-14 12:03:52 +00:00
}
2018-12-18 14:15:00 +00:00
self . refreshStatus ( )
2018-12-14 12:03:52 +00:00
}
self . activationTimer = activationTimer
2019-01-31 11:34:34 +00:00
RunLoop . main . add ( activationTimer , forMode : . common )
2018-12-14 12:03:52 +00:00
}
}
}
2018-12-13 20:54:53 +00:00
var activationAttemptId : String ?
2018-12-14 12:03:52 +00:00
var activationTimer : Timer ?
2019-01-25 12:44:48 +00:00
var deactivationTimer : Timer ?
2018-11-10 10:20:56 +00:00
2018-12-22 05:56:12 +00:00
fileprivate var tunnelProvider : NETunnelProviderManager
2018-10-26 09:25:20 +00:00
2018-12-21 04:52:45 +00:00
var tunnelConfiguration : TunnelConfiguration ? {
2019-01-22 13:41:51 +00:00
return tunnelProvider . tunnelConfiguration
2018-12-21 04:52:45 +00:00
}
2018-12-21 22:34:56 +00:00
2019-03-08 10:42:54 +00:00
var onDemandOption : ActivateOnDemandOption {
return ActivateOnDemandOption ( from : tunnelProvider )
2018-12-21 04:52:45 +00:00
}
2018-12-21 22:34:56 +00:00
2019-06-11 00:03:11 +00:00
#if os ( macOS )
var isTunnelAvailableToUser : Bool {
return ( tunnelProvider . protocolConfiguration as ? NETunnelProviderProtocol ) ? . providerConfiguration ? [ " UID " ] as ? uid_t = = getuid ( )
}
#endif
2018-11-03 02:40:23 +00:00
init ( tunnel : NETunnelProviderManager ) {
2018-12-14 23:12:59 +00:00
name = tunnel . localizedDescription ? ? " Unnamed "
2018-10-26 09:25:20 +00:00
let status = TunnelStatus ( from : tunnel . connection . status )
self . status = status
2018-12-14 23:12:59 +00:00
isActivateOnDemandEnabled = tunnel . isOnDemandEnabled
tunnelProvider = tunnel
2018-10-26 09:25:20 +00:00
super . init ( )
}
2019-01-23 23:00:46 +00:00
func getRuntimeTunnelConfiguration ( completionHandler : @ escaping ( ( TunnelConfiguration ? ) -> Void ) ) {
guard status != . inactive , let session = tunnelProvider . connection as ? NETunnelProviderSession else {
completionHandler ( tunnelConfiguration )
return
}
2019-04-09 05:41:28 +00:00
guard nil != ( try ? session . sendProviderMessage ( Data ( [ UInt8 ( 0 ) ] ) , responseHandler : {
2019-01-23 23:00:46 +00:00
guard self . status != . inactive , let data = $0 , let base = self . tunnelConfiguration , let settings = String ( data : data , encoding : . utf8 ) else {
completionHandler ( self . tunnelConfiguration )
return
}
completionHandler ( ( try ? TunnelConfiguration ( fromUapiConfig : settings , basedOn : base ) ) ? ? self . tunnelConfiguration )
} ) ) else {
completionHandler ( tunnelConfiguration )
return
}
}
2018-11-12 10:34:03 +00:00
func refreshStatus ( ) {
2019-02-24 14:00:14 +00:00
if status = = . restarting {
2019-02-07 18:47:05 +00:00
return
}
2019-01-25 12:44:48 +00:00
status = TunnelStatus ( from : tunnelProvider . connection . status )
2018-12-14 23:12:59 +00:00
isActivateOnDemandEnabled = tunnelProvider . isOnDemandEnabled
2018-11-09 13:49:32 +00:00
}
2018-12-13 18:31:41 +00:00
fileprivate func startActivation ( recursionCount : UInt = 0 , lastError : Error ? = nil , activationDelegate : TunnelsManagerActivationDelegate ? ) {
2018-12-12 18:28:27 +00:00
if recursionCount >= 8 {
2018-12-13 12:00:02 +00:00
wg_log ( . error , message : " startActivation: Failed after 8 attempts. Giving up with \( lastError ! ) " )
2018-12-17 13:26:26 +00:00
activationDelegate ? . tunnelActivationAttemptFailed ( tunnel : self , error : . failedBecauseOfTooManyErrors ( lastSystemError : lastError ! ) )
2018-10-31 14:58:03 +00:00
return
}
2018-12-14 23:12:59 +00:00
wg_log ( . debug , message : " startActivation: Entering (tunnel: \( name ) ) " )
2018-10-31 11:12:29 +00:00
2018-12-14 23:12:59 +00:00
status = . activating // E n s u r e t h a t n o o t h e r t u n n e l c a n a t t e m p t a c t i v a t i o n u n t i l t h i s t u n n e l i s d o n e t r y i n g
2018-12-13 18:04:00 +00:00
2018-12-12 18:28:27 +00:00
guard tunnelProvider . isEnabled else {
2018-10-31 11:12:29 +00:00
// I n c a s e t h e t u n n e l h a d g o t t e n d i s a b l e d , r e - e n a b l e a n d s a v e i t ,
// t h e n c a l l t h i s f u n c t i o n a g a i n .
2018-12-13 12:00:02 +00:00
wg_log ( . debug , staticMessage : " startActivation: Tunnel is disabled. Re-enabling and saving " )
2018-10-31 11:12:29 +00:00
tunnelProvider . isEnabled = true
2018-12-12 21:33:14 +00:00
tunnelProvider . saveToPreferences { [ weak self ] error in
2018-12-13 13:25:20 +00:00
guard let self = self else { return }
2018-12-12 18:28:27 +00:00
if error != nil {
2018-12-13 12:00:02 +00:00
wg_log ( . error , message : " Error saving tunnel after re-enabling: \( error ! ) " )
2018-12-17 13:26:26 +00:00
activationDelegate ? . tunnelActivationAttemptFailed ( tunnel : self , error : . failedWhileSaving ( systemError : error ! ) )
2018-10-31 11:12:29 +00:00
return
}
2018-12-22 01:21:07 +00:00
wg_log ( . debug , staticMessage : " startActivation: Tunnel saved after re-enabling, invoking startActivation " )
2018-12-17 05:51:25 +00:00
self . startActivation ( recursionCount : recursionCount + 1 , lastError : NEVPNError ( NEVPNError . configurationUnknown ) , activationDelegate : activationDelegate )
2018-10-31 11:12:29 +00:00
}
return
}
2018-10-30 11:04:46 +00:00
// S t a r t t h e t u n n e l
2018-10-31 11:12:29 +00:00
do {
2018-12-13 12:00:02 +00:00
wg_log ( . debug , staticMessage : " startActivation: Starting tunnel " )
2018-12-14 23:12:59 +00:00
isAttemptingActivation = true
2018-12-13 20:54:53 +00:00
let activationAttemptId = UUID ( ) . uuidString
self . activationAttemptId = activationAttemptId
try ( tunnelProvider . connection as ? NETunnelProviderSession ) ? . startTunnel ( options : [ " activationAttemptId " : activationAttemptId ] )
2018-12-13 12:00:02 +00:00
wg_log ( . debug , staticMessage : " startActivation: Success " )
2018-12-13 13:25:20 +00:00
activationDelegate ? . tunnelActivationAttemptSucceeded ( tunnel : self )
2018-12-12 18:28:27 +00:00
} catch let error {
2018-12-14 23:12:59 +00:00
isAttemptingActivation = false
2018-12-13 06:50:10 +00:00
guard let systemError = error as ? NEVPNError else {
2018-12-13 12:00:02 +00:00
wg_log ( . error , message : " Failed to activate tunnel: Error: \( error ) " )
2018-11-03 16:23:03 +00:00
status = . inactive
2018-12-17 13:26:26 +00:00
activationDelegate ? . tunnelActivationAttemptFailed ( tunnel : self , error : . failedWhileStarting ( systemError : error ) )
2018-10-31 11:12:29 +00:00
return
}
2018-12-13 06:50:10 +00:00
guard systemError . code = = NEVPNError . configurationInvalid || systemError . code = = NEVPNError . configurationStale else {
2018-12-13 12:00:02 +00:00
wg_log ( . error , message : " Failed to activate tunnel: VPN Error: \( error ) " )
2018-11-06 17:12:53 +00:00
status = . inactive
2018-12-17 13:26:26 +00:00
activationDelegate ? . tunnelActivationAttemptFailed ( tunnel : self , error : . failedWhileStarting ( systemError : systemError ) )
2018-11-06 17:12:53 +00:00
return
2018-10-31 11:12:29 +00:00
}
2018-12-13 12:00:02 +00:00
wg_log ( . debug , staticMessage : " startActivation: Will reload tunnel and then try to start it. " )
2018-12-12 21:33:14 +00:00
tunnelProvider . loadFromPreferences { [ weak self ] error in
2018-12-13 13:25:20 +00:00
guard let self = self else { return }
2018-12-12 18:28:27 +00:00
if error != nil {
2018-12-13 12:00:02 +00:00
wg_log ( . error , message : " startActivation: Error reloading tunnel: \( error ! ) " )
2018-12-13 13:25:20 +00:00
self . status = . inactive
2018-12-17 13:26:26 +00:00
activationDelegate ? . tunnelActivationAttemptFailed ( tunnel : self , error : . failedWhileLoading ( systemError : systemError ) )
2018-10-31 11:12:29 +00:00
return
}
2018-12-22 01:21:07 +00:00
wg_log ( . debug , staticMessage : " startActivation: Tunnel reloaded, invoking startActivation " )
2018-12-13 18:31:41 +00:00
self . startActivation ( recursionCount : recursionCount + 1 , lastError : systemError , activationDelegate : activationDelegate )
2018-10-29 00:30:31 +00:00
}
2018-10-26 09:25:20 +00:00
}
}
2018-10-27 13:00:07 +00:00
fileprivate func startDeactivation ( ) {
2019-02-07 13:23:37 +00:00
wg_log ( . debug , message : " startDeactivation: Tunnel: \( name ) " )
2018-12-12 21:33:14 +00:00
( tunnelProvider . connection as ? NETunnelProviderSession ) ? . stopTunnel ( )
2018-10-26 09:25:20 +00:00
}
}
2019-01-22 13:41:51 +00:00
extension NETunnelProviderManager {
2020-12-11 11:50:31 +00:00
private static var cachedConfigKey : UInt8 = 0
2019-04-05 07:59:17 +00:00
2019-01-22 13:41:51 +00:00
var tunnelConfiguration : TunnelConfiguration ? {
2019-02-04 20:30:33 +00:00
if let cached = objc_getAssociatedObject ( self , & NETunnelProviderManager . cachedConfigKey ) as ? TunnelConfiguration {
return cached
}
let config = ( protocolConfiguration as ? NETunnelProviderProtocol ) ? . asTunnelConfiguration ( called : localizedDescription )
if config != nil {
objc_setAssociatedObject ( self , & NETunnelProviderManager . cachedConfigKey , config , objc_AssociationPolicy . OBJC_ASSOCIATION_RETAIN_NONATOMIC )
}
return config
2019-01-22 13:41:51 +00:00
}
2019-02-08 16:05:58 +00:00
2019-02-07 12:48:04 +00:00
func setTunnelConfiguration ( _ tunnelConfiguration : TunnelConfiguration ) {
protocolConfiguration = NETunnelProviderProtocol ( tunnelConfiguration : tunnelConfiguration , previouslyFrom : protocolConfiguration )
localizedDescription = tunnelConfiguration . name
objc_setAssociatedObject ( self , & NETunnelProviderManager . cachedConfigKey , tunnelConfiguration , objc_AssociationPolicy . OBJC_ASSOCIATION_RETAIN_NONATOMIC )
}
2019-04-05 08:13:05 +00:00
func isEquivalentTo ( _ tunnel : TunnelContainer ) -> Bool {
2019-06-11 00:03:11 +00:00
return localizedDescription = = tunnel . name && tunnelConfiguration = = tunnel . tunnelConfiguration
2019-04-05 08:13:05 +00:00
}
2019-01-22 13:41:51 +00:00
}