MacOS创建NetworkExtension (保姆级流程)

因为自己工作中的项目,是运行在macos系统上,其中的一部分功能是通过NetworkExtension来获取系统中的流量来做相应的处理,所以也想自己创建一个NetworkExtension,三天,不知道踩了多少坑,才真正的把整个流程弄明白,网上关于SystemExtension部分的资料少之又少,没有一个比较完全的extension的创建流程,所以写这篇文章,代码实现使用Objectiv-C。

1.创建App

首先,创建一个App,因为我们的NetwokrExtension需要在app目录下才能被启动。

这里,只填个项目名就可以了,

然后,MyApp就创建好了。

2.创建NetworkExtension

接下来,我们要创建一个SystemExtension,一定要跟着下面的步骤,不要选错。

点击上图的+号,
选择macos->SystemExtension->NetworkExtension,这里一定要选择SystemExtension下的NetworkExtension,之前我一直选的是App Extension下的NetworkExtension,导致配置一直出问题,困扰了我很久。

ProductName我们自己起一个,Provider Type这里有几种选择,我们选择其中一个,就会自动创建对应的函数让你重写,这里我选择Filter Packet,就是对网络包做过滤,语言我选择OC。

创建完成之后,左侧的文件列表长这个样子

xcode帮我们创建了一个FilterPacketProvider类,继承自NEFilterPacketProvider

在FilterPacketProvider.m中,出现了两个函数,startFilterWithCompletionHandlerstopFilterWithReason,extension在启动和关闭extension时,会调用到这两个函数。我们如果对包需要做一些block,都需要将处理逻辑写在这个self.packetHandler函数中,每当有packet过来时,都会进入到这个packet函数中做判断,得到判决的结果。

3.配置App及NetworkExtension项目

现在直接编译,编译是不通过的,我们查看编译器报错

这里,是说我们需要给target设置开发证书以及签名,需要在Signing & Capabilities中,填写你的Bundle Identifier,以及选择你的Provisioning Profile,这些如果你没有的话,需要向苹果进行申请,这里就不讲如何申请了,默认大家都有。
在MyApp和MyNe中都要填入这些数据,取消勾选Automatically manage signing

在MyApp target中,点击左上角的Capability选项,添加App GroupsSystem Extension

然后,下面出现我们选择的这两项,在App Groups中,我们填一个group,这个值通常是$(TeamIdentifierPrefix)$(PRODUCT_BUNDLE_IDENTIFIER),System Extension部分不需要填什么。

在MyNe target中,确认在Signing & Capabilities中,确认NetworkExtension存在,并且至少勾选了Content Filter,如果没有确认NetworkExtension存在的话,也在左上角的Capability中搜索并添加上NetworkExtension。

接下来,需要修改Info.plist,它是NetworkExtension的配置文件,点击info,

这些配置基本不需要动,有几项需要检查一下,NetworkExtension这个dict中,NEMachServiceName最好和App Groups保持一致,这个NEMachServiceName是App在启动extension的关键信息。检查NEProviderClasses中,value是否和你的Provider类名是一样的,正常情况下,NEProviderClasses不需要动。

这样一番操作下来,编译就可以通过了。

然后我们进入到MyApp中,在Contents/Library/SystemExtensions/中应该可以找到我们创建的extension

检查extension的info.plist,其中,CFBundleType的值应该是SYSX,如果是其他值,很有可能是前面创建extension的时候创建错了。

4.代码部分

1.继承来继承OSSystemExtensionRequestDelegate

在创建好工程之后,就需要我们通过App来激活Extension,下面就是代码实现的基本逻辑。
在App中,创建文件创建一个RequestDelegate, 来继承OSSystemExtensionRequestDelegate,并重写下面的方法。

- (void)request:(nonnull OSSystemExtensionRequest *)request
    didFinishWithResult:(OSSystemExtensionRequestResult)result;

- (void)request:(nonnull OSSystemExtensionRequest *)request
    didFailWithError:(nonnull NSError *)error;

- (void)requestNeedsUserApproval:(nonnull OSSystemExtensionRequest *)request;


- (OSSystemExtensionReplacementAction)request:(nonnull OSSystemExtensionRequest *)request actionForReplacingExtension:(nonnull OSSystemExtensionProperties *)existing withExtension:(nonnull OSSystemExtensionProperties *)ext;
  • 当请求完成时,第一个方法会被调用,里面会传入请求的结果。
  • 当请求失出现错误时,第二个方法会被调用
  • 当需要用户进行授权时,第三个方法会被调用
  • 当已经有对应的extension启动过了,第四个方法会被调用,告诉程序是替换还是用旧的。

2.激活SystemExtension

在RequestDelegate类中创建一个方法用来进行下列流程

  1. 发送OSSystemExtensionRequest来给系统发送激活请求


    这个方法需要两个参数,一个参数是bundleId,这个参数对应你的extension的info.list中的CFBundleIdentifier,位于extension目录下的Contents/info.plist,也就是前面配置工程时填的Bundle Identifier。第二个参数是一个queue,可以通过dispatch_queue_create来创建。
OSSystemExtensionRequest*request = [OSSystemExtensionRequest activationRequestForExtension:bundleId queue:workQueue];

request.delegate = self; //self指RequestDelegate对象
[[OSSystemExtensionManager sharedManager] submitRequest:request];
  1. 收到请求完成的信息


    在我们重写的这个函数中,会将请求的结果告诉我们,通过result,可以知道请求的状态,例如完成/需要重启/未知错误,在请求完成的状态下,进行第三步,也就是startLoadPreferences
- (void)request:(nonnull OSSystemExtensionRequest *)request
    didFinishWithResult:(OSSystemExtensionRequestResult)result {
    dispatch_async(workQueue, ^{
        NSLog(@"get activate request callback");
        switch (result) {
            case OSSystemExtensionRequestCompleted:{
                NSLog(@"request completed");
                [self startLoadPreferences];
                break;
            }
            case OSSystemExtensionRequestWillCompleteAfterReboot:{
                NSLog(@"request will complete after rebot");
                break;
            }
            default:{
                NSLog(@"request get unknown result:%ld", result);
                break;
            }
        }
    });
    NSLog(@"activate finish");
}
  1. 加载已有的SystemExtension的配置


    loadFromPreferencesWithCompletionHandler从当前网络扩展的配置中加载设置,加载完成后会调用回调函数,通知加载结果。
- (void) startLoadPreferences {
    NSLog(@"start load preferences");
    
    manager.enabled = NO;
    [[NEFilterManager sharedManager] loadFromPreferencesWithCompletionHandler:^(NSError * error) {
        dispatch_async(self->workQueue, ^{
            if(error){
                NSLog(@"preferences load failed");
                return;
            }
            NSLog(@"preferences load success");
            [self startSavePerferences];
        });
    }];
    sleep(5);
}
  1. 保存SystemExtension的配置


    这里我们开启filterPackets,同时,需要显式的将filterSockets关闭。startPhase3是在保存配置成功后做的一些其他操作,通常是进行XPC连接,这部分可以先不实现。
- (void) startSavePerferences{
    NSLog(@"start save perferences");
    if (manager.providerConfiguration == nil) {
        NSLog(@"set provider configuration");
        manager.providerConfiguration = [[NEFilterProviderConfiguration alloc] init];
    }
    manager.providerConfiguration.vendorConfiguration = [[NSMutableDictionary alloc] init];
    manager.providerConfiguration.filterPackets = true;
    manager.providerConfiguration.filterSockets = false;
    manager.localizedDescription = @"com.trendmicro.icore.netfilter";
    manager.enabled = YES;
    [manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
        dispatch_async(self->workQueue, ^{
            if(error) {
                NSLog(@"save perferences failed");
            }else {
                NSLog(@"save perferences success");
            }
            [self startPhase3];
        });
    }];
}

完整的RequestDelegate代码
RequestDelegate.h

#import <Foundation/Foundation.h>
#import <SystemExtensions/SystemExtensions.h>


NS_ASSUME_NONNULL_BEGIN

@interface RequestDelegate : NSObject<OSSystemExtensionRequestDelegate>

-(instancetype) init;

-(void)startActivateExt:(BOOL)deactive;
- (void)startLoadPreferences;

-(void)registerWithProvider;
@end

NS_ASSUME_NONNULL_END
#import "RequestDelegate.h"
#import <NetworkExtension/NetworkExtension.h>

RequestDelegate.m

@implementation RequestDelegate
{
    NEFilterManager *manager;
    dispatch_queue_t workQueue;
    dispatch_queue_t queue;
    NSBundle* bundle;
    NSString* bundleId;
}

-(instancetype) init {
    self = [super init];
    NSLog(@"init");
    bundleId = @"your boundle id";
    bundle = [self GetSysBundle:bundleId];
    
    manager = [NEFilterManager sharedManager];
    workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
    return self;
}

- (NSBundle*) GetSysBundle:(NSString*) bundleId {
    NSString* path = [NSString stringWithFormat:@"Contents/Library/SystemExtensions/%@.systemextension", bundleId];
    NSURL* url = [NSURL fileURLWithPath:path isDirectory:YES relativeToURL:NSBundle.mainBundle.bundleURL];
    NSBundle* bundle = [NSBundle bundleWithURL:url];
    return bundle;
}

- (NSString *)GetNetExtMachService:(NSBundle *)bundle {
  NSString* keyNE = @"NetworkExtension";
  NSString*  keyMach = @"NEMachServiceName";
  NSDictionary *ne = [bundle objectForInfoDictionaryKey:keyNE];
  NSString *mach = ne[keyMach];
  return mach;
}

-(void)startActivateExt:(BOOL)deactive{
    
    NSLog(@"start activate");
    OSSystemExtensionRequest* request = NULL;
    if(deactive) {
        NSLog(@"deactive");
        request = [OSSystemExtensionRequest deactivationRequestForExtension:bundleId queue:workQueue];
    }else{
        NSLog(@"active");
        request = [OSSystemExtensionRequest activationRequestForExtension:bundleId queue:workQueue];
    }
   

    request.delegate = self;
    [[OSSystemExtensionManager sharedManager] submitRequest:request];
    
}

- (void)request:(nonnull OSSystemExtensionRequest *)request
    didFinishWithResult:(OSSystemExtensionRequestResult)result {
    dispatch_async(workQueue, ^{
        NSLog(@"get activate request callback");
        switch (result) {
            case OSSystemExtensionRequestCompleted:{
                NSLog(@"request completed");
                [self startLoadPreferences];
                break;
            }
            case OSSystemExtensionRequestWillCompleteAfterReboot:{
                NSLog(@"request will complete after rebot");
                break;
            }
            default:{
                NSLog(@"request get unknown result:%ld", result);
                break;
            }
        }
    });
    NSLog(@"activate finish");
}

- (void)request:(nonnull OSSystemExtensionRequest *)request
    didFailWithError:(nonnull NSError *)error {
    NSLog(@"request fail: %@", error.description);
}

- (void)requestNeedsUserApproval:(nonnull OSSystemExtensionRequest *)request {
  NSLog(@"request need approval");
  @synchronized(self) {
  }
}

- (OSSystemExtensionReplacementAction)request:(nonnull OSSystemExtensionRequest *)request actionForReplacingExtension:(nonnull OSSystemExtensionProperties *)existing withExtension:(nonnull OSSystemExtensionProperties *)ext {
    NSLog(@"replace old extension");
    return OSSystemExtensionReplacementActionReplace;
}

- (void) startLoadPreferences {
    NSLog(@"start load preferences");
    
    manager.enabled = NO;
    [[NEFilterManager sharedManager] loadFromPreferencesWithCompletionHandler:^(NSError * error) {
        dispatch_async(self->workQueue, ^{
            if(error){
                NSLog(@"preferences load failed");
                return;
            }
            NSLog(@"preferences load success");
            [self startSavePerferences];
        });
    }];
    sleep(5);

}

- (void) startSavePerferences{
    NSLog(@"start save perferences");
    if (manager.providerConfiguration == nil) {
        NSLog(@"set provider configuration");
        manager.providerConfiguration = [[NEFilterProviderConfiguration alloc] init];
    }
    manager.providerConfiguration.vendorConfiguration = [[NSMutableDictionary alloc] init];
    manager.providerConfiguration.filterPackets = true;
    manager.providerConfiguration.filterSockets = false;
    manager.localizedDescription = @"com.trendmicro.icore.netfilter";
    manager.enabled = YES;
    [manager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable error) {
        dispatch_async(self->workQueue, ^{
            if(error) {
                NSLog(@"save perferences failed");
            }else {
                NSLog(@"save perferences success");
            }
            [self startPhase3];
        });
    }];
}

- (void) startPhase3 {
    NSLog(@"enter phase3");
    NSLog(@"finish phase3");
}

@end

#import <Cocoa/Cocoa.h>
#include <CoreFoundation/CoreFoundation.h>
#import "RequestDelegate.h"

app的main.m

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        bool deactive = true;
        if(argc == 1) {
            deactive = false;
        }
        RequestDelegate* delegate =  [[RequestDelegate alloc] init];
        [delegate startActivateExt:deactive];
        dispatch_main();
    }   
}

运行

将app整体拷贝到/Applications下面,直接在命令行中运行Contents/MacOS下的可执行程序就可以启动我们自己的NetworkExtension了。
下一篇文章将会分享几个工具来观测我们的NetworkExtension的状态。

《C++ Primer》《Effective C++》是C++开发者必不可少的书籍,如果你想入门C++,以及想要精进C++开发技术,这两本书可以说必须要有。此外,《Linux高性能服务器编程》以及《Linux多线程服务端编程:使用muduo C++网络库》.(陈硕)》是快速提高你的linux开发能力的秘籍。《大话设计模式》可以增强我们的模型提取及设计能力,写出更优雅的代码。同时,《操作系统导论》更是开发必读书目,在网上搜索相关资源也要花费一些力气,需要的同学可以关注公众号【程序员DeRozan】,回复【1207】快速免费领取~

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐