Merge pull request #77 from passepartoutvpn/override-network-settings

Override network settings
This commit is contained in:
Davide De Rosa 2019-05-01 23:04:35 +02:00 committed by GitHub
commit 98756438b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1006 additions and 24 deletions

View File

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- Override network settings. [#77](https://github.com/passepartoutvpn/passepartout-ios/pull/77)
## 1.6.0 (2019-05-01)
### Added

View File

@ -233,6 +233,7 @@
<segue destination="NJT-2N-LTa" kind="show" identifier="HostParametersSegueIdentifier" id="aL8-H6-znA"/>
<segue destination="9Kr-G1-asf" kind="show" identifier="ProviderPresetSegueIdentifier" id="kGf-Yu-KdY"/>
<segue destination="KmS-dJ-DVx" kind="show" identifier="DebugLogSegueIdentifier" id="XyI-by-AhD"/>
<segue destination="BCT-c7-ovS" kind="show" identifier="NetworkSettingsSegueIdentifier" id="5nM-PM-q9h"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
@ -384,6 +385,84 @@
</objects>
<point key="canvasLocation" x="1622" y="-270"/>
</scene>
<!--Network Settings View Controller-->
<scene sceneID="BPW-VO-Hgb">
<objects>
<tableViewController id="BCT-c7-ovS" customClass="NetworkSettingsViewController" customModule="Passepartout" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="qQL-jO-j0W">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="SettingTableViewCell" textLabel="NYG-Gq-hRp" detailTextLabel="iDZ-BL-Un5" style="IBUITableViewCellStyleValue1" id="KTK-qM-Z3I" customClass="SettingTableViewCell" customModule="Passepartout" customModuleProvider="target">
<rect key="frame" x="0.0" y="55.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="KTK-qM-Z3I" id="pZZ-fb-3Rb">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="NYG-Gq-hRp">
<rect key="frame" x="16" y="12" width="33.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="iDZ-BL-Un5">
<rect key="frame" x="315" y="12" width="44" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="ToggleTableViewCell" textLabel="chG-HK-4WY" style="IBUITableViewCellStyleDefault" id="JNm-q5-qBn" customClass="ToggleTableViewCell" customModule="Passepartout" customModuleProvider="target">
<rect key="frame" x="0.0" y="99.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="JNm-q5-qBn" id="NG6-Jk-qkq">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="chG-HK-4WY">
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="FieldTableViewCell" textLabel="dXB-Id-HH0" style="IBUITableViewCellStyleDefault" id="3Zq-6c-3Yc" customClass="FieldTableViewCell" customModule="Passepartout" customModuleProvider="target">
<rect key="frame" x="0.0" y="143.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="3Zq-6c-3Yc" id="h7B-7Q-NQr">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="dXB-Id-HH0">
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="BCT-c7-ovS" id="b8X-oR-HW4"/>
<outlet property="delegate" destination="BCT-c7-ovS" id="L7V-gR-mRb"/>
</connections>
</tableView>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="MWy-o4-Doa" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1098" y="1042"/>
</scene>
<!--Debug Log View Controller-->
<scene sceneID="Icu-BA-Eze">
<objects>

View File

@ -21,6 +21,7 @@ internal enum StoryboardSegue {
case debugLogSegueIdentifier = "DebugLogSegueIdentifier"
case endpointSegueIdentifier = "EndpointSegueIdentifier"
case hostParametersSegueIdentifier = "HostParametersSegueIdentifier"
case networkSettingsSegueIdentifier = "NetworkSettingsSegueIdentifier"
case providerPoolSegueIdentifier = "ProviderPoolSegueIdentifier"
case providerPresetSegueIdentifier = "ProviderPresetSegueIdentifier"
}

View File

@ -59,7 +59,7 @@ class ConfigurationViewController: UIViewController, TableModelHost {
}
model.add(.tls)
model.add(.compression)
model.add(.network)
// model.add(.network)
model.add(.other)
// headers

View File

@ -0,0 +1,610 @@
//
// NetworkSettingsViewController.swift
// Passepartout-iOS
//
// Created by Davide De Rosa on 4/29/19.
// Copyright (c) 2019 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import UIKit
import Passepartout_Core
import TunnelKit
import SwiftyBeaver
private let log = SwiftyBeaver.self
private enum FieldTag: Int {
case dnsDomain = 101
case dnsAddress = 200
case proxyAddress = 301
case proxyPort = 302
case proxyBypass = 400
}
private struct Offsets {
static let dnsAddress = 1
static let proxyBypass = 2
}
// FIXME: init networkSettings with HOST profile.sessionConfiguration
// FIXME: omit "Client" for PROVIDER
class NetworkSettingsViewController: UITableViewController {
var profile: ConnectionProfile?
private lazy var networkChoices: ProfileNetworkChoices = {
if let choices = profile?.networkChoices {
return choices
}
if let _ = profile as? ProviderConnectionProfile {
return ProfileNetworkChoices(choice: .server)
}
return ProfileNetworkChoices(choice: .client)
}()
private let networkSettings = ProfileNetworkSettings()
private lazy var clientNetworkSettings: ProfileNetworkSettings? = {
guard let hostProfile = profile as? HostConnectionProfile else {
return nil
}
return ProfileNetworkSettings(from: hostProfile.parameters.sessionConfiguration)
}()
private var choices: [NetworkChoice] {
guard let _ = clientNetworkSettings else {
return [.server, .manual]
}
return [.client, .server, .manual]
}
// MARK: TableModelHost
let model: TableModel<SectionType, RowType> = TableModel()
func reloadModel() {
model.clear()
// sections
model.add(.choices)
if networkChoices.gateway != .server {
model.add(.manualGateway)
}
if networkChoices.dns != .server {
model.add(.manualDNS)
}
if networkChoices.proxy != .server {
model.add(.manualProxy)
}
// headers
model.setHeader("", for: .choices)
model.setHeader(L10n.Configuration.Cells.DefaultGateway.caption, for: .manualGateway)
model.setHeader(L10n.Configuration.Cells.DnsServer.caption, for: .manualDNS)
model.setHeader(L10n.Configuration.Cells.ProxyHttp.caption, for: .manualProxy)
// footers
// model.setFooter(L10n.Configuration.Sections.Reset.footer, for: .reset)
// rows
model.set([.gateway, .dns, .proxy], in: .choices)
model.set([.gatewayIPv4, .gatewayIPv6], in: .manualGateway)
var dnsRows: [RowType] = Array(repeating: .dnsAddress, count: networkSettings.dnsServers?.count ?? 0)
dnsRows.insert(.dnsDomain, at: 0)
if networkChoices.dns == .manual {
dnsRows.append(.dnsAddAddress)
}
model.set(dnsRows, in: .manualDNS)
var proxyRows: [RowType] = Array(repeating: .proxyBypass, count: networkSettings.proxyBypassDomains?.count ?? 0)
proxyRows.insert(.proxyAddress, at: 0)
proxyRows.insert(.proxyPort, at: 1)
if networkChoices.proxy == .manual {
proxyRows.append(.proxyAddBypass)
}
model.set(proxyRows, in: .manualProxy)
}
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
updateGateway(networkChoices.gateway)
updateDNS(networkChoices.dns)
updateProxy(networkChoices.proxy)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
reloadModel()
tableView.reloadData()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
profile?.networkChoices = networkChoices
if networkChoices.gateway == .manual {
let settings = profile?.manualNetworkSettings ?? ProfileNetworkSettings()
settings.copyGateway(from: networkSettings)
profile?.manualNetworkSettings = settings
}
if networkChoices.dns == .manual {
let settings = profile?.manualNetworkSettings ?? ProfileNetworkSettings()
settings.copyDNS(from: networkSettings)
profile?.manualNetworkSettings = settings
}
if networkChoices.proxy == .manual {
let settings = profile?.manualNetworkSettings ?? ProfileNetworkSettings()
settings.copyProxy(from: networkSettings)
profile?.manualNetworkSettings = settings
}
}
// MARK: Actions
private func updateGateway(_ choice: NetworkChoice) {
networkChoices.gateway = choice
switch networkChoices.gateway {
case .client:
if let settings = clientNetworkSettings {
networkSettings.copyGateway(from: settings)
}
case .server:
break
case .manual:
if let settings = profile?.manualNetworkSettings {
networkSettings.copyGateway(from: settings)
}
}
}
private func updateDNS(_ choice: NetworkChoice) {
networkChoices.dns = choice
switch networkChoices.dns {
case .client:
if let settings = clientNetworkSettings {
networkSettings.copyDNS(from: settings)
}
case .server:
break
case .manual:
if let settings = profile?.manualNetworkSettings {
networkSettings.copyDNS(from: settings)
}
}
}
private func updateProxy(_ choice: NetworkChoice) {
networkChoices.proxy = choice
switch networkChoices.proxy {
case .client:
if let settings = clientNetworkSettings {
networkSettings.copyProxy(from: settings)
}
case .server:
break
case .manual:
if let settings = profile?.manualNetworkSettings {
networkSettings.copyProxy(from: settings)
}
}
}
private func commitTextField(_ field: UITextField) {
// DNS: domain, servers
// Proxy: address, port, bypass domains
if field.tag == FieldTag.dnsDomain.rawValue {
networkSettings.dnsDomainName = field.text
} else if field.tag == FieldTag.proxyAddress.rawValue {
networkSettings.proxyAddress = field.text
} else if field.tag == FieldTag.proxyPort.rawValue {
networkSettings.proxyPort = UInt16(field.text ?? "0")
} else if field.tag >= FieldTag.dnsAddress.rawValue && field.tag < FieldTag.proxyAddress.rawValue {
let i = field.tag - FieldTag.dnsAddress.rawValue
networkSettings.dnsServers?[i] = field.text ?? ""
} else if field.tag >= FieldTag.proxyBypass.rawValue {
let i = field.tag - FieldTag.proxyBypass.rawValue
networkSettings.proxyBypassDomains?[i] = field.text ?? ""
}
log.debug("Network settings: \(networkSettings)")
}
}
// MARK: -
extension NetworkSettingsViewController {
enum SectionType: Int {
case choices
case manualGateway
case manualDNS
case manualProxy
}
enum RowType: Int {
case gateway
case dns
case proxy
case gatewayIPv4
case gatewayIPv6
case dnsDomain
case dnsAddress
case dnsAddAddress
case proxyAddress
case proxyPort
case proxyBypass
case proxyAddBypass
}
override func numberOfSections(in tableView: UITableView) -> Int {
return model.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return model.header(for: section)
}
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return model.footer(for: section)
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return model.headerHeight(for: section)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let row = model.row(at: indexPath)
switch row {
case .gateway:
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
cell.leftText = model.header(for: .manualGateway)
cell.rightText = networkChoices.gateway.description
return cell
case .dns:
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
cell.leftText = model.header(for: .manualDNS)
cell.rightText = networkChoices.dns.description
return cell
case .proxy:
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
cell.leftText = model.header(for: .manualProxy)
cell.rightText = networkChoices.proxy.description
return cell
case .gatewayIPv4:
let cell = Cells.toggle.dequeue(from: tableView, for: indexPath, tag: row.rawValue, delegate: self)
cell.caption = "IPv4"
cell.toggle.isEnabled = (networkChoices.gateway == .manual)
cell.isOn = networkSettings.gatewayPolicies?.contains(.IPv4) ?? false
return cell
case .gatewayIPv6:
let cell = Cells.toggle.dequeue(from: tableView, for: indexPath, tag: row.rawValue, delegate: self)
cell.caption = "IPv6"
cell.toggle.isEnabled = (networkChoices.gateway == .manual)
cell.isOn = networkSettings.gatewayPolicies?.contains(.IPv6) ?? false
return cell
case .dnsDomain:
let cell = Cells.field.dequeue(from: tableView, for: indexPath)
cell.caption = L10n.Configuration.Cells.DnsDomain.caption
cell.field.tag = FieldTag.dnsDomain.rawValue
cell.field.text = networkSettings.dnsDomainName
cell.field.clearButtonMode = .always
cell.field.keyboardType = .asciiCapable
cell.captionWidth = 160.0
cell.delegate = self
if networkChoices.dns == .manual {
cell.field.isEnabled = true
cell.field.placeholder = L10n.Global.Cells.none
} else {
cell.field.isEnabled = false
cell.field.placeholder = nil
}
return cell
case .dnsAddress:
let i = indexPath.row - Offsets.dnsAddress
let cell = Cells.field.dequeue(from: tableView, for: indexPath)
cell.caption = L10n.NetworkSettings.Cells.Address.caption
cell.field.tag = FieldTag.dnsAddress.rawValue + i
cell.field.text = networkSettings.dnsServers?[i]
cell.field.clearButtonMode = .always
cell.field.keyboardType = .decimalPad
cell.captionWidth = 160.0
cell.delegate = self
if networkChoices.dns == .manual {
cell.field.isEnabled = true
cell.field.placeholder = L10n.Global.Cells.none
} else {
cell.field.isEnabled = false
cell.field.placeholder = nil
}
return cell
case .dnsAddAddress:
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
cell.applyAction(Theme.current)
cell.leftText = L10n.NetworkSettings.Cells.AddDnsServer.caption
return cell
case .proxyAddress:
let cell = Cells.field.dequeue(from: tableView, for: indexPath)
cell.caption = L10n.NetworkSettings.Cells.Address.caption
cell.field.tag = FieldTag.proxyAddress.rawValue
cell.field.text = networkSettings.proxyAddress
cell.field.clearButtonMode = .always
cell.field.keyboardType = .decimalPad
cell.captionWidth = 160.0
cell.delegate = self
if networkChoices.proxy == .manual {
cell.field.isEnabled = true
cell.field.placeholder = L10n.Global.Cells.none
} else {
cell.field.isEnabled = false
cell.field.placeholder = nil
}
return cell
case .proxyPort:
let cell = Cells.field.dequeue(from: tableView, for: indexPath)
cell.caption = L10n.NetworkSettings.Cells.Port.caption
cell.field.tag = FieldTag.proxyPort.rawValue
cell.field.text = networkSettings.proxyPort?.description
cell.field.clearButtonMode = .always
cell.field.keyboardType = .numberPad
cell.captionWidth = 160.0
cell.delegate = self
if networkChoices.proxy == .manual {
cell.field.isEnabled = true
cell.field.placeholder = L10n.Global.Cells.none
} else {
cell.field.isEnabled = false
cell.field.placeholder = nil
}
return cell
case .proxyBypass:
let i = indexPath.row - Offsets.proxyBypass
let cell = Cells.field.dequeue(from: tableView, for: indexPath)
cell.caption = L10n.NetworkSettings.Cells.ProxyBypass.caption
cell.field.tag = FieldTag.proxyBypass.rawValue + i
cell.field.text = networkSettings.proxyBypassDomains?[i]
cell.field.clearButtonMode = .always
cell.field.keyboardType = .asciiCapable
cell.captionWidth = 160.0
cell.delegate = self
if networkChoices.proxy == .manual {
cell.field.isEnabled = true
cell.field.placeholder = L10n.Global.Cells.none
} else {
cell.field.isEnabled = false
cell.field.placeholder = nil
}
return cell
case .proxyAddBypass:
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
cell.applyAction(Theme.current)
cell.leftText = L10n.NetworkSettings.Cells.AddProxyBypass.caption
return cell
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
switch model.row(at: indexPath) {
case .gateway:
let vc = OptionViewController<NetworkChoice>()
vc.title = (cell as? SettingTableViewCell)?.leftText
vc.options = choices
vc.descriptionBlock = { $0.description }
vc.selectedOption = networkChoices.gateway
vc.selectionBlock = { [weak self] in
self?.updateGateway($0)
self?.navigationController?.popViewController(animated: true)
}
navigationController?.pushViewController(vc, animated: true)
case .dns:
let vc = OptionViewController<NetworkChoice>()
vc.title = (cell as? SettingTableViewCell)?.leftText
vc.options = choices
vc.descriptionBlock = { $0.description }
vc.selectedOption = networkChoices.dns
vc.selectionBlock = { [weak self] in
self?.updateDNS($0)
self?.navigationController?.popViewController(animated: true)
}
navigationController?.pushViewController(vc, animated: true)
case .proxy:
let vc = OptionViewController<NetworkChoice>()
vc.title = (cell as? SettingTableViewCell)?.leftText
vc.options = choices
vc.descriptionBlock = { $0.description }
vc.selectedOption = networkChoices.proxy
vc.selectionBlock = { [weak self] in
self?.updateProxy($0)
self?.navigationController?.popViewController(animated: true)
}
navigationController?.pushViewController(vc, animated: true)
case .dnsAddAddress:
tableView.deselectRow(at: indexPath, animated: true)
var dnsServers = networkSettings.dnsServers ?? []
dnsServers.append("")
networkSettings.dnsServers = dnsServers
reloadModel()
tableView.insertRows(at: [indexPath], with: .automatic)
case .proxyAddBypass:
tableView.deselectRow(at: indexPath, animated: true)
var bypassDomains = networkSettings.proxyBypassDomains ?? []
bypassDomains.append("")
networkSettings.proxyBypassDomains = bypassDomains
reloadModel()
tableView.insertRows(at: [indexPath], with: .automatic)
default:
break
}
}
private func handle(row: RowType, cell: ToggleTableViewCell) {
switch row {
case .gatewayIPv4:
guard networkChoices.gateway == .manual else {
return
}
var policies = networkSettings.gatewayPolicies ?? []
if cell.toggle.isOn {
policies.append(.IPv4)
} else {
policies.removeAll { $0 == .IPv4 }
}
policies.sort { $0.rawValue < $1.rawValue }
networkSettings.gatewayPolicies = policies
case .gatewayIPv6:
guard networkChoices.gateway == .manual else {
return
}
var policies = networkSettings.gatewayPolicies ?? []
if cell.toggle.isOn {
policies.append(.IPv6)
} else {
policies.removeAll { $0 == .IPv6 }
}
policies.sort { $0.rawValue < $1.rawValue }
networkSettings.gatewayPolicies = policies
default:
break
}
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
switch model.row(at: indexPath) {
case .dnsAddress, .proxyBypass:
return true
default:
return false
}
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
switch model.row(at: indexPath) {
case .dnsAddress:
// start at row 1
networkSettings.dnsServers?.remove(at: indexPath.row - Offsets.dnsAddress)
case .proxyBypass:
// start at row 2
networkSettings.proxyBypassDomains?.remove(at: indexPath.row - Offsets.proxyBypass)
default:
break
}
reloadModel()
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
extension NetworkSettingsViewController: ToggleTableViewCellDelegate {
func toggleCell(_ cell: ToggleTableViewCell, didToggleToValue value: Bool) {
guard let item = RowType(rawValue: cell.tag) else {
return
}
handle(row: item, cell: cell)
}
}
extension NetworkSettingsViewController: FieldTableViewCellDelegate {
func fieldCellDidEdit(_ cell: FieldTableViewCell) {
commitTextField(cell.field)
}
func fieldCellDidEnter(_: FieldTableViewCell) {
}
}
extension NetworkChoice: CustomStringConvertible {
public var description: String {
switch self {
case .client:
return L10n.NetworkSettings.Cells.Choice.client
case .server:
return L10n.NetworkSettings.Cells.Choice.server
case .manual:
return L10n.Global.Cells.manual
}
}
}

View File

@ -174,6 +174,11 @@ class ServiceViewController: UIViewController, TableModelHost {
vc?.originalConfigurationURL = service.configurationURL(for: uncheckedHostProfile)
vc?.delegate = self
case .networkSettingsSegueIdentifier:
let vc = destination as? NetworkSettingsViewController
vc?.title = L10n.Service.Cells.NetworkSettings.caption
vc?.profile = profile
case .debugLogSegueIdentifier:
break
}
@ -580,6 +585,8 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
case hostParameters
case networkSettings
case vpnResolvesHostname
case vpnSurvivesSleep
@ -720,6 +727,11 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
}
return cell
case .networkSettings:
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
cell.leftText = L10n.Service.Cells.NetworkSettings.caption
return cell
// provider cells
case .providerPool:
@ -908,6 +920,10 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
perform(segue: StoryboardSegue.Main.hostParametersSegueIdentifier, sender: cell)
return true
case .networkSettings:
perform(segue: StoryboardSegue.Main.networkSettingsSegueIdentifier, sender: cell)
return true
case .trustedAddCurrentWiFi:
if #available(iOS 12, *) {
IntentDispatcher.donateTrustCurrentNetwork()
@ -1059,10 +1075,10 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
}
if isProvider {
model.set([.account], in: .authentication)
model.set([.providerPool, .endpoint, .providerPreset], in: .configuration)
model.set([.providerPool, .endpoint, .providerPreset, .networkSettings], in: .configuration)
model.set([.providerRefresh], in: .providerInfrastructure)
} else {
model.set([.account, .endpoint, .hostParameters], in: .configuration)
model.set([.account, .endpoint, .hostParameters, .networkSettings], in: .configuration)
}
if isActiveProfile {
if isProvider {

View File

@ -110,6 +110,8 @@
0EF56BBB2185AC8500B0C8AB /* SwiftGen+Segues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF56BBA2185AC8500B0C8AB /* SwiftGen+Segues.swift */; };
0EF5CF252141CE58004FF1BD /* HUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF5CF242141CE58004FF1BD /* HUD.swift */; };
0EF5CF292141F31F004FF1BD /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4FD7ED20D539A0002221FF /* Utils.swift */; };
0EFB901822764689006405E4 /* ProfileNetworkSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFB901722764689006405E4 /* ProfileNetworkSettings.swift */; };
0EFB901A2276D7F1006405E4 /* NetworkSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFB90192276D7F1006405E4 /* NetworkSettingsViewController.swift */; };
0EFBFAC121AC464800887A8C /* CreditsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFBFAC021AC464800887A8C /* CreditsViewController.swift */; };
0EFD943E215BE10800529B64 /* IssueReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFD943D215BE10800529B64 /* IssueReporter.swift */; };
0EFD9440215BED8E00529B64 /* LabelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFD943F215BED8E00529B64 /* LabelViewController.swift */; };
@ -286,6 +288,8 @@
0EEB53B1225D525B00746300 /* Downloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Downloader.swift; sourceTree = "<group>"; };
0EF56BBA2185AC8500B0C8AB /* SwiftGen+Segues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+Segues.swift"; sourceTree = "<group>"; };
0EF5CF242141CE58004FF1BD /* HUD.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HUD.swift; sourceTree = "<group>"; };
0EFB901722764689006405E4 /* ProfileNetworkSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileNetworkSettings.swift; sourceTree = "<group>"; };
0EFB90192276D7F1006405E4 /* NetworkSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSettingsViewController.swift; sourceTree = "<group>"; };
0EFBFAC021AC464800887A8C /* CreditsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditsViewController.swift; sourceTree = "<group>"; };
0EFD943D215BE10800529B64 /* IssueReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueReporter.swift; sourceTree = "<group>"; };
0EFD943F215BED8E00529B64 /* LabelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelViewController.swift; sourceTree = "<group>"; };
@ -510,6 +514,7 @@
0EC7F20420E24308004EA58E /* DebugLog.swift */,
0ED38AE621404F100004D387 /* EndpointDataSource.swift */,
0E89DFC4213DF7AE00741BA1 /* Preferences.swift */,
0EFB901722764689006405E4 /* ProfileNetworkSettings.swift */,
0E89DFC7213E8FC500741BA1 /* SessionProxy+Communication.swift */,
0E2B494120FD16540094784C /* TransientStore.swift */,
0E4C9CB820DB9BC600A0C59C /* TrustedNetworks.swift */,
@ -610,6 +615,7 @@
0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */,
0E6BE13E20CFBAB300A6DD36 /* DebugLogViewController.swift */,
0E158AD920E11B0B00C85A82 /* EndpointViewController.swift */,
0EFB90192276D7F1006405E4 /* NetworkSettingsViewController.swift */,
0ED31C2B20CF2D6F0027975F /* ProviderPoolViewController.swift */,
0E1D72B1213BFFCF00BA1586 /* ProviderPresetViewController.swift */,
0E57F63D20C83FC5008323CF /* ServiceViewController.swift */,
@ -1047,6 +1053,7 @@
0E58BD9322404EF1006FB157 /* Intents.intentdefinition in Sources */,
0E3152D3223FA05400F61841 /* EndpointDataSource.swift in Sources */,
0E3152D4223FA05400F61841 /* Preferences.swift in Sources */,
0EFB901822764689006405E4 /* ProfileNetworkSettings.swift in Sources */,
0E3152C0223FA03D00F61841 /* Utils.swift in Sources */,
0E3152CB223FA04D00F61841 /* Pool.swift in Sources */,
0E3152C7223FA04800F61841 /* VPNStatus.swift in Sources */,
@ -1075,6 +1082,7 @@
0E4FD7F120D58618002221FF /* Macros.swift in Sources */,
0EF5CF252141CE58004FF1BD /* HUD.swift in Sources */,
0E05C5D720D1645F006EE732 /* ToggleTableViewCell.swift in Sources */,
0EFB901A2276D7F1006405E4 /* NetworkSettingsViewController.swift in Sources */,
0E05C5D420D1645F006EE732 /* FieldTableViewCell.swift in Sources */,
0E36D25C224034AD006AF062 /* ShortcutsConnectToViewController.swift in Sources */,
0E05C61D20D27C82006EE732 /* Theme.swift in Sources */,

View File

@ -109,6 +109,7 @@
"service.cells.provider.preset.caption" = "Preset";
"service.cells.provider.refresh.caption" = "Refresh infrastructure";
"service.cells.host.parameters.caption" = "Parameters";
"service.cells.network_settings.caption" = "Network settings";
"service.cells.vpn_survives_sleep.caption" = "Keep alive on sleep";
"service.cells.vpn_resolves_hostname.caption" = "Resolve server hostname";
//"service.cells.vpn_prefers_udp.caption" = "Prefer UDP socket";
@ -200,6 +201,14 @@
"configuration.cells.renegotiation_seconds.value.after" = "after %@";
"configuration.cells.random_endpoint.caption" = "Randomize endpoint";
"network_settings.cells.choice.client" = "Read .ovpn";
"network_settings.cells.choice.server" = "Pull from server";
"network_settings.cells.address.caption" = "Address";
"network_settings.cells.port.caption" = "Port";
"network_settings.cells.add_dns_server.caption" = "Add address";
"network_settings.cells.proxy_bypass.caption" = "Bypass domain";
"network_settings.cells.add_proxy_bypass.caption" = "Add bypass domain";
"debug_log.buttons.previous" = "Previous";
"debug_log.buttons.next" = "Next";
"debug_log.alerts.empty_log.message" = "The debug log is empty.";

View File

@ -42,6 +42,10 @@ public protocol ConnectionProfile: class, EndpointDataSource, CustomStringConver
var requiresCredentials: Bool { get }
var networkChoices: ProfileNetworkChoices? { get set }
var manualNetworkSettings: ProfileNetworkSettings? { get set }
func generate(from configuration: TunnelKitProvider.Configuration, preferences: Preferences) throws -> TunnelKitProvider.Configuration
func with(newId: String) -> ConnectionProfile

View File

@ -299,6 +299,7 @@ public class ConnectionService: Codable {
return nil
}
}
return profile
}
@ -352,7 +353,9 @@ public class ConnectionService: Codable {
// fall back to the safer option
var builder = host.parameters.builder()
builder.sessionConfiguration.routingPolicies = [.IPv4, .IPv6]
var sessionBuilder = builder.sessionConfiguration.builder()
sessionBuilder.routingPolicies = [.IPv4, .IPv6]
builder.sessionConfiguration = sessionBuilder.build()
host.parameters = builder.build()
}
cache[entry.key] = host
@ -529,7 +532,19 @@ public class ConnectionService: Codable {
}
}
let cfg = try profile.generate(from: baseConfiguration, preferences: preferences)
var cfg = try profile.generate(from: baseConfiguration, preferences: preferences)
// override network settings
if let choices = profile.networkChoices, let settings = profile.manualNetworkSettings {
var builder = cfg.builder()
var sessionBuilder = builder.sessionConfiguration.builder()
sessionBuilder.applyGateway(from: choices, settings: settings)
sessionBuilder.applyDNS(from: choices, settings: settings)
sessionBuilder.applyProxy(from: choices, settings: settings)
builder.sessionConfiguration = sessionBuilder.build()
cfg = builder.build()
}
let protocolConfiguration = try cfg.generatedTunnelProtocol(
withBundleIdentifier: GroupConstants.App.tunnelIdentifier,
appGroup: appGroup,

View File

@ -0,0 +1,168 @@
//
// ProfileNetworkSettings.swift
// Passepartout
//
// Created by Davide De Rosa on 04/28/19.
// Copyright (c) 2019 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Foundation
import TunnelKit
public enum NetworkChoice: String, Codable {
case client
case server // erase client settings
case manual
}
public class ProfileNetworkChoices: Codable {
public var gateway: NetworkChoice
public var dns: NetworkChoice
public var proxy: NetworkChoice
public init(choice: NetworkChoice) {
gateway = choice
dns = choice
proxy = choice
}
}
public class ProfileNetworkSettings: Codable, CustomStringConvertible {
public var gatewayPolicies: [SessionProxy.RoutingPolicy]?
public var dnsServers: [String]?
public var dnsDomainName: String?
public var proxyAddress: String?
public var proxyPort: UInt16?
public var proxyServer: Proxy? {
guard let address = proxyAddress, let port = proxyPort, !address.isEmpty, port > 0 else {
return nil
}
return Proxy(address, port)
}
public var proxyBypassDomains: [String]?
public init() {
gatewayPolicies = [.IPv4, .IPv6]
}
public init(from configuration: SessionProxy.Configuration) {
gatewayPolicies = configuration.routingPolicies
dnsDomainName = configuration.searchDomain
dnsServers = configuration.dnsServers
proxyAddress = configuration.httpProxy?.address
proxyPort = configuration.httpProxy?.port
proxyBypassDomains = configuration.proxyBypassDomains
}
public func copy(from settings: ProfileNetworkSettings) {
copyGateway(from: settings)
copyDNS(from: settings)
copyProxy(from: settings)
}
public func copyGateway(from settings: ProfileNetworkSettings) {
gatewayPolicies = settings.gatewayPolicies
}
public func copyDNS(from settings: ProfileNetworkSettings) {
dnsDomainName = settings.dnsDomainName
dnsServers = settings.dnsServers?.filter { !$0.isEmpty }
}
public func copyProxy(from settings: ProfileNetworkSettings) {
proxyAddress = settings.proxyAddress
proxyPort = settings.proxyPort
proxyBypassDomains = settings.proxyBypassDomains?.filter { !$0.isEmpty }
}
// MARK: CustomStringConvertible
public var description: String {
let comps: [String] = [
"gw: \(gatewayPolicies?.description ?? "")",
"dns: {domain: \(dnsDomainName ?? ""), servers: \(dnsServers?.description ?? "[]")}",
"proxy: {address: \(proxyAddress ?? ""), port: \(proxyPort?.description ?? ""), bypass: \(proxyBypassDomains?.description ?? "[]")}"
]
return "{\(comps.joined(separator: ", "))}"
}
}
extension SessionProxy.ConfigurationBuilder {
public mutating func applyGateway(from choices: ProfileNetworkChoices, settings: ProfileNetworkSettings) {
switch choices.gateway {
case .client:
break
case .server:
routingPolicies = nil
case .manual:
routingPolicies = settings.gatewayPolicies
}
}
public mutating func applyDNS(from choices: ProfileNetworkChoices, settings: ProfileNetworkSettings) {
switch choices.dns {
case .client:
break
case .server:
dnsServers = nil
searchDomain = nil
case .manual:
dnsServers = settings.dnsServers?.filter { !$0.isEmpty }
searchDomain = settings.dnsDomainName
}
}
public mutating func applyProxy(from choices: ProfileNetworkChoices, settings: ProfileNetworkSettings) {
switch choices.proxy {
case .client:
break
case .server:
httpProxy = nil
httpsProxy = nil
proxyBypassDomains = nil
case .manual:
if let proxyServer = settings.proxyServer {
httpProxy = proxyServer
httpsProxy = proxyServer
proxyBypassDomains = settings.proxyBypassDomains?.filter { !$0.isEmpty }
} else {
httpProxy = nil
httpsProxy = nil
proxyBypassDomains = nil
}
}
}
}

View File

@ -54,6 +54,10 @@ public class HostConnectionProfile: ConnectionProfile, Codable, Equatable {
return false
}
public var networkChoices: ProfileNetworkChoices?
public var manualNetworkSettings: ProfileNetworkSettings?
public func generate(from configuration: TunnelKitProvider.Configuration, preferences: Preferences) throws -> TunnelKitProvider.Configuration {
guard let endpointProtocols = parameters.sessionConfiguration.endpointProtocols, !endpointProtocols.isEmpty else {
preconditionFailure("No endpointProtocols")
@ -67,7 +71,9 @@ public class HostConnectionProfile: ConnectionProfile, Codable, Equatable {
builder.masksPrivateData = configuration.masksPrivateData
// forcibly override hostname with profile hostname (never nil)
builder.sessionConfiguration.hostname = hostname
var sessionBuilder = builder.sessionConfiguration.builder()
sessionBuilder.hostname = hostname
builder.sessionConfiguration = sessionBuilder.build()
return builder.build()
}

View File

@ -35,6 +35,10 @@ public class PlaceholderConnectionProfile: ConnectionProfile {
public var requiresCredentials: Bool = false
public var networkChoices: ProfileNetworkChoices?
public var manualNetworkSettings: ProfileNetworkSettings?
public func generate(from configuration: TunnelKitProvider.Configuration, preferences: Preferences) throws -> TunnelKitProvider.Configuration {
fatalError("Generating configuration from a PlaceholderConnectionProfile")
}

View File

@ -57,6 +57,10 @@ public class ProviderConnectionProfile: ConnectionProfile, Codable, Equatable {
public var manualProtocol: EndpointProtocol?
public var networkChoices: ProfileNetworkChoices?
public var manualNetworkSettings: ProfileNetworkSettings?
public var usesProviderEndpoint: Bool {
return (manualAddress != nil) || (manualProtocol != nil)
}
@ -134,8 +138,9 @@ public class ProviderConnectionProfile: ConnectionProfile, Codable, Equatable {
builder.resolvedAddresses = pool.addresses()
}
var sessionBuilder = builder.sessionConfiguration.builder()
if let proto = manualProtocol {
builder.sessionConfiguration.endpointProtocols = [proto]
sessionBuilder.endpointProtocols = [proto]
} else {
// restrict "Any" protocol to UDP, unless there are no UDP endpoints
@ -145,12 +150,14 @@ public class ProviderConnectionProfile: ConnectionProfile, Codable, Equatable {
endpoints = allEndpoints
}
builder.sessionConfiguration.endpointProtocols = endpoints
// builder.sessionConfiguration.endpointProtocols = [
sessionBuilder.endpointProtocols = endpoints
// sessionBuilder.endpointProtocols = [
// EndpointProtocol(.udp, 8080),
// EndpointProtocol(.tcp, 443)
// ]
}
builder.sessionConfiguration = sessionBuilder.build()
return builder.build()
}

View File

@ -417,6 +417,37 @@ public enum L10n {
}
}
public enum NetworkSettings {
public enum Cells {
public enum AddDnsServer {
/// Add address
public static let caption = L10n.tr("Localizable", "network_settings.cells.add_dns_server.caption")
}
public enum AddProxyBypass {
/// Add bypass domain
public static let caption = L10n.tr("Localizable", "network_settings.cells.add_proxy_bypass.caption")
}
public enum Address {
/// Address
public static let caption = L10n.tr("Localizable", "network_settings.cells.address.caption")
}
public enum Choice {
/// Read .ovpn
public static let client = L10n.tr("Localizable", "network_settings.cells.choice.client")
/// Pull from server
public static let server = L10n.tr("Localizable", "network_settings.cells.choice.server")
}
public enum Port {
/// Port
public static let caption = L10n.tr("Localizable", "network_settings.cells.port.caption")
}
public enum ProxyBypass {
/// Bypass domain
public static let caption = L10n.tr("Localizable", "network_settings.cells.proxy_bypass.caption")
}
}
}
public enum Organizer {
public enum Alerts {
public enum AddHost {
@ -699,6 +730,10 @@ public enum L10n {
/// Mask network data
public static let caption = L10n.tr("Localizable", "service.cells.masks_private_data.caption")
}
public enum NetworkSettings {
/// Network settings
public static let caption = L10n.tr("Localizable", "service.cells.network_settings.caption")
}
public enum Provider {
public enum Pool {
/// Location

View File

@ -3,10 +3,10 @@ platform :ios, '11.0'
use_frameworks!
def shared_pods
pod 'TunnelKit', '~> 1.7.0'
pod 'TunnelKit/LZO', '~> 1.7.0'
#pod 'TunnelKit', :git => 'https://github.com/keeshux/tunnelkit', :commit => 'f799f47'
#pod 'TunnelKit/LZO', :git => 'https://github.com/keeshux/tunnelkit', :commit => 'f799f47'
#pod 'TunnelKit', '~> 1.7.0'
#pod 'TunnelKit/LZO', '~> 1.7.0'
pod 'TunnelKit', :git => 'https://github.com/keeshux/tunnelkit', :commit => '4da64c5'
pod 'TunnelKit/LZO', :git => 'https://github.com/keeshux/tunnelkit', :commit => '4da64c5'
#pod 'TunnelKit', :path => '../../personal/tunnelkit'
#pod 'TunnelKit/LZO', :path => '../../personal/tunnelkit'
pod 'SSZipArchive'

View File

@ -3,22 +3,22 @@ PODS:
- OpenSSL-Apple (1.1.0j.2)
- SSZipArchive (2.1.4)
- SwiftyBeaver (1.7.0)
- TunnelKit (1.7.0):
- TunnelKit/AppExtension (= 1.7.0)
- TunnelKit/Core (= 1.7.0)
- TunnelKit/AppExtension (1.7.0):
- TunnelKit (1.7.1):
- TunnelKit/AppExtension (= 1.7.1)
- TunnelKit/Core (= 1.7.1)
- TunnelKit/AppExtension (1.7.1):
- SwiftyBeaver
- TunnelKit/Core
- TunnelKit/Core (1.7.0):
- TunnelKit/Core (1.7.1):
- OpenSSL-Apple (~> 1.1.0j.2)
- SwiftyBeaver
- TunnelKit/LZO (1.7.0)
- TunnelKit/LZO (1.7.1)
DEPENDENCIES:
- MBProgressHUD
- SSZipArchive
- TunnelKit (~> 1.7.0)
- TunnelKit/LZO (~> 1.7.0)
- TunnelKit (from `https://github.com/keeshux/tunnelkit`, commit `4da64c5`)
- TunnelKit/LZO (from `https://github.com/keeshux/tunnelkit`, commit `4da64c5`)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
@ -26,15 +26,24 @@ SPEC REPOS:
- OpenSSL-Apple
- SSZipArchive
- SwiftyBeaver
- TunnelKit
EXTERNAL SOURCES:
TunnelKit:
:commit: 4da64c5
:git: https://github.com/keeshux/tunnelkit
CHECKOUT OPTIONS:
TunnelKit:
:commit: 4da64c5
:git: https://github.com/keeshux/tunnelkit
SPEC CHECKSUMS:
MBProgressHUD: e7baa36a220447d8aeb12769bf0585582f3866d9
OpenSSL-Apple: e88e1eb314acb4a05e2348069790c4aa49f6d319
SSZipArchive: 41455d4b8d2b6ab93990820b50dc697c2554a322
SwiftyBeaver: 4cc0080d2e23f980652e28978db11a5c9da39165
TunnelKit: 7355382cd0ff4fce26e48c51dc680d0497b35b17
TunnelKit: dd00b33e4c6d84f2d32390448a33ef5e37b1ac52
PODFILE CHECKSUM: 5c6c177586b156662aaed2bc5953ab15560cb112
PODFILE CHECKSUM: 32c9f11727e184e6ffbbdf5bd88ea65f79f1f155
COCOAPODS: 1.6.1

View File

@ -43,6 +43,10 @@ Passepartout strives for transparency, by showing a fairly detailed yet understa
[<img src="res/snap-parameters.png" width="300">](res/snap-parameters.png)
### Override network settings
Override default gateway, DNS and proxy settings right from the app. Don't bother editing the .ovpn file or your pushed server settings. This is especially useful if you want to override your provider settings, e.g. to integrate your own DNS-based ad blocking.
### Disconnect on sleep
Keeping the VPN active in the background provides smoother operation, but may be tough for the battery. You might want to use this feature if you're concerned about battery life. When the device goes to sleep, the VPN will disconnect to then reconnect on device wake-up.

View File

@ -22,6 +22,7 @@ EASY TO USE
- Handle your profiles in one single place.
- Import .ovpn configuration files.
- Fine-tune encryption without tweaking any configuration file.
- Override network settings in a snap (default gateway, DNS, proxy).
OS INTEGRATION