summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/CHANGELOG.md1
-rw-r--r--ios/MullvadVPN/LogStreamerViewController.swift7
-rw-r--r--ios/PacketTunnel/WireguardDevice.swift153
3 files changed, 129 insertions, 32 deletions
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index 9e0e294233..5e28ab1dcc 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -39,6 +39,7 @@ Line wrap the file at 100 chars. Th
### Changed
- Remove the public WireGuard inside the VPN tunnel during the log out, if VPN is active at that
time.
+- Turn off WireGuard when no active network interfaces available.
## [2020.3] - 2020-06-12
### Added
diff --git a/ios/MullvadVPN/LogStreamerViewController.swift b/ios/MullvadVPN/LogStreamerViewController.swift
index d033a8fc70..fc360781e8 100644
--- a/ios/MullvadVPN/LogStreamerViewController.swift
+++ b/ios/MullvadVPN/LogStreamerViewController.swift
@@ -69,6 +69,13 @@ class LogStreamerViewController: UIViewController, UITextViewDelegate {
// Disable autoscroll if user scrolled up
if translation.y > 0 {
autoScroll = false
+ } else if translation.y < 0 {
+ // Enable autoscroll if user scrolled to the bottom of the view
+ let maxScrollY = scrollView.contentSize.height - scrollView.frame.height
+
+ if targetContentOffset.pointee.y >= maxScrollY {
+ autoScroll = true
+ }
}
}
diff --git a/ios/PacketTunnel/WireguardDevice.swift b/ios/PacketTunnel/WireguardDevice.swift
index c24cd3dab0..5fcbe2e057 100644
--- a/ios/PacketTunnel/WireguardDevice.swift
+++ b/ios/PacketTunnel/WireguardDevice.swift
@@ -21,6 +21,10 @@ class WireguardDevice {
/// A failure to obtain the tunnel device file descriptor
case cannotLocateSocketDescriptor
+ /// A failure to duplicate the socket descriptor.
+ /// The associated value contains the `errno` from a syscall to `dup`
+ case cannotDuplicateSocketDescriptor(Int32)
+
/// A failure to start the Wireguard backend
case start(Int32)
@@ -36,7 +40,9 @@ class WireguardDevice {
var errorDescription: String? {
switch self {
case .cannotLocateSocketDescriptor:
- return "Unable to locate the file descriptor for socket."
+ return "Cannot locate the socket file descriptor."
+ case .cannotDuplicateSocketDescriptor(let posixErrorCode):
+ return "Cannot duplicate the socket file descriptor. Errno: \(posixErrorCode)"
case .start(let wgErrorCode):
return "Failed to start Wireguard. Return code: \(wgErrorCode)"
case .notStarted:
@@ -75,8 +81,8 @@ class WireguardDevice {
/// Network routes monitor
private var networkMonitor: NWPathMonitor?
- /// A tunnel device descriptor
- private let tunFd: Int32
+ /// A tunnel device source socket file descriptor
+ private let tunnelFileDescriptor: Int32
/// A wireguard internal handle returned by `wgTurnOn` that's used to associate the calls
/// with the specific Wireguard tunnel.
@@ -85,6 +91,12 @@ class WireguardDevice {
/// Active configuration
private var configuration: WireguardConfiguration?
+ /// A flag that indicates that the device has started
+ private var isStarted = false
+
+ /// A flag that indicates whether the last known network path was satisfied
+ private var isPathSatisfied = true
+
/// Returns a Wireguard version
class var version: String {
String(cString: wgVersion())
@@ -116,15 +128,15 @@ class WireguardDevice {
/// A designated initializer
class func fromPacketFlow(_ packetFlow: NEPacketTunnelFlow) -> Result<WireguardDevice, Error> {
if let fd = packetFlow.value(forKeyPath: "socket.fileDescriptor") as? Int32 {
- return .success(.init(tunFd: fd))
+ return .success(.init(tunnelFileDescriptor: fd))
} else {
return .failure(.cannotLocateSocketDescriptor)
}
}
/// Private initializer
- private init(tunFd: Int32) {
- self.tunFd = tunFd
+ private init(tunnelFileDescriptor: Int32) {
+ self.tunnelFileDescriptor = tunnelFileDescriptor
}
deinit {
@@ -135,38 +147,39 @@ class WireguardDevice {
func start(configuration: WireguardConfiguration, completionHandler: @escaping (Result<(), Error>) -> Void) {
workQueue.async {
- guard self.wireguardHandle == nil else {
+ guard !self.isStarted else {
completionHandler(.failure(.alreadyStarted))
return
}
+ assert(self.wireguardHandle == nil)
+
let resolvedConfiguration = self.resolveConfiguration(configuration)
- let handle = resolvedConfiguration
- .uapiConfiguration()
- .toRawWireguardConfigString()
- .withCString { wgTurnOn($0, self.tunFd) }
- if handle >= 0 {
- self.wireguardHandle = handle
+ switch self.startWireguardBackend(resolvedConfiguration: resolvedConfiguration) {
+ case .success:
+ self.isStarted = true
+ self.isPathSatisfied = true
self.configuration = configuration
self.startNetworkMonitor()
completionHandler(.success(()))
- } else {
- completionHandler(.failure(.start(handle)))
+
+ case .failure(let error):
+ completionHandler(.failure(error))
}
}
}
func stop(completionHandler: @escaping (Result<(), Error>) -> Void) {
workQueue.async {
- if let handle = self.wireguardHandle {
+ if self.isStarted {
self.networkMonitor?.cancel()
self.networkMonitor = nil
- wgTurnOff(handle)
- self.wireguardHandle = nil
+ self.stopWireguardBackend()
+ self.isStarted = false
completionHandler(.success(()))
} else {
@@ -177,11 +190,13 @@ class WireguardDevice {
func setConfiguration(_ newConfiguration: WireguardConfiguration, completionHandler: @escaping (Result<(), Error>) -> Void) {
workQueue.async {
- if let handle = self.wireguardHandle {
- let resolvedConfiguration = self.resolveConfiguration(newConfiguration)
- let commands = resolvedConfiguration.uapiConfiguration()
+ if self.isStarted {
+ if let handle = self.wireguardHandle {
+ let resolvedConfiguration = self.resolveConfiguration(newConfiguration)
+ let commands = resolvedConfiguration.uapiConfiguration()
- Self.setWireguardConfig(handle: handle, commands: commands)
+ Self.setWireguardConfig(handle: handle, commands: commands)
+ }
self.configuration = newConfiguration
@@ -200,7 +215,7 @@ class WireguardDevice {
var ifnameSize = socklen_t(IFNAMSIZ)
let result = getsockopt(
- self.tunFd,
+ self.tunnelFileDescriptor,
2 /* SYSPROTO_CONTROL */,
2 /* UTUN_OPT_IFNAME */,
baseAddress,
@@ -216,6 +231,42 @@ class WireguardDevice {
// MARK: - Private methods
+ private func startWireguardBackend(resolvedConfiguration: WireguardConfiguration) -> Result<(), Error> {
+ assert(self.wireguardHandle == nil)
+
+ // Duplicate the tunnel file descriptor to prevent `wgTurnOff` from closing it
+ let duplicateFileDescriptor = dup(self.tunnelFileDescriptor)
+ if duplicateFileDescriptor == -1 {
+ return .failure(.cannotDuplicateSocketDescriptor(errno))
+ }
+
+ let handle = resolvedConfiguration
+ .uapiConfiguration()
+ .toRawWireguardConfigString()
+ .withCString { wgTurnOn($0, duplicateFileDescriptor) }
+
+ if handle >= 0 {
+ self.wireguardHandle = handle
+
+ return .success(())
+ } else {
+ // `wgTurnOn` does not cover all of the code paths and may leave the file descriptor
+ // open on failure
+ if close(duplicateFileDescriptor) == -1 {
+ self.logger.warning("Failed to close the duplicate tunnel file descriptor. Error: \(errno)")
+ }
+
+ return .failure(.start(handle))
+ }
+ }
+
+ private func stopWireguardBackend() {
+ guard let handle = self.wireguardHandle else { return }
+
+ wgTurnOff(handle)
+ self.wireguardHandle = nil
+ }
+
private class func setWireguardConfig(handle: Int32, commands: [WireguardCommand]) {
// Ignore empty payloads
guard !commands.isEmpty else { return }
@@ -283,20 +334,44 @@ class WireguardDevice {
private func didReceiveNetworkPathUpdate(path: Network.NWPath) {
workQueue.async {
- guard let handle = self.wireguardHandle else { return }
+ guard self.isStarted else { return }
+
+ self.logger.info("Network change detected. Status: \(path.status), interfaces \(path.availableInterfaces).")
+
+ let oldPathSatisfied = self.isPathSatisfied
+ let newPathSatisfied = path.status.isSatisfiable
+
+ self.isPathSatisfied = newPathSatisfied
- self.logger.debug("Network change detected. Status: \(path.status), interfaces \(path.availableInterfaces).")
+ switch (oldPathSatisfied, newPathSatisfied) {
+ case (true, false):
+ self.logger.info("Stop wireguard backend")
+ self.stopWireguardBackend()
+
+ case (false, true), (true, true):
+ guard let currentConfiguration = self.configuration else { return }
+
+ self.logger.info("Re-resolve endpoints")
- // Re-resolve endpoints on network changes
- if let currentConfiguration = self.configuration {
let resolvedConfiguration = self.resolveConfiguration(currentConfiguration)
- let commands = resolvedConfiguration.endpointUapiConfiguration()
- Self.setWireguardConfig(handle: handle, commands: commands)
- }
+ if let handle = self.wireguardHandle {
+ let commands = resolvedConfiguration.endpointUapiConfiguration()
+ Self.setWireguardConfig(handle: handle, commands: commands)
+
+ wgBumpSockets(handle)
+ } else {
+ self.logger.info("Start wireguard backend")
- // Tell Wireguard to re-open sockets and bind them to the new network interface
- wgBumpSockets(handle)
+ if case .failure(let error) = self.startWireguardBackend(resolvedConfiguration: resolvedConfiguration) {
+ self.logger.error(chainedError: error, message: "Failed to turn on WireGuard")
+ }
+ }
+
+ case (false, false):
+ // No-op: device remains offline
+ break
+ }
}
}
}
@@ -319,6 +394,20 @@ enum WireguardLogLevel: Int32 {
}
}
+private extension Network.NWPath.Status {
+ /// Returns `true` if the path is potentially satisfiable
+ var isSatisfiable: Bool {
+ switch self {
+ case .requiresConnection, .satisfied:
+ return true
+ case .unsatisfied:
+ return false
+ @unknown default:
+ return true
+ }
+ }
+}
+
extension Network.NWPath.Status: CustomDebugStringConvertible {
public var debugDescription: String {
var output = "NWPath.Status."