More formatting nits and cyclomatic complexity fixes
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
This commit is contained in:
parent
e4ac48bc75
commit
8a916beb38
|
@ -1,6 +1,7 @@
|
|||
disabled_rules:
|
||||
- line_length
|
||||
- trailing_whitespace
|
||||
- todo
|
||||
opt_in_rules:
|
||||
- unneeded_parentheses_in_closure_argument
|
||||
# - trailing_closure
|
||||
|
|
|
@ -26,10 +26,10 @@ final class TunnelConfiguration: Codable {
|
|||
struct InterfaceConfiguration: Codable {
|
||||
var name: String
|
||||
var privateKey: Data
|
||||
var addresses: [IPAddressRange] = []
|
||||
var addresses = [IPAddressRange]()
|
||||
var listenPort: UInt16?
|
||||
var mtu: UInt16?
|
||||
var dns: [DNSServer] = []
|
||||
var dns = [DNSServer]()
|
||||
|
||||
init(name: String, privateKey: Data) {
|
||||
self.name = name
|
||||
|
@ -55,7 +55,7 @@ struct PeerConfiguration: Codable {
|
|||
}
|
||||
}
|
||||
}
|
||||
var allowedIPs: [IPAddressRange] = []
|
||||
var allowedIPs = [IPAddressRange]()
|
||||
var endpoint: Endpoint?
|
||||
var persistentKeepAlive: UInt16?
|
||||
|
||||
|
|
|
@ -75,9 +75,7 @@ extension IPAddressRange: Codable {
|
|||
default: return nil
|
||||
}
|
||||
}()
|
||||
guard let ipAddress = ipAddressFromData else {
|
||||
throw DecodingError.invalidData
|
||||
}
|
||||
guard let ipAddress = ipAddressFromData else { throw DecodingError.invalidData }
|
||||
address = ipAddress
|
||||
}
|
||||
enum DecodingError: Error {
|
||||
|
|
|
@ -54,13 +54,11 @@ class WgQuickConfigFileParser {
|
|||
} else {
|
||||
attributes[key] = value
|
||||
}
|
||||
} else {
|
||||
if lowercasedLine != "[interface]" && lowercasedLine != "[peer]" {
|
||||
throw ParseError.invalidLine(line)
|
||||
}
|
||||
} else if lowercasedLine != "[interface]" && lowercasedLine != "[peer]" {
|
||||
throw ParseError.invalidLine(line)
|
||||
}
|
||||
|
||||
let isLastLine = (lineIndex == lines.count - 1)
|
||||
let isLastLine = lineIndex == lines.count - 1
|
||||
|
||||
if isLastLine || lowercasedLine == "[interface]" || lowercasedLine == "[peer]" {
|
||||
// Previous section has ended; process the attributes collected so far
|
||||
|
@ -109,7 +107,7 @@ class WgQuickConfigFileParser {
|
|||
}
|
||||
// wg-quick fields
|
||||
if let addressesString = attributes["address"] {
|
||||
var addresses: [IPAddressRange] = []
|
||||
var addresses = [IPAddressRange]()
|
||||
for addressString in addressesString.split(separator: ",") {
|
||||
let trimmedString = addressString.trimmingCharacters(in: .whitespaces)
|
||||
guard let address = IPAddressRange(from: trimmedString) else { return nil }
|
||||
|
@ -118,7 +116,7 @@ class WgQuickConfigFileParser {
|
|||
interface.addresses = addresses
|
||||
}
|
||||
if let dnsString = attributes["dns"] {
|
||||
var dnsServers: [DNSServer] = []
|
||||
var dnsServers = [DNSServer]()
|
||||
for dnsServerString in dnsString.split(separator: ",") {
|
||||
let trimmedString = dnsServerString.trimmingCharacters(in: .whitespaces)
|
||||
guard let dnsServer = DNSServer(from: trimmedString) else { return nil }
|
||||
|
@ -144,7 +142,7 @@ class WgQuickConfigFileParser {
|
|||
peer.preSharedKey = preSharedKey
|
||||
}
|
||||
if let allowedIPsString = attributes["allowedips"] {
|
||||
var allowedIPs: [IPAddressRange] = []
|
||||
var allowedIPs = [IPAddressRange]()
|
||||
for allowedIPString in allowedIPsString.split(separator: ",") {
|
||||
let trimmedString = allowedIPString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
guard let allowedIP = IPAddressRange(from: trimmedString) else { return nil }
|
||||
|
|
|
@ -37,8 +37,8 @@ class TunnelViewModel {
|
|||
static let keyLengthInBase64 = 44
|
||||
|
||||
class InterfaceData {
|
||||
var scratchpad: [InterfaceField: String] = [:]
|
||||
var fieldsWithError: Set<InterfaceField> = []
|
||||
var scratchpad = [InterfaceField: String]()
|
||||
var fieldsWithError = Set<InterfaceField>()
|
||||
var validatedConfiguration: InterfaceConfiguration?
|
||||
|
||||
subscript(field: InterfaceField) -> String {
|
||||
|
@ -114,9 +114,9 @@ class TunnelViewModel {
|
|||
return .error("Interface's private key must be a 32-byte key in base64 encoding")
|
||||
}
|
||||
var config = InterfaceConfiguration(name: name, privateKey: privateKey)
|
||||
var errorMessages: [String] = []
|
||||
var errorMessages = [String]()
|
||||
if let addressesString = scratchpad[.addresses] {
|
||||
var addresses: [IPAddressRange] = []
|
||||
var addresses = [IPAddressRange]()
|
||||
for addressString in addressesString.split(separator: ",") {
|
||||
let trimmedString = addressString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
if let address = IPAddressRange(from: trimmedString) {
|
||||
|
@ -145,7 +145,7 @@ class TunnelViewModel {
|
|||
}
|
||||
}
|
||||
if let dnsString = scratchpad[.dns] {
|
||||
var dnsServers: [DNSServer] = []
|
||||
var dnsServers = [DNSServer]()
|
||||
for dnsServerString in dnsString.split(separator: ",") {
|
||||
let trimmedString = dnsServerString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
if let dnsServer = DNSServer(from: trimmedString) {
|
||||
|
@ -177,14 +177,14 @@ class TunnelViewModel {
|
|||
|
||||
class PeerData {
|
||||
var index: Int
|
||||
var scratchpad: [PeerField: String] = [:]
|
||||
var fieldsWithError: Set<PeerField> = []
|
||||
var scratchpad = [PeerField: String]()
|
||||
var fieldsWithError = Set<PeerField>()
|
||||
var validatedConfiguration: PeerConfiguration?
|
||||
|
||||
// For exclude private IPs
|
||||
var shouldAllowExcludePrivateIPsControl: Bool = false /* Read-only from the VC's point of view */
|
||||
var excludePrivateIPsValue: Bool = false /* Read-only from the VC's point of view */
|
||||
fileprivate var numberOfPeers: Int = 0
|
||||
private(set) var shouldAllowExcludePrivateIPsControl = false
|
||||
private(set) var excludePrivateIPsValue = false
|
||||
fileprivate var numberOfPeers = 0
|
||||
|
||||
init(index: Int) {
|
||||
self.index = index
|
||||
|
@ -251,7 +251,7 @@ class TunnelViewModel {
|
|||
return .error("Peer's public key must be a 32-byte key in base64 encoding")
|
||||
}
|
||||
var config = PeerConfiguration(publicKey: publicKey)
|
||||
var errorMessages: [String] = []
|
||||
var errorMessages = [String]()
|
||||
if let preSharedKeyString = scratchpad[.preSharedKey] {
|
||||
if let preSharedKey = Data(base64Encoded: preSharedKeyString), preSharedKey.count == TunnelConfiguration.keyLength {
|
||||
config.preSharedKey = preSharedKey
|
||||
|
@ -261,7 +261,7 @@ class TunnelViewModel {
|
|||
}
|
||||
}
|
||||
if let allowedIPsString = scratchpad[.allowedIPs] {
|
||||
var allowedIPs: [IPAddressRange] = []
|
||||
var allowedIPs = [IPAddressRange]()
|
||||
for allowedIPString in allowedIPsString.split(separator: ",") {
|
||||
let trimmedString = allowedIPString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
if let allowedIP = IPAddressRange(from: trimmedString) {
|
||||
|
@ -352,11 +352,9 @@ class TunnelViewModel {
|
|||
let ipv6Addresses = allowedIPStrings.filter { $0.contains(":") }
|
||||
let modifiedAllowedIPStrings: [String]
|
||||
if isOn {
|
||||
modifiedAllowedIPStrings = ipv6Addresses +
|
||||
TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String + dnsServerStrings
|
||||
modifiedAllowedIPStrings = ipv6Addresses + TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String + dnsServerStrings
|
||||
} else {
|
||||
modifiedAllowedIPStrings = ipv6Addresses +
|
||||
[TunnelViewModel.PeerData.ipv4DefaultRouteString]
|
||||
modifiedAllowedIPStrings = ipv6Addresses + [TunnelViewModel.PeerData.ipv4DefaultRouteString]
|
||||
}
|
||||
scratchpad[.allowedIPs] = modifiedAllowedIPStrings.joined(separator: ", ")
|
||||
validatedConfiguration = nil // The configuration has been modified, and needs to be saved
|
||||
|
@ -374,7 +372,7 @@ class TunnelViewModel {
|
|||
|
||||
init(tunnelConfiguration: TunnelConfiguration?) {
|
||||
let interfaceData: InterfaceData = InterfaceData()
|
||||
var peersData: [PeerData] = []
|
||||
var peersData = [PeerData]()
|
||||
if let tunnelConfiguration = tunnelConfiguration {
|
||||
interfaceData.validatedConfiguration = tunnelConfiguration.interface
|
||||
for (index, peerConfiguration) in tunnelConfiguration.peers.enumerated() {
|
||||
|
@ -423,7 +421,7 @@ class TunnelViewModel {
|
|||
case .error(let errorMessage):
|
||||
return .error(errorMessage)
|
||||
case .saved(let interfaceConfiguration):
|
||||
var peerConfigurations: [PeerConfiguration] = []
|
||||
var peerConfigurations = [PeerConfiguration]()
|
||||
peerConfigurations.reserveCapacity(peerSaveResults.count)
|
||||
for peerSaveResult in peerSaveResults {
|
||||
switch peerSaveResult {
|
||||
|
|
|
@ -5,10 +5,10 @@ import UIKit
|
|||
import os.log
|
||||
|
||||
class ErrorPresenter {
|
||||
static func showErrorAlert(error: WireGuardAppError, from sourceVC: UIViewController?,
|
||||
onPresented: (() -> Void)? = nil, onDismissal: (() -> Void)? = nil) {
|
||||
static func showErrorAlert(error: WireGuardAppError, from sourceVC: UIViewController?, onPresented: (() -> Void)? = nil, onDismissal: (() -> Void)? = nil) {
|
||||
guard let sourceVC = sourceVC else { return }
|
||||
guard let (title, message) = error.alertText() else { return }
|
||||
|
||||
let (title, message) = error.alertText()
|
||||
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
|
||||
onDismissal?()
|
||||
}
|
||||
|
@ -18,9 +18,9 @@ class ErrorPresenter {
|
|||
sourceVC.present(alert, animated: true, completion: onPresented)
|
||||
}
|
||||
|
||||
static func showErrorAlert(title: String, message: String, from sourceVC: UIViewController?,
|
||||
onPresented: (() -> Void)? = nil, onDismissal: (() -> Void)? = nil) {
|
||||
static func showErrorAlert(title: String, message: String, from sourceVC: UIViewController?, onPresented: (() -> Void)? = nil, onDismissal: (() -> Void)? = nil) {
|
||||
guard let sourceVC = sourceVC else { return }
|
||||
|
||||
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
|
||||
onDismissal?()
|
||||
}
|
||||
|
|
|
@ -322,9 +322,9 @@ private class KeyValueCell: CopyableLabelTableViewCell {
|
|||
let keyLabel: UILabel
|
||||
let valueLabel: ScrollableLabel
|
||||
|
||||
var isStackedHorizontally: Bool = false
|
||||
var isStackedVertically: Bool = false
|
||||
var contentSizeBasedConstraints: [NSLayoutConstraint] = []
|
||||
var isStackedHorizontally = false
|
||||
var isStackedVertically = false
|
||||
var contentSizeBasedConstraints = [NSLayoutConstraint]()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
keyLabel = UILabel()
|
||||
|
@ -364,7 +364,7 @@ private class KeyValueCell: CopyableLabelTableViewCell {
|
|||
}
|
||||
|
||||
func configureForContentSize() {
|
||||
var constraints: [NSLayoutConstraint] = []
|
||||
var constraints = [NSLayoutConstraint]()
|
||||
if self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
|
||||
// Stack vertically
|
||||
if !isStackedVertically {
|
||||
|
|
|
@ -334,52 +334,45 @@ extension TunnelEditTableViewController {
|
|||
switch field {
|
||||
case .publicKey:
|
||||
cell.placeholderText = "Required"
|
||||
case .preSharedKey, .endpoint, .allowedIPs:
|
||||
cell.keyboardType = .default
|
||||
case .preSharedKey, .endpoint:
|
||||
cell.placeholderText = "Optional"
|
||||
cell.keyboardType = .default
|
||||
case .allowedIPs:
|
||||
cell.placeholderText = "Optional"
|
||||
cell.keyboardType = .numbersAndPunctuation
|
||||
case .persistentKeepAlive:
|
||||
cell.placeholderText = "Off"
|
||||
case .excludePrivateIPs, .deletePeer:
|
||||
break
|
||||
}
|
||||
|
||||
switch field {
|
||||
case .persistentKeepAlive:
|
||||
cell.keyboardType = .numberPad
|
||||
case .allowedIPs:
|
||||
cell.keyboardType = .numbersAndPunctuation
|
||||
default:
|
||||
case .excludePrivateIPs, .deletePeer:
|
||||
cell.keyboardType = .default
|
||||
}
|
||||
|
||||
// Show erroring fields
|
||||
cell.isValueValid = (!peerData.fieldsWithError.contains(field))
|
||||
// Bind values to view model
|
||||
cell.isValueValid = !peerData.fieldsWithError.contains(field)
|
||||
cell.value = peerData[field]
|
||||
if field != .allowedIPs {
|
||||
cell.onValueChanged = { [weak peerData] value in
|
||||
peerData?[field] = value
|
||||
}
|
||||
}
|
||||
// Compute state of exclude private IPs live
|
||||
|
||||
if field == .allowedIPs {
|
||||
cell.onValueBeingEdited = { [weak self, weak peerData] value in
|
||||
if let peerData = peerData, let self = self {
|
||||
let oldValue = peerData.shouldAllowExcludePrivateIPsControl
|
||||
peerData[.allowedIPs] = value
|
||||
if oldValue != peerData.shouldAllowExcludePrivateIPsControl {
|
||||
if let row = self.peerFields.firstIndex(of: .excludePrivateIPs) {
|
||||
if peerData.shouldAllowExcludePrivateIPsControl {
|
||||
self.tableView.insertRows(at: [IndexPath(row: row, section: indexPath.section)], with: .fade)
|
||||
} else {
|
||||
self.tableView.deleteRows(at: [IndexPath(row: row, section: indexPath.section)], with: .fade)
|
||||
}
|
||||
guard let self = self, let peerData = peerData else { return }
|
||||
|
||||
let oldValue = peerData.shouldAllowExcludePrivateIPsControl
|
||||
peerData[.allowedIPs] = value
|
||||
if oldValue != peerData.shouldAllowExcludePrivateIPsControl {
|
||||
if let row = self.peerFields.firstIndex(of: .excludePrivateIPs) {
|
||||
if peerData.shouldAllowExcludePrivateIPsControl {
|
||||
self.tableView.insertRows(at: [IndexPath(row: row, section: indexPath.section)], with: .fade)
|
||||
} else {
|
||||
self.tableView.deleteRows(at: [IndexPath(row: row, section: indexPath.section)], with: .fade)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cell.onValueBeingEdited = nil
|
||||
cell.onValueChanged = { [weak peerData] value in
|
||||
peerData?[field] = value
|
||||
}
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
|
@ -410,7 +403,7 @@ extension TunnelEditTableViewController {
|
|||
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) }
|
||||
let indexPaths = (1 ..< 4).map { IndexPath(row: $0, section: indexPath.section) }
|
||||
if isOn {
|
||||
self.activateOnDemandSetting.isActivateOnDemandEnabled = true
|
||||
if self.activateOnDemandSetting.activateOnDemandOption == .none {
|
||||
|
@ -529,7 +522,7 @@ private class KeyValueCell: UITableViewCell {
|
|||
|
||||
var isStackedHorizontally: Bool = false
|
||||
var isStackedVertically: Bool = false
|
||||
var contentSizeBasedConstraints: [NSLayoutConstraint] = []
|
||||
var contentSizeBasedConstraints = [NSLayoutConstraint]()
|
||||
|
||||
private var textFieldValueOnBeginEditing: String = ""
|
||||
|
||||
|
@ -573,7 +566,7 @@ private class KeyValueCell: UITableViewCell {
|
|||
}
|
||||
|
||||
func configureForContentSize() {
|
||||
var constraints: [NSLayoutConstraint] = []
|
||||
var constraints = [NSLayoutConstraint]()
|
||||
if self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
|
||||
// Stack vertically
|
||||
if !isStackedVertically {
|
||||
|
|
|
@ -173,7 +173,7 @@ class TunnelsListTableViewController: UIViewController {
|
|||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||
return
|
||||
}
|
||||
let configs: [TunnelConfiguration?] = result.value!
|
||||
let configs = result.value!
|
||||
tunnelsManager.addMultiple(tunnelConfigurations: configs.compactMap { $0 }) { [weak self] numberSuccessful in
|
||||
if numberSuccessful == configs.count {
|
||||
completionHandler?()
|
||||
|
|
|
@ -32,7 +32,8 @@ enum TunnelsManagerError: WireGuardAppError {
|
|||
case tunnelActivationFailedInternalError // startTunnel() succeeded, but activation failed
|
||||
case tunnelActivationFailedNoInternetConnection // startTunnel() succeeded, but activation failed since no internet
|
||||
|
||||
func alertText() -> (String, String)? {
|
||||
//swiftlint:disable:next cyclomatic_complexity
|
||||
func alertText() -> AlertText {
|
||||
switch self {
|
||||
case .tunnelNameEmpty:
|
||||
return ("No name provided", "Can't create tunnel with an empty name")
|
||||
|
@ -46,7 +47,6 @@ enum TunnelsManagerError: WireGuardAppError {
|
|||
return ("Unable to modify tunnel", "Internal error")
|
||||
case .vpnSystemErrorOnRemoveTunnel:
|
||||
return ("Unable to remove tunnel", "Internal error")
|
||||
|
||||
case .attemptingActivationWhenTunnelIsNotInactive:
|
||||
return ("Activation failure", "The tunnel is already active or in the process of being activated")
|
||||
case .attemptingActivationWhenAnotherTunnelIsOperational(let otherTunnelName):
|
||||
|
@ -267,44 +267,41 @@ class TunnelsManager {
|
|||
}
|
||||
|
||||
private func startObservingTunnelStatuses() {
|
||||
if statusObservationToken != nil { return }
|
||||
statusObservationToken = NotificationCenter.default.addObserver(
|
||||
forName: .NEVPNStatusDidChange,
|
||||
object: nil,
|
||||
queue: OperationQueue.main) { [weak self] statusChangeNotification in
|
||||
guard let self = self else { return }
|
||||
guard let session = statusChangeNotification.object as? NETunnelProviderSession else { return }
|
||||
guard let tunnelProvider = session.manager as? NETunnelProviderManager else { return }
|
||||
guard let tunnel = self.tunnels.first(where: { $0.tunnelProvider == tunnelProvider }) else { return }
|
||||
|
||||
os_log("Tunnel '%{public}@' connection status changed to '%{public}@'",
|
||||
log: OSLog.default, type: .debug, tunnel.name, "\(tunnel.tunnelProvider.connection.status)")
|
||||
|
||||
// In case our attempt to start the tunnel, didn't succeed
|
||||
if tunnel == self.tunnelBeingActivated {
|
||||
if session.status == .disconnected {
|
||||
if InternetReachability.currentStatus() == .notReachable {
|
||||
let error = TunnelsManagerError.tunnelActivationFailedNoInternetConnection
|
||||
self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: error)
|
||||
}
|
||||
self.tunnelBeingActivated = nil
|
||||
} else if session.status == .connected {
|
||||
self.tunnelBeingActivated = nil
|
||||
guard statusObservationToken == nil else { return }
|
||||
|
||||
statusObservationToken = NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: nil, queue: OperationQueue.main) { [weak self] statusChangeNotification in
|
||||
guard let self = self else { return }
|
||||
guard let session = statusChangeNotification.object as? NETunnelProviderSession else { return }
|
||||
guard let tunnelProvider = session.manager as? NETunnelProviderManager else { return }
|
||||
guard let tunnel = self.tunnels.first(where: { $0.tunnelProvider == tunnelProvider }) else { return }
|
||||
|
||||
os_log("Tunnel '%{public}@' connection status changed to '%{public}@'",
|
||||
log: OSLog.default, type: .debug, tunnel.name, "\(tunnel.tunnelProvider.connection.status)")
|
||||
|
||||
// In case our attempt to start the tunnel, didn't succeed
|
||||
if tunnel == self.tunnelBeingActivated {
|
||||
if session.status == .disconnected {
|
||||
if InternetReachability.currentStatus() == .notReachable {
|
||||
let error = TunnelsManagerError.tunnelActivationFailedNoInternetConnection
|
||||
self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: error)
|
||||
}
|
||||
self.tunnelBeingActivated = nil
|
||||
} else if session.status == .connected {
|
||||
self.tunnelBeingActivated = nil
|
||||
}
|
||||
|
||||
// In case we're restarting the tunnel
|
||||
if (tunnel.status == .restarting) && (session.status == .disconnected || session.status == .disconnecting) {
|
||||
// Don't change tunnel.status when disconnecting for a restart
|
||||
if session.status == .disconnected {
|
||||
self.tunnelBeingActivated = tunnel
|
||||
tunnel.startActivation { _ in }
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// In case we're restarting the tunnel
|
||||
if (tunnel.status == .restarting) && (session.status == .disconnected || session.status == .disconnecting) {
|
||||
// Don't change tunnel.status when disconnecting for a restart
|
||||
if session.status == .disconnected {
|
||||
self.tunnelBeingActivated = tunnel
|
||||
tunnel.startActivation { _ in }
|
||||
}
|
||||
|
||||
// Update tunnel status
|
||||
tunnel.refreshStatus()
|
||||
return
|
||||
}
|
||||
|
||||
tunnel.refreshStatus()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
protocol WireGuardAppError: Error {
|
||||
func alertText() -> (/* title */ String, /* message */ String)?
|
||||
typealias AlertText = (title: String, message: String)
|
||||
func alertText() -> AlertText
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ enum ZipArchiveError: WireGuardAppError {
|
|||
case cantOpenOutputZipFileForWriting
|
||||
case badArchive
|
||||
|
||||
func alertText() -> (String, String)? {
|
||||
func alertText() -> AlertText {
|
||||
switch self {
|
||||
case .cantOpenInputZipFile:
|
||||
return ("Unable to read zip archive", "The zip archive could not be read.")
|
||||
|
@ -41,7 +41,7 @@ class ZipArchive {
|
|||
|
||||
static func unarchive(url: URL, requiredFileExtensions: [String]) throws -> [(fileBaseName: String, contents: Data)] {
|
||||
|
||||
var results: [(fileBaseName: String, contents: Data)] = []
|
||||
var results = [(fileBaseName: String, contents: Data)]()
|
||||
|
||||
guard let zipFile = unzOpen64(url.path) else {
|
||||
throw ZipArchiveError.cantOpenInputZipFile
|
||||
|
|
|
@ -6,7 +6,7 @@ import UIKit
|
|||
enum ZipExporterError: WireGuardAppError {
|
||||
case noTunnelsToExport
|
||||
|
||||
func alertText() -> (String, String)? {
|
||||
func alertText() -> AlertText {
|
||||
switch self {
|
||||
case .noTunnelsToExport:
|
||||
return ("Nothing to export", "There are no tunnels to export")
|
||||
|
@ -15,8 +15,7 @@ enum ZipExporterError: WireGuardAppError {
|
|||
}
|
||||
|
||||
class ZipExporter {
|
||||
static func exportConfigFiles(tunnelConfigurations: [TunnelConfiguration], to url: URL,
|
||||
completion: @escaping (WireGuardAppError?) -> Void) {
|
||||
static func exportConfigFiles(tunnelConfigurations: [TunnelConfiguration], to url: URL, completion: @escaping (WireGuardAppError?) -> Void) {
|
||||
|
||||
guard !tunnelConfigurations.isEmpty else {
|
||||
completion(ZipExporterError.noTunnelsToExport)
|
||||
|
|
|
@ -6,7 +6,7 @@ import UIKit
|
|||
enum ZipImporterError: WireGuardAppError {
|
||||
case noTunnelsInZipArchive
|
||||
|
||||
func alertText() -> (String, String)? {
|
||||
func alertText() -> AlertText {
|
||||
switch self {
|
||||
case .noTunnelsInZipArchive:
|
||||
return ("No tunnels in zip archive", "No .conf tunnel files were found inside the zip archive.")
|
||||
|
|
|
@ -42,7 +42,7 @@ class DNSResolver {
|
|||
|
||||
dispatchGroup.wait() // TODO: Timeout?
|
||||
|
||||
var hostnamesWithDnsResolutionFailure: [String] = []
|
||||
var hostnamesWithDnsResolutionFailure = [String]()
|
||||
assert(endpoints.count == resolvedEndpoints.count)
|
||||
for tuple in zip(endpoints, resolvedEndpoints) {
|
||||
let endpoint = tuple.0
|
||||
|
|
|
@ -31,8 +31,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
}
|
||||
|
||||
/// Begin the process of establishing the tunnel.
|
||||
override func startTunnel(options: [String: NSObject]?,
|
||||
completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) {
|
||||
override func startTunnel(options: [String: NSObject]?, completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) {
|
||||
|
||||
guard let tunnelProviderProtocol = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let tunnelConfiguration = tunnelProviderProtocol.tunnelConfiguration() else {
|
||||
|
@ -58,7 +57,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||
// Resolve endpoint domains
|
||||
|
||||
let endpoints = tunnelConfiguration.peers.map { $0.endpoint }
|
||||
var resolvedEndpoints: [Endpoint?] = []
|
||||
var resolvedEndpoints = [Endpoint?]()
|
||||
do {
|
||||
resolvedEndpoints = try DNSResolver.resolveSync(endpoints: endpoints)
|
||||
} catch DNSResolverError.dnsResolutionFailed(let hostnames) {
|
||||
|
|
|
@ -60,16 +60,13 @@ class PacketTunnelSettingsGenerator {
|
|||
}
|
||||
|
||||
func generateNetworkSettings() -> NEPacketTunnelNetworkSettings {
|
||||
|
||||
// Remote address
|
||||
|
||||
/* iOS requires a tunnel endpoint, whereas in WireGuard it's valid for
|
||||
* a tunnel to have no endpoint, or for there to be many endpoints, in
|
||||
* which case, displaying a single one in settings doesn't really
|
||||
* make sense. So, we fill it in with this placeholder, which is not
|
||||
* a valid IP address that will actually route over the Internet.
|
||||
*/
|
||||
var remoteAddress: String = "0.0.0.0"
|
||||
var remoteAddress = "0.0.0.0"
|
||||
let endpointsCompact = resolvedEndpoints.compactMap { $0 }
|
||||
if endpointsCompact.count == 1 {
|
||||
switch endpointsCompact.first!.host {
|
||||
|
@ -83,16 +80,12 @@ class PacketTunnelSettingsGenerator {
|
|||
}
|
||||
|
||||
let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: remoteAddress)
|
||||
|
||||
// DNS
|
||||
|
||||
|
||||
let dnsServerStrings = tunnelConfiguration.interface.dns.map { $0.stringRepresentation() }
|
||||
let dnsSettings = NEDNSSettings(servers: dnsServerStrings)
|
||||
dnsSettings.matchDomains = [""] // All DNS queries must first go through the VPN's DNS
|
||||
networkSettings.dnsSettings = dnsSettings
|
||||
|
||||
// MTU
|
||||
|
||||
|
||||
let mtu = tunnelConfiguration.interface.mtu ?? 0
|
||||
if mtu == 0 {
|
||||
// 0 imples automatic MTU, where we set overhead as 80 bytes, which is the worst case for WireGuard
|
||||
|
@ -100,104 +93,25 @@ class PacketTunnelSettingsGenerator {
|
|||
} else {
|
||||
networkSettings.mtu = NSNumber(value: mtu)
|
||||
}
|
||||
|
||||
// Addresses from interface addresses
|
||||
|
||||
var ipv4Addresses: [String] = []
|
||||
var ipv4SubnetMasks: [String] = []
|
||||
|
||||
var ipv6Addresses: [String] = []
|
||||
var ipv6NetworkPrefixLengths: [NSNumber] = []
|
||||
|
||||
for addressRange in tunnelConfiguration.interface.addresses {
|
||||
if addressRange.address is IPv4Address {
|
||||
ipv4Addresses.append("\(addressRange.address)")
|
||||
ipv4SubnetMasks.append(PacketTunnelSettingsGenerator.ipv4SubnetMaskString(of: addressRange))
|
||||
} else if addressRange.address is IPv6Address {
|
||||
ipv6Addresses.append("\(addressRange.address)")
|
||||
ipv6NetworkPrefixLengths.append(NSNumber(value: addressRange.networkPrefixLength))
|
||||
}
|
||||
}
|
||||
|
||||
// Included routes from AllowedIPs
|
||||
|
||||
var ipv4IncludedRouteAddresses: [String] = []
|
||||
var ipv4IncludedRouteSubnetMasks: [String] = []
|
||||
|
||||
var ipv6IncludedRouteAddresses: [String] = []
|
||||
var ipv6IncludedRouteNetworkPrefixLengths: [NSNumber] = []
|
||||
|
||||
for peer in tunnelConfiguration.peers {
|
||||
for addressRange in peer.allowedIPs {
|
||||
if addressRange.address is IPv4Address {
|
||||
ipv4IncludedRouteAddresses.append("\(addressRange.address)")
|
||||
ipv4IncludedRouteSubnetMasks.append(PacketTunnelSettingsGenerator.ipv4SubnetMaskString(of: addressRange))
|
||||
} else if addressRange.address is IPv6Address {
|
||||
ipv6IncludedRouteAddresses.append("\(addressRange.address)")
|
||||
ipv6IncludedRouteNetworkPrefixLengths.append(NSNumber(value: addressRange.networkPrefixLength))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Excluded routes from endpoints
|
||||
|
||||
var ipv4ExcludedRouteAddresses: [String] = []
|
||||
var ipv4ExcludedRouteSubnetMasks: [String] = []
|
||||
|
||||
var ipv6ExcludedRouteAddresses: [String] = []
|
||||
var ipv6ExcludedRouteNetworkPrefixLengths: [NSNumber] = []
|
||||
|
||||
for endpoint in resolvedEndpoints {
|
||||
guard let endpoint = endpoint else { continue }
|
||||
switch endpoint.host {
|
||||
case .ipv4(let address):
|
||||
ipv4ExcludedRouteAddresses.append("\(address)")
|
||||
ipv4ExcludedRouteSubnetMasks.append("255.255.255.255") // A single IPv4 address
|
||||
case .ipv6(let address):
|
||||
ipv6ExcludedRouteAddresses.append("\(address)")
|
||||
ipv6ExcludedRouteNetworkPrefixLengths.append(NSNumber(value: UInt8(128))) // A single IPv6 address
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
// Apply IPv4 settings
|
||||
|
||||
let ipv4Settings = NEIPv4Settings(addresses: ipv4Addresses, subnetMasks: ipv4SubnetMasks)
|
||||
assert(ipv4IncludedRouteAddresses.count == ipv4IncludedRouteSubnetMasks.count)
|
||||
ipv4Settings.includedRoutes = zip(ipv4IncludedRouteAddresses, ipv4IncludedRouteSubnetMasks).map {
|
||||
NEIPv4Route(destinationAddress: $0.0, subnetMask: $0.1)
|
||||
}
|
||||
assert(ipv4ExcludedRouteAddresses.count == ipv4ExcludedRouteSubnetMasks.count)
|
||||
ipv4Settings.excludedRoutes = zip(ipv4ExcludedRouteAddresses, ipv4ExcludedRouteSubnetMasks).map {
|
||||
NEIPv4Route(destinationAddress: $0.0, subnetMask: $0.1)
|
||||
}
|
||||
|
||||
let (ipv4Routes, ipv6Routes) = routes()
|
||||
let (ipv4IncludedRoutes, ipv6IncludedRoutes) = includedRoutes()
|
||||
let (ipv4ExcludedRoutes, ipv6ExcludedRoutes) = excludedRoutes()
|
||||
|
||||
let ipv4Settings = NEIPv4Settings(addresses: ipv4Routes.map { $0.destinationAddress }, subnetMasks: ipv4Routes.map { $0.destinationSubnetMask })
|
||||
ipv4Settings.includedRoutes = ipv4IncludedRoutes
|
||||
ipv4Settings.excludedRoutes = ipv4ExcludedRoutes
|
||||
networkSettings.ipv4Settings = ipv4Settings
|
||||
|
||||
// Apply IPv6 settings
|
||||
|
||||
/* Big fat ugly hack for broken iOS networking stack: the smallest prefix that will have
|
||||
* any effect on iOS is a /120, so we clamp everything above to /120. This is potentially
|
||||
* very bad, if various network parameters were actually relying on that subnet being
|
||||
* intentionally small. TODO: talk about this with upstream iOS devs.
|
||||
*/
|
||||
let ipv6Settings = NEIPv6Settings(addresses: ipv6Addresses, networkPrefixLengths: ipv6NetworkPrefixLengths.map { NSNumber(value: min(120, $0.intValue)) })
|
||||
assert(ipv6IncludedRouteAddresses.count == ipv6IncludedRouteNetworkPrefixLengths.count)
|
||||
ipv6Settings.includedRoutes = zip(ipv6IncludedRouteAddresses, ipv6IncludedRouteNetworkPrefixLengths).map {
|
||||
NEIPv6Route(destinationAddress: $0.0, networkPrefixLength: $0.1)
|
||||
}
|
||||
assert(ipv6ExcludedRouteAddresses.count == ipv6ExcludedRouteNetworkPrefixLengths.count)
|
||||
ipv6Settings.excludedRoutes = zip(ipv6ExcludedRouteAddresses, ipv6ExcludedRouteNetworkPrefixLengths).map {
|
||||
NEIPv6Route(destinationAddress: $0.0, networkPrefixLength: $0.1)
|
||||
}
|
||||
|
||||
let ipv6Settings = NEIPv6Settings(addresses: ipv6Routes.map { $0.destinationAddress }, networkPrefixLengths: ipv6Routes.map { $0.destinationNetworkPrefixLength })
|
||||
ipv6Settings.includedRoutes = ipv6IncludedRoutes
|
||||
ipv6Settings.excludedRoutes = ipv6ExcludedRoutes
|
||||
networkSettings.ipv6Settings = ipv6Settings
|
||||
|
||||
// Done
|
||||
|
||||
return networkSettings
|
||||
}
|
||||
|
||||
static func ipv4SubnetMaskString(of addressRange: IPAddressRange) -> String {
|
||||
private func ipv4SubnetMaskString(of addressRange: IPAddressRange) -> String {
|
||||
let length: UInt8 = addressRange.networkPrefixLength
|
||||
assert(length <= 32)
|
||||
var octets: [UInt8] = [0, 0, 0, 0]
|
||||
|
@ -208,6 +122,57 @@ class PacketTunnelSettingsGenerator {
|
|||
octets[3] = UInt8(truncatingIfNeeded: subnetMask)
|
||||
return octets.map { String($0) }.joined(separator: ".")
|
||||
}
|
||||
|
||||
private func routes() -> ([NEIPv4Route], [NEIPv6Route]) {
|
||||
var ipv4Routes = [NEIPv4Route]()
|
||||
var ipv6Routes = [NEIPv6Route]()
|
||||
for addressRange in tunnelConfiguration.interface.addresses {
|
||||
if addressRange.address is IPv4Address {
|
||||
ipv4Routes.append(NEIPv4Route(destinationAddress: "\(addressRange.address)", subnetMask: ipv4SubnetMaskString(of: addressRange)))
|
||||
} else if addressRange.address is IPv6Address {
|
||||
/* Big fat ugly hack for broken iOS networking stack: the smallest prefix that will have
|
||||
* any effect on iOS is a /120, so we clamp everything above to /120. This is potentially
|
||||
* very bad, if various network parameters were actually relying on that subnet being
|
||||
* intentionally small. TODO: talk about this with upstream iOS devs.
|
||||
*/
|
||||
ipv6Routes.append(NEIPv6Route(destinationAddress: "\(addressRange.address)", networkPrefixLength: NSNumber(value: min(120, addressRange.networkPrefixLength))))
|
||||
}
|
||||
}
|
||||
return (ipv4Routes, ipv6Routes)
|
||||
}
|
||||
|
||||
private func includedRoutes() -> ([NEIPv4Route], [NEIPv6Route]) {
|
||||
var ipv4IncludedRoutes = [NEIPv4Route]()
|
||||
var ipv6IncludedRoutes = [NEIPv6Route]()
|
||||
for peer in tunnelConfiguration.peers {
|
||||
for addressRange in peer.allowedIPs {
|
||||
if addressRange.address is IPv4Address {
|
||||
ipv4IncludedRoutes.append(NEIPv4Route(destinationAddress: "\(addressRange.address)", subnetMask: ipv4SubnetMaskString(of: addressRange)))
|
||||
} else if addressRange.address is IPv6Address {
|
||||
ipv6IncludedRoutes.append(NEIPv6Route(destinationAddress: "\(addressRange.address)", networkPrefixLength: NSNumber(value: addressRange.networkPrefixLength)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return (ipv4IncludedRoutes, ipv6IncludedRoutes)
|
||||
}
|
||||
|
||||
private func excludedRoutes() -> ([NEIPv4Route], [NEIPv6Route]) {
|
||||
var ipv4ExcludedRoutes = [NEIPv4Route]()
|
||||
var ipv6ExcludedRoutes = [NEIPv6Route]()
|
||||
for endpoint in resolvedEndpoints {
|
||||
guard let endpoint = endpoint else { continue }
|
||||
switch endpoint.host {
|
||||
case .ipv4(let address):
|
||||
ipv4ExcludedRoutes.append(NEIPv4Route(destinationAddress: "\(address)", subnetMask: "255.255.255.255"))
|
||||
case .ipv6(let address):
|
||||
ipv6ExcludedRoutes.append(NEIPv6Route(destinationAddress: "\(address)", networkPrefixLength: NSNumber(value: UInt8(128))))
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
return (ipv4ExcludedRoutes, ipv6ExcludedRoutes)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension Data {
|
||||
|
|
Loading…
Reference in New Issue