Added swiftlint and fixed all errors (and a bunch, but not all, warnings)
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
This commit is contained in:
parent
af78fa9a1c
commit
de14b76b4d
|
@ -1,12 +0,0 @@
|
||||||
disabled_rules:
|
|
||||||
- line_length
|
|
||||||
- trailing_comma
|
|
||||||
excluded:
|
|
||||||
- Pods
|
|
||||||
file_length:
|
|
||||||
warning: 500
|
|
||||||
type_name:
|
|
||||||
min_length: 2 # only warning
|
|
||||||
max_length: # warning and error
|
|
||||||
warning: 50
|
|
||||||
error: 60
|
|
|
@ -23,6 +23,12 @@ $ cp WireGuard/WireGuard/Config/Developer.xcconfig.template WireGuard/WireGuard/
|
||||||
$ vim WireGuard/WireGuard/Config/Developer.xcconfig
|
$ vim WireGuard/WireGuard/Config/Developer.xcconfig
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Install swiftlint:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ brew install swiftlint
|
||||||
|
```
|
||||||
|
|
||||||
- Open project in XCode:
|
- Open project in XCode:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
disabled_rules:
|
||||||
|
- force_cast
|
||||||
|
- line_length
|
||||||
|
file_length:
|
||||||
|
warning: 500
|
||||||
|
cyclomatic_complexity:
|
||||||
|
warning: 10
|
||||||
|
error: 25
|
|
@ -20,8 +20,8 @@ extension FileManager {
|
||||||
static func deleteFile(at url: URL) -> Bool {
|
static func deleteFile(at url: URL) -> Bool {
|
||||||
do {
|
do {
|
||||||
try FileManager.default.removeItem(at: url)
|
try FileManager.default.removeItem(at: url)
|
||||||
} catch(let e) {
|
} catch let error {
|
||||||
os_log("Failed to delete file '%{public}@': %{public}@", log: OSLog.default, type: .debug, url.absoluteString, e.localizedDescription)
|
os_log("Failed to delete file '%{public}@': %{public}@", log: OSLog.default, type: .debug, url.absoluteString, error.localizedDescription)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -16,7 +16,7 @@ final class TunnelConfiguration: Codable {
|
||||||
|
|
||||||
let peerPublicKeysArray = peers.map { $0.publicKey }
|
let peerPublicKeysArray = peers.map { $0.publicKey }
|
||||||
let peerPublicKeysSet = Set<Data>(peerPublicKeysArray)
|
let peerPublicKeysSet = Set<Data>(peerPublicKeysArray)
|
||||||
if (peerPublicKeysArray.count != peerPublicKeysSet.count) {
|
if peerPublicKeysArray.count != peerPublicKeysSet.count {
|
||||||
fatalError("Two or more peers cannot have the same public key")
|
fatalError("Two or more peers cannot have the same public key")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,12 @@ struct InterfaceConfiguration: Codable {
|
||||||
init(name: String, privateKey: Data) {
|
init(name: String, privateKey: Data) {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.privateKey = privateKey
|
self.privateKey = privateKey
|
||||||
if (name.isEmpty) { fatalError("Empty name") }
|
if name.isEmpty {
|
||||||
if (privateKey.count != TunnelConfiguration.keyLength) { fatalError("Invalid private key") }
|
fatalError("Empty name")
|
||||||
|
}
|
||||||
|
if privateKey.count != TunnelConfiguration.keyLength {
|
||||||
|
fatalError("Invalid private key")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +49,9 @@ struct PeerConfiguration: Codable {
|
||||||
var preSharedKey: Data? {
|
var preSharedKey: Data? {
|
||||||
didSet(value) {
|
didSet(value) {
|
||||||
if let value = value {
|
if let value = value {
|
||||||
if (value.count != TunnelConfiguration.keyLength) { fatalError("Invalid preshared key") }
|
if value.count != TunnelConfiguration.keyLength {
|
||||||
|
fatalError("Invalid preshared key")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +61,8 @@ struct PeerConfiguration: Codable {
|
||||||
|
|
||||||
init(publicKey: Data) {
|
init(publicKey: Data) {
|
||||||
self.publicKey = publicKey
|
self.publicKey = publicKey
|
||||||
if (publicKey.count != TunnelConfiguration.keyLength) { fatalError("Invalid public key") }
|
if publicKey.count != TunnelConfiguration.keyLength {
|
||||||
|
fatalError("Invalid public key")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ extension DNSServer: Codable {
|
||||||
let container = try decoder.singleValueContainer()
|
let container = try decoder.singleValueContainer()
|
||||||
var data = try container.decode(Data.self)
|
var data = try container.decode(Data.self)
|
||||||
let ipAddressFromData: IPAddress? = {
|
let ipAddressFromData: IPAddress? = {
|
||||||
switch (data.count) {
|
switch data.count {
|
||||||
case 4: return IPv4Address(data)
|
case 4: return IPv4Address(data)
|
||||||
case 16: return IPv6Address(data)
|
case 16: return IPv6Address(data)
|
||||||
default: return nil
|
default: return nil
|
||||||
|
|
|
@ -35,8 +35,8 @@ extension Endpoint {
|
||||||
hostString = String(string[string.startIndex ..< endOfHost])
|
hostString = String(string[string.startIndex ..< endOfHost])
|
||||||
}
|
}
|
||||||
guard let endpointPort = NWEndpoint.Port(String(string[startOfPort ..< string.endIndex])) else { return nil }
|
guard let endpointPort = NWEndpoint.Port(String(string[startOfPort ..< string.endIndex])) else { return nil }
|
||||||
let invalidCharacterIndex = hostString.unicodeScalars.firstIndex { (c) -> Bool in
|
let invalidCharacterIndex = hostString.unicodeScalars.firstIndex { char in
|
||||||
return !CharacterSet.urlHostAllowed.contains(c)
|
return !CharacterSet.urlHostAllowed.contains(char)
|
||||||
}
|
}
|
||||||
guard (invalidCharacterIndex == nil) else { return nil }
|
guard (invalidCharacterIndex == nil) else { return nil }
|
||||||
host = NWEndpoint.Host(hostString)
|
host = NWEndpoint.Host(hostString)
|
||||||
|
@ -79,11 +79,11 @@ extension Endpoint: Codable {
|
||||||
extension Endpoint {
|
extension Endpoint {
|
||||||
func hasHostAsIPAddress() -> Bool {
|
func hasHostAsIPAddress() -> Bool {
|
||||||
switch (host) {
|
switch (host) {
|
||||||
case .name(_, _):
|
case .name:
|
||||||
return false
|
return false
|
||||||
case .ipv4(_):
|
case .ipv4:
|
||||||
return true
|
return true
|
||||||
case .ipv6(_):
|
case .ipv6:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,9 +92,9 @@ extension Endpoint {
|
||||||
switch (host) {
|
switch (host) {
|
||||||
case .name(let hostname, _):
|
case .name(let hostname, _):
|
||||||
return hostname
|
return hostname
|
||||||
case .ipv4(_):
|
case .ipv4:
|
||||||
return nil
|
return nil
|
||||||
case .ipv6(_):
|
case .ipv6:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,9 +27,9 @@ extension IPAddressRange {
|
||||||
}
|
}
|
||||||
let maxNetworkPrefixLength: UInt8 = (address is IPv4Address) ? 32 : 128
|
let maxNetworkPrefixLength: UInt8 = (address is IPv4Address) ? 32 : 128
|
||||||
var networkPrefixLength: UInt8
|
var networkPrefixLength: UInt8
|
||||||
if (endOfIPAddress < string.endIndex) { // "/" was located
|
if endOfIPAddress < string.endIndex { // "/" was located
|
||||||
let indexOfNetworkPrefixLength = string.index(after: endOfIPAddress)
|
let indexOfNetworkPrefixLength = string.index(after: endOfIPAddress)
|
||||||
guard (indexOfNetworkPrefixLength < string.endIndex) else { return nil }
|
guard indexOfNetworkPrefixLength < string.endIndex else { return nil }
|
||||||
let networkPrefixLengthSubstring = string[indexOfNetworkPrefixLength ..< string.endIndex]
|
let networkPrefixLengthSubstring = string[indexOfNetworkPrefixLength ..< string.endIndex]
|
||||||
guard let npl = UInt8(networkPrefixLengthSubstring) else { return nil }
|
guard let npl = UInt8(networkPrefixLengthSubstring) else { return nil }
|
||||||
networkPrefixLength = min(npl, maxNetworkPrefixLength)
|
networkPrefixLength = min(npl, maxNetworkPrefixLength)
|
||||||
|
@ -69,7 +69,7 @@ extension IPAddressRange: Codable {
|
||||||
var data = try container.decode(Data.self)
|
var data = try container.decode(Data.self)
|
||||||
networkPrefixLength = data.removeLast()
|
networkPrefixLength = data.removeLast()
|
||||||
let ipAddressFromData: IPAddress? = {
|
let ipAddressFromData: IPAddress? = {
|
||||||
switch (data.count) {
|
switch data.count {
|
||||||
case 4: return IPv4Address(data)
|
case 4: return IPv4Address(data)
|
||||||
case 16: return IPv6Address(data)
|
case 16: return IPv6Address(data)
|
||||||
default: return nil
|
default: return nil
|
||||||
|
|
|
@ -384,6 +384,7 @@
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 6F5D0C25218352EF000F85AD /* Build configuration list for PBXNativeTarget "WireGuardNetworkExtension" */;
|
buildConfigurationList = 6F5D0C25218352EF000F85AD /* Build configuration list for PBXNativeTarget "WireGuardNetworkExtension" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
5F45417B21C0906F00994C13 /* Swiftlint */,
|
||||||
6F61F1EC21BA4D4700483816 /* Extract wireguard-go Version */,
|
6F61F1EC21BA4D4700483816 /* Extract wireguard-go Version */,
|
||||||
6F5D0C16218352EF000F85AD /* Sources */,
|
6F5D0C16218352EF000F85AD /* Sources */,
|
||||||
6F5D0C17218352EF000F85AD /* Frameworks */,
|
6F5D0C17218352EF000F85AD /* Frameworks */,
|
||||||
|
@ -403,6 +404,7 @@
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 6FF4AC26211EC472002C96EB /* Build configuration list for PBXNativeTarget "WireGuard" */;
|
buildConfigurationList = 6FF4AC26211EC472002C96EB /* Build configuration list for PBXNativeTarget "WireGuard" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
5F45417A21C0902400994C13 /* Swiftlint */,
|
||||||
6B87860E2189532500C099FB /* Extract wireguard-go Version */,
|
6B87860E2189532500C099FB /* Extract wireguard-go Version */,
|
||||||
6FF4AC10211EC46F002C96EB /* Sources */,
|
6FF4AC10211EC46F002C96EB /* Sources */,
|
||||||
6FF4AC11211EC46F002C96EB /* Frameworks */,
|
6FF4AC11211EC46F002C96EB /* Frameworks */,
|
||||||
|
@ -494,6 +496,42 @@
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
5F45417A21C0902400994C13 /* Swiftlint */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = Swiftlint;
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
|
||||||
|
};
|
||||||
|
5F45417B21C0906F00994C13 /* Swiftlint */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = Swiftlint;
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
|
||||||
|
};
|
||||||
6B87860E2189532500C099FB /* Extract wireguard-go Version */ = {
|
6B87860E2189532500C099FB /* Extract wireguard-go Version */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
|
|
@ -379,8 +379,8 @@ class TunnelViewModel {
|
||||||
var peersData: [PeerData] = []
|
var peersData: [PeerData] = []
|
||||||
if let tunnelConfiguration = tunnelConfiguration {
|
if let tunnelConfiguration = tunnelConfiguration {
|
||||||
interfaceData.validatedConfiguration = tunnelConfiguration.interface
|
interfaceData.validatedConfiguration = tunnelConfiguration.interface
|
||||||
for (i, peerConfiguration) in tunnelConfiguration.peers.enumerated() {
|
for (index, peerConfiguration) in tunnelConfiguration.peers.enumerated() {
|
||||||
let peerData = PeerData(index: i)
|
let peerData = PeerData(index: index)
|
||||||
peerData.validatedConfiguration = peerConfiguration
|
peerData.validatedConfiguration = peerConfiguration
|
||||||
peersData.append(peerData)
|
peersData.append(peerData)
|
||||||
}
|
}
|
||||||
|
@ -397,22 +397,22 @@ class TunnelViewModel {
|
||||||
func appendEmptyPeer() {
|
func appendEmptyPeer() {
|
||||||
let peer = PeerData(index: peersData.count)
|
let peer = PeerData(index: peersData.count)
|
||||||
peersData.append(peer)
|
peersData.append(peer)
|
||||||
for p in peersData {
|
for peer in peersData {
|
||||||
p.numberOfPeers = peersData.count
|
peer.numberOfPeers = peersData.count
|
||||||
p.updateExcludePrivateIPsFieldState()
|
peer.updateExcludePrivateIPsFieldState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func deletePeer(peer: PeerData) {
|
func deletePeer(peer: PeerData) {
|
||||||
let removedPeer = peersData.remove(at: peer.index)
|
let removedPeer = peersData.remove(at: peer.index)
|
||||||
assert(removedPeer.index == peer.index)
|
assert(removedPeer.index == peer.index)
|
||||||
for p in peersData[peer.index ..< peersData.count] {
|
for peer in peersData[peer.index ..< peersData.count] {
|
||||||
assert(p.index > 0)
|
assert(peer.index > 0)
|
||||||
p.index = p.index - 1
|
peer.index -= 1
|
||||||
}
|
}
|
||||||
for p in peersData {
|
for peer in peersData {
|
||||||
p.numberOfPeers = peersData.count
|
peer.numberOfPeers = peersData.count
|
||||||
p.updateExcludePrivateIPsFieldState()
|
peer.updateExcludePrivateIPsFieldState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,20 +42,21 @@ class MainViewController: UISplitViewController {
|
||||||
|
|
||||||
// Create the tunnels manager, and when it's ready, inform tunnelsListVC
|
// Create the tunnels manager, and when it's ready, inform tunnelsListVC
|
||||||
TunnelsManager.create { [weak self] result in
|
TunnelsManager.create { [weak self] result in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
if let error = result.error {
|
if let error = result.error {
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let tunnelsManager: TunnelsManager = result.value!
|
let tunnelsManager: TunnelsManager = result.value!
|
||||||
guard let s = self else { return }
|
|
||||||
|
|
||||||
s.tunnelsManager = tunnelsManager
|
self.tunnelsManager = tunnelsManager
|
||||||
s.tunnelsListVC?.setTunnelsManager(tunnelsManager: tunnelsManager)
|
self.tunnelsListVC?.setTunnelsManager(tunnelsManager: tunnelsManager)
|
||||||
|
|
||||||
tunnelsManager.activationDelegate = s
|
tunnelsManager.activationDelegate = self
|
||||||
|
|
||||||
s.onTunnelsManagerReady?(tunnelsManager)
|
self.onTunnelsManagerReady?(tunnelsManager)
|
||||||
s.onTunnelsManagerReady = nil
|
self.onTunnelsManagerReady = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ class QRScanViewController: UIViewController {
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
tipLabel.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor),
|
tipLabel.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor),
|
||||||
tipLabel.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor),
|
tipLabel.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor),
|
||||||
tipLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -32),
|
tipLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -32)
|
||||||
])
|
])
|
||||||
|
|
||||||
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video),
|
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video),
|
||||||
|
@ -118,12 +118,11 @@ class QRScanViewController: UIViewController {
|
||||||
self?.dismiss(animated: true, completion: nil)
|
self?.dismiss(animated: true, completion: nil)
|
||||||
}))
|
}))
|
||||||
alert.addAction(UIAlertAction(title: NSLocalizedString("Save", comment: ""), style: .default, handler: { [weak self] _ in
|
alert.addAction(UIAlertAction(title: NSLocalizedString("Save", comment: ""), style: .default, handler: { [weak self] _ in
|
||||||
let title = alert.textFields?[0].text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
guard let title = alert.textFields?[0].text?.trimmingCharacters(in: .whitespacesAndNewlines), !title.isEmpty else { return }
|
||||||
if (title.isEmpty) { return }
|
|
||||||
tunnelConfiguration.interface.name = title
|
tunnelConfiguration.interface.name = title
|
||||||
if let s = self {
|
if let self = self {
|
||||||
s.delegate?.addScannedQRCode(tunnelConfiguration: tunnelConfiguration, qrScanViewController: s) {
|
self.delegate?.addScannedQRCode(tunnelConfiguration: tunnelConfiguration, qrScanViewController: self) {
|
||||||
s.dismiss(animated: true, completion: nil)
|
self.dismiss(animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -34,7 +34,7 @@ class ScrollableLabel: UIScrollView {
|
||||||
label.topAnchor.constraint(equalTo: self.contentLayoutGuide.topAnchor),
|
label.topAnchor.constraint(equalTo: self.contentLayoutGuide.topAnchor),
|
||||||
label.bottomAnchor.constraint(equalTo: self.contentLayoutGuide.bottomAnchor),
|
label.bottomAnchor.constraint(equalTo: self.contentLayoutGuide.bottomAnchor),
|
||||||
label.rightAnchor.constraint(equalTo: self.contentLayoutGuide.rightAnchor),
|
label.rightAnchor.constraint(equalTo: self.contentLayoutGuide.rightAnchor),
|
||||||
label.heightAnchor.constraint(equalTo: self.heightAnchor),
|
label.heightAnchor.constraint(equalTo: self.heightAnchor)
|
||||||
])
|
])
|
||||||
// If label has less content, it should expand to fit the scrollView,
|
// If label has less content, it should expand to fit the scrollView,
|
||||||
// so that right-alignment works in the label.
|
// so that right-alignment works in the label.
|
||||||
|
|
|
@ -57,8 +57,8 @@ class SettingsTableViewController: UITableViewController {
|
||||||
guard let logo = self.tableView.tableFooterView else { return }
|
guard let logo = self.tableView.tableFooterView else { return }
|
||||||
let bottomPadding = max(self.tableView.layoutMargins.bottom, CGFloat(10))
|
let bottomPadding = max(self.tableView.layoutMargins.bottom, CGFloat(10))
|
||||||
let fullHeight = max(self.tableView.contentSize.height, self.tableView.bounds.size.height - self.tableView.layoutMargins.top - bottomPadding)
|
let fullHeight = max(self.tableView.contentSize.height, self.tableView.bounds.size.height - self.tableView.layoutMargins.top - bottomPadding)
|
||||||
let e = logo.frame
|
let frame = logo.frame
|
||||||
logo.frame = CGRect(x: e.minX, y: fullHeight - e.height, width: e.width, height: e.height)
|
logo.frame = CGRect(x: frame.minX, y: fullHeight - frame.height, width: frame.width, height: frame.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func doneTapped() {
|
@objc func doneTapped() {
|
||||||
|
@ -116,7 +116,8 @@ class SettingsTableViewController: UITableViewController {
|
||||||
do {
|
do {
|
||||||
try FileManager.default.copyItem(at: networkExtensionLogFileURL, to: destinationURL)
|
try FileManager.default.copyItem(at: networkExtensionLogFileURL, to: destinationURL)
|
||||||
} catch {
|
} catch {
|
||||||
os_log("Failed to copy file: %{public}@ to %{public}@: %{public}@", log: OSLog.default, type: .error, networkExtensionLogFileURL.absoluteString, destinationURL.absoluteString, error.localizedDescription)
|
os_log("Failed to copy file: %{public}@ to %{public}@: %{public}@", log: OSLog.default, type: .error,
|
||||||
|
networkExtensionLogFileURL.absoluteString, destinationURL.absoluteString, error.localizedDescription)
|
||||||
ErrorPresenter.showErrorAlert(title: "Log export failed", message: "The log could not be copied", from: self)
|
ErrorPresenter.showErrorAlert(title: "Log export failed", message: "The log could not be copied", from: self)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,10 @@ class TunnelDetailTableViewController: UITableViewController {
|
||||||
let tunnel: TunnelContainer
|
let tunnel: TunnelContainer
|
||||||
var tunnelViewModel: TunnelViewModel
|
var tunnelViewModel: TunnelViewModel
|
||||||
|
|
||||||
init(tunnelsManager tm: TunnelsManager, tunnel t: TunnelContainer) {
|
init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer) {
|
||||||
tunnelsManager = tm
|
self.tunnelsManager = tunnelsManager
|
||||||
tunnel = t
|
self.tunnel = tunnel
|
||||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: t.tunnelConfiguration())
|
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration())
|
||||||
super.init(style: .grouped)
|
super.init(style: .grouped)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,11 +152,11 @@ extension TunnelDetailTableViewController {
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewStatusCell.id, for: indexPath) as! TunnelDetailTableViewStatusCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewStatusCell.id, for: indexPath) as! TunnelDetailTableViewStatusCell
|
||||||
cell.tunnel = self.tunnel
|
cell.tunnel = self.tunnel
|
||||||
cell.onSwitchToggled = { [weak self] isOn in
|
cell.onSwitchToggled = { [weak self] isOn in
|
||||||
guard let s = self else { return }
|
guard let self = self else { return }
|
||||||
if (isOn) {
|
if (isOn) {
|
||||||
s.tunnelsManager.startActivation(of: s.tunnel) { [weak s] error in
|
self.tunnelsManager.startActivation(of: self.tunnel) { [weak self] error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: s, onPresented: {
|
ErrorPresenter.showErrorAlert(error: error, from: self, onPresented: {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
cell.statusSwitch.isOn = false
|
cell.statusSwitch.isOn = false
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@ extension TunnelDetailTableViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.tunnelsManager.startDeactivation(of: s.tunnel)
|
self.tunnelsManager.startDeactivation(of: self.tunnel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
|
@ -198,16 +198,16 @@ extension TunnelDetailTableViewController {
|
||||||
cell.buttonText = "Delete tunnel"
|
cell.buttonText = "Delete tunnel"
|
||||||
cell.hasDestructiveAction = true
|
cell.hasDestructiveAction = true
|
||||||
cell.onTapped = { [weak self] in
|
cell.onTapped = { [weak self] in
|
||||||
guard let s = self else { return }
|
guard let self = self else { return }
|
||||||
s.showConfirmationAlert(message: "Delete this tunnel?", buttonTitle: "Delete", from: cell) { [weak s] in
|
self.showConfirmationAlert(message: "Delete this tunnel?", buttonTitle: "Delete", from: cell) { [weak self] in
|
||||||
guard let tunnelsManager = s?.tunnelsManager, let tunnel = s?.tunnel else { return }
|
guard let tunnelsManager = self?.tunnelsManager, let tunnel = self?.tunnel else { return }
|
||||||
tunnelsManager.remove(tunnel: tunnel) { (error) in
|
tunnelsManager.remove(tunnel: tunnel) { (error) in
|
||||||
if (error != nil) {
|
if (error != nil) {
|
||||||
print("Error removing tunnel: \(String(describing: error))")
|
print("Error removing tunnel: \(String(describing: error))")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s?.navigationController?.navigationController?.popToRootViewController(animated: true)
|
self?.navigationController?.navigationController?.popToRootViewController(animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
|
@ -374,7 +374,7 @@ class TunnelDetailTableViewKeyValueCell: CopyableLabelTableViewCell {
|
||||||
constraints = [
|
constraints = [
|
||||||
contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5),
|
contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5),
|
||||||
valueLabel.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1),
|
valueLabel.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1),
|
||||||
valueLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5),
|
valueLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5)
|
||||||
]
|
]
|
||||||
isStackedHorizontally = true
|
isStackedHorizontally = true
|
||||||
isStackedVertically = false
|
isStackedVertically = false
|
||||||
|
|
|
@ -37,19 +37,22 @@ class TunnelEditTableViewController: UITableViewController {
|
||||||
let tunnelViewModel: TunnelViewModel
|
let tunnelViewModel: TunnelViewModel
|
||||||
var activateOnDemandSetting: ActivateOnDemandSetting
|
var activateOnDemandSetting: ActivateOnDemandSetting
|
||||||
|
|
||||||
init(tunnelsManager tm: TunnelsManager, tunnel t: TunnelContainer) {
|
private var interfaceSectionCount: Int { return interfaceFieldsBySection.count }
|
||||||
|
private var peerSectionCount: Int { return tunnelViewModel.peersData.count }
|
||||||
|
|
||||||
|
init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer) {
|
||||||
// Use this initializer to edit an existing tunnel.
|
// Use this initializer to edit an existing tunnel.
|
||||||
tunnelsManager = tm
|
self.tunnelsManager = tunnelsManager
|
||||||
tunnel = t
|
self.tunnel = tunnel
|
||||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: t.tunnelConfiguration())
|
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration())
|
||||||
activateOnDemandSetting = t.activateOnDemandSetting()
|
activateOnDemandSetting = tunnel.activateOnDemandSetting()
|
||||||
super.init(style: .grouped)
|
super.init(style: .grouped)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(tunnelsManager tm: TunnelsManager, tunnelConfiguration: TunnelConfiguration?) {
|
init(tunnelsManager: TunnelsManager, tunnelConfiguration: TunnelConfiguration?) {
|
||||||
// Use this initializer to create a new tunnel.
|
// Use this initializer to create a new tunnel.
|
||||||
// If tunnelConfiguration is passed, data will be prepopulated from that configuration.
|
// If tunnelConfiguration is passed, data will be prepopulated from that configuration.
|
||||||
tunnelsManager = tm
|
self.tunnelsManager = tunnelsManager
|
||||||
tunnel = nil
|
tunnel = nil
|
||||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnelConfiguration)
|
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnelConfiguration)
|
||||||
activateOnDemandSetting = ActivateOnDemandSetting.defaultSetting
|
activateOnDemandSetting = ActivateOnDemandSetting.defaultSetting
|
||||||
|
@ -62,18 +65,18 @@ class TunnelEditTableViewController: UITableViewController {
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
self.title = (tunnel == nil) ? "New configuration" : "Edit configuration"
|
self.title = tunnel == nil ? "New configuration" : "Edit configuration"
|
||||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveTapped))
|
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveTapped))
|
||||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelTapped))
|
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelTapped))
|
||||||
|
|
||||||
self.tableView.estimatedRowHeight = 44
|
self.tableView.estimatedRowHeight = 44
|
||||||
self.tableView.rowHeight = UITableView.automaticDimension
|
self.tableView.rowHeight = UITableView.automaticDimension
|
||||||
|
|
||||||
self.tableView.register(TunnelEditTableViewKeyValueCell.self, forCellReuseIdentifier: TunnelEditTableViewKeyValueCell.id)
|
self.tableView.register(TunnelEditTableViewKeyValueCell.self, forCellReuseIdentifier: TunnelEditTableViewKeyValueCell.reuseIdentifier)
|
||||||
self.tableView.register(TunnelEditTableViewReadOnlyKeyValueCell.self, forCellReuseIdentifier: TunnelEditTableViewReadOnlyKeyValueCell.id)
|
self.tableView.register(TunnelEditTableViewReadOnlyKeyValueCell.self, forCellReuseIdentifier: TunnelEditTableViewReadOnlyKeyValueCell.reuseIdentifier)
|
||||||
self.tableView.register(TunnelEditTableViewButtonCell.self, forCellReuseIdentifier: TunnelEditTableViewButtonCell.id)
|
self.tableView.register(TunnelEditTableViewButtonCell.self, forCellReuseIdentifier: TunnelEditTableViewButtonCell.reuseIdentifier)
|
||||||
self.tableView.register(TunnelEditTableViewSwitchCell.self, forCellReuseIdentifier: TunnelEditTableViewSwitchCell.id)
|
self.tableView.register(TunnelEditTableViewSwitchCell.self, forCellReuseIdentifier: TunnelEditTableViewSwitchCell.reuseIdentifier)
|
||||||
self.tableView.register(TunnelEditTableViewSelectionListCell.self, forCellReuseIdentifier: TunnelEditTableViewSelectionListCell.id)
|
self.tableView.register(TunnelEditTableViewSelectionListCell.self, forCellReuseIdentifier: TunnelEditTableViewSelectionListCell.reuseIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func saveTapped() {
|
@objc func saveTapped() {
|
||||||
|
@ -123,26 +126,20 @@ class TunnelEditTableViewController: UITableViewController {
|
||||||
|
|
||||||
extension TunnelEditTableViewController {
|
extension TunnelEditTableViewController {
|
||||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||||
let numberOfInterfaceSections = interfaceFieldsBySection.count
|
return interfaceSectionCount + peerSectionCount + 1 /* Add Peer */ + 1 /* On-Demand */
|
||||||
let numberOfPeerSections = tunnelViewModel.peersData.count
|
|
||||||
|
|
||||||
return numberOfInterfaceSections + numberOfPeerSections + 1 /* Add Peer */ + 1 /* On-Demand */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
let numberOfInterfaceSections = interfaceFieldsBySection.count
|
if (section < interfaceSectionCount) {
|
||||||
let numberOfPeerSections = tunnelViewModel.peersData.count
|
|
||||||
|
|
||||||
if (section < numberOfInterfaceSections) {
|
|
||||||
// Interface
|
// Interface
|
||||||
return interfaceFieldsBySection[section].count
|
return interfaceFieldsBySection[section].count
|
||||||
} else if ((numberOfPeerSections > 0) && (section < (numberOfInterfaceSections + numberOfPeerSections))) {
|
} else if ((peerSectionCount > 0) && (section < (interfaceSectionCount + peerSectionCount))) {
|
||||||
// Peer
|
// Peer
|
||||||
let peerIndex = (section - numberOfInterfaceSections)
|
let peerIndex = (section - interfaceSectionCount)
|
||||||
let peerData = tunnelViewModel.peersData[peerIndex]
|
let peerData = tunnelViewModel.peersData[peerIndex]
|
||||||
let peerFieldsToShow = peerData.shouldAllowExcludePrivateIPsControl ? peerFields : peerFields.filter { $0 != .excludePrivateIPs }
|
let peerFieldsToShow = peerData.shouldAllowExcludePrivateIPsControl ? peerFields : peerFields.filter { $0 != .excludePrivateIPs }
|
||||||
return peerFieldsToShow.count
|
return peerFieldsToShow.count
|
||||||
} else if (section < (numberOfInterfaceSections + numberOfPeerSections + 1)) {
|
} else if (section < (interfaceSectionCount + peerSectionCount + 1)) {
|
||||||
// Add peer
|
// Add peer
|
||||||
return 1
|
return 1
|
||||||
} else {
|
} else {
|
||||||
|
@ -156,284 +153,279 @@ extension TunnelEditTableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||||
let numberOfInterfaceSections = interfaceFieldsBySection.count
|
if (section < interfaceSectionCount) {
|
||||||
let numberOfPeerSections = tunnelViewModel.peersData.count
|
|
||||||
|
|
||||||
if (section < numberOfInterfaceSections) {
|
|
||||||
// Interface
|
// Interface
|
||||||
return (section == 0) ? "Interface" : nil
|
return (section == 0) ? "Interface" : nil
|
||||||
} else if ((numberOfPeerSections > 0) && (section < (numberOfInterfaceSections + numberOfPeerSections))) {
|
} else if ((peerSectionCount > 0) && (section < (interfaceSectionCount + peerSectionCount))) {
|
||||||
// Peer
|
// Peer
|
||||||
return "Peer"
|
return "Peer"
|
||||||
} else if (section == (numberOfInterfaceSections + numberOfPeerSections)) {
|
} else if (section == (interfaceSectionCount + peerSectionCount)) {
|
||||||
// Add peer
|
// Add peer
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
assert(section == (numberOfInterfaceSections + numberOfPeerSections + 1))
|
assert(section == (interfaceSectionCount + peerSectionCount + 1))
|
||||||
return "On-Demand Activation"
|
return "On-Demand Activation"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let numberOfInterfaceSections = interfaceFieldsBySection.count
|
if (indexPath.section < interfaceSectionCount) {
|
||||||
let numberOfPeerSections = tunnelViewModel.peersData.count
|
return interfaceFieldCell(for: tableView, at: indexPath)
|
||||||
|
} else if ((peerSectionCount > 0) && (indexPath.section < (interfaceSectionCount + peerSectionCount))) {
|
||||||
|
return peerCell(for: tableView, at: indexPath)
|
||||||
|
} else if (indexPath.section == (interfaceSectionCount + peerSectionCount)) {
|
||||||
|
return addPeerCell(for: tableView, at: indexPath)
|
||||||
|
} else {
|
||||||
|
return onDemandCell(for: tableView, at: indexPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let section = indexPath.section
|
private func interfaceFieldCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
let row = indexPath.row
|
let interfaceData = tunnelViewModel.interfaceData
|
||||||
|
let field = interfaceFieldsBySection[indexPath.section][indexPath.row]
|
||||||
if (section < numberOfInterfaceSections) {
|
if (field == .generateKeyPair) {
|
||||||
// Interface
|
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewButtonCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewButtonCell
|
||||||
let interfaceData = tunnelViewModel.interfaceData
|
cell.buttonText = field.rawValue
|
||||||
let field = interfaceFieldsBySection[section][row]
|
cell.onTapped = { [weak self, weak interfaceData] in
|
||||||
if (field == .generateKeyPair) {
|
if let interfaceData = interfaceData, let self = self {
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewButtonCell.id, for: indexPath) as! TunnelEditTableViewButtonCell
|
interfaceData[.privateKey] = Curve25519.generatePrivateKey().base64EncodedString()
|
||||||
cell.buttonText = field.rawValue
|
if let privateKeyRow = self.interfaceFieldsBySection[indexPath.section].firstIndex(of: .privateKey),
|
||||||
cell.onTapped = { [weak self, weak interfaceData] in
|
let publicKeyRow = self.interfaceFieldsBySection[indexPath.section].firstIndex(of: .publicKey) {
|
||||||
if let interfaceData = interfaceData, let s = self {
|
let privateKeyIndex = IndexPath(row: privateKeyRow, section: indexPath.section)
|
||||||
interfaceData[.privateKey] = Curve25519.generatePrivateKey().base64EncodedString()
|
let publicKeyIndex = IndexPath(row: publicKeyRow, section: indexPath.section)
|
||||||
if let privateKeyRow = s.interfaceFieldsBySection[section].firstIndex(of: .privateKey),
|
self.tableView.reloadRows(at: [privateKeyIndex, publicKeyIndex], with: .automatic)
|
||||||
let publicKeyRow = s.interfaceFieldsBySection[section].firstIndex(of: .publicKey) {
|
|
||||||
let privateKeyIndex = IndexPath(row: privateKeyRow, section: section)
|
|
||||||
let publicKeyIndex = IndexPath(row: publicKeyRow, section: section)
|
|
||||||
s.tableView.reloadRows(at: [privateKeyIndex, publicKeyIndex], with: .automatic)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cell
|
|
||||||
} else if (field == .publicKey) {
|
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewReadOnlyKeyValueCell.id, for: indexPath) as! TunnelEditTableViewReadOnlyKeyValueCell
|
|
||||||
cell.key = field.rawValue
|
|
||||||
cell.value = interfaceData[field]
|
|
||||||
return cell
|
|
||||||
} else {
|
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewKeyValueCell.id, for: indexPath) as! TunnelEditTableViewKeyValueCell
|
|
||||||
// Set key
|
|
||||||
cell.key = field.rawValue
|
|
||||||
// Set placeholder text
|
|
||||||
switch (field) {
|
|
||||||
case .name:
|
|
||||||
cell.placeholderText = "Required"
|
|
||||||
case .privateKey:
|
|
||||||
cell.placeholderText = "Required"
|
|
||||||
case .addresses:
|
|
||||||
cell.placeholderText = "Optional"
|
|
||||||
case .listenPort:
|
|
||||||
cell.placeholderText = "Automatic"
|
|
||||||
case .mtu:
|
|
||||||
cell.placeholderText = "Automatic"
|
|
||||||
case .dns:
|
|
||||||
cell.placeholderText = "Optional"
|
|
||||||
case .publicKey: break
|
|
||||||
case .generateKeyPair: break
|
|
||||||
}
|
|
||||||
// Set keyboardType
|
|
||||||
if (field == .mtu || field == .listenPort) {
|
|
||||||
cell.keyboardType = .numberPad
|
|
||||||
} else if (field == .addresses || field == .dns) {
|
|
||||||
cell.keyboardType = .numbersAndPunctuation
|
|
||||||
}
|
|
||||||
// Show erroring fields
|
|
||||||
cell.isValueValid = (!interfaceData.fieldsWithError.contains(field))
|
|
||||||
// Bind values to view model
|
|
||||||
cell.value = interfaceData[field]
|
|
||||||
if (field == .dns) { // While editing DNS, you might directly set exclude private IPs
|
|
||||||
cell.onValueBeingEdited = { [weak interfaceData] value in
|
|
||||||
interfaceData?[field] = value
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cell.onValueChanged = { [weak interfaceData] value in
|
|
||||||
interfaceData?[field] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Compute public key live
|
|
||||||
if (field == .privateKey) {
|
|
||||||
cell.onValueBeingEdited = { [weak self, weak interfaceData] value in
|
|
||||||
if let interfaceData = interfaceData, let s = self {
|
|
||||||
interfaceData[.privateKey] = value
|
|
||||||
if let row = s.interfaceFieldsBySection[section].firstIndex(of: .publicKey) {
|
|
||||||
s.tableView.reloadRows(at: [IndexPath(row: row, section: section)], with: .none)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cell
|
|
||||||
}
|
}
|
||||||
} else if ((numberOfPeerSections > 0) && (section < (numberOfInterfaceSections + numberOfPeerSections))) {
|
return cell
|
||||||
// Peer
|
} else if field == .publicKey {
|
||||||
let peerIndex = (section - numberOfInterfaceSections)
|
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewReadOnlyKeyValueCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewReadOnlyKeyValueCell
|
||||||
let peerData = tunnelViewModel.peersData[peerIndex]
|
cell.key = field.rawValue
|
||||||
let peerFieldsToShow = peerData.shouldAllowExcludePrivateIPsControl ? peerFields : peerFields.filter { $0 != .excludePrivateIPs }
|
cell.value = interfaceData[field]
|
||||||
let field = peerFieldsToShow[row]
|
return cell
|
||||||
if (field == .deletePeer) {
|
} else {
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewButtonCell.id, for: indexPath) as! TunnelEditTableViewButtonCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewKeyValueCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewKeyValueCell
|
||||||
cell.buttonText = field.rawValue
|
// Set key
|
||||||
cell.hasDestructiveAction = true
|
cell.key = field.rawValue
|
||||||
cell.onTapped = { [weak self, weak peerData] in
|
// Set placeholder text
|
||||||
guard let peerData = peerData else { return }
|
switch field {
|
||||||
guard let s = self else { return }
|
case .name:
|
||||||
s.showConfirmationAlert(message: "Delete this peer?",
|
cell.placeholderText = "Required"
|
||||||
buttonTitle: "Delete", from: cell,
|
case .privateKey:
|
||||||
onConfirmed: { [weak s] in
|
cell.placeholderText = "Required"
|
||||||
guard let s = s else { return }
|
case .addresses:
|
||||||
let removedSectionIndices = s.deletePeer(peer: peerData)
|
cell.placeholderText = "Optional"
|
||||||
let shouldShowExcludePrivateIPs = (s.tunnelViewModel.peersData.count == 1 &&
|
case .listenPort:
|
||||||
s.tunnelViewModel.peersData[0].shouldAllowExcludePrivateIPsControl)
|
cell.placeholderText = "Automatic"
|
||||||
tableView.performBatchUpdates({
|
case .mtu:
|
||||||
s.tableView.deleteSections(removedSectionIndices, with: .automatic)
|
cell.placeholderText = "Automatic"
|
||||||
if (shouldShowExcludePrivateIPs) {
|
case .dns:
|
||||||
if let row = s.peerFields.firstIndex(of: .excludePrivateIPs) {
|
cell.placeholderText = "Optional"
|
||||||
let rowIndexPath = IndexPath(row: row, section: numberOfInterfaceSections /* First peer section */)
|
case .publicKey: break
|
||||||
s.tableView.insertRows(at: [rowIndexPath], with: .automatic)
|
case .generateKeyPair: break
|
||||||
}
|
}
|
||||||
|
// Set keyboardType
|
||||||
|
if field == .mtu || field == .listenPort {
|
||||||
|
cell.keyboardType = .numberPad
|
||||||
|
} else if field == .addresses || field == .dns {
|
||||||
|
cell.keyboardType = .numbersAndPunctuation
|
||||||
|
}
|
||||||
|
// Show erroring fields
|
||||||
|
cell.isValueValid = (!interfaceData.fieldsWithError.contains(field))
|
||||||
|
// Bind values to view model
|
||||||
|
cell.value = interfaceData[field]
|
||||||
|
if field == .dns { // While editing DNS, you might directly set exclude private IPs
|
||||||
|
cell.onValueBeingEdited = { [weak interfaceData] value in
|
||||||
|
interfaceData?[field] = value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cell.onValueChanged = { [weak interfaceData] value in
|
||||||
|
interfaceData?[field] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Compute public key live
|
||||||
|
if field == .privateKey {
|
||||||
|
cell.onValueBeingEdited = { [weak self, weak interfaceData] value in
|
||||||
|
if let interfaceData = interfaceData, let self = self {
|
||||||
|
interfaceData[.privateKey] = value
|
||||||
|
if let row = self.interfaceFieldsBySection[indexPath.section].firstIndex(of: .publicKey) {
|
||||||
|
self.tableView.reloadRows(at: [IndexPath(row: row, section: indexPath.section)], with: .none)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
private func peerCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
})
|
let peerIndex = indexPath.section - interfaceFieldsBySection.count
|
||||||
|
let peerData = tunnelViewModel.peersData[peerIndex]
|
||||||
|
let peerFieldsToShow = peerData.shouldAllowExcludePrivateIPsControl ? peerFields : peerFields.filter { $0 != .excludePrivateIPs }
|
||||||
|
let field = peerFieldsToShow[indexPath.row]
|
||||||
|
if field == .deletePeer {
|
||||||
|
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewButtonCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewButtonCell
|
||||||
|
cell.buttonText = field.rawValue
|
||||||
|
cell.hasDestructiveAction = true
|
||||||
|
cell.onTapped = { [weak self, weak peerData] in
|
||||||
|
guard let peerData = peerData else { return }
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.showConfirmationAlert(message: "Delete this peer?", buttonTitle: "Delete", from: cell) { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
let removedSectionIndices = self.deletePeer(peer: peerData)
|
||||||
|
let shouldShowExcludePrivateIPs = (self.tunnelViewModel.peersData.count == 1 && self.tunnelViewModel.peersData[0].shouldAllowExcludePrivateIPsControl)
|
||||||
|
tableView.performBatchUpdates({
|
||||||
|
self.tableView.deleteSections(removedSectionIndices, with: .automatic)
|
||||||
|
if shouldShowExcludePrivateIPs {
|
||||||
|
if let row = self.peerFields.firstIndex(of: .excludePrivateIPs) {
|
||||||
|
let rowIndexPath = IndexPath(row: row, section: self.interfaceFieldsBySection.count /* First peer section */)
|
||||||
|
self.tableView.insertRows(at: [rowIndexPath], with: .automatic)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return cell
|
}
|
||||||
} else if (field == .excludePrivateIPs) {
|
return cell
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewSwitchCell.id, for: indexPath) as! TunnelEditTableViewSwitchCell
|
} else if field == .excludePrivateIPs {
|
||||||
cell.message = field.rawValue
|
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewSwitchCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewSwitchCell
|
||||||
cell.isEnabled = peerData.shouldAllowExcludePrivateIPsControl
|
cell.message = field.rawValue
|
||||||
cell.isOn = peerData.excludePrivateIPsValue
|
cell.isEnabled = peerData.shouldAllowExcludePrivateIPsControl
|
||||||
cell.onSwitchToggled = { [weak self] (isOn) in
|
cell.isOn = peerData.excludePrivateIPsValue
|
||||||
guard let s = self else { return }
|
cell.onSwitchToggled = { [weak self] (isOn) in
|
||||||
peerData.excludePrivateIPsValueChanged(isOn: isOn, dnsServers: s.tunnelViewModel.interfaceData[.dns])
|
guard let self = self else { return }
|
||||||
if let row = s.peerFields.firstIndex(of: .allowedIPs) {
|
peerData.excludePrivateIPsValueChanged(isOn: isOn, dnsServers: self.tunnelViewModel.interfaceData[.dns])
|
||||||
s.tableView.reloadRows(at: [IndexPath(row: row, section: section)], with: .none)
|
if let row = self.peerFields.firstIndex(of: .allowedIPs) {
|
||||||
}
|
self.tableView.reloadRows(at: [IndexPath(row: row, section: indexPath.section)], with: .none)
|
||||||
}
|
}
|
||||||
return cell
|
}
|
||||||
} else {
|
return cell
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewKeyValueCell.id, for: indexPath) as! TunnelEditTableViewKeyValueCell
|
} else {
|
||||||
// Set key
|
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewKeyValueCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewKeyValueCell
|
||||||
cell.key = field.rawValue
|
// Set key
|
||||||
// Set placeholder text
|
cell.key = field.rawValue
|
||||||
switch (field) {
|
// Set placeholder text
|
||||||
case .publicKey:
|
switch field {
|
||||||
cell.placeholderText = "Required"
|
case .publicKey:
|
||||||
case .preSharedKey:
|
cell.placeholderText = "Required"
|
||||||
cell.placeholderText = "Optional"
|
case .preSharedKey:
|
||||||
case .endpoint:
|
cell.placeholderText = "Optional"
|
||||||
cell.placeholderText = "Optional"
|
case .endpoint:
|
||||||
case .allowedIPs:
|
cell.placeholderText = "Optional"
|
||||||
cell.placeholderText = "Optional"
|
case .allowedIPs:
|
||||||
case .persistentKeepAlive:
|
cell.placeholderText = "Optional"
|
||||||
cell.placeholderText = "Off"
|
case .persistentKeepAlive:
|
||||||
case .excludePrivateIPs: break
|
cell.placeholderText = "Off"
|
||||||
case .deletePeer: break
|
case .excludePrivateIPs: break
|
||||||
|
case .deletePeer: break
|
||||||
|
}
|
||||||
|
// Set keyboardType
|
||||||
|
if field == .persistentKeepAlive {
|
||||||
|
cell.keyboardType = .numberPad
|
||||||
|
} else if field == .allowedIPs {
|
||||||
|
cell.keyboardType = .numbersAndPunctuation
|
||||||
|
}
|
||||||
|
// Show erroring fields
|
||||||
|
cell.isValueValid = (!peerData.fieldsWithError.contains(field))
|
||||||
|
// Bind values to view model
|
||||||
|
cell.value = peerData[field]
|
||||||
|
if field != .allowedIPs {
|
||||||
|
cell.onValueChanged = { [weak peerData] value in
|
||||||
|
peerData?[field] = value
|
||||||
}
|
}
|
||||||
// Set keyboardType
|
}
|
||||||
if (field == .persistentKeepAlive) {
|
// Compute state of exclude private IPs live
|
||||||
cell.keyboardType = .numberPad
|
if field == .allowedIPs {
|
||||||
} else if (field == .allowedIPs) {
|
cell.onValueBeingEdited = { [weak self, weak peerData] value in
|
||||||
cell.keyboardType = .numbersAndPunctuation
|
if let peerData = peerData, let self = self {
|
||||||
}
|
let oldValue = peerData.shouldAllowExcludePrivateIPsControl
|
||||||
// Show erroring fields
|
peerData[.allowedIPs] = value
|
||||||
cell.isValueValid = (!peerData.fieldsWithError.contains(field))
|
if oldValue != peerData.shouldAllowExcludePrivateIPsControl {
|
||||||
// Bind values to view model
|
if let row = self.peerFields.firstIndex(of: .excludePrivateIPs) {
|
||||||
cell.value = peerData[field]
|
if peerData.shouldAllowExcludePrivateIPsControl {
|
||||||
if (field != .allowedIPs) {
|
self.tableView.insertRows(at: [IndexPath(row: row, section: indexPath.section)], with: .automatic)
|
||||||
cell.onValueChanged = { [weak peerData] value in
|
} else {
|
||||||
peerData?[field] = value
|
self.tableView.deleteRows(at: [IndexPath(row: row, section: indexPath.section)], with: .automatic)
|
||||||
}
|
|
||||||
}
|
|
||||||
// Compute state of exclude private IPs live
|
|
||||||
if (field == .allowedIPs) {
|
|
||||||
cell.onValueBeingEdited = { [weak self, weak peerData] value in
|
|
||||||
if let peerData = peerData, let s = self {
|
|
||||||
let oldValue = peerData.shouldAllowExcludePrivateIPsControl
|
|
||||||
peerData[.allowedIPs] = value
|
|
||||||
if (oldValue != peerData.shouldAllowExcludePrivateIPsControl) {
|
|
||||||
if let row = s.peerFields.firstIndex(of: .excludePrivateIPs) {
|
|
||||||
if (peerData.shouldAllowExcludePrivateIPsControl) {
|
|
||||||
s.tableView.insertRows(at: [IndexPath(row: row, section: section)], with: .automatic)
|
|
||||||
} else {
|
|
||||||
s.tableView.deleteRows(at: [IndexPath(row: row, section: section)], with: .automatic)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cell
|
|
||||||
}
|
}
|
||||||
} else if (section == (numberOfInterfaceSections + numberOfPeerSections)) {
|
return cell
|
||||||
// Add peer
|
}
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewButtonCell.id, for: indexPath) as! TunnelEditTableViewButtonCell
|
}
|
||||||
cell.buttonText = "Add peer"
|
|
||||||
cell.onTapped = { [weak self] in
|
private func addPeerCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
guard let s = self else { return }
|
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewButtonCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewButtonCell
|
||||||
let shouldHideExcludePrivateIPs = (s.tunnelViewModel.peersData.count == 1 &&
|
cell.buttonText = "Add peer"
|
||||||
s.tunnelViewModel.peersData[0].shouldAllowExcludePrivateIPsControl)
|
cell.onTapped = { [weak self] in
|
||||||
let addedSectionIndices = s.appendEmptyPeer()
|
guard let self = self else { return }
|
||||||
tableView.performBatchUpdates({
|
let shouldHideExcludePrivateIPs = (self.tunnelViewModel.peersData.count == 1 && self.tunnelViewModel.peersData[0].shouldAllowExcludePrivateIPsControl)
|
||||||
tableView.insertSections(addedSectionIndices, with: .automatic)
|
let addedSectionIndices = self.appendEmptyPeer()
|
||||||
if (shouldHideExcludePrivateIPs) {
|
tableView.performBatchUpdates({
|
||||||
if let row = s.peerFields.firstIndex(of: .excludePrivateIPs) {
|
tableView.insertSections(addedSectionIndices, with: .automatic)
|
||||||
let rowIndexPath = IndexPath(row: row, section: numberOfInterfaceSections /* First peer section */)
|
if shouldHideExcludePrivateIPs {
|
||||||
s.tableView.deleteRows(at: [rowIndexPath], with: .automatic)
|
if let row = self.peerFields.firstIndex(of: .excludePrivateIPs) {
|
||||||
}
|
let rowIndexPath = IndexPath(row: row, section: self.interfaceFieldsBySection.count /* First peer section */)
|
||||||
|
self.tableView.deleteRows(at: [rowIndexPath], with: .automatic)
|
||||||
}
|
}
|
||||||
}, completion: nil)
|
}
|
||||||
|
}, completion: nil)
|
||||||
|
}
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
assert(indexPath.section == interfaceSectionCount + peerSectionCount + 1)
|
||||||
|
if indexPath.row == 0 {
|
||||||
|
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewSwitchCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewSwitchCell
|
||||||
|
cell.message = "Activate on demand"
|
||||||
|
cell.isOn = activateOnDemandSetting.isActivateOnDemandEnabled
|
||||||
|
cell.onSwitchToggled = { [weak self] (isOn) in
|
||||||
|
guard let self = self else { return }
|
||||||
|
let indexPaths: [IndexPath] = (1 ..< 4).map { IndexPath(row: $0, section: indexPath.section) }
|
||||||
|
if isOn {
|
||||||
|
self.activateOnDemandSetting.isActivateOnDemandEnabled = true
|
||||||
|
if self.activateOnDemandSetting.activateOnDemandOption == .none {
|
||||||
|
self.activateOnDemandSetting.activateOnDemandOption = TunnelViewModel.defaultActivateOnDemandOption()
|
||||||
|
}
|
||||||
|
self.tableView.insertRows(at: indexPaths, with: .automatic)
|
||||||
|
} else {
|
||||||
|
self.activateOnDemandSetting.isActivateOnDemandEnabled = false
|
||||||
|
self.tableView.deleteRows(at: indexPaths, with: .automatic)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
} else {
|
} else {
|
||||||
assert(section == (numberOfInterfaceSections + numberOfPeerSections + 1))
|
assert(indexPath.row < 4)
|
||||||
if (row == 0) {
|
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewSelectionListCell.reuseIdentifier, for: indexPath) as! TunnelEditTableViewSelectionListCell
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewSwitchCell.id, for: indexPath) as! TunnelEditTableViewSwitchCell
|
let rowOption = activateOnDemandOptions[indexPath.row - 1]
|
||||||
cell.message = "Activate on demand"
|
let selectedOption = activateOnDemandSetting.activateOnDemandOption
|
||||||
cell.isOn = activateOnDemandSetting.isActivateOnDemandEnabled
|
assert(selectedOption != .none)
|
||||||
cell.onSwitchToggled = { [weak self] (isOn) in
|
cell.message = TunnelViewModel.activateOnDemandOptionText(for: rowOption)
|
||||||
guard let s = self else { return }
|
cell.isChecked = (selectedOption == rowOption)
|
||||||
let indexPaths: [IndexPath] = (1 ..< 4).map { IndexPath(row: $0, section: section) }
|
return cell
|
||||||
if (isOn) {
|
|
||||||
s.activateOnDemandSetting.isActivateOnDemandEnabled = true
|
|
||||||
if (s.activateOnDemandSetting.activateOnDemandOption == .none) {
|
|
||||||
s.activateOnDemandSetting.activateOnDemandOption = TunnelViewModel.defaultActivateOnDemandOption()
|
|
||||||
}
|
|
||||||
s.tableView.insertRows(at: indexPaths, with: .automatic)
|
|
||||||
} else {
|
|
||||||
s.activateOnDemandSetting.isActivateOnDemandEnabled = false
|
|
||||||
s.tableView.deleteRows(at: indexPaths, with: .automatic)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cell
|
|
||||||
} else {
|
|
||||||
assert(row < 4)
|
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: TunnelEditTableViewSelectionListCell.id, for: indexPath) as! TunnelEditTableViewSelectionListCell
|
|
||||||
let rowOption = activateOnDemandOptions[row - 1]
|
|
||||||
let selectedOption = activateOnDemandSetting.activateOnDemandOption
|
|
||||||
assert(selectedOption != .none)
|
|
||||||
cell.message = TunnelViewModel.activateOnDemandOptionText(for: rowOption)
|
|
||||||
cell.isChecked = (selectedOption == rowOption)
|
|
||||||
return cell
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendEmptyPeer() -> IndexSet {
|
func appendEmptyPeer() -> IndexSet {
|
||||||
let numberOfInterfaceSections = interfaceFieldsBySection.count
|
|
||||||
|
|
||||||
tunnelViewModel.appendEmptyPeer()
|
tunnelViewModel.appendEmptyPeer()
|
||||||
let addedPeerIndex = tunnelViewModel.peersData.count - 1
|
let addedPeerIndex = tunnelViewModel.peersData.count - 1
|
||||||
|
|
||||||
let addedSectionIndices = IndexSet(integer: (numberOfInterfaceSections + addedPeerIndex))
|
let addedSectionIndices = IndexSet(integer: interfaceSectionCount + addedPeerIndex)
|
||||||
return addedSectionIndices
|
return addedSectionIndices
|
||||||
}
|
}
|
||||||
|
|
||||||
func deletePeer(peer: TunnelViewModel.PeerData) -> IndexSet {
|
func deletePeer(peer: TunnelViewModel.PeerData) -> IndexSet {
|
||||||
let numberOfInterfaceSections = interfaceFieldsBySection.count
|
|
||||||
|
|
||||||
assert(peer.index < tunnelViewModel.peersData.count)
|
assert(peer.index < tunnelViewModel.peersData.count)
|
||||||
tunnelViewModel.deletePeer(peer: peer)
|
tunnelViewModel.deletePeer(peer: peer)
|
||||||
|
|
||||||
let removedSectionIndices = IndexSet(integer: (numberOfInterfaceSections + peer.index))
|
let removedSectionIndices = IndexSet(integer: (interfaceSectionCount + peer.index))
|
||||||
return removedSectionIndices
|
return removedSectionIndices
|
||||||
}
|
}
|
||||||
|
|
||||||
func showConfirmationAlert(message: String, buttonTitle: String, from sourceView: UIView,
|
func showConfirmationAlert(message: String, buttonTitle: String, from sourceView: UIView, onConfirmed: @escaping (() -> Void)) {
|
||||||
onConfirmed: @escaping (() -> Void)) {
|
|
||||||
let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { (_) in
|
let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { (_) in
|
||||||
onConfirmed()
|
onConfirmed()
|
||||||
}
|
}
|
||||||
|
@ -454,27 +446,18 @@ extension TunnelEditTableViewController {
|
||||||
|
|
||||||
extension TunnelEditTableViewController {
|
extension TunnelEditTableViewController {
|
||||||
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||||
let numberOfInterfaceSections = interfaceFieldsBySection.count
|
if indexPath.section == (interfaceSectionCount + peerSectionCount + 1) {
|
||||||
let numberOfPeerSections = tunnelViewModel.peersData.count
|
return (indexPath.row > 0) ? indexPath : nil
|
||||||
|
|
||||||
let section = indexPath.section
|
|
||||||
let row = indexPath.row
|
|
||||||
|
|
||||||
if (section == (numberOfInterfaceSections + numberOfPeerSections + 1)) {
|
|
||||||
return (row > 0) ? indexPath : nil
|
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
let numberOfInterfaceSections = interfaceFieldsBySection.count
|
|
||||||
let numberOfPeerSections = tunnelViewModel.peersData.count
|
|
||||||
|
|
||||||
let section = indexPath.section
|
let section = indexPath.section
|
||||||
let row = indexPath.row
|
let row = indexPath.row
|
||||||
|
|
||||||
assert(section == (numberOfInterfaceSections + numberOfPeerSections + 1))
|
assert(section == (interfaceSectionCount + peerSectionCount + 1))
|
||||||
assert(row > 0)
|
assert(row > 0)
|
||||||
|
|
||||||
let option = activateOnDemandOptions[row - 1]
|
let option = activateOnDemandOptions[row - 1]
|
||||||
|
@ -487,7 +470,7 @@ extension TunnelEditTableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TunnelEditTableViewKeyValueCell: UITableViewCell {
|
class TunnelEditTableViewKeyValueCell: UITableViewCell {
|
||||||
static let id: String = "TunnelEditTableViewKeyValueCell"
|
static let reuseIdentifier = "TunnelEditTableViewKeyValueCell"
|
||||||
var key: String {
|
var key: String {
|
||||||
get { return keyLabel.text ?? "" }
|
get { return keyLabel.text ?? "" }
|
||||||
set(value) {keyLabel.text = value }
|
set(value) {keyLabel.text = value }
|
||||||
|
@ -502,7 +485,7 @@ class TunnelEditTableViewKeyValueCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
var isValueValid: Bool = true {
|
var isValueValid: Bool = true {
|
||||||
didSet {
|
didSet {
|
||||||
if (isValueValid) {
|
if isValueValid {
|
||||||
keyLabel.textColor = UIColor.black
|
keyLabel.textColor = UIColor.black
|
||||||
} else {
|
} else {
|
||||||
keyLabel.textColor = UIColor.red
|
keyLabel.textColor = UIColor.red
|
||||||
|
@ -584,7 +567,7 @@ class TunnelEditTableViewKeyValueCell: UITableViewCell {
|
||||||
constraints = [
|
constraints = [
|
||||||
contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5),
|
contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5),
|
||||||
valueTextField.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1),
|
valueTextField.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1),
|
||||||
valueTextField.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5),
|
valueTextField.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5)
|
||||||
]
|
]
|
||||||
isStackedHorizontally = true
|
isStackedHorizontally = true
|
||||||
isStackedVertically = false
|
isStackedVertically = false
|
||||||
|
@ -636,7 +619,7 @@ extension TunnelEditTableViewKeyValueCell: UITextFieldDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TunnelEditTableViewReadOnlyKeyValueCell: CopyableLabelTableViewCell {
|
class TunnelEditTableViewReadOnlyKeyValueCell: CopyableLabelTableViewCell {
|
||||||
static let id: String = "TunnelEditTableViewReadOnlyKeyValueCell"
|
static let reuseIdentifier = "TunnelEditTableViewReadOnlyKeyValueCell"
|
||||||
var key: String {
|
var key: String {
|
||||||
get { return keyLabel.text ?? "" }
|
get { return keyLabel.text ?? "" }
|
||||||
set(value) {keyLabel.text = value }
|
set(value) {keyLabel.text = value }
|
||||||
|
@ -684,7 +667,7 @@ class TunnelEditTableViewReadOnlyKeyValueCell: CopyableLabelTableViewCell {
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
valueLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
valueLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
||||||
valueLabel.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1),
|
valueLabel.leftAnchor.constraint(equalToSystemSpacingAfter: keyLabel.rightAnchor, multiplier: 1),
|
||||||
valueLabel.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor),
|
valueLabel.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -704,7 +687,7 @@ class TunnelEditTableViewReadOnlyKeyValueCell: CopyableLabelTableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TunnelEditTableViewButtonCell: UITableViewCell {
|
class TunnelEditTableViewButtonCell: UITableViewCell {
|
||||||
static let id: String = "TunnelEditTableViewButtonCell"
|
static let reuseIdentifier = "TunnelEditTableViewButtonCell"
|
||||||
var buttonText: String {
|
var buttonText: String {
|
||||||
get { return button.title(for: .normal) ?? "" }
|
get { return button.title(for: .normal) ?? "" }
|
||||||
set(value) { button.setTitle(value, for: .normal) }
|
set(value) { button.setTitle(value, for: .normal) }
|
||||||
|
@ -751,7 +734,7 @@ class TunnelEditTableViewButtonCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TunnelEditTableViewSwitchCell: UITableViewCell {
|
class TunnelEditTableViewSwitchCell: UITableViewCell {
|
||||||
static let id: String = "TunnelEditTableViewSwitchCell"
|
static let reuseIdentifier = "TunnelEditTableViewSwitchCell"
|
||||||
var message: String {
|
var message: String {
|
||||||
get { return textLabel?.text ?? "" }
|
get { return textLabel?.text ?? "" }
|
||||||
set(value) { textLabel!.text = value }
|
set(value) { textLabel!.text = value }
|
||||||
|
@ -796,7 +779,7 @@ class TunnelEditTableViewSwitchCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TunnelEditTableViewSelectionListCell: UITableViewCell {
|
class TunnelEditTableViewSelectionListCell: UITableViewCell {
|
||||||
static let id: String = "TunnelEditTableViewSelectionListCell"
|
static let reuseIdentifier = "TunnelEditTableViewSelectionListCell"
|
||||||
var message: String {
|
var message: String {
|
||||||
get { return textLabel?.text ?? "" }
|
get { return textLabel?.text ?? "" }
|
||||||
set(value) { textLabel!.text = value }
|
set(value) { textLabel!.text = value }
|
||||||
|
|
|
@ -116,8 +116,8 @@ class TunnelsListTableViewController: UIViewController {
|
||||||
alert.addAction(scanQRCodeAction)
|
alert.addAction(scanQRCodeAction)
|
||||||
|
|
||||||
let createFromScratchAction = UIAlertAction(title: "Create from scratch", style: .default) { [weak self] (_) in
|
let createFromScratchAction = UIAlertAction(title: "Create from scratch", style: .default) { [weak self] (_) in
|
||||||
if let s = self, let tunnelsManager = s.tunnelsManager {
|
if let self = self, let tunnelsManager = self.tunnelsManager {
|
||||||
s.presentViewControllerForTunnelCreation(tunnelsManager: tunnelsManager, tunnelConfiguration: nil)
|
self.presentViewControllerForTunnelCreation(tunnelsManager: tunnelsManager, tunnelConfiguration: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
alert.addAction(createFromScratchAction)
|
alert.addAction(createFromScratchAction)
|
||||||
|
@ -246,11 +246,11 @@ extension TunnelsListTableViewController: UITableViewDataSource {
|
||||||
let tunnel = tunnelsManager.tunnel(at: indexPath.row)
|
let tunnel = tunnelsManager.tunnel(at: indexPath.row)
|
||||||
cell.tunnel = tunnel
|
cell.tunnel = tunnel
|
||||||
cell.onSwitchToggled = { [weak self] isOn in
|
cell.onSwitchToggled = { [weak self] isOn in
|
||||||
guard let s = self, let tunnelsManager = s.tunnelsManager else { return }
|
guard let self = self, let tunnelsManager = self.tunnelsManager else { return }
|
||||||
if (isOn) {
|
if (isOn) {
|
||||||
tunnelsManager.startActivation(of: tunnel) { [weak s] error in
|
tunnelsManager.startActivation(of: tunnel) { [weak self] error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: s, onPresented: {
|
ErrorPresenter.showErrorAlert(error: error, from: self, onPresented: {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
cell.statusSwitch.isOn = false
|
cell.statusSwitch.isOn = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,11 +118,11 @@ class TunnelsManager {
|
||||||
completionHandler(.failure(TunnelsManagerError.vpnSystemErrorOnAddTunnel))
|
completionHandler(.failure(TunnelsManagerError.vpnSystemErrorOnAddTunnel))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let s = self {
|
if let self = self {
|
||||||
let tunnel = TunnelContainer(tunnel: tunnelProviderManager)
|
let tunnel = TunnelContainer(tunnel: tunnelProviderManager)
|
||||||
s.tunnels.append(tunnel)
|
self.tunnels.append(tunnel)
|
||||||
s.tunnels.sort { $0.name < $1.name }
|
self.tunnels.sort { $0.name < $1.name }
|
||||||
s.tunnelsListDelegate?.tunnelAdded(at: s.tunnels.firstIndex(of: tunnel)!)
|
self.tunnelsListDelegate?.tunnelAdded(at: self.tunnels.firstIndex(of: tunnel)!)
|
||||||
completionHandler(.success(tunnel))
|
completionHandler(.success(tunnel))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,14 +175,14 @@ class TunnelsManager {
|
||||||
completionHandler(TunnelsManagerError.vpnSystemErrorOnModifyTunnel)
|
completionHandler(TunnelsManagerError.vpnSystemErrorOnModifyTunnel)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let s = self {
|
if let self = self {
|
||||||
if (isNameChanged) {
|
if (isNameChanged) {
|
||||||
let oldIndex = s.tunnels.firstIndex(of: tunnel)!
|
let oldIndex = self.tunnels.firstIndex(of: tunnel)!
|
||||||
s.tunnels.sort { $0.name < $1.name }
|
self.tunnels.sort { $0.name < $1.name }
|
||||||
let newIndex = s.tunnels.firstIndex(of: tunnel)!
|
let newIndex = self.tunnels.firstIndex(of: tunnel)!
|
||||||
s.tunnelsListDelegate?.tunnelMoved(at: oldIndex, to: newIndex)
|
self.tunnelsListDelegate?.tunnelMoved(at: oldIndex, to: newIndex)
|
||||||
}
|
}
|
||||||
s.tunnelsListDelegate?.tunnelModified(at: s.tunnels.firstIndex(of: tunnel)!)
|
self.tunnelsListDelegate?.tunnelModified(at: self.tunnels.firstIndex(of: tunnel)!)
|
||||||
|
|
||||||
if (tunnel.status == .active || tunnel.status == .activating || tunnel.status == .reasserting) {
|
if (tunnel.status == .active || tunnel.status == .activating || tunnel.status == .reasserting) {
|
||||||
// Turn off the tunnel, and then turn it back on, so the changes are made effective
|
// Turn off the tunnel, and then turn it back on, so the changes are made effective
|
||||||
|
@ -219,10 +219,10 @@ class TunnelsManager {
|
||||||
completionHandler(TunnelsManagerError.vpnSystemErrorOnRemoveTunnel)
|
completionHandler(TunnelsManagerError.vpnSystemErrorOnRemoveTunnel)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let s = self {
|
if let self = self {
|
||||||
let index = s.tunnels.firstIndex(of: tunnel)!
|
let index = self.tunnels.firstIndex(of: tunnel)!
|
||||||
s.tunnels.remove(at: index)
|
self.tunnels.remove(at: index)
|
||||||
s.tunnelsListDelegate?.tunnelRemoved(at: index)
|
self.tunnelsListDelegate?.tunnelRemoved(at: index)
|
||||||
}
|
}
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
}
|
}
|
||||||
|
@ -264,9 +264,7 @@ class TunnelsManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshStatuses() {
|
func refreshStatuses() {
|
||||||
for t in tunnels {
|
tunnels.forEach { $0.refreshStatus() }
|
||||||
t.refreshStatus()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func startObservingTunnelStatuses() {
|
private func startObservingTunnelStatuses() {
|
||||||
|
@ -275,24 +273,24 @@ class TunnelsManager {
|
||||||
forName: .NEVPNStatusDidChange,
|
forName: .NEVPNStatusDidChange,
|
||||||
object: nil,
|
object: nil,
|
||||||
queue: OperationQueue.main) { [weak self] (statusChangeNotification) in
|
queue: OperationQueue.main) { [weak self] (statusChangeNotification) in
|
||||||
|
guard let self = self else { return }
|
||||||
guard let session = statusChangeNotification.object as? NETunnelProviderSession else { return }
|
guard let session = statusChangeNotification.object as? NETunnelProviderSession else { return }
|
||||||
guard let tunnelProvider = session.manager as? NETunnelProviderManager else { return }
|
guard let tunnelProvider = session.manager as? NETunnelProviderManager else { return }
|
||||||
guard let tunnel = self?.tunnels.first(where: { $0.tunnelProvider == tunnelProvider }) else { return }
|
guard let tunnel = self.tunnels.first(where: { $0.tunnelProvider == tunnelProvider }) else { return }
|
||||||
guard let s = self else { return }
|
|
||||||
|
|
||||||
os_log("Tunnel '%{public}@' connection status changed to '%{public}@'",
|
os_log("Tunnel '%{public}@' connection status changed to '%{public}@'",
|
||||||
log: OSLog.default, type: .debug, tunnel.name, "\(tunnel.tunnelProvider.connection.status)")
|
log: OSLog.default, type: .debug, tunnel.name, "\(tunnel.tunnelProvider.connection.status)")
|
||||||
|
|
||||||
// In case our attempt to start the tunnel, didn't succeed
|
// In case our attempt to start the tunnel, didn't succeed
|
||||||
if (tunnel == s.tunnelBeingActivated) {
|
if (tunnel == self.tunnelBeingActivated) {
|
||||||
if (session.status == .disconnected) {
|
if (session.status == .disconnected) {
|
||||||
if (InternetReachability.currentStatus() == .notReachable) {
|
if (InternetReachability.currentStatus() == .notReachable) {
|
||||||
let error = TunnelsManagerError.tunnelActivationFailedNoInternetConnection
|
let error = TunnelsManagerError.tunnelActivationFailedNoInternetConnection
|
||||||
s.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: error)
|
self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: error)
|
||||||
}
|
}
|
||||||
s.tunnelBeingActivated = nil
|
self.tunnelBeingActivated = nil
|
||||||
} else if (session.status == .connected) {
|
} else if (session.status == .connected) {
|
||||||
s.tunnelBeingActivated = nil
|
self.tunnelBeingActivated = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,7 +298,7 @@ class TunnelsManager {
|
||||||
if ((tunnel.status == .restarting) && (session.status == .disconnected || session.status == .disconnecting)) {
|
if ((tunnel.status == .restarting) && (session.status == .disconnected || session.status == .disconnecting)) {
|
||||||
// Don't change tunnel.status when disconnecting for a restart
|
// Don't change tunnel.status when disconnecting for a restart
|
||||||
if (session.status == .disconnected) {
|
if (session.status == .disconnected) {
|
||||||
s.tunnelBeingActivated = tunnel
|
self.tunnelBeingActivated = tunnel
|
||||||
tunnel.startActivation(completionHandler: { _ in })
|
tunnel.startActivation(completionHandler: { _ in })
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -387,7 +385,8 @@ class TunnelContainer: NSObject {
|
||||||
}
|
}
|
||||||
os_log("startActivation: Tunnel saved after re-enabling", log: OSLog.default, type: .info)
|
os_log("startActivation: Tunnel saved after re-enabling", log: OSLog.default, type: .info)
|
||||||
os_log("startActivation: Invoking startActivation", log: OSLog.default, type: .debug)
|
os_log("startActivation: Invoking startActivation", log: OSLog.default, type: .debug)
|
||||||
self?.startActivation(recursionCount: recursionCount + 1, lastError: NEVPNError(NEVPNError.configurationUnknown), tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler)
|
self?.startActivation(recursionCount: recursionCount + 1, lastError: NEVPNError(NEVPNError.configurationUnknown),
|
||||||
|
tunnelConfiguration: tunnelConfiguration, completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,27 +2,27 @@
|
||||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
enum WireGuardResult<T> {
|
enum WireGuardResult<T> {
|
||||||
case success(T)
|
case success(_ value: T)
|
||||||
case failure(WireGuardAppError)
|
case failure(_ error: WireGuardAppError)
|
||||||
|
|
||||||
var value: T? {
|
var value: T? {
|
||||||
switch (self) {
|
switch (self) {
|
||||||
case .success(let v): return v
|
case .success(let value): return value
|
||||||
case .failure(_): return nil
|
case .failure: return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var error: WireGuardAppError? {
|
var error: WireGuardAppError? {
|
||||||
switch (self) {
|
switch (self) {
|
||||||
case .success(_): return nil
|
case .success: return nil
|
||||||
case .failure(let e): return e
|
case .failure(let error): return error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var isSuccess: Bool {
|
var isSuccess: Bool {
|
||||||
switch (self) {
|
switch (self) {
|
||||||
case .success(_): return true
|
case .success: return true
|
||||||
case .failure(_): return false
|
case .failure: return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ enum ZipExporterError: WireGuardAppError {
|
||||||
|
|
||||||
class ZipExporter {
|
class ZipExporter {
|
||||||
static func exportConfigFiles(tunnelConfigurations: [TunnelConfiguration], to url: URL,
|
static func exportConfigFiles(tunnelConfigurations: [TunnelConfiguration], to url: URL,
|
||||||
completion: @escaping (WireGuardAppError?) -> Void) {
|
completion: @escaping (WireGuardAppError?) -> Void) {
|
||||||
|
|
||||||
guard (!tunnelConfigurations.isEmpty) else {
|
guard (!tunnelConfigurations.isEmpty) else {
|
||||||
completion(ZipExporterError.noTunnelsToExport)
|
completion(ZipExporterError.noTunnelsToExport)
|
||||||
|
|
|
@ -20,13 +20,13 @@ class ZipImporter {
|
||||||
var unarchivedFiles: [(fileBaseName: String, contents: Data)]
|
var unarchivedFiles: [(fileBaseName: String, contents: Data)]
|
||||||
do {
|
do {
|
||||||
unarchivedFiles = try ZipArchive.unarchive(url: url, requiredFileExtensions: ["conf"])
|
unarchivedFiles = try ZipArchive.unarchive(url: url, requiredFileExtensions: ["conf"])
|
||||||
for (i, unarchivedFile) in unarchivedFiles.enumerated().reversed() {
|
for (index, unarchivedFile) in unarchivedFiles.enumerated().reversed() {
|
||||||
let fileBaseName = unarchivedFile.fileBaseName
|
let fileBaseName = unarchivedFile.fileBaseName
|
||||||
let trimmedName = fileBaseName.trimmingCharacters(in: .whitespacesAndNewlines)
|
let trimmedName = fileBaseName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if (!trimmedName.isEmpty) {
|
if (!trimmedName.isEmpty) {
|
||||||
unarchivedFiles[i].fileBaseName = trimmedName
|
unarchivedFiles[index].fileBaseName = trimmedName
|
||||||
} else {
|
} else {
|
||||||
unarchivedFiles.remove(at: i)
|
unarchivedFiles.remove(at: index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,8 +42,8 @@ class ZipImporter {
|
||||||
|
|
||||||
unarchivedFiles.sort { $0.fileBaseName < $1.fileBaseName }
|
unarchivedFiles.sort { $0.fileBaseName < $1.fileBaseName }
|
||||||
var configs = Array<TunnelConfiguration?>(repeating: nil, count: unarchivedFiles.count)
|
var configs = Array<TunnelConfiguration?>(repeating: nil, count: unarchivedFiles.count)
|
||||||
for (i, file) in unarchivedFiles.enumerated() {
|
for (index, file) in unarchivedFiles.enumerated() {
|
||||||
if (i > 0 && file == unarchivedFiles[i - 1]) {
|
if (index > 0 && file == unarchivedFiles[index - 1]) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
guard let fileContents = String(data: file.contents, encoding: .utf8) else {
|
guard let fileContents = String(data: file.contents, encoding: .utf8) else {
|
||||||
|
@ -52,7 +52,7 @@ class ZipImporter {
|
||||||
guard let tunnelConfig = try? WgQuickConfigFileParser.parse(fileContents, name: file.fileBaseName) else {
|
guard let tunnelConfig = try? WgQuickConfigFileParser.parse(fileContents, name: file.fileBaseName) else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
configs[i] = tunnelConfig
|
configs[index] = tunnelConfig
|
||||||
}
|
}
|
||||||
DispatchQueue.main.async { completion(.success(configs)) }
|
DispatchQueue.main.async { completion(.success(configs)) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,13 +28,13 @@ class DNSResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
var resolvedEndpoints: [Endpoint?] = Array<Endpoint?>(repeating: nil, count: endpoints.count)
|
var resolvedEndpoints: [Endpoint?] = Array<Endpoint?>(repeating: nil, count: endpoints.count)
|
||||||
for (i, endpoint) in endpoints.enumerated() {
|
for (index, endpoint) in endpoints.enumerated() {
|
||||||
guard let endpoint = endpoint else { continue }
|
guard let endpoint = endpoint else { continue }
|
||||||
if (endpoint.hasHostAsIPAddress()) {
|
if (endpoint.hasHostAsIPAddress()) {
|
||||||
resolvedEndpoints[i] = endpoint
|
resolvedEndpoints[index] = endpoint
|
||||||
} else {
|
} else {
|
||||||
let workItem = DispatchWorkItem {
|
let workItem = DispatchWorkItem {
|
||||||
resolvedEndpoints[i] = DNSResolver.resolveSync(endpoint: endpoint)
|
resolvedEndpoints[index] = DNSResolver.resolveSync(endpoint: endpoint)
|
||||||
}
|
}
|
||||||
DispatchQueue.global(qos: .userInitiated).async(group: dispatchGroup, execute: workItem)
|
DispatchQueue.global(qos: .userInitiated).async(group: dispatchGroup, execute: workItem)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ class ErrorNotifier {
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case .savedProtocolConfigurationIsInvalid:
|
case .savedProtocolConfigurationIsInvalid:
|
||||||
return ("Activation failure", "Could not retrieve tunnel information from the saved configuration")
|
return ("Activation failure", "Could not retrieve tunnel information from the saved configuration")
|
||||||
case .dnsResolutionFailure(_):
|
case .dnsResolutionFailure:
|
||||||
return ("DNS resolution failure", "One or more endpoint domains could not be resolved")
|
return ("DNS resolution failure", "One or more endpoint domains could not be resolved")
|
||||||
case .couldNotStartWireGuard:
|
case .couldNotStartWireGuard:
|
||||||
return ("Activation failure", "WireGuard backend could not be started")
|
return ("Activation failure", "WireGuard backend could not be started")
|
||||||
|
|
|
@ -99,12 +99,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
wg_log(.debug, message: "Network change detected, re-establishing sockets and IPs: \(path.availableInterfaces)")
|
wg_log(.debug, message: "Network change detected, re-establishing sockets and IPs: \(path.availableInterfaces)")
|
||||||
let endpointString = packetTunnelSettingsGenerator.endpointUapiConfiguration(currentListenPort: wgGetListenPort(handle))
|
let endpointString = packetTunnelSettingsGenerator.endpointUapiConfiguration(currentListenPort: wgGetListenPort(handle))
|
||||||
let err = endpointString.withCString {
|
let err = endpointString.withCString {
|
||||||
wgSetConfig(handle, gostring_t(p: $0, n: endpointString.utf8.count))
|
wgSetConfig(handle, gostring_t(p: $0, n: endpointString.utf8.count))
|
||||||
}
|
}
|
||||||
if err == -EADDRINUSE {
|
if err == -EADDRINUSE {
|
||||||
let endpointString = packetTunnelSettingsGenerator.endpointUapiConfiguration(currentListenPort: 0)
|
let endpointString = packetTunnelSettingsGenerator.endpointUapiConfiguration(currentListenPort: 0)
|
||||||
_ = endpointString.withCString {
|
_ = endpointString.withCString {
|
||||||
wgSetConfig(handle, gostring_t(p: $0, n: endpointString.utf8.count))
|
wgSetConfig(handle, gostring_t(p: $0, n: endpointString.utf8.count))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,9 @@ class PacketTunnelSettingsGenerator {
|
||||||
func endpointUapiConfiguration(currentListenPort: UInt16) -> String {
|
func endpointUapiConfiguration(currentListenPort: UInt16) -> String {
|
||||||
var wgSettings = "listen_port=\(tunnelConfiguration.interface.listenPort ?? currentListenPort)\n"
|
var wgSettings = "listen_port=\(tunnelConfiguration.interface.listenPort ?? currentListenPort)\n"
|
||||||
|
|
||||||
for (i, peer) in tunnelConfiguration.peers.enumerated() {
|
for (index, peer) in tunnelConfiguration.peers.enumerated() {
|
||||||
wgSettings.append("public_key=\(peer.publicKey.hexEncodedString())\n")
|
wgSettings.append("public_key=\(peer.publicKey.hexEncodedString())\n")
|
||||||
if let endpoint = resolvedEndpoints[i] {
|
if let endpoint = resolvedEndpoints[index] {
|
||||||
if case .name(_, _) = endpoint.host { assert(false, "Endpoint is not resolved") }
|
if case .name(_, _) = endpoint.host { assert(false, "Endpoint is not resolved") }
|
||||||
wgSettings.append("endpoint=\(endpoint.stringRepresentation())\n")
|
wgSettings.append("endpoint=\(endpoint.stringRepresentation())\n")
|
||||||
}
|
}
|
||||||
|
@ -40,12 +40,12 @@ class PacketTunnelSettingsGenerator {
|
||||||
wgSettings.append("replace_peers=true\n")
|
wgSettings.append("replace_peers=true\n")
|
||||||
}
|
}
|
||||||
assert(tunnelConfiguration.peers.count == resolvedEndpoints.count)
|
assert(tunnelConfiguration.peers.count == resolvedEndpoints.count)
|
||||||
for (i, peer) in tunnelConfiguration.peers.enumerated() {
|
for (index, peer) in tunnelConfiguration.peers.enumerated() {
|
||||||
wgSettings.append("public_key=\(peer.publicKey.hexEncodedString())\n")
|
wgSettings.append("public_key=\(peer.publicKey.hexEncodedString())\n")
|
||||||
if let preSharedKey = peer.preSharedKey {
|
if let preSharedKey = peer.preSharedKey {
|
||||||
wgSettings.append("preshared_key=\(preSharedKey.hexEncodedString())\n")
|
wgSettings.append("preshared_key=\(preSharedKey.hexEncodedString())\n")
|
||||||
}
|
}
|
||||||
if let endpoint = resolvedEndpoints[i] {
|
if let endpoint = resolvedEndpoints[index] {
|
||||||
if case .name(_, _) = endpoint.host { assert(false, "Endpoint is not resolved") }
|
if case .name(_, _) = endpoint.host { assert(false, "Endpoint is not resolved") }
|
||||||
wgSettings.append("endpoint=\(endpoint.stringRepresentation())\n")
|
wgSettings.append("endpoint=\(endpoint.stringRepresentation())\n")
|
||||||
}
|
}
|
||||||
|
@ -200,10 +200,10 @@ class PacketTunnelSettingsGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ipv4SubnetMaskString(of addressRange: IPAddressRange) -> String {
|
static func ipv4SubnetMaskString(of addressRange: IPAddressRange) -> String {
|
||||||
let n: UInt8 = addressRange.networkPrefixLength
|
let length: UInt8 = addressRange.networkPrefixLength
|
||||||
assert(n <= 32)
|
assert(length <= 32)
|
||||||
var octets: [UInt8] = [0, 0, 0, 0]
|
var octets: [UInt8] = [0, 0, 0, 0]
|
||||||
let subnetMask: UInt32 = n > 0 ? ~UInt32(0) << (32 - n) : UInt32(0)
|
let subnetMask: UInt32 = length > 0 ? ~UInt32(0) << (32 - length) : UInt32(0)
|
||||||
octets[0] = UInt8(truncatingIfNeeded: subnetMask >> 24)
|
octets[0] = UInt8(truncatingIfNeeded: subnetMask >> 24)
|
||||||
octets[1] = UInt8(truncatingIfNeeded: subnetMask >> 16)
|
octets[1] = UInt8(truncatingIfNeeded: subnetMask >> 16)
|
||||||
octets[2] = UInt8(truncatingIfNeeded: subnetMask >> 8)
|
octets[2] = UInt8(truncatingIfNeeded: subnetMask >> 8)
|
||||||
|
|
Loading…
Reference in New Issue