Skip to content

Instantly share code, notes, and snippets.

@icodesign
Last active August 2, 2017 06:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save icodesign/d7d0b605801c6be39901cb2a47bfc87c to your computer and use it in GitHub Desktop.
Save icodesign/d7d0b605801c6be39901cb2a47bfc87c to your computer and use it in GitHub Desktop.
VPNManager
#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