summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2023-08-24 13:02:43 +0200
committerAndrej Mihajlov <and@mullvad.net>2023-08-25 12:06:54 +0200
commit2130af52d6430f11a1ff2fa61d5ab748d4fd02b9 (patch)
tree2745b5ba0011ca4f4b9a799002cec400a5f48e1a
parent28d73680445b15179b89345c7e2f0b22c67f52e8 (diff)
downloadmullvadvpn-2130af52d6430f11a1ff2fa61d5ab748d4fd02b9.tar.xz
mullvadvpn-2130af52d6430f11a1ff2fa61d5ab748d4fd02b9.zip
Change CachedAddresses schema to invalidate cache when updating from 2023.2 to 2023.3
We had a couple of reports pointing at persistent timeouts and SSL errors in API communication using any transport. The user that happened to be using a broken bridge IP while on 2023.2 would keep using it indefinitely after update to 2023.3 because we used to move the currently used IP to the first position in the list. After update to 2023.3 we would only use that first IP.
-rw-r--r--ios/CHANGELOG.md5
-rw-r--r--ios/MullvadREST/AddressCache.swift81
-rw-r--r--ios/MullvadVPNTests/AddressCacheTests.swift29
3 files changed, 48 insertions, 67 deletions
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index 5b2571b5fc..1bab652812 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -27,6 +27,11 @@ Line wrap the file at 100 chars. Th
- Allow deleting account in account view.
- Add new account flow
+### Fixed
+- Invalidate API IP address cache to fix connectivity issues for some of devices updating from
+ 2023.2 or earlier.
+
+
## [2023.3 - 2023-07-15]
### Added
- Add search functionality to location selection view.
diff --git a/ios/MullvadREST/AddressCache.swift b/ios/MullvadREST/AddressCache.swift
index 774c779fe8..8db610fc48 100644
--- a/ios/MullvadREST/AddressCache.swift
+++ b/ios/MullvadREST/AddressCache.swift
@@ -16,10 +16,10 @@ extension REST {
private let logger = Logger(label: "AddressCache")
/// Disk cache.
- private let fileCache: any FileCacheProtocol<CachedAddresses>
+ private let fileCache: any FileCacheProtocol<StoredAddressCache>
/// Memory cache.
- private var cache: CachedAddresses = defaultCachedAddresses
+ private var cache: StoredAddressCache = defaultStoredCache
/// Lock used for synchronizing access to instance members.
private let cacheLock = NSLock()
@@ -27,10 +27,10 @@ extension REST {
/// Whether address cache can be written to.
private let canWriteToCache: Bool
- /// The default set of endpoints to use as a fallback mechanism
- private static let defaultCachedAddresses = CachedAddresses(
+ /// The default endpoint to use as a fallback mechanism.
+ private static let defaultStoredCache = StoredAddressCache(
updatedAt: Date(timeIntervalSince1970: 0),
- endpoints: [REST.defaultAPIEndpoint]
+ endpoint: REST.defaultAPIEndpoint
)
// MARK: - Public API
@@ -44,7 +44,7 @@ extension REST {
}
/// Initializer that accepts a file cache implementation and can be used in tests.
- init(canWriteToCache: Bool, fileCache: some FileCacheProtocol<CachedAddresses>) {
+ init(canWriteToCache: Bool, fileCache: some FileCacheProtocol<StoredAddressCache>) {
self.fileCache = fileCache
self.canWriteToCache = canWriteToCache
}
@@ -54,23 +54,19 @@ extension REST {
/// When running from the Network Extension, this method will read from the cache before returning.
/// - Returns: The latest available endpoint, or a default endpoint if no endpoints are available
public func getCurrentEndpoint() -> AnyIPEndpoint {
- cacheLock.lock()
- defer { cacheLock.unlock() }
- var currentEndpoint = cache.endpoints.first ?? REST.defaultAPIEndpoint
-
- // Reload from disk cache when in the Network Extension as there is no `AddressCacheTracker` running
- // there
- if canWriteToCache == false {
- do {
- cache = try fileCache.read()
- if let firstEndpoint = cache.endpoints.first {
- currentEndpoint = firstEndpoint
+ cacheLock.withLock {
+ // Reload from disk cache when in the Network Extension as there is no `AddressCacheTracker` running
+ // there
+ if canWriteToCache == false {
+ do {
+ cache = try fileCache.read()
+ } catch {
+ logger.error(error: error, message: "Failed to read address cache from disk.")
}
- } catch {
- logger.error(error: error, message: "Failed to read address cache from disk.")
}
+
+ return cache.endpoint
}
- return currentEndpoint
}
/// Updates the available endpoints to use
@@ -79,27 +75,20 @@ extension REST {
/// This method will only modify the on disk cache when running from the UI process.
/// - Parameter endpoints: The new endpoints to use for API requests
public func setEndpoints(_ endpoints: [AnyIPEndpoint]) {
- cacheLock.lock()
- defer { cacheLock.unlock() }
+ cacheLock.withLock {
+ guard let firstEndpoint = endpoints.first else { return }
- guard let firstEndpoint = endpoints.first else { return }
- if Set(cache.endpoints) == Set(endpoints) {
- cache.updatedAt = Date()
- } else {
- cache = CachedAddresses(
- updatedAt: Date(),
- endpoints: [firstEndpoint]
- )
- }
+ cache = StoredAddressCache(updatedAt: Date(), endpoint: firstEndpoint)
- guard canWriteToCache else { return }
- do {
- try fileCache.write(cache)
- } catch {
- logger.error(
- error: error,
- message: "Failed to write address cache after setting new endpoints."
- )
+ guard canWriteToCache else { return }
+ do {
+ try fileCache.write(cache)
+ } catch {
+ logger.error(
+ error: error,
+ message: "Failed to write address cache after setting new endpoints."
+ )
+ }
}
}
@@ -107,10 +96,7 @@ extension REST {
///
/// - Returns: The `Date` when the cache was last updated at
public func getLastUpdateDate() -> Date {
- cacheLock.lock()
- defer { cacheLock.unlock() }
-
- return cache.updatedAt
+ return cacheLock.withLock { cache.updatedAt }
}
/// Initializes cache by reading it from file on disk.
@@ -129,17 +115,18 @@ extension REST {
}
logger.debug("Initialized cache with default API endpoint.")
- cache = Self.defaultCachedAddresses
+ cache = Self.defaultStoredCache
}
}
}
}
- public struct CachedAddresses: Codable, Equatable {
+ /// Serializable address cache representation stored on disk.
+ struct StoredAddressCache: Codable, Equatable {
/// Date when the cached addresses were last updated.
var updatedAt: Date
- /// API endpoints.
- var endpoints: [AnyIPEndpoint]
+ /// API endpoint.
+ var endpoint: AnyIPEndpoint
}
}
diff --git a/ios/MullvadVPNTests/AddressCacheTests.swift b/ios/MullvadVPNTests/AddressCacheTests.swift
index d0e1d823b1..5a5dedfe1a 100644
--- a/ios/MullvadVPNTests/AddressCacheTests.swift
+++ b/ios/MullvadVPNTests/AddressCacheTests.swift
@@ -68,18 +68,17 @@ final class AddressCacheTests: XCTestCase {
let firstIPEndpoint = try XCTUnwrap(ipAddresses.first)
- let fileCache = MockFileCache<REST.CachedAddresses>()
+ let fileCache = MockFileCache<REST.StoredAddressCache>()
let addressCache = REST.AddressCache(canWriteToCache: true, fileCache: fileCache)
addressCache.setEndpoints(ipAddresses)
let fileState = fileCache.getState()
- XCTAssertTrue(fileState.isExists)
- guard case let .exists(cachedAddresses) = fileState else {
+ guard case let .exists(storedAddressCache) = fileState else {
XCTFail("State is expected to contain cached addresses.")
return
}
- XCTAssertEqual(cachedAddresses.endpoints.count, 1)
+ XCTAssertEqual(storedAddressCache.endpoint, firstIPEndpoint)
XCTAssertEqual(addressCache.getCurrentEndpoint(), firstIPEndpoint)
}
@@ -88,7 +87,7 @@ final class AddressCacheTests: XCTestCase {
let addressCache = REST.AddressCache(
canWriteToCache: true,
fileCache: MockFileCache(initialState: .exists(
- REST.CachedAddresses(updatedAt: fixedDate, endpoints: [apiEndpoint])
+ REST.StoredAddressCache(updatedAt: fixedDate, endpoint: apiEndpoint)
))
)
addressCache.loadFromFile()
@@ -98,7 +97,7 @@ final class AddressCacheTests: XCTestCase {
}
func testCacheWritesToDiskWhenSettingNewEndpoints() throws {
- let fileCache = MockFileCache<REST.CachedAddresses>()
+ let fileCache = MockFileCache<REST.StoredAddressCache>()
let addressCache = REST.AddressCache(canWriteToCache: true, fileCache: fileCache)
XCTAssertEqual(fileCache.getState(), .fileNotFound)
@@ -107,32 +106,22 @@ final class AddressCacheTests: XCTestCase {
let fileState = fileCache.getState()
XCTAssertTrue(fileState.isExists)
- guard case let .exists(cachedAddresses) = fileState else {
+ guard case let .exists(storedAddressCache) = fileState else {
XCTFail("State is expected to contain cached addresses.")
return
}
- XCTAssertEqual(cachedAddresses.endpoints, [addressCache.getCurrentEndpoint()])
- XCTAssertEqual(cachedAddresses.updatedAt, addressCache.getLastUpdateDate())
+ XCTAssertEqual(storedAddressCache.endpoint, addressCache.getCurrentEndpoint())
+ XCTAssertEqual(storedAddressCache.updatedAt, addressCache.getLastUpdateDate())
}
func testGetCurrentEndpointReadsFromCacheWhenReadOnly() throws {
let addressCache = REST.AddressCache(
canWriteToCache: false,
fileCache: MockFileCache(initialState: .exists(
- REST.CachedAddresses(updatedAt: Date(), endpoints: [apiEndpoint])
+ REST.StoredAddressCache(updatedAt: Date(), endpoint: apiEndpoint)
))
)
XCTAssertEqual(addressCache.getCurrentEndpoint(), apiEndpoint)
}
-
- func testGetCurrentEndpointHasDefaultEndpointIfCacheIsEmpty() throws {
- let addressCache = REST.AddressCache(
- canWriteToCache: false,
- fileCache: MockFileCache(initialState: .exists(REST.CachedAddresses(updatedAt: Date(), endpoints: [])))
- )
- addressCache.loadFromFile()
-
- XCTAssertEqual(addressCache.getCurrentEndpoint(), REST.defaultAPIEndpoint)
- }
}