More formatting nits and cyclomatic complexity fixes

Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
This commit is contained in:
Eric Kuck 2018-12-12 21:09:52 -06:00
parent e4ac48bc75
commit 8a916beb38
17 changed files with 174 additions and 225 deletions

View File

@ -1,6 +1,7 @@
disabled_rules: disabled_rules:
- line_length - line_length
- trailing_whitespace - trailing_whitespace
- todo
opt_in_rules: opt_in_rules:
- unneeded_parentheses_in_closure_argument - unneeded_parentheses_in_closure_argument
# - trailing_closure # - trailing_closure

View File

@ -26,10 +26,10 @@ final class TunnelConfiguration: Codable {
struct InterfaceConfiguration: Codable { struct InterfaceConfiguration: Codable {
var name: String var name: String
var privateKey: Data var privateKey: Data
var addresses: [IPAddressRange] = [] var addresses = [IPAddressRange]()
var listenPort: UInt16? var listenPort: UInt16?
var mtu: UInt16? var mtu: UInt16?
var dns: [DNSServer] = [] var dns = [DNSServer]()
init(name: String, privateKey: Data) { init(name: String, privateKey: Data) {
self.name = name self.name = name
@ -55,7 +55,7 @@ struct PeerConfiguration: Codable {
} }
} }
} }
var allowedIPs: [IPAddressRange] = [] var allowedIPs = [IPAddressRange]()
var endpoint: Endpoint? var endpoint: Endpoint?
var persistentKeepAlive: UInt16? var persistentKeepAlive: UInt16?

View File

@ -75,9 +75,7 @@ extension IPAddressRange: Codable {
default: return nil default: return nil
} }
}() }()
guard let ipAddress = ipAddressFromData else { guard let ipAddress = ipAddressFromData else { throw DecodingError.invalidData }
throw DecodingError.invalidData
}
address = ipAddress address = ipAddress
} }
enum DecodingError: Error { enum DecodingError: Error {

View File

@ -54,13 +54,11 @@ class WgQuickConfigFileParser {
} else { } else {
attributes[key] = value attributes[key] = value
} }
} else { } else if lowercasedLine != "[interface]" && lowercasedLine != "[peer]" {
if lowercasedLine != "[interface]" && lowercasedLine != "[peer]" { throw ParseError.invalidLine(line)
throw ParseError.invalidLine(line)
}
} }
let isLastLine = (lineIndex == lines.count - 1) let isLastLine = lineIndex == lines.count - 1
if isLastLine || lowercasedLine == "[interface]" || lowercasedLine == "[peer]" { if isLastLine || lowercasedLine == "[interface]" || lowercasedLine == "[peer]" {
// Previous section has ended; process the attributes collected so far // Previous section has ended; process the attributes collected so far
@ -109,7 +107,7 @@ class WgQuickConfigFileParser {
} }
// wg-quick fields // wg-quick fields
if let addressesString = attributes["address"] { if let addressesString = attributes["address"] {
var addresses: [IPAddressRange] = [] var addresses = [IPAddressRange]()
for addressString in addressesString.split(separator: ",") { for addressString in addressesString.split(separator: ",") {
let trimmedString = addressString.trimmingCharacters(in: .whitespaces) let trimmedString = addressString.trimmingCharacters(in: .whitespaces)
guard let address = IPAddressRange(from: trimmedString) else { return nil } guard let address = IPAddressRange(from: trimmedString) else { return nil }
@ -118,7 +116,7 @@ class WgQuickConfigFileParser {
interface.addresses = addresses interface.addresses = addresses
} }
if let dnsString = attributes["dns"] { if let dnsString = attributes["dns"] {
var dnsServers: [DNSServer] = [] var dnsServers = [DNSServer]()
for dnsServerString in dnsString.split(separator: ",") { for dnsServerString in dnsString.split(separator: ",") {
let trimmedString = dnsServerString.trimmingCharacters(in: .whitespaces) let trimmedString = dnsServerString.trimmingCharacters(in: .whitespaces)
guard let dnsServer = DNSServer(from: trimmedString) else { return nil } guard let dnsServer = DNSServer(from: trimmedString) else { return nil }
@ -144,7 +142,7 @@ class WgQuickConfigFileParser {
peer.preSharedKey = preSharedKey peer.preSharedKey = preSharedKey
} }
if let allowedIPsString = attributes["allowedips"] { if let allowedIPsString = attributes["allowedips"] {
var allowedIPs: [IPAddressRange] = [] var allowedIPs = [IPAddressRange]()
for allowedIPString in allowedIPsString.split(separator: ",") { for allowedIPString in allowedIPsString.split(separator: ",") {
let trimmedString = allowedIPString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) let trimmedString = allowedIPString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
guard let allowedIP = IPAddressRange(from: trimmedString) else { return nil } guard let allowedIP = IPAddressRange(from: trimmedString) else { return nil }

View File

@ -37,8 +37,8 @@ class TunnelViewModel {
static let keyLengthInBase64 = 44 static let keyLengthInBase64 = 44
class InterfaceData { class InterfaceData {
var scratchpad: [InterfaceField: String] = [:] var scratchpad = [InterfaceField: String]()
var fieldsWithError: Set<InterfaceField> = [] var fieldsWithError = Set<InterfaceField>()
var validatedConfiguration: InterfaceConfiguration? var validatedConfiguration: InterfaceConfiguration?
subscript(field: InterfaceField) -> String { 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") return .error("Interface's private key must be a 32-byte key in base64 encoding")
} }
var config = InterfaceConfiguration(name: name, privateKey: privateKey) var config = InterfaceConfiguration(name: name, privateKey: privateKey)
var errorMessages: [String] = [] var errorMessages = [String]()
if let addressesString = scratchpad[.addresses] { if let addressesString = scratchpad[.addresses] {
var addresses: [IPAddressRange] = [] var addresses = [IPAddressRange]()
for addressString in addressesString.split(separator: ",") { for addressString in addressesString.split(separator: ",") {
let trimmedString = addressString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) let trimmedString = addressString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if let address = IPAddressRange(from: trimmedString) { if let address = IPAddressRange(from: trimmedString) {
@ -145,7 +145,7 @@ class TunnelViewModel {
} }
} }
if let dnsString = scratchpad[.dns] { if let dnsString = scratchpad[.dns] {
var dnsServers: [DNSServer] = [] var dnsServers = [DNSServer]()
for dnsServerString in dnsString.split(separator: ",") { for dnsServerString in dnsString.split(separator: ",") {
let trimmedString = dnsServerString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) let trimmedString = dnsServerString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if let dnsServer = DNSServer(from: trimmedString) { if let dnsServer = DNSServer(from: trimmedString) {
@ -177,14 +177,14 @@ class TunnelViewModel {
class PeerData { class PeerData {
var index: Int var index: Int
var scratchpad: [PeerField: String] = [:] var scratchpad = [PeerField: String]()
var fieldsWithError: Set<PeerField> = [] var fieldsWithError = Set<PeerField>()
var validatedConfiguration: PeerConfiguration? var validatedConfiguration: PeerConfiguration?
// For exclude private IPs // For exclude private IPs
var shouldAllowExcludePrivateIPsControl: Bool = false /* Read-only from the VC's point of view */ private(set) var shouldAllowExcludePrivateIPsControl = false
var excludePrivateIPsValue: Bool = false /* Read-only from the VC's point of view */ private(set) var excludePrivateIPsValue = false
fileprivate var numberOfPeers: Int = 0 fileprivate var numberOfPeers = 0
init(index: Int) { init(index: Int) {
self.index = index self.index = index
@ -251,7 +251,7 @@ class TunnelViewModel {
return .error("Peer's public key must be a 32-byte key in base64 encoding") return .error("Peer's public key must be a 32-byte key in base64 encoding")
} }
var config = PeerConfiguration(publicKey: publicKey) var config = PeerConfiguration(publicKey: publicKey)
var errorMessages: [String] = [] var errorMessages = [String]()
if let preSharedKeyString = scratchpad[.preSharedKey] { if let preSharedKeyString = scratchpad[.preSharedKey] {
if let preSharedKey = Data(base64Encoded: preSharedKeyString), preSharedKey.count == TunnelConfiguration.keyLength { if let preSharedKey = Data(base64Encoded: preSharedKeyString), preSharedKey.count == TunnelConfiguration.keyLength {
config.preSharedKey = preSharedKey config.preSharedKey = preSharedKey
@ -261,7 +261,7 @@ class TunnelViewModel {
} }
} }
if let allowedIPsString = scratchpad[.allowedIPs] { if let allowedIPsString = scratchpad[.allowedIPs] {
var allowedIPs: [IPAddressRange] = [] var allowedIPs = [IPAddressRange]()
for allowedIPString in allowedIPsString.split(separator: ",") { for allowedIPString in allowedIPsString.split(separator: ",") {
let trimmedString = allowedIPString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) let trimmedString = allowedIPString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if let allowedIP = IPAddressRange(from: trimmedString) { if let allowedIP = IPAddressRange(from: trimmedString) {
@ -352,11 +352,9 @@ class TunnelViewModel {
let ipv6Addresses = allowedIPStrings.filter { $0.contains(":") } let ipv6Addresses = allowedIPStrings.filter { $0.contains(":") }
let modifiedAllowedIPStrings: [String] let modifiedAllowedIPStrings: [String]
if isOn { if isOn {
modifiedAllowedIPStrings = ipv6Addresses + modifiedAllowedIPStrings = ipv6Addresses + TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String + dnsServerStrings
TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String + dnsServerStrings
} else { } else {
modifiedAllowedIPStrings = ipv6Addresses + modifiedAllowedIPStrings = ipv6Addresses + [TunnelViewModel.PeerData.ipv4DefaultRouteString]
[TunnelViewModel.PeerData.ipv4DefaultRouteString]
} }
scratchpad[.allowedIPs] = modifiedAllowedIPStrings.joined(separator: ", ") scratchpad[.allowedIPs] = modifiedAllowedIPStrings.joined(separator: ", ")
validatedConfiguration = nil // The configuration has been modified, and needs to be saved validatedConfiguration = nil // The configuration has been modified, and needs to be saved
@ -374,7 +372,7 @@ class TunnelViewModel {
init(tunnelConfiguration: TunnelConfiguration?) { init(tunnelConfiguration: TunnelConfiguration?) {
let interfaceData: InterfaceData = InterfaceData() let interfaceData: InterfaceData = InterfaceData()
var peersData: [PeerData] = [] var peersData = [PeerData]()
if let tunnelConfiguration = tunnelConfiguration { if let tunnelConfiguration = tunnelConfiguration {
interfaceData.validatedConfiguration = tunnelConfiguration.interface interfaceData.validatedConfiguration = tunnelConfiguration.interface
for (index, peerConfiguration) in tunnelConfiguration.peers.enumerated() { for (index, peerConfiguration) in tunnelConfiguration.peers.enumerated() {
@ -423,7 +421,7 @@ class TunnelViewModel {
case .error(let errorMessage): case .error(let errorMessage):
return .error(errorMessage) return .error(errorMessage)
case .saved(let interfaceConfiguration): case .saved(let interfaceConfiguration):
var peerConfigurations: [PeerConfiguration] = [] var peerConfigurations = [PeerConfiguration]()
peerConfigurations.reserveCapacity(peerSaveResults.count) peerConfigurations.reserveCapacity(peerSaveResults.count)
for peerSaveResult in peerSaveResults { for peerSaveResult in peerSaveResults {
switch peerSaveResult { switch peerSaveResult {

View File

@ -5,10 +5,10 @@ import UIKit
import os.log import os.log
class ErrorPresenter { class ErrorPresenter {
static func showErrorAlert(error: WireGuardAppError, from sourceVC: UIViewController?, static func showErrorAlert(error: WireGuardAppError, from sourceVC: UIViewController?, onPresented: (() -> Void)? = nil, onDismissal: (() -> Void)? = nil) {
onPresented: (() -> Void)? = nil, onDismissal: (() -> Void)? = nil) {
guard let sourceVC = sourceVC else { return } 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 let okAction = UIAlertAction(title: "OK", style: .default) { _ in
onDismissal?() onDismissal?()
} }
@ -18,9 +18,9 @@ class ErrorPresenter {
sourceVC.present(alert, animated: true, completion: onPresented) sourceVC.present(alert, animated: true, completion: onPresented)
} }
static func showErrorAlert(title: String, message: String, from sourceVC: UIViewController?, static func showErrorAlert(title: String, message: String, from sourceVC: UIViewController?, onPresented: (() -> Void)? = nil, onDismissal: (() -> Void)? = nil) {
onPresented: (() -> Void)? = nil, onDismissal: (() -> Void)? = nil) {
guard let sourceVC = sourceVC else { return } guard let sourceVC = sourceVC else { return }
let okAction = UIAlertAction(title: "OK", style: .default) { _ in let okAction = UIAlertAction(title: "OK", style: .default) { _ in
onDismissal?() onDismissal?()
} }

View File

@ -322,9 +322,9 @@ private class KeyValueCell: CopyableLabelTableViewCell {
let keyLabel: UILabel let keyLabel: UILabel
let valueLabel: ScrollableLabel let valueLabel: ScrollableLabel
var isStackedHorizontally: Bool = false var isStackedHorizontally = false
var isStackedVertically: Bool = false var isStackedVertically = false
var contentSizeBasedConstraints: [NSLayoutConstraint] = [] var contentSizeBasedConstraints = [NSLayoutConstraint]()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
keyLabel = UILabel() keyLabel = UILabel()
@ -364,7 +364,7 @@ private class KeyValueCell: CopyableLabelTableViewCell {
} }
func configureForContentSize() { func configureForContentSize() {
var constraints: [NSLayoutConstraint] = [] var constraints = [NSLayoutConstraint]()
if self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory { if self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
// Stack vertically // Stack vertically
if !isStackedVertically { if !isStackedVertically {

View File

@ -334,52 +334,45 @@ extension TunnelEditTableViewController {
switch field { switch field {
case .publicKey: case .publicKey:
cell.placeholderText = "Required" cell.placeholderText = "Required"
case .preSharedKey, .endpoint, .allowedIPs: cell.keyboardType = .default
case .preSharedKey, .endpoint:
cell.placeholderText = "Optional" cell.placeholderText = "Optional"
cell.keyboardType = .default
case .allowedIPs:
cell.placeholderText = "Optional"
cell.keyboardType = .numbersAndPunctuation
case .persistentKeepAlive: case .persistentKeepAlive:
cell.placeholderText = "Off" cell.placeholderText = "Off"
case .excludePrivateIPs, .deletePeer:
break
}
switch field {
case .persistentKeepAlive:
cell.keyboardType = .numberPad cell.keyboardType = .numberPad
case .allowedIPs: case .excludePrivateIPs, .deletePeer:
cell.keyboardType = .numbersAndPunctuation
default:
cell.keyboardType = .default cell.keyboardType = .default
} }
// Show erroring fields cell.isValueValid = !peerData.fieldsWithError.contains(field)
cell.isValueValid = (!peerData.fieldsWithError.contains(field))
// Bind values to view model
cell.value = peerData[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 { if field == .allowedIPs {
cell.onValueBeingEdited = { [weak self, weak peerData] value in cell.onValueBeingEdited = { [weak self, weak peerData] value in
if let peerData = peerData, let self = self { guard let self = self, let peerData = peerData else { return }
let oldValue = peerData.shouldAllowExcludePrivateIPsControl
peerData[.allowedIPs] = value let oldValue = peerData.shouldAllowExcludePrivateIPsControl
if oldValue != peerData.shouldAllowExcludePrivateIPsControl { peerData[.allowedIPs] = value
if let row = self.peerFields.firstIndex(of: .excludePrivateIPs) { if oldValue != peerData.shouldAllowExcludePrivateIPsControl {
if peerData.shouldAllowExcludePrivateIPsControl { if let row = self.peerFields.firstIndex(of: .excludePrivateIPs) {
self.tableView.insertRows(at: [IndexPath(row: row, section: indexPath.section)], with: .fade) if peerData.shouldAllowExcludePrivateIPsControl {
} else { self.tableView.insertRows(at: [IndexPath(row: row, section: indexPath.section)], with: .fade)
self.tableView.deleteRows(at: [IndexPath(row: row, section: indexPath.section)], with: .fade) } else {
} self.tableView.deleteRows(at: [IndexPath(row: row, section: indexPath.section)], with: .fade)
} }
} }
} }
} }
} else { } else {
cell.onValueBeingEdited = nil cell.onValueChanged = { [weak peerData] value in
peerData?[field] = value
}
} }
return cell return cell
} }
@ -410,7 +403,7 @@ extension TunnelEditTableViewController {
cell.isOn = activateOnDemandSetting.isActivateOnDemandEnabled cell.isOn = activateOnDemandSetting.isActivateOnDemandEnabled
cell.onSwitchToggled = { [weak self] isOn in cell.onSwitchToggled = { [weak self] isOn in
guard let self = self else { return } 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 { if isOn {
self.activateOnDemandSetting.isActivateOnDemandEnabled = true self.activateOnDemandSetting.isActivateOnDemandEnabled = true
if self.activateOnDemandSetting.activateOnDemandOption == .none { if self.activateOnDemandSetting.activateOnDemandOption == .none {
@ -529,7 +522,7 @@ private class KeyValueCell: UITableViewCell {
var isStackedHorizontally: Bool = false var isStackedHorizontally: Bool = false
var isStackedVertically: Bool = false var isStackedVertically: Bool = false
var contentSizeBasedConstraints: [NSLayoutConstraint] = [] var contentSizeBasedConstraints = [NSLayoutConstraint]()
private var textFieldValueOnBeginEditing: String = "" private var textFieldValueOnBeginEditing: String = ""
@ -573,7 +566,7 @@ private class KeyValueCell: UITableViewCell {
} }
func configureForContentSize() { func configureForContentSize() {
var constraints: [NSLayoutConstraint] = [] var constraints = [NSLayoutConstraint]()
if self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory { if self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
// Stack vertically // Stack vertically
if !isStackedVertically { if !isStackedVertically {

View File

@ -173,7 +173,7 @@ class TunnelsListTableViewController: UIViewController {
ErrorPresenter.showErrorAlert(error: error, from: self) ErrorPresenter.showErrorAlert(error: error, from: self)
return return
} }
let configs: [TunnelConfiguration?] = result.value! let configs = result.value!
tunnelsManager.addMultiple(tunnelConfigurations: configs.compactMap { $0 }) { [weak self] numberSuccessful in tunnelsManager.addMultiple(tunnelConfigurations: configs.compactMap { $0 }) { [weak self] numberSuccessful in
if numberSuccessful == configs.count { if numberSuccessful == configs.count {
completionHandler?() completionHandler?()

View File

@ -32,7 +32,8 @@ enum TunnelsManagerError: WireGuardAppError {
case tunnelActivationFailedInternalError // startTunnel() succeeded, but activation failed case tunnelActivationFailedInternalError // startTunnel() succeeded, but activation failed
case tunnelActivationFailedNoInternetConnection // startTunnel() succeeded, but activation failed since no internet case tunnelActivationFailedNoInternetConnection // startTunnel() succeeded, but activation failed since no internet
func alertText() -> (String, String)? { //swiftlint:disable:next cyclomatic_complexity
func alertText() -> AlertText {
switch self { switch self {
case .tunnelNameEmpty: case .tunnelNameEmpty:
return ("No name provided", "Can't create tunnel with an empty name") 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") return ("Unable to modify tunnel", "Internal error")
case .vpnSystemErrorOnRemoveTunnel: case .vpnSystemErrorOnRemoveTunnel:
return ("Unable to remove tunnel", "Internal error") return ("Unable to remove tunnel", "Internal error")
case .attemptingActivationWhenTunnelIsNotInactive: case .attemptingActivationWhenTunnelIsNotInactive:
return ("Activation failure", "The tunnel is already active or in the process of being activated") return ("Activation failure", "The tunnel is already active or in the process of being activated")
case .attemptingActivationWhenAnotherTunnelIsOperational(let otherTunnelName): case .attemptingActivationWhenAnotherTunnelIsOperational(let otherTunnelName):
@ -267,44 +267,41 @@ class TunnelsManager {
} }
private func startObservingTunnelStatuses() { private func startObservingTunnelStatuses() {
if statusObservationToken != nil { return } 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}@'", statusObservationToken = NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: nil, queue: OperationQueue.main) { [weak self] statusChangeNotification in
log: OSLog.default, type: .debug, tunnel.name, "\(tunnel.tunnelProvider.connection.status)") 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 }
// In case our attempt to start the tunnel, didn't succeed os_log("Tunnel '%{public}@' connection status changed to '%{public}@'",
if tunnel == self.tunnelBeingActivated { log: OSLog.default, type: .debug, tunnel.name, "\(tunnel.tunnelProvider.connection.status)")
if session.status == .disconnected {
if InternetReachability.currentStatus() == .notReachable { // In case our attempt to start the tunnel, didn't succeed
let error = TunnelsManagerError.tunnelActivationFailedNoInternetConnection if tunnel == self.tunnelBeingActivated {
self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: error) if session.status == .disconnected {
} if InternetReachability.currentStatus() == .notReachable {
self.tunnelBeingActivated = nil let error = TunnelsManagerError.tunnelActivationFailedNoInternetConnection
} else if session.status == .connected { self.activationDelegate?.tunnelActivationFailed(tunnel: tunnel, error: error)
self.tunnelBeingActivated = nil
} }
self.tunnelBeingActivated = nil
} else if session.status == .connected {
self.tunnelBeingActivated = nil
} }
}
// In case we're restarting the tunnel // In case we're restarting the tunnel
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 {
self.tunnelBeingActivated = tunnel self.tunnelBeingActivated = tunnel
tunnel.startActivation { _ in } tunnel.startActivation { _ in }
}
return
} }
return
}
// Update tunnel status tunnel.refreshStatus()
tunnel.refreshStatus()
} }
} }

View File

@ -2,5 +2,6 @@
// Copyright © 2018 WireGuard LLC. All Rights Reserved. // Copyright © 2018 WireGuard LLC. All Rights Reserved.
protocol WireGuardAppError: Error { protocol WireGuardAppError: Error {
func alertText() -> (/* title */ String, /* message */ String)? typealias AlertText = (title: String, message: String)
func alertText() -> AlertText
} }

View File

@ -8,7 +8,7 @@ enum ZipArchiveError: WireGuardAppError {
case cantOpenOutputZipFileForWriting case cantOpenOutputZipFileForWriting
case badArchive case badArchive
func alertText() -> (String, String)? { func alertText() -> AlertText {
switch self { switch self {
case .cantOpenInputZipFile: case .cantOpenInputZipFile:
return ("Unable to read zip archive", "The zip archive could not be read.") 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)] { 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 { guard let zipFile = unzOpen64(url.path) else {
throw ZipArchiveError.cantOpenInputZipFile throw ZipArchiveError.cantOpenInputZipFile

View File

@ -6,7 +6,7 @@ import UIKit
enum ZipExporterError: WireGuardAppError { enum ZipExporterError: WireGuardAppError {
case noTunnelsToExport case noTunnelsToExport
func alertText() -> (String, String)? { func alertText() -> AlertText {
switch self { switch self {
case .noTunnelsToExport: case .noTunnelsToExport:
return ("Nothing to export", "There are no tunnels to export") return ("Nothing to export", "There are no tunnels to export")
@ -15,8 +15,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)

View File

@ -6,7 +6,7 @@ import UIKit
enum ZipImporterError: WireGuardAppError { enum ZipImporterError: WireGuardAppError {
case noTunnelsInZipArchive case noTunnelsInZipArchive
func alertText() -> (String, String)? { func alertText() -> AlertText {
switch self { switch self {
case .noTunnelsInZipArchive: case .noTunnelsInZipArchive:
return ("No tunnels in zip archive", "No .conf tunnel files were found inside the zip archive.") return ("No tunnels in zip archive", "No .conf tunnel files were found inside the zip archive.")

View File

@ -42,7 +42,7 @@ class DNSResolver {
dispatchGroup.wait() // TODO: Timeout? dispatchGroup.wait() // TODO: Timeout?
var hostnamesWithDnsResolutionFailure: [String] = [] var hostnamesWithDnsResolutionFailure = [String]()
assert(endpoints.count == resolvedEndpoints.count) assert(endpoints.count == resolvedEndpoints.count)
for tuple in zip(endpoints, resolvedEndpoints) { for tuple in zip(endpoints, resolvedEndpoints) {
let endpoint = tuple.0 let endpoint = tuple.0

View File

@ -31,8 +31,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
} }
/// Begin the process of establishing the tunnel. /// Begin the process of establishing the tunnel.
override func startTunnel(options: [String: NSObject]?, override func startTunnel(options: [String: NSObject]?, completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) {
completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) {
guard let tunnelProviderProtocol = self.protocolConfiguration as? NETunnelProviderProtocol, guard let tunnelProviderProtocol = self.protocolConfiguration as? NETunnelProviderProtocol,
let tunnelConfiguration = tunnelProviderProtocol.tunnelConfiguration() else { let tunnelConfiguration = tunnelProviderProtocol.tunnelConfiguration() else {
@ -58,7 +57,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
// Resolve endpoint domains // Resolve endpoint domains
let endpoints = tunnelConfiguration.peers.map { $0.endpoint } let endpoints = tunnelConfiguration.peers.map { $0.endpoint }
var resolvedEndpoints: [Endpoint?] = [] var resolvedEndpoints = [Endpoint?]()
do { do {
resolvedEndpoints = try DNSResolver.resolveSync(endpoints: endpoints) resolvedEndpoints = try DNSResolver.resolveSync(endpoints: endpoints)
} catch DNSResolverError.dnsResolutionFailed(let hostnames) { } catch DNSResolverError.dnsResolutionFailed(let hostnames) {

View File

@ -60,16 +60,13 @@ class PacketTunnelSettingsGenerator {
} }
func generateNetworkSettings() -> NEPacketTunnelNetworkSettings { func generateNetworkSettings() -> NEPacketTunnelNetworkSettings {
// Remote address
/* iOS requires a tunnel endpoint, whereas in WireGuard it's valid for /* 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 * 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 * which case, displaying a single one in settings doesn't really
* make sense. So, we fill it in with this placeholder, which is not * make sense. So, we fill it in with this placeholder, which is not
* a valid IP address that will actually route over the Internet. * 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 } let endpointsCompact = resolvedEndpoints.compactMap { $0 }
if endpointsCompact.count == 1 { if endpointsCompact.count == 1 {
switch endpointsCompact.first!.host { switch endpointsCompact.first!.host {
@ -84,15 +81,11 @@ class PacketTunnelSettingsGenerator {
let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: remoteAddress) let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: remoteAddress)
// DNS
let dnsServerStrings = tunnelConfiguration.interface.dns.map { $0.stringRepresentation() } let dnsServerStrings = tunnelConfiguration.interface.dns.map { $0.stringRepresentation() }
let dnsSettings = NEDNSSettings(servers: dnsServerStrings) let dnsSettings = NEDNSSettings(servers: dnsServerStrings)
dnsSettings.matchDomains = [""] // All DNS queries must first go through the VPN's DNS dnsSettings.matchDomains = [""] // All DNS queries must first go through the VPN's DNS
networkSettings.dnsSettings = dnsSettings networkSettings.dnsSettings = dnsSettings
// MTU
let mtu = tunnelConfiguration.interface.mtu ?? 0 let mtu = tunnelConfiguration.interface.mtu ?? 0
if mtu == 0 { if mtu == 0 {
// 0 imples automatic MTU, where we set overhead as 80 bytes, which is the worst case for WireGuard // 0 imples automatic MTU, where we set overhead as 80 bytes, which is the worst case for WireGuard
@ -101,103 +94,24 @@ class PacketTunnelSettingsGenerator {
networkSettings.mtu = NSNumber(value: mtu) networkSettings.mtu = NSNumber(value: mtu)
} }
// Addresses from interface addresses let (ipv4Routes, ipv6Routes) = routes()
let (ipv4IncludedRoutes, ipv6IncludedRoutes) = includedRoutes()
let (ipv4ExcludedRoutes, ipv6ExcludedRoutes) = excludedRoutes()
var ipv4Addresses: [String] = [] let ipv4Settings = NEIPv4Settings(addresses: ipv4Routes.map { $0.destinationAddress }, subnetMasks: ipv4Routes.map { $0.destinationSubnetMask })
var ipv4SubnetMasks: [String] = [] ipv4Settings.includedRoutes = ipv4IncludedRoutes
ipv4Settings.excludedRoutes = ipv4ExcludedRoutes
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)
}
networkSettings.ipv4Settings = ipv4Settings networkSettings.ipv4Settings = ipv4Settings
// Apply IPv6 settings let ipv6Settings = NEIPv6Settings(addresses: ipv6Routes.map { $0.destinationAddress }, networkPrefixLengths: ipv6Routes.map { $0.destinationNetworkPrefixLength })
ipv6Settings.includedRoutes = ipv6IncludedRoutes
/* Big fat ugly hack for broken iOS networking stack: the smallest prefix that will have ipv6Settings.excludedRoutes = ipv6ExcludedRoutes
* 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)
}
networkSettings.ipv6Settings = ipv6Settings networkSettings.ipv6Settings = ipv6Settings
// Done
return networkSettings return networkSettings
} }
static func ipv4SubnetMaskString(of addressRange: IPAddressRange) -> String { private func ipv4SubnetMaskString(of addressRange: IPAddressRange) -> String {
let length: UInt8 = addressRange.networkPrefixLength let length: UInt8 = addressRange.networkPrefixLength
assert(length <= 32) assert(length <= 32)
var octets: [UInt8] = [0, 0, 0, 0] var octets: [UInt8] = [0, 0, 0, 0]
@ -208,6 +122,57 @@ class PacketTunnelSettingsGenerator {
octets[3] = UInt8(truncatingIfNeeded: subnetMask) octets[3] = UInt8(truncatingIfNeeded: subnetMask)
return octets.map { String($0) }.joined(separator: ".") 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 { private extension Data {