From 42aeb8636c7514f3b4adac6d0f8a3190bd59caef Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Wed, 12 Dec 2018 21:09:52 -0600 Subject: [PATCH] More formatting nits and cyclomatic complexity fixes --- WireGuard/.swiftlint.yml | 1 + WireGuard/Shared/Model/Configuration.swift | 6 +- WireGuard/Shared/Model/IPAddressRange.swift | 4 +- .../ConfigFile/WgQuickConfigFileParser.swift | 14 +- WireGuard/WireGuard/UI/TunnelViewModel.swift | 34 ++-- .../WireGuard/UI/iOS/ErrorPresenter.swift | 10 +- .../iOS/TunnelDetailTableViewController.swift | 8 +- .../iOS/TunnelEditTableViewController.swift | 59 +++--- .../iOS/TunnelsListTableViewController.swift | 2 +- WireGuard/WireGuard/VPN/TunnelsManager.swift | 71 ++++---- WireGuard/WireGuard/WireGuardAppError.swift | 3 +- .../WireGuard/ZipArchive/ZipArchive.swift | 4 +- .../WireGuard/ZipArchive/ZipExporter.swift | 5 +- .../WireGuard/ZipArchive/ZipImporter.swift | 2 +- .../DNSResolver.swift | 2 +- .../PacketTunnelProvider.swift | 5 +- .../PacketTunnelSettingsGenerator.swift | 169 +++++++----------- 17 files changed, 174 insertions(+), 225 deletions(-) diff --git a/WireGuard/.swiftlint.yml b/WireGuard/.swiftlint.yml index df6266a..2c414b6 100644 --- a/WireGuard/.swiftlint.yml +++ b/WireGuard/.swiftlint.yml @@ -1,6 +1,7 @@ disabled_rules: - line_length - trailing_whitespace + - todo opt_in_rules: - unneeded_parentheses_in_closure_argument # - trailing_closure diff --git a/WireGuard/Shared/Model/Configuration.swift b/WireGuard/Shared/Model/Configuration.swift index 09fdd6b..77dfe97 100644 --- a/WireGuard/Shared/Model/Configuration.swift +++ b/WireGuard/Shared/Model/Configuration.swift @@ -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? diff --git a/WireGuard/Shared/Model/IPAddressRange.swift b/WireGuard/Shared/Model/IPAddressRange.swift index e3c1441..468e3aa 100644 --- a/WireGuard/Shared/Model/IPAddressRange.swift +++ b/WireGuard/Shared/Model/IPAddressRange.swift @@ -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 { diff --git a/WireGuard/WireGuard/ConfigFile/WgQuickConfigFileParser.swift b/WireGuard/WireGuard/ConfigFile/WgQuickConfigFileParser.swift index 138ca62..14a6dfb 100644 --- a/WireGuard/WireGuard/ConfigFile/WgQuickConfigFileParser.swift +++ b/WireGuard/WireGuard/ConfigFile/WgQuickConfigFileParser.swift @@ -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 } diff --git a/WireGuard/WireGuard/UI/TunnelViewModel.swift b/WireGuard/WireGuard/UI/TunnelViewModel.swift index af7ff4c..80a6092 100644 --- a/WireGuard/WireGuard/UI/TunnelViewModel.swift +++ b/WireGuard/WireGuard/UI/TunnelViewModel.swift @@ -37,8 +37,8 @@ class TunnelViewModel { static let keyLengthInBase64 = 44 class InterfaceData { - var scratchpad: [InterfaceField: String] = [:] - var fieldsWithError: Set = [] + var scratchpad = [InterfaceField: String]() + var fieldsWithError = Set() 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 = [] + var scratchpad = [PeerField: String]() + var fieldsWithError = Set() 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 { diff --git a/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift b/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift index 7c28495..2889694 100644 --- a/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift +++ b/WireGuard/WireGuard/UI/iOS/ErrorPresenter.swift @@ -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?() } diff --git a/WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift b/WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift index e6ad024..c5816e8 100644 --- a/WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift @@ -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 { diff --git a/WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift b/WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift index b26992d..7cd96a2 100644 --- a/WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/TunnelEditTableViewController.swift @@ -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 { diff --git a/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift b/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift index e5d5af1..f24b452 100644 --- a/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift @@ -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?() diff --git a/WireGuard/WireGuard/VPN/TunnelsManager.swift b/WireGuard/WireGuard/VPN/TunnelsManager.swift index 9716eaf..9d48cdb 100644 --- a/WireGuard/WireGuard/VPN/TunnelsManager.swift +++ b/WireGuard/WireGuard/VPN/TunnelsManager.swift @@ -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() } } diff --git a/WireGuard/WireGuard/WireGuardAppError.swift b/WireGuard/WireGuard/WireGuardAppError.swift index 3e4707d..ba83ac5 100644 --- a/WireGuard/WireGuard/WireGuardAppError.swift +++ b/WireGuard/WireGuard/WireGuardAppError.swift @@ -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 } diff --git a/WireGuard/WireGuard/ZipArchive/ZipArchive.swift b/WireGuard/WireGuard/ZipArchive/ZipArchive.swift index 12cfb69..ad74d0e 100644 --- a/WireGuard/WireGuard/ZipArchive/ZipArchive.swift +++ b/WireGuard/WireGuard/ZipArchive/ZipArchive.swift @@ -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 diff --git a/WireGuard/WireGuard/ZipArchive/ZipExporter.swift b/WireGuard/WireGuard/ZipArchive/ZipExporter.swift index b0e6b15..cdc9ac9 100644 --- a/WireGuard/WireGuard/ZipArchive/ZipExporter.swift +++ b/WireGuard/WireGuard/ZipArchive/ZipExporter.swift @@ -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) diff --git a/WireGuard/WireGuard/ZipArchive/ZipImporter.swift b/WireGuard/WireGuard/ZipArchive/ZipImporter.swift index e2767f2..523614b 100644 --- a/WireGuard/WireGuard/ZipArchive/ZipImporter.swift +++ b/WireGuard/WireGuard/ZipArchive/ZipImporter.swift @@ -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.") diff --git a/WireGuard/WireGuardNetworkExtension/DNSResolver.swift b/WireGuard/WireGuardNetworkExtension/DNSResolver.swift index 57093c8..9f85743 100644 --- a/WireGuard/WireGuardNetworkExtension/DNSResolver.swift +++ b/WireGuard/WireGuardNetworkExtension/DNSResolver.swift @@ -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 diff --git a/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift b/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift index b1571d5..5cac333 100644 --- a/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift +++ b/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift @@ -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) { diff --git a/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift b/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift index 1e5ae8e..d97699d 100644 --- a/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift +++ b/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift @@ -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 {