TunnelMonitorKit

Build status badge codecov MIT License badge Swift Package Manager badge

TunnelMonitorKit 是一个 Swift 包,旨在简化 IPC(进程间通信),例如用于 App 与其 NEPacketTunnelProvider 网络扩展之间的通信。它还定义了一个模拟数据包隧道提供程序的框架,允许网络扩展逻辑在 App 层执行和测试。 这使得数据包隧道提供程序实现也能在模拟器目标环境下执行。

为什么使用?

实现 IPC(进程间通信)可能是一项冗长、繁琐且容易出错的任务。 TunnelMonitorKit 的目标是通过为在两个进程(例如宿主应用程序及其 NEPacketTunnelProviderNEAppProxyProvider App 扩展)之间实现 IPC 提供可靠的结构来简化此过程。

示例用法展示了如何将 TunnelMonitorKit 与网络扩展集成,但该概念可以抽象出来并应用于需要相互通信的其他进程集。

约束

具体的消息传递实现旨在与网络扩展 API 一起使用,允许宿主 App 向 App 扩展发送请求。 App 扩展可以选择使用给定的完成处理程序向此请求发送响应。 这意味着,对于此特定用例,通信只能可靠地由宿主 App 发起 - 尽管 App 可以定期探测扩展以获取更新。

NETunnelProviderSession.sendProviderMessage(_ messageData: Data, responseHandler: ((Data?) -> Void)? = nil) throws
NETunnelProvider.handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil)

用法

MessageRouter 可以处理由任何实现 Codable 的结构体定义的消息。 这些消息由 MessageContainer 包装,后者携带有关消息内容类型的信息。 只要您的消息类型符合此协议,就可以发送和接收它们,并自动路由到已分配给该特定消息类型的任何处理程序。 处理程序有责任将序列化的消息内容解码为正确的类型。

let stateRequestHandler = { (data: Data?, completion: ResponseCompletion) -> Void in
    // Decode message request
    let message = try! JSONDecoder().decode(StateRequest.self, from: data!)
    
    // Form a response
    let responseData = JSONEncoder().encode(StateResponse(...))
    
    // Pass it to the ResponseCompletion handler
    completion?(responseData)
}

在网络扩展用例中,重写 NEPacketTunnelProvider 的类应定义一个 MessageRouter,并为可能接收到的每种消息类型注册消息处理程序。

let router = MessageRouter()
router.addHandler(stateRequestHandler, for: StateRequest.self)

实际的消息数据将通过 handleAppMessageNEPacketTunnelProvider 超类接收,并且应将其传递到路由器,路由器将根据消息内容的类型调用正确的处理程序。 completionHandler 参数用于将响应发送回宿主 App - 这是您定义的每个处理程序的 ResponseCompletion 部分。

override open func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
    let request = try! JSONDecoder().decode(MessageContainer.self, from: messageData)
    router.handle(message: request, completionHandler: handler)
}

模拟

TunnelMonitorKit 允许单个数据包隧道提供程序实现作为网络扩展目标上的隧道提供程序以及在容器 App 目标中执行。 这允许在部署到模拟器目标环境时模拟和测试隧道提供程序实现。 局限性包括在模拟时无法访问 packetFlow 对象,这使得在 App 层运行时几乎不可能实现实际的 VPN 实现。

TMPacketTunnelProvider 必须是一个协议,因为 NEPacketTunnelProvider 及其子类的实例无法在非网络扩展目标上实例化,而本机数据包隧道提供程序必须从此类继承才能由系统实例化。 解决方法是定义一个通用子类,该子类实现用于在网络扩展目标上运行的提供程序协议 (TMPacketTunnelProviderNative<T: TMPacketTunnelProvider>),并创建一个从相同提供程序协议实现继承的类用于模拟 (TMMockTunnelProviderManager)。 这允许在网络扩展目标内部和外部实例化单个实现。

示例用法

首先,不要通过子类化 NEPacketTunnelProvider 来定义您的网络服务逻辑,而是实现 TMPacketTunnelProvider 协议。

public class MyTunnelProvider: TMPacketTunnelProvider {

    required init() {
        // Peform any setup that doesn't require user configuration
        // Register any necessary message handlers using a `MessageRouter` in order to take advantage of `TunnelMonitor` functionality
    }

    func configureTunnel(
        userConfigurationData: Data?,
        settingsApplicationBlock: @escaping (NETunnelNetworkSettings?, ((Error?) -> Void)?) -> Void,
        completionHandler: @escaping (TMTunnelConfigurationError?) -> Void
    ) {
        // If special configuration is required, decode it from `userConfigurationData`.
        // Call the completion handler once the tunnel has been configured.
    }

    func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
        // Start the service (asynchronously if necessary) and call the completion handler when finished.
    }

    func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
        // Perform any cleanup actions, stop the service and call the completion handler.
    }

    func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
        // Decode the request from `messageData` and pass the request to a `MessageRouter` to respond using the correct message handler
    }

}

然后,您必须对 TMPacketTunnelProviderNative 进行子类化,并将泛型 TunnelProvider 约束为您的 TMPacketTunnelProvider 协议的实现。

open class MyNativeTunnelProvider: TMPacketTunnelProviderNative<MyTunnelProvider> { }

数据包隧道目标必须仍然定义一个 TMPacketTunnelProviderNative 子类,该子类约束为 TMPacketTunnelProvider 协议的实现,并且 info.plist 文件通过 NSExtensionPrincipalClass 条目指向它。

启动模拟/本机隧道

使用 TMTunnelProviderManagerFactory 实例化/加载模拟和本机隧道。 编译器指令可用于在为模拟器目标环境构建时自动强制创建模拟隧道提供程序。

func loadProviderManager<UserConfiguration: Codable, ProviderType: TMPacketTunnelProvider>(
      ofType type: ProviderType.Type,
      completionHandler: @escaping (TMTunnelProviderManager?) -> Void
) {
#if targetEnvironment(simulator)
    completionHandler(try? TMTunnelProviderManagerFactory.createMockProviderManager(...))
#else
    TMTunnelProviderManagerFactory.loadNativeProviderManager(...) { providerManager in
        completionHandler(providerManager)
    }
#endif
}

日志记录

出于调试目的,可以通过将 TMLogger 的实例附加到 TunnelMonitorKit.loggers 来启用日志记录。 TMLogger 是一个协议,可以由基于现有日志记录框架的自定义日志记录实现来实现。 TMOSLogger 是基于统一系统记录器的示例实现。 它还区分来自宿主应用程序和网络扩展的日志。

let logger = TMOSLogger()
logger.setLogLevel(.warning)
TunnelMonitorKit.loggers.append(logger)

依赖项

通过 Swift Package Manager 分发。 目前没有外部依赖项。

许可证

TunnelMonitorKit 在 MIT 许可证下发布。 有关详细信息,请参阅 LICENSE.md