OpenVPNAdapter/README.md

209 lines
8.2 KiB
Markdown
Raw Normal View History

2017-09-26 17:43:15 +08:00
# OpenVPNAdapter
![Platforms](https://img.shields.io/badge/Platforms-iOS%20%7C%20macOS-lightgrey.svg)
2018-02-05 18:20:02 +08:00
![iOS Versions](https://img.shields.io/badge/iOS-9.0+-yellow.svg)
![macOS Versions](https://img.shields.io/badge/macOS-10.11+-yellow.svg)
![Xcode Version](https://img.shields.io/badge/Xcode-9.0+-yellow.svg)
2018-02-05 17:44:36 +08:00
![Carthage Compatible](https://img.shields.io/badge/Carthage-Compatible-4BC51D.svg?style=flat)
![License](https://img.shields.io/badge/License-AGPLv3-lightgrey.svg)
2018-02-05 17:44:55 +08:00
## Overview
## Installation
### Requirements
- iOS 9.0+ or macOS 10.11+
2018-02-05 18:29:19 +08:00
- Xcode 9.0+
2018-02-05 17:44:55 +08:00
### Carthage
2018-02-05 18:29:19 +08:00
To install OpenVPNAdapter with Carthage, add the following line to your `Cartfile`.
```
github "ss-abramchuk/OpenVPNAdapter"
```
Then run `carthage update` command. For details of the installation and usage of Carthage, visit [its project page](https://github.com/Carthage/Carthage).
2018-02-05 17:44:55 +08:00
## Usage
OpenVPNAdapter is designed to use in conjunction with [`NetworkExtension`](https://developer.apple.com/documentation/networkextension) framework. So at first, you need to add a Packet Tunnel Provider extension to the project and configure provision profiles for both the container app and the extension. There are official documentation and many tutorials describing how to do it so we won't dwell on this in detail.
Packet Tunnel Provider extension uses [`NEPacketTunnelProvider`](https://developer.apple.com/documentation/networkextension/nepackettunnelprovider) subclass to configure and establish VPN connection. Therefore, that class is the right place to configure OpenVPNAdapter. The following example shows how you can setup it:
```swift
import NetworkExtension
import OpenVPNAdapter
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) {
2018-02-07 22:37:54 +08:00
// 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.
let ovpnFileContent: NSData = ... // Retrieve content of a ovpn file
let ovpnSettings: [String : String] = ... // Retrieve settings as key:value pairs
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnFileContent
configuration.settings = ovpnSettings
2018-02-07 22:37:54 +08:00
// Apply OpenVPN configuration
let properties: OpenVPNProperties
do {
properties = try vpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
2018-02-07 22:37:54 +08:00
// Provide credentials if needed
if !properties.autologin {
2018-02-07 22:37:54 +08:00
// 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.
let username: String = ... // Retrieve a username
let password: String = ... // Retrieve a password
let credentials = OpenVPNCredentials()
credentials.username = username
credentials.password = password
do {
try vpnAdapter.provide(credentials: credentials)
} catch {
completionHandler(error)
return
}
}
2018-02-07 22:37:54 +08:00
// 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 != .notReachable else { return }
self?.vpnAdapter.reconnect(interval: 5)
}
2018-02-07 22:37:54 +08:00
// Establish connection and wait for .connected event
startHandler = completionHandler
vpnAdapter.connect()
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
vpnAdapter.disconnect()
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
2018-02-07 22:37:54 +08:00
// 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 (OpenVPNAdapterPacketFlow?) -> Void) {
setTunnelNetworkSettings(settings) { (error) in
completionHandler(error == nil ? self.packetFlow : nil)
}
}
2018-02-07 22:37:54 +08:00
// 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
}
}
2018-02-07 22:37:54 +08:00
// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
2018-02-07 22:37:54 +08:00
// 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)
}
}
2018-02-07 22:37:54 +08:00
// Use this method to process any log message returned by OpenVPN library.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
2018-02-07 22:37:54 +08:00
// Handle log messages
}
}
2018-02-07 22:37:54 +08:00
// 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 {}
```
2018-02-05 17:44:55 +08:00
## Contributing
2018-02-06 21:31:17 +08:00
Any contributions and suggestions are welcome! But before creating a PR or an issue please read the [Contribution Guide](CONTRIBUTING.md).
2018-02-05 17:44:55 +08:00
## Acknowledgments
2018-02-07 23:06:58 +08:00
Special thanks goes to @JonathanDowning for great help in development of this project and bug fixing.
2018-02-05 17:44:55 +08:00
## License
2018-02-06 21:32:04 +08:00
OpenVPNAdapter is available under the AGPLv3 license. See the [LICENSE](LICENSE) file for more info. Also this project has a few dependencies:
2018-02-06 21:04:00 +08:00
- [openvpn3](https://github.com/OpenVPN/openvpn3)
- [asio](https://github.com/chriskohlhoff/asio)
- [lz4](https://github.com/lz4/lz4)
- [mbedtls](https://github.com/ARMmbed/mbedtls)
2018-02-06 21:39:39 +08:00
See the [NOTICE](NOTICE) file for more info about their licenses and copyrights.