仅允许某些应用程序使用我的 VPN - Swift

Allow only certain apps to use my VPN - Swift

我正在制作一个基于 OpenVPN 激活 VPN 连接的应用程序,从数据库中检索证书,并使用 NEPacketTunnelProviderNetworkExtension 打开隧道。

我使用了 following repository,现在我的 VPN 工作正常。

但问题是我只想允许一个应用程序在启用时使用此 VPN(确切地说是 WhatsApp),并且我想限制所有其他应用程序使用它。

在 Android 上,可以通过将允许的应用程序的捆绑包标识符提供给 PackageManager

你能帮帮我吗?

这是我的 PacketTunnelProvider class:

import NetworkExtension
import OpenVPNAdapter

extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}

class PacketTunnelProvider: NEPacketTunnelProvider {

lazy var vpnAdapter: OpenVPNAdapter = {
    let adapter = OpenVPNAdapter()
    adapter.delegate = self

    return adapter
}()

let vpnReachability = OpenVPNReachability()

var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?

override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
    // There are many ways to provide OpenVPN settings to the tunnel provider. For instance,
    // you can use `options` argument of `startTunnel(options:completionHandler:)` method or get
    // settings from `protocolConfiguration.providerConfiguration` property of `NEPacketTunnelProvider`
    // class. Also you may provide just content of a ovpn file or use key:value pairs
    // that may be provided exclusively or in addition to file content.

    // In our case we need providerConfiguration dictionary to retrieve content
    // of the OpenVPN configuration file. Other options related to the tunnel
    // provider also can be stored there.
    print("started!")
    guard
        let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,
        let providerConfiguration = protocolConfiguration.providerConfiguration
    else {
        fatalError()
    }

    guard let ovpnFileContent: Data = providerConfiguration["ovpn"] as? Data else {
        fatalError()
    }

    let configuration = OpenVPNConfiguration()
    configuration.fileContent = ovpnFileContent
//        configuration.settings = [
//            // Additional parameters as key:value pairs may be provided here
//        ]

    // Uncomment this line if you want to keep TUN interface active during pauses or reconnections
    // configuration.tunPersist = true

    // Apply OpenVPN configuration
    let evaluation: OpenVPNConfigurationEvaluation
    do {
        evaluation = try vpnAdapter.apply(configuration: configuration)
    } catch {
        completionHandler(error)
        return
    }

    // Provide credentials if needed
    if !evaluation.autologin {
        // If your VPN configuration requires user credentials you can provide them by
        // `protocolConfiguration.username` and `protocolConfiguration.passwordReference`
        // properties. It is recommended to use persistent keychain reference to a keychain
        // item containing the password.

        guard let username: String = protocolConfiguration.username else {
            fatalError()
        }

        // Retrieve a password from the keychain
//            guard let password: String = ... {
//                fatalError()
//            }

        let credentials = OpenVPNCredentials()
        credentials.username = username
//            credentials.password = password

        do {
            try vpnAdapter.provide(credentials: credentials)
        } catch {
            completionHandler(error)
            return
        }
    }

    // Checking reachability. In some cases after switching from cellular to
    // WiFi the adapter still uses cellular data. Changing reachability forces
    // reconnection so the adapter will use actual connection.
    vpnReachability.startTracking { [weak self] status in
        guard status == .reachableViaWiFi else { return }
        self?.vpnAdapter.reconnect(afterTimeInterval: 5)
    }

    // Establish connection and wait for .connected event
    startHandler = completionHandler
    vpnAdapter.connect(using: packetFlow)
}

override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
    stopHandler = completionHandler

    if vpnReachability.isTracking {
        vpnReachability.stopTracking()
    }

    vpnAdapter.disconnect()
}

}

extension PacketTunnelProvider: OpenVPNAdapterDelegate {

// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
// protocol if the tunnel is configured without errors. Otherwise send nil.
// `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
// you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
// send `self.packetFlow` to `completionHandler` callback.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?, completionHandler: @escaping (Error?) -> Void) {
    // In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
    // send empty string to NEDNSSettings.matchDomains
    networkSettings?.dnsSettings?.matchDomains = [""]

    // Set the network settings for the current tunneling session.
    setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
}

// Process events returned by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleEvent event: OpenVPNAdapterEvent, message: String?) {
    switch event {
    case .connected:
        if reasserting {
            reasserting = false
        }

        guard let startHandler = startHandler else { return }

        startHandler(nil)
        self.startHandler = nil

    case .disconnected:
        guard let stopHandler = stopHandler else { return }

        if vpnReachability.isTracking {
            vpnReachability.stopTracking()
        }

        stopHandler()
        self.stopHandler = nil

    case .reconnecting:
        reasserting = true

    default:
        break
    }
}

// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
    // Handle only fatal errors
    guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool, fatal == true else {
        return
    }

    if vpnReachability.isTracking {
        vpnReachability.stopTracking()
    }

    if let startHandler = startHandler {
        startHandler(error)
        self.startHandler = nil
    } else {
        cancelTunnelWithError(error)
    }
}

// Use this method to process any log message returned by OpenVPN library.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
    // Handle log messages
    print(logMessage)
}

}

这是我的 VPN 视图模型中用于启动隧道的函数:

    func configureVPN(serverAddress: String, username: String, password: String) {
    var configData:Data = Data.init()
    self.getCertificate{certificate in
        configData = certificate!
        
        guard
            //If we want to read from a file
 //                let configData = self.readFile(name: "vtest2"),
            let providerManager = self.providerManager
        else {
            return
        }
        
        self.providerManager?.loadFromPreferences { error in
            if error == nil {
                let tunnelProtocol = NETunnelProviderProtocol()
                tunnelProtocol.username = username
                tunnelProtocol.serverAddress = serverAddress
                tunnelProtocol.providerBundleIdentifier = self.providerId // bundle id of the network extension target
                tunnelProtocol.providerConfiguration = ["ovpn": configData]
                tunnelProtocol.disconnectOnSleep = false
                providerManager.protocolConfiguration = tunnelProtocol
                providerManager.localizedDescription = "Slyfone Guard" // the title of the VPN profile which will appear on Settings
                providerManager.isEnabled = true
                providerManager.saveToPreferences(completionHandler: { (error) in
                    if error == nil  {
                        providerManager.loadFromPreferences(completionHandler: { (error) in
                            do {
                                try providerManager.connection.startVPNTunnel(options: nil) // starts the VPN tunnel.
                            } catch let error {
                                print(error.localizedDescription)
                            }
                        })
                    }
                })
            }
        }
    }
}

作为 engineer from Apple said:

方法是使用 Per-App VPN。 See the Per-App VPN On Demand section in the NETunnelProviderManager documentation.

在 macOS(自 10.15.4 起)上使用 NEPacketTunnelProvider,您可以使用 NEAppRule 自行设置。设置 Safari 以触发 VPN 的一个非常通用的示例是:

var perAppManager = NETunnelProviderManager.forPerAppVPN()
/* ... */
NETunnelProviderManager.forPerAppVPN().loadFromPreferences(completionHandler: { error in
  precondition(Thread.isMainThread)
  /* ... */
  let proto = (perAppManager.protocolConfiguration as? NETunnelProviderProtocol) ?? NETunnelProviderProtocol()
  proto.serverAddress = "server.vpn.com"
  proto.providerBundleIdentifier = "com.perapp-vpn.macOSPacketTunnel.PacketTunnelTest"
  var appRules = [NEAppRule]()
  let appRule = NEAppRule(signingIdentifier: "com.apple.Safari", designatedRequirement: "identifier \"com.apple.Safari\" and anchor apple")
  appRule.matchDomains = ["example.com"]
  appRules.append(appRule)
  perAppManager.appRules = appRules
  perAppManager.isOnDemandEnabled = true
    
  perAppManager.protocolConfiguration = proto
  perAppManager.isEnabled = true
  perAppManager.localizedDescription = "Testing Per-App VPN"
  self.perAppManager.saveToPreferences { saveError in
    /* Proceed to connect */
}
})

这是一个非常普遍的案例,forPerAppVPN() 仅适用于 macOS。 iOS 更真实的案例世界案例是通过 MDM 创建此流程。我之前提到的文档中解释了整个流程。我将从在 Configurator 2 中创建配置文件并对其进行测试开始。

不知道它是否适用于 OpenVPN