OpenVPN プロトコルをサポートする独自のアプリケーションを作成する方法を見てみましょう。人のために聞くについて この初めて、審査資料へのリンクは、ウィキペディアに加えて、以下の通りです。
どこから始めますか?
Pod、Carthage、SPM を使用してインストールされた、Objective-C で記述された OpenVPNAdapter フレームワークから始めましょう。サポートされる最小の OS バージョンは 9.0 です。
インストール後、メイン アプリケーションのターゲットにネットワーク拡張機能を追加する必要があります。この場合、現時点ではパケット トンネル オプションが必要になります。
ネットワーク拡張
次に、新しいターゲット - Network Extension を追加します。
この後に生成されるクラス 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) {
guard
let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration
else {
fatalError()
}
guard let ovpnContent = providerConfiguration["ovpn"] as? String else {
fatalError()
}
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnContent.data(using: .utf8)
configuration.settings = [:]
configuration.tunPersist = true
let evaluation: OpenVPNConfigurationEvaluation
do {
evaluation = try vpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
if !evaluation.autologin {
guard let username: String = protocolConfiguration.username else {
fatalError()
}
guard let password: String = providerConfiguration["password"] as? String else {
fatalError()
}
let credentials = OpenVPNCredentials()
credentials.username = username
credentials.password = password
do {
try vpnAdapter.provide(credentials: credentials)
} catch {
completionHandler(error)
return
}
}
vpnReachability.startTracking { [weak self] status in
guard status == .reachableViaWiFi else { return }
self?.vpnAdapter.reconnect(afterTimeInterval: 5)
}
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 {
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?, completionHandler: @escaping (Error?) -> Void) {
networkSettings?.dnsSettings?.matchDomains = [""]
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
}
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
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
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)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
}
}
そして再びコード
メインのアプリケーションに戻ります。インポート後に NetworkExtension を操作する必要があります。VPN 接続を管理できる NETunnelProviderManagerクラスと、新しい接続のパラメーターを設定するNETunnelProviderProtocolに注目してください 。OpenVPN 構成の転送に加えて、必要に応じてログインとパスワードを転送する機能を設定します。
var providerManager: NETunnelProviderManager!
override func viewDidLoad() {
super.viewDidLoad()
loadProviderManager {
self.configureVPN(serverAddress: "127.0.0.1", username: "", password: "")
}
}
func loadProviderManager(completion:@escaping () -> Void) {
NETunnelProviderManager.loadAllFromPreferences { (managers, error) in
if error == nil {
self.providerManager = managers?.first ?? NETunnelProviderManager()
completion()
}
}
}
func configureVPN(serverAddress: String, username: String, password: String) {
providerManager?.loadFromPreferences { error in
if error == nil {
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.username = username
tunnelProtocol.serverAddress = serverAddress
tunnelProtocol.providerBundleIdentifier = "com.myBundle.myApp"
tunnelProtocol.providerConfiguration = ["ovpn": configData, "username": username, "password": password]
tunnelProtocol.disconnectOnSleep = false
self.providerManager.protocolConfiguration = tunnelProtocol
self.providerManager.localizedDescription = "Light VPN"
self.providerManager.isEnabled = true
self.providerManager.saveToPreferences(completionHandler: { (error) in
if error == nil {
self.providerManager.loadFromPreferences(completionHandler: { (error) in
do {
try self.providerManager.connection.startVPNTunnel()
} catch let error {
print(error.localizedDescription)
}
})
}
})
}
}
}
その結果、システムはユーザーに新しい構成を追加する許可を求めます。そのためには、デバイスからパスワードを入力する必要があります。その後、接続が他の設定の隣にある [設定] に表示されます。
VPN 接続をオフにする機能を追加しましょう。
do {
try providerManager?.connection.stopVPNTunnel()
completion()
} catch let error {
print(error.localizedDescription)
}
removeFromPreferences (completionHandler :)メソッドを使用して接続を切断することもできますが 、これは過激すぎて、ダウンロードした接続データの最終的かつ不可逆的な破棄を目的としています:)
ステータスを使用してアプリケーションで VPN の接続ステータスを確認でき ます.
if providerManager.connection.status == .connected {
defaults.set(true, forKey: "serverIsOn")
}
これらのステータスは 6 つあります。
@available(iOS 8.0, *)
public enum NEVPNStatus : Int {
/** @const NEVPNStatusInvalid The VPN is not configured. */
case invalid = 0
/** @const NEVPNStatusDisconnected The VPN is disconnected. */
case disconnected = 1
/** @const NEVPNStatusConnecting The VPN is connecting. */
case connecting = 2
/** @const NEVPNStatusConnected The VPN is connected. */
case connected = 3
/** @const NEVPNStatusReasserting The VPN is reconnecting following loss of underlying network connectivity. */
case reasserting = 4
/** @const NEVPNStatusDisconnecting The VPN is disconnecting. */
case disconnecting = 5
}
このコードにより、必要最小限の機能を備えたアプリケーションを構築できます。OpenVPN 構成自体は、読み取りのためにアクセスできる別のファイルに保存することをお勧めします。
役に立つリンク:
OpenVPNAdapter
Habr
Test Configs