MacOS创建NetworkExtension 【保姆级流程】

news/2024/7/5 9:21:18

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】快速免费领取~


http://lihuaxi.xjx100.cn/news/1436060.html

相关文章

typeScript 之 运算符

工具&#xff1a; PlayGround 算术运算符 运算符描述加-减*乘/除%取模(求余)自增–自减 注意和--&#xff0c;实例&#xff1a; let value 0; console.log(value); //0, 先显示再增加后为1 console.log(value); //2&#xff0c;先增加后为2再显示关系运算符 运算符描述 …

K8S调度

K8S调度 一、List-Watch 机制 controller-manager、scheduler、kubelet 通过 List-Watch 机制监听 apiserver 发出的事件&#xff0c;apiserver 通过 List-Watch 机制监听 etcd 发出的事件1.scheduler 的调度策略 预选策略/预算策略&#xff1a;通过调度算法过滤掉不满足条件…

leetcode做题笔记69

给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。 由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。 注意&#xff1a;不允许使用任何内置指数函数和算符&#xff0c;例如 pow(x, 0.5) 或者 x ** 0.5 。 思路一&#xff…

Kubernetes(K8s)入门

一、Kubernetes是什么 Kubernetes是什么? 首先&#xff0c;它是一个全新的基于容器技术的分布式架构领先方案。这个方案虽然还很新&#xff0c;但它是谷歌十几年以来大规模应用容器技术的经验积累和升华的一个重要成果。确切地说&#xff0c;Kubernetes是谷歌严格保密十几年的…

【学习FreeRTOS】第6章——FreeRTOS中断管理

【本篇文章的也可参考STM32中断文章http://t.csdn.cn/foF9I&#xff0c;结合着学习效果更好】 1.什么是中断 中断&#xff1a;让CPU打断正常运行的程序&#xff0c;转而去处理紧急的事件&#xff08;程序&#xff09;&#xff0c;就叫中断中断执行机制&#xff0c;可简单概括…

新华三超融合态势感知标准版

产品概述&#xff1a; H3C SecCenter CSAP-XS 超融合态势感知一体机产品集合了态势感知和安全流量分析探针设备能无需复杂配置&#xff1b;态势感知平台具备强大的安全分析和可视化呈现功能&#xff1b;同时具备远程专家会诊功能&#xff0c;通过云端协同实现外部安全服务资源的…

1323:【例6.5】活动选择

【题目描述】 学校在最近几天有nn个活动&#xff0c;这些活动都需要使用学校的大礼堂&#xff0c;在同一时间&#xff0c;礼堂只能被一个活动使用。由于有些活动时间上有冲突&#xff0c;学校办公室人员只好让一些活动放弃使用礼堂而使用其他教室。 现在给出nn个活动使用礼堂的…

el-table :span-method=“arraySpanMethod“ 合并单元格 vue3

垂直方向上合并单元格 // 具体使用data就是接口获取的值表格数据 state.rowMergeArrs rowMergeHandle(state.needMergeArr, data) ); // :span-method"arraySpanMethod"const state reactive({needMergeArr: [{colName: "riskAreaId",mergeCheckNames…