苹果是从iOS 8开始,才开放了新的框架 NetworkExtension ,该框架提供了配置和控制VPN支持和wifi热点相关的接口。通过该框架,我们可以很方便的实现VPN以及wifi热点相应的功能
下面以IKEV2为例,话不多说,直接开始
————————————————————————————————————
1.首先打开程序VPN权限
屏幕快照 2017-09-08 下午3.38.26.png2.去账号后台的APPID内确保权限开启
屏幕快照 2017-09-08 下午3.40.20.png3.代码集成
在这里我们简单封装了一个管理类,主要提供一个简单的思路。
h文件
#import <Foundation/Foundation.h>
@interface IkEV2Client : NSObject
/**
*VPN服务器地址
*/
@property(nonatomic,copy)NSString *serviceIP;
/**
*VPN PSK Password
*/
@property(nonatomic,copy)NSString *PSKPassword;
/**
*VPN 用户名
*/
@property(nonatomic,copy)NSString *userName;
/**
*VPN 用户密码
*/
@property(nonatomic,copy)NSString *passWord;
+ (IkEV2Client *)sharedMYSocketManager;
/**连接*/
-(void)startVPNConnect;
/**结束连接*/
-(void)endVPNConnect;
/**判断VPN是否在连接服务器*/
-(BOOL)isVPNConnectedService;
@end
M文件
#import "IkEV2Client.h"
#import <ifaddrs.h>
#import <NetworkExtension/NetworkExtension.h>
@interface IkEV2Client ()
@property (nonatomic, strong) NEVPNManager *manage;
@end
@implementation IkEV2Client
+ (IkEV2Client *)sharedMYSocketManager
{
static dispatch_once_t onceToken;
static IkEV2Client *instance;
dispatch_once(&onceToken, ^{
instance = [[IkEV2Client alloc] init];
});
return instance;
}
-(instancetype)init{
self = [super init];
if (self) {
self.manage = [NEVPNManager sharedManager];
}
return self;
}
-(void)startVPNConnect
{
[self prepareVPNConnectWithCompleteHandle:^(NSError *error) {
if(error) {
NSLog(@"Save error1111: %@", error);
}
else {
NSLog(@"Saved!");
NSError *error = nil;
//开始连接
[self.manage.connection startVPNTunnelAndReturnError:&error];
if(error) {
NSLog(@"Start error2222: %@", error.localizedDescription);
[self startVPNConnect];
}
else
{
NSLog(@"Connection established!");
}
}
}];
}
-(void)endVPNConnect
{
//重置配置的时候会断开VPN连接
[self prepareVPNConnectWithCompleteHandle:nil];
}
-(void)prepareVPNConnectWithCompleteHandle:(void(^)(NSError *error))complete{
[self.manage loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
NSError *errors = error;
if (errors) {
NSLog(@"错误:%@",errors);
}
else{
NEVPNProtocolIKEv2 *p = [[NEVPNProtocolIKEv2 alloc] init];
//用户名
p.username = self.userName;
//服务器地址
p.serverAddress = self.serviceIP;
//密码
[self createKeychainValue:self.passWord forIdentifier:@"VPN_PASSWORD"];
p.passwordReference = [self searchKeychainCopyMatching:@"VPN_PASSWORD"];
//共享秘钥 可以和密码同一个.
[self createKeychainValue:self.PSKPassword forIdentifier:@"PSK"];
p.sharedSecretReference = [self searchKeychainCopyMatching:@"PSK"];
//本地id(非必填)
p.localIdentifier = @"";
//远程id,默认为服务器ip
p.remoteIdentifier = self.serviceIP;
// p.disableMOBIKE=YES;//此属性开启,将会维持当前网络状态,系统将不切换网络环境
// /*! @const NEVPNIKEAuthenticationMethodNone Do not authenticate with the IPSec server */
// NEVPNIKEAuthenticationMethodNone = 0,
/*! @const NEVPNIKEAuthenticationMethodCertificate Use a certificate and private key as the authentication credential */
// NEVPNIKEAuthenticationMethodCertificate = 1,证书认证(如果选用这个,需要引导用户手动导入证书,导入方式:邮件发送或者safari打开证书所在链接)
/*! @const NEVPNIKEAuthenticationMethodSharedSecret Use a shared secret as the authentication credential */
// NEVPNIKEAuthenticationMethodSharedSecret = 2,共享秘钥认证
p.authenticationMethod = NEVPNIKEAuthenticationMethodNone;//认证模式
p.useExtendedAuthentication = YES;
p.disconnectOnSleep = YES;//手机休眠是否断开VPN(节电)
//配置规则(非必需)
//***************************************************
// self.manage.onDemandEnabled = YES;
// NSMutableArray *rules = [[NSMutableArray alloc] init];
// NEOnDemandRuleDisconnect *connectRule = [NEOnDemandRuleDisconnect new];
// connectRule.interfaceTypeMatch=NEOnDemandRuleInterfaceTypeWiFi;
// [rules addObject:connectRule];
//*****************************************************
[self.manage setOnDemandRules:[NSArray arrayWithArray:rules]];
[self.manage setProtocolConfiguration:p];
self.manage.localizedDescription = @"WoVPNForStore";
self.manage.enabled = true;
[self.manage saveToPreferencesWithCompletionHandler:complete];
}
}];
- (NSData *)searchKeychainCopyMatching:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
[searchDictionary setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[searchDictionary setObject:@YES forKey:(__bridge id)kSecReturnPersistentRef];
CFTypeRef result = NULL;
SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary, &result);
return (__bridge_transfer NSData *)result;
}
- (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
// creat a new item
NSMutableDictionary *dictionary = [self newSearchDictionary:identifier];
//OSStatus 就是一个返回状态的code 不同的类返回的结果不同
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)dictionary);
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
[dictionary setObject:passwordData forKey:(__bridge id)kSecValueData];
status = SecItemAdd((__bridge CFDictionaryRef)dictionary, NULL);
if (status == errSecSuccess) {
return YES;
}
return NO;
}
- (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier {
// keychain item creat
NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
// extern CFTypeRef kSecClassGenericPassword 一般密码
// extern CFTypeRef kSecClassInternetPassword 网络密码
// extern CFTypeRef kSecClassCertificate 证书
// extern CFTypeRef kSecClassKey 秘钥
// extern CFTypeRef kSecClassIdentity 带秘钥的证书
[searchDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
[searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrGeneric];
//ksecClass 主键
[searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrAccount];
[searchDictionary setObject:self.serviceIP forKey:(__bridge id)kSecAttrService];
return searchDictionary;
}
-(BOOL)isVPNConnectedService
{
NSError *error;
NSURL *ipURL = [NSURL URLWithString:@"http://ipof.in/txt"];
NSString *ip = [NSString stringWithContentsOfURL:ipURL encoding:NSUTF8StringEncoding error:&error];
if ([ip isEqualToString:self.serviceIP]) {
return YES;
}else{
return NO;
}
}
说下原理:开启一个NEVPNManager,根据需要触发startVPNConnect(开始连接方法),首先调用loadFromPrefreferencesWithCompletionHandler函数加载相关配置,如果没有发生错误,开始创建配置文件在prepareVPNConnectWithCompleteHandle:(void(^)(NSError *error))complete方法内体现,具体参数说明,注释里讲的很清楚了。创建完成后,通过saveToPreferencesWithCompletionHandler保存配置。然后这个配置文件就会写入到手机系统内。
需要解释的地方:
//密码
[self createKeychainValue:self.passWord forIdentifier:@"VPN_PASSWORD"];
p.passwordReference = [self searchKeychainCopyMatching:@"VPN_PASSWORD"];
//共享秘钥 可以和密码同一个.
[self createKeychainValue:self.PSKPassword forIdentifier:@"PSK"];
p.sharedSecretReference = [self searchKeychainCopyMatching:@"PSK"];
密码和共享密钥需要保存到钥匙串内,这一段,是创建钥匙串,放入密码和共享密钥。
self.manage.onDemandEnabled = YES;
NSMutableArray *rules = [[NSMutableArray alloc] init];
NEOnDemandRuleDisconnect *connectRule = [NEOnDemandRuleDisconnect new];
connectRule.interfaceTypeMatch=NEOnDemandRuleInterfaceTypeWiFi;
[rules addObject:connectRule];
有时候,可能要配置链接或断开规则,链接对应NEOnDemandRuleConnect类,断开对应NEOnDemandRuleDisconnect类,在这两个类下配置对应规则,可以匹配条件,实现VPN自动链接或关闭。可匹配的条件包括网络状态,probeURL,DNS地DNSServerAddressMatch,SSIDMatch,.......具体可以查看系统NetworkExtension框架下的NEOnDemandRule.h文件。
这里配置的规则是检测当前网络是wifi环境,VPN自动断开。这里只是提供一个方式,具体业务可根据需要自己调整。
{
NSError *error;
NSURL *ipURL = [NSURL URLWithString:@"http://ipof.in/txt"];
NSString *ip = [NSString stringWithContentsOfURL:ipURL encoding:NSUTF8StringEncoding error:&error];
if ([ip isEqualToString:self.serviceIP]) {
return YES;
}else{
return NO;
}
}
这一段是检测当前是否是通过代理上网。原理是获取当前公网IP,如果正通过代理上网,公网ip应该为我们自己的代理服务器ip。