/VPNClient

iOS VPNClient demo use openVPN

Primary LanguageSwift

iOS游戏加速器开发指南

1. 基本原理

通过连接对应的代理服务器以降低延迟,获得更好的游戏体验。

2.准备工作

1.一台服务器

2.iOS真机设备(NetworkExtension)只支持真机调试

3.付费开发者账号

3. 本文环境

服务器系统为

centos

协议选用

OpenVPN

4. 服务器配置

参考此链接我们可以根据readme非常方便的在服务器上安装openvpn

如果你的网络不行

  1. 手动拷贝链接中的 openvpn-install.sh 脚本到服务器上

  2. 调整权限

     $ chmod +x openvpn-install.sh
    
  3. 运行脚本

     $ sudo ./openvpn-install.sh
    

运行成功后你会看到以下信息:

	Welcome to the OpenVPN installer!
	The git repository is available at: https://github.com/angristan/openvpn-install
	
	I need to ask you a few questions before starting the setup.
	You can leave the default options and just press enter if you are ok with them.
	
	I need to know the IPv4 address of the network interface you want OpenVPN listening to.
	Unless your server is behind NAT, it should be your public IPv4 address.
	IP address: xxxxxxx

第一次运行它时,你需要回答几个问题来设置你的VPN服务器。如果没有什么特殊的需求,可以一直确认。最后,安装成功

Client vpnclient added.

The configuration file has been written to /home/parallels/vpnclient.ovpn.
Download the .ovpn file and import it in your OpenVPN client

安装完成后你能看到以下信息,注意保存vpnclient.ovpn 文件,后续我们连接此服务器的时候会用到。 .

5. iOS工程配置

  1. Signing & Capabilities中添加Network Extensions

  2. 勾选 Packet Tunnel

  3. Xcode中选择File -> New -> Target添加Network Extension

  4. 并将Network Extension 命名为vpn-tunnel

  5. 同时需要在vpn-tunnel这个target的Signing & Capabilities中也添加 Network extension并勾选 Packet Tunnel

  6. 然后将服务器生成的 vpnclient.ovpn文件拷贝到工程中

  7. 我们使用的是openvpn协议,如果全部自己实现非常繁琐,这里我们导入已经写好的OpenVPNAdapter这个第三方库协助我们快速完成连接 需要注意的是,这个库是提供给vpn-tunnel这个target使用的,不要导入到主工程中 ​

6.代码部分

  1. 我们创建VPNManager类管理我们的连接

     import NetworkExtension
     
     class VPNManager {
         
         static let shared = VPNManager()
         
         var manager: NETunnelProviderManager?
             
         private init(){ }
     }
    
  2. 首先我们需要初始化manager 首先加载,如果没有对应的manager我们就新创建一个

         //加载已保存的NETunnelProvider configurations
         func loadManager() {
             NETunnelProviderManager.loadAllFromPreferences { (managers, error) in
                 guard error == nil else {
                     return
                 }
                 if let manager = managers?.first {
                     self.manager = manager
                 } else {
                     //新建
                     self.manager = NETunnelProviderManager()
                     self.manager?.localizedDescription = "myVPN"
                 }
             }
         }
    
  3. 有了manager我们来看一下connect方法 。非常简单,等待manager初始化完成后我们调用loadPreferences加载vpn配置,加载完成后会自动调用连接县对应的方法

         func connect() {
             guard self.manager != nil else {
                 print("未初始化完成")
                 return
             }
             self.loadPreferences()
         }
    
  4. loadPreferences中我们首先读取是否有先前创建好的配置信息。如果没有配置信息,我们需要新建配置。 然后将isEnabled设置为true进行保存。

    第一次写这部分代码的时候我在保存配置信息后直接进行连接, 但是这样操作会造成连接失败,查询后发现需要重新加载一下配置再进行连接,一切就正常了。 所以保存后我们需要调用

    //加载当前xxx配置 func loadPreferences() { guard let manager = self.manager else { return }

     self.manager?.loadFromPreferences { (error) in
         guard error == nil else {
             return
         }
         
         // 如果没有对应的配置,我们需要新建配置
         if manager.protocolConfiguration == nil {
             manager.protocolConfiguration = self.newConfiguration()
         }
         
         // 设置完isEnabled需要保存配置,启动当前配置
         manager.isEnabled = true
         manager.saveToPreferences { (error) in
             guard error == nil else {
                 // 用户拒绝保存等情况,清空配置
                 manager.protocolConfiguration = nil
                 return
             }
             // 保存完成后我们需要重新加载配置,进行连接,
             //https://stackoverflow.com/questions/47550706/error-domain-nevpnerrordomain-code-1-null-while-connecting-vpn-server
             self.loadPreferencesAndStartTunnel()
         }
         
     }
     }
    
  5. 最终,加载配置,并进行连接。

       func loadPreferencesAndStartTunnel()  {
             self.manager?.loadFromPreferences(completionHandler: { (error) in
                 guard error == nil else {
                     return
                 }
                 self.startTunnel()
             })
         }
     
         private func startTunnel() {
             do {
                 try self.manager?.connection.startVPNTunnel()
             } catch  {
                 print(error)
             }
         }
    
  6. vpn配置部分,首先读取工程中的ovpn文件,然后我们在这里关联了vpn-tunnel这个target

         func newConfiguration() -> NETunnelProviderProtocol {
             //加载ovpn文件
             guard
                 let configurationFileURL = Bundle.main.url(forResource: "vpnclient", withExtension: "ovpn"),
                 let configurationFileContent = try? Data(contentsOf: configurationFileURL)
             else {
                 fatalError()
             }
             
             let tunnelProtocol = NETunnelProviderProtocol()
             tunnelProtocol.serverAddress = ""
             //指定NetworkExtension 确保bundleIdentifier和network extension target的id一致
             tunnelProtocol.providerBundleIdentifier = Bundle.main.bundleIdentifier!.appending(".vpn-tunnel")
             tunnelProtocol.providerConfiguration = ["ovpn": configurationFileContent]
             
             return tunnelProtocol
         }
    
  7. vpn-tunnel 也就是network extension部分调整PacketTunnelProvider中的代码,参照项目中的实现

     import NetworkExtension
     import UIKit
     import OpenVPNAdapter
     
     
     // Extend NEPacketTunnelFlow to adopt OpenVPNAdapterPacketFlow protocol so that
     // `self.packetFlow` could be sent to `completionHandler` callback of OpenVPNAdapterDelegate
     // method openVPNAdapter(openVPNAdapter:configureTunnelWithNetworkSettings:completionHandler).
     extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
     
     class PacketTunnelProvider: NEPacketTunnelProvider {
     .........
     }
    

到这里关键部分的代码就已经完成了,你可以调用connect方法进行连接测试,记得把 ovpn 替换成自己的然后进行测试。

7. Debug

  1. 调试 Extension比较麻烦, 我们需要在Debug / Attach to Process by PID or Name输入Extension名字。 对于此工程我们输入vpn-tunnel

  2. 我们可以看到vpn-tunnel正在等待连接,等到extension启动的时候。xcode会自动帮我们建立连接,这时候我们就可以正常调试了。

  3. 我们可以在startTunnel打断点进行调试是否能正常调试

x.参考链接

  1. What's New in Network Extension and VPN
  2. VPN, Part 1: VPN Profiles