MacOS创建NetworkExtension 【保姆级流程】

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/69366.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux系统USB转串口芯片 GPIO使用教程

一、简介 WCH的多款USB转单路/多路异步串口芯片&#xff0c;除串口接口以外&#xff0c;还提供独立的GPIO接口&#xff0c;各GPIO引脚支持独立的输出输入&#xff0c;GPIO功能的使用需要与计算机端厂商驱动程序和应用软件配合使用。各芯片的默认GPIO引脚状态有所区别&#xff…

深入理解机器学习与极大似然之间的联系

似然函数&#xff1a;事件A的发生含着有许多其它事件的发生。所以我就把这些其它事件发生的联合概率来作为事件A的概率&#xff0c;也就是似然函数。数据类型的不同&#xff08;离散型和连续性&#xff09;就有不同的似然函数 极大似然极大似然估计方法&#xff08;Maximum Li…

【逗老师的PMP学习笔记】10、项目沟通管理

目录 一、规划沟通管理1、【关键工具】沟通技术2、【关键工具】沟通模型&#xff08;沟通模式&#xff09;3、【关键工具】沟通方法4、【关键工具】文化意识5、【关键输出】沟通管理计划 二、管理沟通1、【关键工具】会议管理 三、监督沟通 一、规划沟通管理 规划沟通管理是基于…

Java集合知识回顾:从分类到工具类,掌握精髓

文章目录 1. 集合的分类2. Collection 接口3. Map 接口4. 泛型5. Collections 工具类总结 在Java编程世界中&#xff0c;集合是一项极为重要的知识&#xff0c;为我们的程序设计提供了强大的数据结构和处理手段。在本篇文章中&#xff0c;我们将回顾集合的分类以及相关的重要概…

dotNet 之网络TCP

**硬件支持型号 点击 查看 硬件支持 详情** DTU701 产品详情 DTU702 产品详情 DTU801 产品详情 DTU802 产品详情 DTU902 产品详情 G5501 产品详情 ARM dotnet 编程 dotNet使用TCP&#xff0c;可以使用Socket和TcpClient 、TcpListener类 2种&#xff0c;对于高级用户&…

nbcio-boot因升级mybatis-plus到3.5.3.1和JSQLParser 到4.6引起的online表单开发的数据库导入出错解决

更多功能看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbacheng/nbcio-boot 前端代码&#xff1a;https://gitee.com/nbacheng/nbcio-vue.git 在线演示&#xff08;包括H5&#xff09; &#xff1a; http://122.227.135.243:9888 nbcio-boot因升级…

怎样学会单片机

0、学单片机首先要明白&#xff0c;一个单片机啥也干不了&#xff0c;学单片机的目的是学习怎么用单片机驱动外部设备&#xff0c;比如数码管&#xff0c;电机&#xff0c;液晶屏等&#xff0c;这个需要外围电路的配合&#xff0c;所以学习单片机在这个层面上可以等同为学习单片…

一起学数据结构(3)——万字解析:链表的概念及单链表的实现

上篇文章介绍了数据结构的一些基本概念&#xff0c;以及顺序表的概念和实现&#xff0c;本文来介绍链表的概念和单链表的实现&#xff0c;在此之前&#xff0c;首先来回顾以下顺序表的特点&#xff1a; 1.顺序表特点回顾&#xff1a; 1. 顺序表是一组地址连续的存储单元依次存…

11_Pulsar Adaptors适配器、kafka适配器、Spark适配器

2.3. Pulsar Adaptors适配器 2.3.1.kafka适配器 2.3.2.Spark适配器 2.3. Pulsar Adaptors适配器 2.3.1.kafka适配器 Pulsar 为使用 Apache Kafka Java 客户端 API 编写的应用程序提供了一个简单的解决方案。 在生产者中, 如果想不改变原有kafka的代码架构, 就切换到Pulsar的…

sentinel核心流程源码解析

sentinel的处理槽(ProcessorSlot) 可以说&#xff0c;sentinel实现的各种功能就是由各处理槽完成的 ,ProcessorSlot定义了四个方法&#xff1a; 当进入该处理槽时触发该方法 处理完 entry方法之后触发该方法 退出该处理槽时触发该方法 exit方法处理完成时触发该方法 sentinel的…

【MySQL安装】卸载与安装MySQL 5.7.X版本

最近由于各种原因&#xff0c;需要重新安装MySQL。之前我的版本是8.0版本&#xff0c;现在装的5.7版本。记录一下自己的安装过程。 目录 1、卸载MySQL8.0 2、安装MySQL5.7 1、卸载MySQL8.0 如何彻底卸载MySQL_mysql 完全卸载_m0小麦麦的博客-CSDN博客相信不少小伙伴们在安装…

ddia(3)----Chapter3. Storage and Retrieval

However, first we’ll start this chapter by talking about storage engines that are used in the kinds of databases that you’re probably familiar with: traditional relational databases, and also most so-called NoSQL databases. We will examine two families o…

捕捉时刻:将PDF文件中的图像提取为个性化的瑰宝(从pdf提取图像)

应用场景&#xff1a; 该功能的用途是从PDF文件中提取图像。这在以下情况下可能会很有用&#xff1a; 图片提取和转换&#xff1a;可能需要将PDF文件中的图像提取出来&#xff0c;并保存为单独的图像文件&#xff0c;以便在其他应用程序中使用或进行进一步处理。例如&#xff…

pdf怎么压缩到1m?这样做压缩率高!

PDF是目前使用率比较高的一种文档格式&#xff0c;因为它具有很高的安全性&#xff0c;还易于传输等&#xff0c;但有时候当文件体积过大时&#xff0c;会给我们带来不便&#xff0c;这时候简单的解决方法就是将其压缩变小。 想要将PDF文件压缩到1M&#xff0c;也要根据具体的情…

QGIS开发五:VS使用QT插件创建UI界面

前面我们说了在创建项目时创建的是一个空项目&#xff0c;即不使用 Qt 提供的综合开发套件 Qt Creator&#xff0c;也不使用 Qt Visual Studio Tools 这类工具。 但是后面发现&#xff0c;如果我想要有更加满意的界面布局&#xff0c;还是要自己写一个UI文件&#xff0c;如果不…

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

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

MySQL索引3——Explain关键字和索引使用规则(SQL提示、索引失效、最左前缀法则)

目录 Explain关键字 索引性能分析 Id ——select的查询序列号 Select_type——select查询的类型 Table——表名称 Type——select的连接类型 Possible_key ——显示可能应用在这张表的索引 Key——实际用到的索引 Key_len——实际索引使用到的字节数 Ref ——索引命…

机器学习深度学习——注意力提示、注意力池化(核回归)

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——常见循环神经网络结构&#xff08;RNN、LSTM、GRU&#xff09; &#x1f4da;订阅专栏&#xff1a;机器…

SqlServer基础之(触发器)

概念&#xff1a; 触发器&#xff08;trigger&#xff09;是SQL server 提供给程序员和数据分析员来保证数据完整性的一种方法&#xff0c;它是与表事件相关的特殊的存储过程&#xff0c;它的执行不是由程序调用&#xff0c;也不是手工启动&#xff0c;而是由事件来触发&#x…

JVM G1垃圾回收机制介绍

G1(Garbage First)收集器 (标记-整理算法)&#xff1a; Java堆并行收集器&#xff0c;G1收集器是JDK1.7提供的一个新收集器&#xff0c;G1收集器基于“标记-整理”算法实现&#xff0c;也就是说不会产生内存碎片。此外&#xff0c;G1收集器不同于之前的收集器的一个重要特点是&…