MacOS创建NetworkExtension 【保姆级流程】

news2024/12/27 16:17:20

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://www.coloradmin.cn/o/865958.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

世微AP2400 电动车 摩托车灯照明 汽车灯照明 手电筒照明LED灯降压恒流驱动IC

PCB 布板参考 1. 大电流路径走线要粗&#xff0c;铺铜走线比较好。 2. 大电路回路面积以最短、最宽路径完成比较好。 3. 开关切换连接点&#xff1a;电感 L、开关管漏级与续流肖特基二极管&#xff0c;走线要短与粗&#xff0c;铺铜走线比较好&#xff0c;但同时需要适当面积作…

Redis_分片集群

10. 分片集群 10.1简介 业务场景&#xff0c;需要存储50G的数据。对于内存和硬盘配置不足&#xff0c;选用两种方式 一种&#xff1a;纵向扩展&#xff1a;加内存&#xff0c;加硬盘&#xff0c;提高CPU。简单、直接。RDB存储效率要考虑。成本要考虑。二种&#xff1a;横向扩…

C# Linq源码分析之Take方法

概要 Take方法作为IEnumerable的扩展方法&#xff0c;具体对应两个重载方法。本文主要分析第一个接收整数参数的重载方法。 源码解析 Take方法的基本定义 public static System.Collections.Generic.IEnumerable Take (this System.Collections.Generic.IEnumerable source…

【Tomcat】tomcat的多实例和动静分离

多实例&#xff1a; 在一台服务器上有多台Tomcat&#xff1b;就算是多实例 安装telnet服务&#xff0c;可以用来测试端口通信是否正常 yum -y install telnettelnet 192.168.220.112 80 tomcat的日志文件 cd /usr/local/tomcat/logsvim catalina.out Tomcat多实例部署&…

一定要会用selenium的等待,3种等待方式解读

很多人问&#xff0c;这个下拉框定位不到、那个弹出框定位不到…各种定位不到&#xff0c;其实大多数情况下就是两种问题&#xff1a; 有frame 没有加等待 殊不知&#xff0c;你的代码运行速度是什么量级的&#xff0c;而浏览器加载渲染速度又是什么量级的&#xff0c;就好比…

CEC2009无约束多目标测试集(UF1-UF10、附Matlab代码)

目录 一、CEC2009无约束多目标测试集 二、CEC2009无约束多目标测试集UF1-UF10Matlab代码 三、多目标灰狼算法测试CEC2009无约束多目标测试集 一、CEC2009无约束多目标测试集 二、CEC2009无约束多目标测试集UF1-UF10Matlab代码 % cec09.m % The Matlab version of the test i…

C# Winform DataGridView 数据刷新问题

目录 一、问题 二、创建项目 三、绑定空的数据源 四、绑定有数据的数据源 五、修改绑定的数据源 六、解决数据源刷新问题 七、解决刷新数据界面闪烁 一、问题 DataGridView 是比较常用的表格控件&#xff0c;在 DataGridView 中显示数据&#xff0c; 一般使用 dataGrid…

自动化更新导致的各种问题解决办法

由于最近自动化频频更新导致出现各种问题&#xff0c;因此在创建驱动对象代码时改成这种方式 我最近就遇到了由于更新而导致的代码报错&#xff0c;报错信息如下&#xff1a; 复制内容如下&#xff1a; Exception in thread “main” org.openqa.selenium.remote.http.Connecti…

搭建了个腾讯滑块服务,直接取ticket的,仅供测试.

最近闲着没事搭建了个TX滑块验证码服务,C#写的. 接口是rest接口 提交任务POST http://47.104.132.20:19090/task/addTask 提交数据: { "url": "https://ssl.captcha.qq.com/template/wireless_mqq_captcha.html?stylesimple&aid16&uin3557247785…

html实现iphone同款开关

一、背景 想实现一个开关的按钮&#xff0c;来触发一些操作&#xff0c;网上找了总感觉看着别扭&#xff0c;忽然想到iphone的开关挺好&#xff0c;搞一个 二、代码实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8&qu…

内生性的蒙特卡罗模拟

这是一个很好的例子,通过蒙特卡洛模拟展示了忽略相关变量时,回归系数估计的偏差。 %% 蒙特卡洛模拟&#xff1a;内生性会造成回归系数的巨大误差 times 300; % 蒙特卡洛的次数 R zeros(times,1); % 用来储存扰动项u和x1的相关系数 K zeros(times,1); % 用来储存遗漏了x2…

利用自动校对软件优化新闻稿件的拼写和语法

利用自动校对软件优化新闻稿件的拼写和语法&#xff0c;您可以按照以下步骤进行&#xff1a; 1.选择适合的校对软件&#xff1a;市场上有多种拼写和语法校对软件可供选择。根据您的需求和预算&#xff0c;选择一个功能强大且适合新闻稿件的软件。 2.导入稿件&#xff1a;将待校…

msvcr110.dll文件丢失的解决方法有哪些?高效率修复msvcr110.dll缺失

msvcr110.dll是一个重要的系统动态链接库&#xff08;DLL&#xff09;文件&#xff0c;它在Windows操作系统中起到关键的作用。如果出现msvcr110.dll文件缺失的情况&#xff0c;可能会导致应用程序无法正常运行、错误提示以及系统不稳定性等问题。这时候我们就需要去解决它了&a…

freeswitch的mod_xml_curl模块动态获取configuration

概述 freeswitch是一款简单好用的VOIP开源软交换平台。 mod_xml_curl模块支持从web服务获取xml配置&#xff0c;本文介绍如何动态获取acl配置。 环境 centos&#xff1a;CentOS release 7.0 (Final)或以上版本 freeswitch&#xff1a;v1.6.20 GCC&#xff1a;4.8.5 web…

SysML V1.2 Model Elements

一、概述 SysML的ModelElements包定义了可以显示在多种SysML图类型上的通用构造。这些包括包、模型、各种类型的依赖(例如&#xff0c;导入、访问、细化、实现)、约束和注释。本章中定义的包图用于通过将模型元素划分为可打包的元素并在包内建立包和&#xff08;/或&#xff0…

【启明智显分享】基于开阳ARK630HV100的车规开发板

基于开阳ARK630HV100的车规开发板 专用HMI芯片&#xff1a;5寸智能液晶摩托车仪表采用专用的HMI芯片&#xff0c;确保高效的数据处理和显示性能。这种芯片专为摩托车仪表设计&#xff0c;具有更快的响应速度和更稳定的性能&#xff0c;可以提供流畅的用户体验。高分辨率液晶显…

nodejs+vue+elementui社区流浪猫狗救助救援网站_4a4i2

基于此背景&#xff0c;本研究结合管理员即时发布流浪猫狗救助救援信息与用户的需求&#xff0c;设计并实现了流浪猫狗救助救援网站。系统采用B/S架构&#xff0c;java语言作为主要开发语言&#xff0c;MySQL技术创建和管理数据库。系统主要分为管理员和用户两大功能模块。通过…

安装ubuntu22.04系统,配置国内源以及ssh远程登录

一、安装ubuntu22.04系统 原文连接&#xff1a;Ubuntu操作系统22.04版本安装教程-VMware虚拟机_wx63f86e949a470的技术博客_51CTO博客 1.点击界面左侧的开启此虚拟机&#xff0c;即可进入Ubuntu操作系统安装界面&#xff0c;点击​​Try or Install Ubuntu ​​即可开始安装 …

【解密算法:时间与空间的博弈】

本章重点 ​​什么是数据结构&#xff1f; 什么是算法&#xff1f; 算法效率 时间复杂度 空间复杂度 常见时间复杂度以及复杂度oj练习 1. 什么是数据结构&#xff1f; 数据结构(Data Structure)是计算机存储、组织数据的方式&#xff0c;指相互之间存在一种或多种特定关系…

【2023 华数杯全国大学生数学建模竞赛】 C题 母亲身心健康对婴儿成长的影响 45页论文及python代码

【2023 华数杯全国大学生数学建模竞赛】 C题 母亲身心健康对婴儿成长的影响 45页论文及python代码 1 题目 母亲是婴儿生命中最重要的人之一&#xff0c;她不仅为婴儿提供营养物质和身体保护&#xff0c; 还为婴儿提供情感支持和安全感。母亲心理健康状态的不良状况&#xff0c…