Commit 6071334e authored by Jose Blaya's avatar Jose Blaya
Browse files

- Select a custom DNS from Settings

parent a7b9f671
This diff is collapsed.
......@@ -61,6 +61,7 @@ struct AppConfiguration {
builder.mtu = 1400
builder.shouldDebug = true
builder.debugLogKey = "LastVPNLog"
builder.dnsServers = []
return builder
}()
......
//
// CustomDNSSettingsViewController.swift
// PIA VPN
//
// Created by Jose Antonio Blaya Garcia on 24/10/2018.
// Copyright © 2018 London Trust Media. All rights reserved.
//
import Foundation
import PIALibrary
class CustomDNSSettingsViewController: AutolayoutViewController {
var primaryDNSValue: String?
var secondaryDNSValue: String?
@IBOutlet private weak var labelPrimaryDNS: UILabel!
@IBOutlet private weak var textPrimaryDNS: BorderedTextField!
@IBOutlet private weak var labelSecondaryDNS: UILabel!
@IBOutlet private weak var textSecondaryDNS: BorderedTextField!
weak var delegate: SettingsViewControllerDelegate?
override func viewDidLoad() {
self.title = L10n.Settings.Dns.Custom.dns
configureTextfields()
configureNavigationBar()
super.viewDidLoad()
}
// MARK: Actions
@objc private func update(_ sender: Any?) {
if isValidForm() {
var ips: [String] = []
if let primaryDNS = textPrimaryDNS.text {
ips.append(primaryDNS)
}
if let secondaryDNS = textSecondaryDNS.text,
!secondaryDNS.isEmpty {
ips.append(secondaryDNS)
}
DNSList.shared.addNewServerWithName(DNSList.CUSTOM_DNS_KEY,
andIPs: ips)
self.delegate?.updateSetting(Setting.vpnDns,
withValue: DNSList.CUSTOM_DNS_KEY)
self.navigationController?.popToRootViewController(animated: true)
}
}
@objc private func clear(_ sender: Any?) {
let alertController = Macros.alert(L10n.Settings.Dns.Alert.Clear.title,
L10n.Settings.Dns.Alert.Clear.message)
let saveAction = UIAlertAction(title: L10n.Global.ok, style: UIAlertActionStyle.default, handler: { alert -> Void in
if let firstKey = DNSList.shared.firstKey() {
DNSList.shared.removeServer(name: DNSList.CUSTOM_DNS_KEY)
self.delegate?.updateSetting(Setting.vpnDns,
withValue: firstKey)
}
self.navigationController?.popToRootViewController(animated: true)
})
let cancelAction = UIAlertAction(title: L10n.Global.cancel, style: UIAlertActionStyle.default,
handler: nil)
alertController.addAction(saveAction)
alertController.addAction(cancelAction)
self.present(alertController,
animated: true,
completion: nil)
}
// MARK: Restylable
override func viewShouldRestyle() {
super.viewShouldRestyle()
if let viewContainer = viewContainer {
Theme.current.applyLightBackground(viewContainer)
}
for label in [labelPrimaryDNS!, labelSecondaryDNS!] {
Theme.current.applyLabel(label, appearance: .dark)
}
Theme.current.applyInput(textPrimaryDNS)
Theme.current.applyInput(textSecondaryDNS)
}
// MARK: Private
private func configureNavigationBar() {
navigationItem.leftBarButtonItem = UIBarButtonItem(
title: L10n.Global.clear,
style: .plain,
target: self,
action: #selector(clear(_:))
)
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: L10n.Global.update,
style: .plain,
target: self,
action: #selector(update(_:))
)
}
private func configureTextfields() {
labelPrimaryDNS.text = L10n.Settings.Dns.primaryDNS
labelSecondaryDNS.text = L10n.Settings.Dns.secondaryDNS
textPrimaryDNS.placeholder = L10n.Global.required
textSecondaryDNS.placeholder = L10n.Global.optional
textPrimaryDNS.keyboardType = .decimalPad
textSecondaryDNS.keyboardType = .decimalPad
textPrimaryDNS.text = primaryDNSValue
textSecondaryDNS.text = secondaryDNSValue
}
/// Validates if the primary DNS is not empty and is valid and if the secondary DNS is valid
private func isValidForm() -> Bool {
if (textPrimaryDNS.text == nil ||
(textPrimaryDNS.text != nil && textPrimaryDNS.text!.isEmpty)) {
let alert = Macros.alert(L10n.Settings.Dns.Custom.dns,
L10n.Settings.Dns.Validation.Primary.mandatory)
alert.addCancelAction(L10n.Global.ok)
self.present(alert,
animated: true,
completion: nil)
return false
}
if let primaryDNS = textPrimaryDNS.text,
!isValidAddress(primaryDNS) {
let alert = Macros.alert(L10n.Settings.Dns.Validation.Primary.invalid,
nil)
alert.addCancelAction(L10n.Global.ok)
self.present(alert,
animated: true,
completion: nil)
return false
}
if let secondaryDNS = textSecondaryDNS.text,
!secondaryDNS.isEmpty,
!isValidAddress(secondaryDNS) {
let alert = Macros.alert(L10n.Settings.Dns.Validation.Secondary.invalid,
nil)
alert.addCancelAction(L10n.Global.ok)
self.present(alert,
animated: true,
completion: nil)
return false
}
return true
}
///Validates the address
/// - Parameters:
/// - ip: The ip to validate
/// - Returns:
/// - Bool: The result of the validation
private func isValidAddress(_ ip: String) -> Bool {
let validIP = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
if ((ip.count == 0) || (ip.range(of: validIP,
options: .regularExpression) == nil)) {
return false
}
return true
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>PIA DNS</key>
<array/>
</dict>
<dict>
<key>PIA DNS + Handshake</key>
<array>
<string>159.89.233.131</string>
</array>
</dict>
</array>
</plist>
//
// DNSList.swift
// PIA VPN
//
// Created by Jose Antonio Blaya Garcia on 23/10/2018.
// Copyright © 2018 London Trust Media. All rights reserved.
//
import Foundation
class DNSList: NSObject {
static let shared = DNSList()
static let CUSTOM_DNS_KEY: String = "Custom"
private(set) var dnsList: [[String:[String]]]!
private var plistPathInDocument: String!
private override init() {
super.init()
self.preparePlistForUse()
self.load(from: self.plistPathInDocument)
}
/// Load the values of the plist into the dnsList object
/// - Parameters:
/// - path: The local path of the plist file.
private func load(from path: String) {
guard let dnsList = NSArray(contentsOfFile: path) as? [[String:[String]]] else {
fatalError("Couldn't load plist from \(path)")
}
self.dnsList = dnsList
}
/// Creates a new dns.plist file in the document directory if it doesn't exist
private func preparePlistForUse(){
if let rootPath = NSSearchPathForDirectoriesInDomains(.documentDirectory,
.userDomainMask,
true).first {
plistPathInDocument = rootPath.appendingFormat("/DNS.plist")
if !FileManager.default.fileExists(atPath: plistPathInDocument){
if let path = Bundle.main.path(forResource: "DNS", ofType: "plist") {
do {
try FileManager.default.copyItem(atPath: path,
toPath: plistPathInDocument)
}catch{
fatalError("Error occurred while copying file to document \(error)")
}
}
}
}
}
/// Reset the DNS plist file
func resetPlist() {
do {
try FileManager.default.removeItem(atPath: plistPathInDocument)
preparePlistForUse()
self.load(from: self.plistPathInDocument)
}catch{
fatalError("Error occurred while removing file \(error)")
}
}
/// Adds a new server to the dnsList object
/// - Parameters:
/// - name: The name for the DNS.
/// - ips: The IP addresses of the DNS.
func addNewServerWithName(_ name: String,
andIPs ips: [String]) {
self.removeServer(name: name)
self.dnsList.append([name: ips])
self.updatePlist()
}
/// Removes a server from the dnsList object
/// - Parameters:
/// - name: The name for the DNS.
func removeServer(name: String) {
self.dnsList = self.dnsList.filter({
for (key, _) in $0 {
return key != name
}
return false
})
self.updatePlist()
}
/// Returns the value of the first key of the array of DNS
/// - Returns:
/// - key: The firt key
func firstKey() -> String? {
if let firstDictionary = self.dnsList.first,
let firstEntry = firstDictionary.first {
return firstEntry.key
}
return nil
}
/// Returns the array of servers for the given key
/// - Returns:
/// - ips: The array of IPs
func valueForKey(_ key: String) -> [String] {
for dns in self.dnsList {
for (theKey, value) in dns {
if theKey == key {
return value
}
}
}
return []
}
/// Returns the description of the key
/// - Returns:
/// - description: The description of the key or how the key should be displayed
func descriptionForKey(_ key: String) -> String {
for dns in self.dnsList {
for (theKey, value) in dns {
if theKey == key {
if key == DNSList.CUSTOM_DNS_KEY { //L10n.Global.custom
switch value.count {
case 0:
return L10n.Settings.Dns.custom
case 1:
return L10n.Settings.Dns.custom + " (" + value.first! + ")"
default:
return L10n.Settings.Dns.custom + " (" + value.first! + " / " + value.last! + ")"
}
}
return key
}
}
}
return L10n.Settings.Dns.custom
}
/// Updates the content of the dnsList object into the plist
private func updatePlist() {
(self.dnsList as NSArray).write(toFile: self.plistPathInDocument,
atomically: true)
}
}
......@@ -26,6 +26,8 @@
<true/>
<key>enablesEncryptionSettings</key>
<true/>
<key>enablesDNSSettings</key>
<true/>
<key>enablesDevelopmentSettings</key>
<true/>
<key>customizesVPNRenegotiation</key>
......
......@@ -41,6 +41,8 @@ class Flags: NSObject {
@objc private(set) var enablesDevelopmentSettings = false
@objc private(set) var customizesVPNRenegotiation = false
@objc private(set) var enablesDNSSettings = true
private override init() {
super.init()
......
......@@ -23,7 +23,62 @@ private extension String {
}
}
enum Setting: Int {
case vpnProtocolSelection
case vpnSocket
case vpnPort
case vpnDns
case encryptionCipher
case encryptionDigest
case encryptionHandshake
case automaticReconnection
case contentBlockerState
case contentBlockerRefreshRules
case mace
case darkTheme
case sendDebugLog
case resetSettings
// development
// case truncateDebugLog
//
// case recalculatePingTimes
//
// case invokeMACERequest
case resolveGoogleAdsDomain
}
protocol SettingsViewControllerDelegate: class {
/**
Called to update the setting sent as parameter.
- Parameter setting: The setting to update.
- Parameter value: Optional value to update the setting
*/
func updateSetting(_ setting: Setting, withValue value: Any?)
}
class SettingsViewController: AutolayoutViewController {
fileprivate static let DNS: String = "DNS"
fileprivate static let AUTOMATIC_SOCKET = "automatic"
fileprivate static let AUTOMATIC_PORT: UInt16 = 0
......@@ -44,44 +99,6 @@ class SettingsViewController: AutolayoutViewController {
case development
}
private enum Setting: Int {
case vpnProtocolSelection
case vpnSocket
case vpnPort
case encryptionCipher
case encryptionDigest
case encryptionHandshake
case automaticReconnection
case contentBlockerState
case contentBlockerRefreshRules
case mace
case darkTheme
case sendDebugLog
case resetSettings
// development
// case truncateDebugLog
//
// case recalculatePingTimes
//
// case invokeMACERequest
case resolveGoogleAdsDomain
}
private static let allSections: [Section] = [
.connection,
.encryption,
......@@ -97,7 +114,8 @@ class SettingsViewController: AutolayoutViewController {
.connection: [
.vpnProtocolSelection,
.vpnSocket,
.vpnPort
.vpnPort,
.vpnDns
],
.encryption: [
.encryptionCipher,
......@@ -186,6 +204,8 @@ class SettingsViewController: AutolayoutViewController {
}
pendingOpenVPNSocketType = AppPreferences.shared.piaSocketType
pendingOpenVPNConfiguration = currentOpenVPNConfiguration.builder()
validateDNSList()
if #available(iOS 11, *) {
tableView.sectionFooterHeight = UITableViewAutomaticDimension
......@@ -203,7 +223,6 @@ class SettingsViewController: AutolayoutViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
refreshContentBlockerState()
}
......@@ -215,8 +234,24 @@ class SettingsViewController: AutolayoutViewController {
}
}
// MARK: Actions
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let customDNSSettingsVC = segue.destination as? CustomDNSSettingsViewController {
customDNSSettingsVC.delegate = self
let ips = DNSList.shared.valueForKey(DNSList.CUSTOM_DNS_KEY)
if ips.count > 0 {
customDNSSettingsVC.primaryDNSValue = ips.first
if ips.count > 1 {
customDNSSettingsVC.secondaryDNSValue = ips.last
}
}
}
}
// MARK: Actions
@objc private func edit(_ sender: Any?) {
self.perform(segue: StoryboardSegue.Main.customDNSSegueIdentifier)
}
@objc private func togglePersistentConnection(_ sender: UISwitch) {
pendingPreferences.isPersistentConnection = sender.isOn
redisplaySettings()
......@@ -323,6 +358,7 @@ class SettingsViewController: AutolayoutViewController {
fatalError("No default VPN custom configuration provided for PIA protocol")
}
AppPreferences.shared.reset()
DNSList.shared.resetPlist()
pendingOpenVPNSocketType = AppPreferences.shared.piaSocketType
pendingOpenVPNConfiguration = currentOpenVPNConfiguration.builder()
......@@ -455,7 +491,7 @@ class SettingsViewController: AutolayoutViewController {
}
let alert = Macros.alert(nil, addresses.joined(separator: ","))
alert.addCancelAction("Close")
alert.addCancelAction(L10n.Global.close)
self.present(alert, animated: true, completion: nil)
}
}
......@@ -499,11 +535,12 @@ class SettingsViewController: AutolayoutViewController {
visibleSections = sections
if (pendingPreferences.vpnType == PIATunnelProfile.vpnType) {
rowsBySection[.connection] = [
.vpnProtocolSelection,
.vpnSocket,
.vpnPort
]
let dnsSettingsEnabled = Flags.shared.enablesDNSSettings
rowsBySection[.connection] = dnsSettingsEnabled ?
[.vpnProtocolSelection, .vpnSocket, .vpnPort, .vpnDns] :
[.vpnProtocolSelection, .vpnSocket, .vpnPort]
} else {
rowsBySection[.connection] = [
.vpnProtocolSelection
......@@ -538,6 +575,27 @@ class SettingsViewController: AutolayoutViewController {
Theme.current.applyDividerToSeparator(tableView)
tableView.reloadData()
}
///Check if the current value of the DNS is valid. If not, reset to default PIA server
private func validateDNSList() {
if Flags.shared.enablesDNSSettings {
var isValid = false
for dns in DNSList.shared.dnsList {
for (_, value) in dns {
if pendingOpenVPNConfiguration.dnsServers == value {
isValid = true
break
}
}
}
if !isValid {
pendingOpenVPNConfiguration.dnsServers = []
}
}
}
}
extension SettingsViewController: UITableViewDataSource, UITableViewDelegate {
......@@ -630,6 +688,24 @@ extension SettingsViewController: UITableViewDataSource, UITableViewDelegate {
}
cell.detailTextLabel?.text = pendingOpenVPNConfiguration.currentPort?.description ?? L10n.Global.automatic
case .vpnDns:
cell.textLabel?.text = SettingsViewController.DNS
let dnsValue = pendingOpenVPNConfiguration.dnsServers
for dns in DNSList.shared.dnsList {
for (key, value) in dns {
if dnsValue == value {
cell.detailTextLabel?.text = DNSList.shared.descriptionForKey(key)
break
}
}
}