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

如何解决只允许某些应用程序使用我的 VPN - Swift

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

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

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

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

你能帮我吗?

这是我的 PacketTunnelProvider 课:

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 上的 NEPacketTunnelProvider(从 10.15.4 开始),您可以使用 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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)> insert overwrite table dwd_trade_cart_add_inc > select data.id, > data.user_id, > data.course_id, > date_format(
错误1 hive (edu)> insert into huanhuan values(1,'haoge'); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive> show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 <configuration> <property> <name>yarn.nodemanager.res