[Разработка под iOS, Swift] Делаем OpenVPN клиент для iOS
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Привет всем!
Давайте рассмотрим как создать собственное приложение, поддерживающее OpenVPN-протокол. Для тех, кто об этом слышит впервые ссылки на обзорные материалы, помимо Википедии, приведены ниже.
С чего начать?
Начнем с фреймворка OpenVPNAdapter — написан на Objective-C, ставится с помощью Pods, Carthage, SPM. Минимальная поддерживаемая версия ОС — 9.0.
После установки необходимо будет добавить Network Extensions для таргета основного приложения, в данном случае нам понадобится пока Packet tunnel опция.
Network Extension
Затем добавляем новый таргет — 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, предварительно импортировав его. Обращу внимание на классы NETunnelProviderManager, с помощью которого можно управлять VPN-соединением, и 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
Конфиги для теста
===========
Источник:
habr.com
===========
Похожие новости:
- [Настройка Linux, Информационная безопасность, Open source, *nix] Linux-дистрибутивы для анонимной работы в интернете — что нового?
- [Разработка под iOS, Разработка под MacOS] Что ждать разработчику от WWDC 2021
- [Разработка под iOS, Разработка под MacOS, Гаджеты, Софт, IT-компании] iOS 15, iPadOS 15 и другие новинки WWDC 2021
- [Информационная безопасность, Поисковые технологии, IT-инфраструктура, IT-стандарты] Наша анонимность утрачена?
- [Разработка под iOS, Разработка мобильных приложений, Дизайн мобильных приложений] Как сделать экран подтверждения СМС-кода на iOS
- [Разработка под iOS] Архитектурные паттерны в iOS: привет от дядюшки Боба, или Clean Architecture
- [Обработка изображений, Космонавтика, Транспорт, Астрономия] «Кьюриосити» снял редкие облака Марса
- [Разработка под iOS, Разработка мобильных приложений, Usability, Accessibility] Доступность на iOS началась с «36 секунд» (перевод)
- [Разработка под iOS, Разработка мобильных приложений, Разработка под Android, Дизайн мобильных приложений] За что банит Apple(и Google)
- [Разработка под Android, Dart, Flutter] Как я хотел поработать нативным Android разработчиком, но устроился Flutter разрабом
Теги для поиска: #_razrabotka_pod_ios (Разработка под iOS), #_swift, #_swift, #_vpn, #_openvpn, #_ios, #_razrabotka_pod_ios (
Разработка под iOS
), #_swift
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:02
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Привет всем! Давайте рассмотрим как создать собственное приложение, поддерживающее OpenVPN-протокол. Для тех, кто об этом слышит впервые ссылки на обзорные материалы, помимо Википедии, приведены ниже. С чего начать? Начнем с фреймворка OpenVPNAdapter — написан на Objective-C, ставится с помощью Pods, Carthage, SPM. Минимальная поддерживаемая версия ОС — 9.0. После установки необходимо будет добавить Network Extensions для таргета основного приложения, в данном случае нам понадобится пока Packet tunnel опция. Network Extension Затем добавляем новый таргет — 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, предварительно импортировав его. Обращу внимание на классы NETunnelProviderManager, с помощью которого можно управлять VPN-соединением, и 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 Конфиги для теста =========== Источник: habr.com =========== Похожие новости:
Разработка под iOS ), #_swift |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:02
Часовой пояс: UTC + 5