564 lines
19 KiB
Swift
564 lines
19 KiB
Swift
//
|
|
// 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 PassepartoutCore
|
|
import TunnelKit
|
|
import SwiftyBeaver
|
|
import Convenience
|
|
|
|
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
|
|
}
|
|
|
|
class NetworkSettingsViewController: UITableViewController {
|
|
var profile: ConnectionProfile?
|
|
|
|
private lazy var networkChoices = ProfileNetworkChoices.with(profile: profile)
|
|
|
|
private lazy var clientNetworkSettings = profile?.clientNetworkSettings
|
|
|
|
private let networkSettings = ProfileNetworkSettings()
|
|
|
|
// MARK: StrongTableHost
|
|
|
|
let model: StrongTableModel<SectionType, RowType> = StrongTableModel()
|
|
|
|
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("", forSection: .choices)
|
|
model.setHeader(L10n.Core.NetworkSettings.Gateway.title, forSection: .manualGateway)
|
|
model.setHeader(L10n.Core.NetworkSettings.Dns.title, forSection: .manualDNS)
|
|
model.setHeader(L10n.Core.NetworkSettings.Proxy.title, forSection: .manualProxy)
|
|
|
|
// footers
|
|
// model.setFooter(L10n.Core.Configuration.Sections.Reset.footer, for: .reset)
|
|
|
|
// rows
|
|
model.set([.gateway, .dns, .proxy], forSection: .choices)
|
|
model.set([.gatewayIPv4, .gatewayIPv6], forSection: .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, forSection: .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, forSection: .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)
|
|
|
|
commitChanges()
|
|
}
|
|
|
|
// 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)")
|
|
}
|
|
|
|
private func commitChanges() {
|
|
let settings = profile?.manualNetworkSettings ?? ProfileNetworkSettings()
|
|
profile?.networkChoices = networkChoices
|
|
if networkChoices.gateway == .manual {
|
|
settings.copyGateway(from: networkSettings)
|
|
}
|
|
if networkChoices.dns == .manual {
|
|
settings.copyDNS(from: networkSettings)
|
|
}
|
|
if networkChoices.proxy == .manual {
|
|
settings.copyProxy(from: networkSettings)
|
|
}
|
|
profile?.manualNetworkSettings = settings
|
|
}
|
|
}
|
|
|
|
// 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.numberOfSections
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
|
return model.header(forSection: section)
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
|
return model.footer(forSection: 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.numberOfRows(forSection: 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(forSection: .manualGateway)
|
|
cell.rightText = networkChoices.gateway.description
|
|
return cell
|
|
|
|
case .dns:
|
|
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
|
|
cell.leftText = model.header(forSection: .manualDNS)
|
|
cell.rightText = networkChoices.dns.description
|
|
return cell
|
|
|
|
case .proxy:
|
|
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
|
|
cell.leftText = model.header(forSection: .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.Core.NetworkSettings.Dns.Cells.Domain.caption
|
|
cell.field.tag = FieldTag.dnsDomain.rawValue
|
|
cell.field.placeholder = L10n.Core.Global.Values.none
|
|
cell.field.text = networkSettings.dnsDomainName
|
|
cell.field.clearButtonMode = .always
|
|
cell.field.keyboardType = .asciiCapable
|
|
cell.captionWidth = 160.0
|
|
cell.delegate = self
|
|
cell.field.isEnabled = (networkChoices.dns == .manual)
|
|
return cell
|
|
|
|
case .dnsAddress:
|
|
let i = indexPath.row - Offsets.dnsAddress
|
|
|
|
let cell = Cells.field.dequeue(from: tableView, for: indexPath)
|
|
cell.caption = L10n.Core.Global.Captions.address
|
|
cell.field.tag = FieldTag.dnsAddress.rawValue + i
|
|
cell.field.placeholder = L10n.Core.Global.Values.none
|
|
cell.field.text = networkSettings.dnsServers?[i]
|
|
cell.field.clearButtonMode = .always
|
|
cell.field.keyboardType = .numbersAndPunctuation
|
|
cell.captionWidth = 160.0
|
|
cell.delegate = self
|
|
cell.field.isEnabled = (networkChoices.dns == .manual)
|
|
return cell
|
|
|
|
case .dnsAddAddress:
|
|
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
|
|
cell.applyAction(Theme.current)
|
|
cell.leftText = L10n.App.NetworkSettings.Cells.AddDnsServer.caption
|
|
return cell
|
|
|
|
case .proxyAddress:
|
|
let cell = Cells.field.dequeue(from: tableView, for: indexPath)
|
|
cell.caption = L10n.Core.Global.Captions.address
|
|
cell.field.tag = FieldTag.proxyAddress.rawValue
|
|
cell.field.placeholder = L10n.Core.Global.Values.none
|
|
cell.field.text = networkSettings.proxyAddress
|
|
cell.field.clearButtonMode = .always
|
|
cell.field.keyboardType = .numbersAndPunctuation
|
|
cell.captionWidth = 160.0
|
|
cell.delegate = self
|
|
cell.field.isEnabled = (networkChoices.proxy == .manual)
|
|
return cell
|
|
|
|
case .proxyPort:
|
|
let cell = Cells.field.dequeue(from: tableView, for: indexPath)
|
|
cell.caption = L10n.Core.Global.Captions.port
|
|
cell.field.tag = FieldTag.proxyPort.rawValue
|
|
cell.field.placeholder = L10n.Core.Global.Values.none
|
|
cell.field.text = networkSettings.proxyPort?.description
|
|
cell.field.clearButtonMode = .always
|
|
cell.field.keyboardType = .numberPad
|
|
cell.captionWidth = 160.0
|
|
cell.delegate = self
|
|
cell.field.isEnabled = (networkChoices.proxy == .manual)
|
|
return cell
|
|
|
|
case .proxyBypass:
|
|
let i = indexPath.row - Offsets.proxyBypass
|
|
|
|
let cell = Cells.field.dequeue(from: tableView, for: indexPath)
|
|
cell.caption = L10n.App.NetworkSettings.Cells.ProxyBypass.caption
|
|
cell.field.tag = FieldTag.proxyBypass.rawValue + i
|
|
cell.field.placeholder = L10n.Core.Global.Values.none
|
|
cell.field.text = networkSettings.proxyBypassDomains?[i]
|
|
cell.field.clearButtonMode = .always
|
|
cell.field.keyboardType = .asciiCapable
|
|
cell.captionWidth = 160.0
|
|
cell.delegate = self
|
|
cell.field.isEnabled = (networkChoices.proxy == .manual)
|
|
return cell
|
|
|
|
case .proxyAddBypass:
|
|
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
|
|
cell.applyAction(Theme.current)
|
|
cell.leftText = L10n.App.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 = SingleOptionViewController<NetworkChoice>()
|
|
vc.title = (cell as? SettingTableViewCell)?.leftText
|
|
vc.options = NetworkChoice.choices(for: profile)
|
|
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 = SingleOptionViewController<NetworkChoice>()
|
|
vc.title = (cell as? SettingTableViewCell)?.leftText
|
|
vc.options = NetworkChoice.choices(for: profile)
|
|
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 = SingleOptionViewController<NetworkChoice>()
|
|
vc.title = (cell as? SettingTableViewCell)?.leftText
|
|
vc.options = NetworkChoice.choices(for: profile)
|
|
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.Core.NetworkChoice.client
|
|
|
|
case .server:
|
|
return L10n.Core.NetworkChoice.server
|
|
|
|
case .manual:
|
|
return L10n.Core.Global.Values.manual
|
|
}
|
|
}
|
|
}
|