Mac网络开发的相关知识点(一)
系统扩展
概述:
苹果公司在macOS10.15及以后的系统中包含了三种系统扩展(网络扩展、端点安全、驱动扩展),这也意味着内核扩展退出历史的舞台,这是非常重要的进步。因为系统扩展是运行在内核之外的用户空间中,这样在内核空间中运行的第三方代码更少,可以使macOS系统具有更高的安全性和稳定性。对于开发者来说,现在可以用OC和Swift等语言进行开发系统扩展,而以前只能用C++或者c语言开发内核扩展,同样在功能开发和调试相对于内核扩展更加简单了。
- 网络扩展(Network Extension):是替换网络内核扩展的扩展。
- 端点安全(Endpoint Security Extension): 是替换KAUTH接口来拦截和监视与安全相关事件的扩展。
- 驱动扩展 (DriverKit Extension): 是替换IOKit的驱动扩展,是一个新的SDK。
框架:
-
sysextd是一个守护进程,提供管理本机上系统扩展的服务,它没有配置选项,用户不应手动运行。
-
kextd 是内核扩展服务器。 它作为独立的 launchd(8) 守护进程运行,以处理来自内核和来自其他用户空间进程加载内核扩展(kext)或提供有关它们的信息。
-
endpointsecurityd 是一个管理ES组件的守护进程,负责初始化和启动ES系统扩展,以及监控健康状况。
-
nesessionmanager 是网络扩展框架的一部分,它负责启动和停止网络扩展会话以及设置和维护与网络扩展会话关联的网络配置和策略。网络扩展包括(VPN、Content Filter)。
-
luanch 是系统和用户守护进程和代理进程的管理器,管理着整个系统和单个用户的进程。还管理者捆绑在系统上的应用程序和框架内的XPC服务,不能直接调用。
详情请看系统扩展内容
官方文档解读:
-
System Extensions--entitlement
需要在应用中添加key: com.apple.developer.system-extensions.install和value:true的键值对,它会决定宿主应用是否有权利激活或者停用系统扩展。
-
NSSystemExtensionUsageDescriptionKey
除 DriverKit 扩展之外的所有系统扩展都需要此密钥,并且必须位于扩展的 Info.plist 文件中。 不包含此密钥会导致激活时出错。 对于 DriverKit 扩展,请改用 OSBundleUsageDescriptionKey。
-
Debugging and testing system extensions
-
在系统扩展开发阶段可以使用开发者模式来避免只能将系统扩展安装包只能放在宿主程/Contents/Library/SystemExtensions目录中的问题,这样系统在加载系统扩展之前不会检查系统所在的位置。具体须在终端执行命令
sudo systemextensionsctl developer on开启。sudo systemextensionsctl developer off关闭。 -
关闭macOS中系统完整性保护(SIP),可以在开发周期中,不会对应用程序或者系统扩展进行公证,从而可以更快的调试代码。
-
使用 lldb中的
process attach --pid xxx(pid)命令来挂载系统扩展到进程进行调试。调试可以通过Xcode中的工具来挂载系统扩展,也可以通过输出日志的形式进行,如使用挂载进程方式需要选择root权限进行,见下图: -
arm64架构下开发Driver extensions (dexts)驱动扩展的说明和注意点。
-
-
Implementing drivers, system extensions, and kexts
创建驱动程序和系统扩展以与硬件通信并提供低级服务,并且仅将内核扩展用于少数任务。
- DriverKit 扩展 (dext) 管理公司的硬件设备与系统其余部分之间的通信。
- 系统扩展实现需要内核级协作的功能,例如自定义安全性和网络行为。
- 内核扩展 (kext) 支持无法使用驱动扩展或系统扩展实现的任何低级服务。
- 驱动程序、系统扩展、内核扩展和内核之前关系如下图:从图中可以看出内核扩展在内核内部运行。 驱动程序和系统扩展在用户空间中运行,并与内核进行通信以满足关键需求。
- 系统扩展框架支持以前需要 kext 的一类内核级功能。 系统扩展在用户空间中运行并与内核交互以执行特定任务。 例如,端点安全系统扩展监视系统事件是否存在潜在的安全威胁。因此macOS11以后,系统扩展将代替内核扩展实现功能。
- 使用驱动扩展来与自定义硬件进行通信,同样在macOS11以后,驱动扩展也会替代内核扩展实现功能。
- 使用内核扩展的限制:内核扩展在内核内部运行,必须支持支持与其他内核代码相同体系结构,因此在Apple silicon的kext必须支持arm64e架构,该架构包含了 pointer authentication codes(PAC) 来检测并防止恶意或意外修改内存中的指针。Kext 在内核完整性保护 (KIP) 下运行, 系统初始化内核和 kext 后,KIP 会锁定内核内存页以防止修改内核和驱动程序代码。
- 安装内核扩展最常见的方法是使用自定义安装程序包。(与应用程序和系统扩展不同,您无法通过 Mac App Store 分发 kext。)通常,您使用单个程序包将每个 kext 和任何支持软件复制到用户的系统,然后使用脚本加载和配置 kext执行。
- 在macOS11及以后需要在安装完成后需要重启系统,并且在系统重启前,用户必须打开设置->安全性与隐私 允许内核扩展加载。
- 对于Apple silicon 用户需要重启系统至恢复模式,并降低安全级别,允许加载第三方内核扩展,最后重启回到系统。
- macOS11及以上使用 kmutil 工具替换了 kextload/kextunload 进行安装卸载,不过老的工具毅然可以使用。
- 更多细节可以查阅官方文档: 安装自定义内核扩展
-
Installing System Extensions and Drivers
-
无需使用安装程序单独安装系统扩展,而是将其放入宿主应用程序的安装包中并从应用程序安装。通过以下代码来激活系统扩展:
// Create an activation request and assign a delegate to // receive reports of success or failure. let request = OSSystemExtensionRequest.activationRequest(forExtensionWithIdentifier: driverID, queue: DispatchQueue.main) request.delegate = self // Submit the request to the system. let extensionManager = OSSystemExtensionManager.shared extensionManager.submitRequest(request) -
在激活过程中,系统会验证以下内容:
-
扩展是否安装在宿主应用程序的
/Contents/Library/SystemExtensions目录的文件夹中。 -
宿主应用程序是否安装在系统
/Applications目录下。 -
系统扩展需要进行签名,并且与宿主应用程序使用同一个
TeamID。 -
entitlement文件中的权限需要匹配开发者团队申请的provisionprofile配置文件中的权限。 -
安装扩展时调用代码传入的标识符要与扩展安装包的标识符匹配。
-
系统扩展的
bundle ID不能被其他系统扩展使用。
-
-
激活结果会通过
OSSystemExtensionRequestDelegate代理方法告知://激活失败,根据错误码进行相应的修改,在开发过程中这些错误码基本大多数能遇到。 func request(_ request: OSSystemExtensionRequest, didFailWithError error: Error) { } //当系统检测到同一个bundle ID的系统扩展存在,就会将两个系统扩展的版本信息通过该代理方法下发,通过该方法可以处理需要安装的版 本。平时我们通过修改系统扩展版本号来达到更新系统扩展内容的目的,如果不修改其版本号可能会出现更新不成功的情况。 func request(_ request: OSSystemExtensionRequest, actionForReplacingExtension existing: OSSystemExtensionProperties, withExtension ext: OSSystemExtensionProperties) -> OSSystemExtensionRequest.ReplacementAction { return .replace } //激活成功,接下来就可以做相应的业务处理了。 func request(_ request: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result) { } //告知用户需要授权后,才能激活扩展 func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) { } -
系统激活成功后就可以通过
systemextensionsctl list查看本机所安装的系统扩展了,也可以通过systemextensionsctl reset对扩展进行删除,该操作需要关闭SIP的情况下才能操作成功。systemextensionsctl的几种用法见下图。 -
通过
systemextensionsctl list展示的系统扩展我们可以在/Library/SystemExtensions/目录中找到相应的文件。按我个人理解是系统将宿主程序中的系统扩展包在激活过程中拷贝到此处进行统一管理,依据就是我通过查找进程发现系统扩展的运行路径是在/Library/SystemExtensions/目录下而非宿主应用程序目录中,从而进一步验证了为啥系统扩展一定要放在宿主程序的/Contents/Library/SystemExtensions目录下,因为系统需要将系统扩展从固定路径中去复制到/Library/SystemExtensions/中去,此处纯是个人猜想,不要被带偏。 -
停用系统扩展:
let request = OSSystemExtensionRequest.deactivationRequest(forExtensionWithIdentifier: driverID, queue: DispatchQueue.main) request.delegate = self // Submit the request to the system. let extensionManager = OSSystemExtensionManager.shared extensionManager.submitRequest(request)系统扩展的状态有
[activated waiting for user][activated enabled][terminated for unistall][terminated waiting to uninstall on reboot]。有时会执行不成功出现OSSystemExtensionErrorDomain error 13的错误。在网上也查询了些关于卸载系统扩展的资料具体详情, 如下图:因此我非常疑惑该如何处理,尝试过很多方法没有成功,一个偶然的机会,我在WWDC2019中关于介绍系统扩展的技术视频中找到了一个方向,经过尝试并验证,当我们将宿主应用程序移至废纸篓中,安装的系统扩展也会随之全部清除干净。因为之前我们的程序应用不全是安装在
/Applications目录中,所以需要运行的自定义卸载脚本,存在系统扩展卸载不彻底的情况。找到方案之后,我们更改了原有的卸载方式,采取让用户自己将应用程序移至废纸篓的方式,这样就彻底的解决了卸载不干净的问题。希望这个小收获能够对有需要的老铁有所帮助。
-
总结:
- 初次写技术文章,有很多地方没有总结到位,还有些开发经验方面的没办法以文字的方式呈现,也许文章还存在些错误知识点,因此希望各位大佬多多包涵,有问题可以提出来我改正。