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中,出现了两个函数,startFilterWithCompletionHandler
和stopFilterWithReason,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 Groups
和System 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类中创建一个方法用来进行下列流程
- 发送OSSystemExtensionRequest来给系统发送激活请求
这个方法需要两个参数,一个参数是bundleId
,这个参数对应你的extension的info.list中的CFBundleIdentifier
,位于extension目录下的Contents/info.plist,也就是前面配置工程时填的Bundle Identifie
r。第二个参数是一个queue
,可以通过dispatch_queue_create
来创建。
OSSystemExtensionRequest*request = [OSSystemExtensionRequest activationRequestForExtension:bundleId queue:workQueue];
request.delegate = self; //self指RequestDelegate对象
[[OSSystemExtensionManager sharedManager] submitRequest:request];
- 收到请求完成的信息
在我们重写的这个函数中,会将请求的结果告诉我们,通过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");
}
- 加载已有的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);
}
- 保存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】快速免费领取~