您的当前位置:首页正文

iOS VPN

来源:图艺博知识网

苹果是从iOS 8开始,才开放了新的框架 NetworkExtension ,该框架提供了配置和控制VPN支持和wifi热点相关的接口。通过该框架,我们可以很方便的实现VPN以及wifi热点相应的功能
下面以IKEV2为例,话不多说,直接开始
————————————————————————————————————

1.首先打开程序VPN权限

屏幕快照 2017-09-08 下午3.38.26.png

2.去账号后台的APPID内确保权限开启

屏幕快照 2017-09-08 下午3.40.20.png

3.代码集成

在这里我们简单封装了一个管理类,主要提供一个简单的思路。
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。

Top