Last active
August 2, 2017 06:28
-
-
Save icodesign/d7d0b605801c6be39901cb2a47bfc87c to your computer and use it in GitHub Desktop.
VPNManager
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "VPNManager.h" | |
#import <NetworkExtension/NetworkExtension.h> | |
NSString * const VPNManagerStatusDidChanged = @"VPNManagerStatusDidChanged"; | |
@interface VPNManager () | |
@property (nonatomic) NEVPNStatus status; | |
@end | |
@implementation VPNManager | |
+ (VPNManager *)shared { | |
static VPNManager *manager; | |
static dispatch_once_t onceToken; | |
dispatch_once(&onceToken, ^{ | |
manager = [VPNManager new]; | |
}); | |
return manager; | |
} | |
- (void)setupVPNStatus:(void (^)())completion { | |
[VPNManager getVPN:^(NETunnelProviderManager *manager) { | |
[self updateVPNStatus:manager]; | |
[self addVPNStatusObserver:manager]; | |
if (completion) { | |
completion(); | |
} | |
}]; | |
} | |
- (void)startVPN:(void (^)(NSError *))completion { | |
[VPNManager startVPNWithOptions:nil completion:completion]; | |
} | |
- (void)stopVPN:(void (^)(NSError *))completion { | |
[VPNManager stopVPN:completion]; | |
} | |
- (void)switchVPN:(void (^)(NSError *error))completion { | |
switch (self.status) { | |
case NEVPNStatusInvalid: | |
case NEVPNStatusDisconnected: | |
[self startVPN:completion]; | |
break; | |
case NEVPNStatusConnected: | |
[self stopVPN:completion]; | |
break; | |
default: | |
return ; | |
} | |
} | |
- (void)updateVPNStatus:(NETunnelProviderManager *)manager { | |
self.status = manager.connection.status; | |
} | |
- (void)addVPNStatusObserver:(NETunnelProviderManager *)manager { | |
[[NSNotificationCenter defaultCenter] removeObserver:self]; | |
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleVPNStatusChanged:) name:NEVPNStatusDidChangeNotification object:manager.connection]; | |
} | |
- (void)handleVPNStatusChanged:(NSNotification *)notification { | |
[VPNManager getVPN:^(NETunnelProviderManager *manager) { | |
[self updateVPNStatus:manager]; | |
}]; | |
} | |
- (void)setStatus:(NEVPNStatus)status { | |
_status = status; | |
[[NSNotificationCenter defaultCenter] postNotificationName:VPNManagerStatusDidChanged object:nil]; | |
} | |
- (NSString *)statusString { | |
return [VPNManager statusString:self.status]; | |
} | |
+ (void)getVPN:(void (^)(NETunnelProviderManager *))completion { | |
[NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) { | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
if (completion) { | |
completion(managers.firstObject); | |
} | |
}); | |
}]; | |
} | |
+ (void)createVPNIfNeeded:(void (^)(NSError *))completion { | |
[self getVPN:^(NETunnelProviderManager *manager) { | |
if (manager) { | |
if (completion) { | |
completion(nil); | |
} | |
} else { | |
NETunnelProviderManager *manager = [[NETunnelProviderManager alloc] init]; | |
[VPNManager updateVPNConfig:manager]; | |
[manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) { | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
[[VPNManager shared] setupVPNStatus:nil]; | |
if (completion) { | |
completion(error); | |
} | |
}); | |
}]; | |
} | |
}]; | |
} | |
+ (void)resetVPN:(void (^)(NSError *))completion { | |
[self getVPN:^(NETunnelProviderManager *manager) { | |
if (!manager) { | |
[self createVPNIfNeeded:completion]; | |
} else { | |
[manager removeFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) { | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
[self createVPNIfNeeded:completion]; | |
}); | |
}]; | |
} | |
}]; | |
} | |
+ (void)startVPNWithOptions: (NSDictionary<NSString *, NSObject *> *)options completion:(void (^)(NSError *))completion { | |
[self createVPNIfNeeded:^(NSError *error) { | |
if (error) { | |
if (completion) { | |
completion(error); | |
} | |
} else { | |
[self enableVPN:^(NSError *error) { | |
if (error) { | |
if (completion) { | |
completion(error); | |
} | |
} else { | |
[self getVPN:^(NETunnelProviderManager *manager) { | |
[((NETunnelProviderSession *)manager.connection) stopTunnel]; | |
NSError *error = nil; | |
[((NETunnelProviderSession *)manager.connection) startTunnelWithOptions:options andReturnError:&error]; | |
if (completion) { | |
completion(error); | |
} | |
}]; | |
} | |
}]; | |
} | |
}]; | |
} | |
+ (void)ping { | |
[self getVPN:^(NETunnelProviderManager *manager) { | |
if (!manager) { | |
return; | |
} | |
[((NETunnelProviderSession *)manager.connection) sendProviderMessage:[[NSData alloc] init] returnError:nil responseHandler:nil]; | |
}]; | |
} | |
+ (void)stopVPN:(void (^)(NSError *error))completion { | |
[self getVPN:^(NETunnelProviderManager *manager) { | |
if (manager) { | |
[((NETunnelProviderSession *)manager.connection) stopTunnel]; | |
} else { | |
if (completion) { | |
completion([NSError errorWithDomain:NSCocoaErrorDomain code:1000 userInfo:@{NSLocalizedDescriptionKey: @"No NETunnelProviderManager exists"}]); | |
} | |
} | |
}]; | |
} | |
+ (void)enableVPN:(void (^)(NSError *error))completion { | |
[self getVPN:^(NETunnelProviderManager *manager) { | |
if (manager) { | |
[VPNManager updateVPNConfig:manager]; | |
manager.enabled = YES; | |
[manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) { | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
if (completion) { | |
completion(error); | |
} | |
}); | |
}]; | |
} else { | |
if (completion) { | |
completion([NSError errorWithDomain:NSCocoaErrorDomain code:1000 userInfo:@{NSLocalizedDescriptionKey: @"No NETunnelProviderManager exists"}]); | |
} | |
} | |
}]; | |
} | |
+ (void)updateVPNConfig:(NETunnelProviderManager *)manager { | |
NETunnelProviderProtocol *protocol = [[NETunnelProviderProtocol alloc] init]; | |
protocol.serverAddress = AppEnv.appName; | |
protocol.disconnectOnSleep = NO; | |
protocol.providerBundleIdentifier = ONTunnelIdentifier; | |
manager.protocolConfiguration = protocol; | |
manager.localizedDescription = AppEnv.appName; | |
manager.onDemandEnabled = NO; | |
} | |
+ (NSString *)statusString:(NEVPNStatus)status { | |
switch (status) { | |
case NEVPNStatusInvalid: | |
case NEVPNStatusDisconnected: | |
return @"Disconnected"; | |
case NEVPNStatusConnected: | |
return @"Connected"; | |
case NEVPNStatusConnecting: | |
case NEVPNStatusReasserting: | |
return @"Connecting..."; | |
case NEVPNStatusDisconnecting: | |
return @"Disconnecting..."; | |
default: | |
return nil; | |
} | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment