57e60d08dc49iOS开发iOS - 网络处理

关于NEKit使用的一些心得

2017-07-16  本文已影响4015人  秦砖

花了一些时间终于把NEKit跑通了,ShadowSocks服务器也连上了,Google/Facebook/Twitter等墙外世界拥入眼中。甚至今天在简书上还无意中知道Pornhub这个东东,我去,忽然感觉被墙起来的世界也不错啊!废话不多说,具体说下跑NEKit中遇到的一些坑。

目标与参考资料

使用NEKit框架搭建ShadowSocks在iOS平台上的客户端,具体搭建流程参考了这篇文章。虽然很感谢这位作者的辛勤劳作,但还是要吐槽下:文章作者的思维跳跃幅度过大,且对一些基础性的东西末作更为透彻的说明,导致该文章参考难度过大,很容易跳进坑中而不知。

应用创建

首先创建一个普通的应用,主要功能就是打开关闭VPN,其UI界面大家自己随意,没什么可说的。

为实现全局VPN,我们还需要为应用添加一个Extension,
最新版本的xcode移除了该Extension的创建模板,所以我们需要下载并安装该模板

然后为VPN应用添加一个target,target选择xcode里新出现的Package Tunnrl Provider,设定Extension名称、选择开发语言为swift并继续,Extension就生成完毕了。创建生成的Extension有且仅有一个代码文件,是后面我们配置ShadowSocks代理的主体文件。

Extension生成模板

权限配置

权限是指VPN应用所需要的NeworkExtension使用权限。网上很多文章包括上面说的Demo都说该权限需要向Apple申请,且Apple审核的也比较严格,还给出了填写申请的地址。这是要强调的是:现在不用这么麻烦了,权限不需要申请了,直接在xcode里打开并使用就可以了。

在应用的两个target的Capabilities选项栏中,打开Personal VPN与Network Extensions两项。注意:是要在应用与Extension这两个target中都打开这两个开关。然后我们就可以在工程文件中看到新生成的两个entitlements文件了。

创建VPN并保存

上面那个demo里说的比较杂乱,通读一遍下来,可能完全get不到文章要表达的意识,或者get的比较模糊而不得要领。其实很简单:创建vpn管理类并保存VPN配置,加载配置后可以打开关闭VPN。

-(void)loadVpn
{
    [NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) {
        if (error == nil && [managers count] > 0) {
            self.manager = [managers firstObject];
            if ([managers count] > 1) {
                for (NETunnelProviderManager* manager in managers) {
                    [manager removeFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
                        if (error == nil) {
                            NSLog(@"remove dumplicate VPN config successful!");
                        }else{
                            NSLog(@"remove dumplicate VPN config failed with %@", error);
                        }
                    }];
                }
            }
        }
        if (self.manager == nil) {
            [self createVPN];
            [self saveVPN];
        }
    }];
}

-(void)createVPN
{
    self.manager = [[NETunnelProviderManager alloc] init];
    NETunnelProviderProtocol* config = [[NETunnelProviderProtocol alloc] init];
    config.serverAddress = @"DeepSS";
    NSString* rule = [self ruleConfig];
    config.providerConfiguration = @{@"ss_address":@"*.*.*.*", @"ss_port":@17238,@"ss_method":@"CHACHA20",@"ss_password":@"******",@"ymal_conf":rule};
    self.manager.protocolConfiguration = config;
    self.manager.localizedDescription = @"WXQ";
    self.manager.enabled = YES;
}

-(void)saveVPN
{
    [self.manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
        if (error == nil) {
            //注意这里保存配置成功后,一定要再次load,否则会导致后面StartVPN出异常
            [self.manager loadFromPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
                if (error == nil) {
                    NSLog(@"save vpn success");
                }
            }];
        }
    }];
}

在手机上运行后,应用就会在设置的vpn页中添加一项(该VPN页只有存在两项以上配置时才可进入查看,只有一项时不能进入,只可开关),参考如下图:


保存Extension

开关并监听VPN状态

开关VPN只需简单的调用NetworkExtension为我们提供的接口即可,NetworkExtension人在前面添加的Extension中将数据的控制权移交给我们,在Extension中我们可以决定手机上不同的数据使用不同的处理手段,进而达到翻墙的目的。

可以通过NEVPNStatusDidChangeNotification通知来获取当前VPN的状态。

-(instancetype)init
{
    if (self = [super init]) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(vpnStatusChanged:) name:NEVPNStatusDidChangeNotification object:nil];
        [self loadVpn];
    }
    return self;
}

-(void)startVPN
{
    NSError* error = nil;
    [self.manager.connection startVPNTunnelWithOptions:nil andReturnError:&error];
    if (error == nil) {
        NSLog(@"Start VPN success");
    }else{
        NSLog(@"%@", error);
    }
}

-(void)stopVPN
{
    [self.manager.connection stopVPNTunnel];
}

-(void)vpnStatusChanged:(id)sender
{
    switch (self.manager.connection.status) {
        case NEVPNStatusReasserting:
            NSLog(@"********************ReConnecting******************");
            break;
        case NEVPNStatusConnecting:
            NSLog(@"********************Connecting******************");
            break;
        case NEVPNStatusConnected:
            NSLog(@"********************Connected******************");
            break;
        case NEVPNStatusDisconnecting:
            NSLog(@"********************Disconnectiong******************");
            break;
        case NEVPNStatusDisconnected:
            NSLog(@"********************Disconnected******************");
            break;
        case NEVPNStatusInvalid:
            NSLog(@"********************Invalid******************");
            break;
        default:
            break;
    }
}

ShadowSocks相关

应该有同学注意到了,直到现在我们都没有用到NEKit!是的,没错,NEKit只在Extension中才会使用到。这里因为到网络相关知识了解不多,就将上面demo中Extension代码原样的copy过来了。主要内容是根据域名配置文件定义不同域名使用不同的代理模式:被墙的域名要使用NEkit提供的ShadowsocksAdapterFactory模式、没被墙的国内的域名使用DirectAdapterFactory模式等,大家参考下Demo中的代码。

Extension是不能调试的,上面的demo说可以用attach的方式来调试Extension代码,但我没有验证成功过。所以最好的调试方法是加LOG,LOG当然也不会在xcode的输出日志中出现,需要我们在window-Devices里查看机器的全量日志,是不是有点做Android开发的感觉!

NEKit

还没对该开源方案的具体原理深入研究过,只说下使用上的几个点。NEKit使用Carthage而不是cocoaPods,整个项目中还使用了另外九个开源swift方案,在编译时要将这九个项目的framework库拷贝到应用发布包里,所以需要在Build Phase中添加下图中的拷贝脚本:


拷贝脚本

后记

对swift不是很熟,Extension的开发语言第一选择是OC的,但后来发现NEKit提供的OC接口竟然无法完成ShadowSocks的代理开发。然后尝试对NEKit进行定制使其暴露足够的API给OC侧使用,又发现以目前的Swift水平无法完成这样的工作。只得放弃Extension使用OC开发的想法。幸运的是,系统支持应用使用OC,Extension使用Swift这种混合式编程。后续会将整理后的Demo发布到Github上。

上一篇下一篇

猜你喜欢

热点阅读