Skip to content

Commit

Permalink
Fix the cached relays to include daita information in new app version
Browse files Browse the repository at this point in the history
  • Loading branch information
buggmagnet committed Sep 5, 2024
1 parent 1c5fb0a commit 1afb6ad
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 12 deletions.
29 changes: 29 additions & 0 deletions ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,36 @@ extension REST {
daita: daita
)
}

public func override(daita: Bool) -> Self {
return ServerRelay(
hostname: hostname,
active: active,
owned: owned,
location: location,
provider: provider,
weight: weight,
ipv4AddrIn: ipv4AddrIn,
ipv6AddrIn: ipv6AddrIn,
publicKey: publicKey,
includeInCountry: includeInCountry,
daita: daita
)
}
}

public struct ServerWireguardTunnels: Codable, Equatable {
public let ipv4Gateway: IPv4Address
public let ipv6Gateway: IPv6Address
public let portRanges: [[UInt16]]
public let relays: [ServerRelay]

public init(ipv4Gateway: IPv4Address, ipv6Gateway: IPv6Address, portRanges: [[UInt16]], relays: [ServerRelay]) {
self.ipv4Gateway = ipv4Gateway
self.ipv6Gateway = ipv6Gateway
self.portRanges = portRanges
self.relays = relays
}
}

public struct ServerShadowsocks: Codable, Equatable {
Expand All @@ -103,5 +126,11 @@ extension REST {
public let locations: [String: ServerLocation]
public let wireguard: ServerWireguardTunnels
public let bridge: ServerBridges

public init(locations: [String: ServerLocation], wireguard: ServerWireguardTunnels, bridge: ServerBridges) {
self.locations = locations
self.wireguard = wireguard
self.bridge = bridge
}
}
}
7 changes: 7 additions & 0 deletions ios/MullvadREST/Relay/IPOverrideWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ public class IPOverrideWrapper: RelayCacheProtocol {
return CachedRelays(relays: relayResponse, updatedAt: cache.updatedAt)
}

public func readPrebundledRelays() throws -> CachedRelays {
let prebundledRelays = try relayCache.readPrebundledRelays()
let relayResponse = apply(overrides: ipOverrideRepository.fetchAll(), to: prebundledRelays.relays)

return CachedRelays(relays: relayResponse, updatedAt: prebundledRelays.updatedAt)
}

public func write(record: CachedRelays) throws {
try relayCache.write(record: record)
}
Expand Down
9 changes: 8 additions & 1 deletion ios/MullvadREST/Relay/RelayCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ import Foundation
import MullvadTypes

public protocol RelayCacheProtocol {
/// Reads from a cached list,
/// which falls back to reading from prebundled relays if there was no cache hit
func read() throws -> CachedRelays
/// Reads the relays file that were prebundled with the app installation.
///
/// > Warning: Prefer `read()` over this unless there is an explicit need to read
/// relays from the bundle, because those might contain stale data.
func readPrebundledRelays() throws -> CachedRelays
func write(record: CachedRelays) throws
}

Expand Down Expand Up @@ -49,7 +56,7 @@ public final class RelayCache: RelayCacheProtocol {
}

/// Read pre-bundled relays file from disk.
private func readPrebundledRelays() throws -> CachedRelays {
public func readPrebundledRelays() throws -> CachedRelays {
guard let prebundledRelaysFileURL = Bundle(for: Self.self).url(forResource: "relays", withExtension: "json")
else { throw CocoaError(.fileNoSuchFile) }

Expand Down
72 changes: 61 additions & 11 deletions ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol {
private let application: UIApplication

/// Lock used for synchronization.
private let nslock = NSLock()
private let relayCacheLock = NSLock()

/// Internal operation queue.
private let operationQueue = AsyncOperationQueue.makeSerial()
Expand All @@ -64,6 +64,7 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol {

do {
cachedRelays = try cache.read()
try hotfixRelaysThatDoNotHaveDaita()
} catch {
logger.error(
error: error,
Expand All @@ -74,9 +75,58 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol {
}
}

/// This method updates the cached relay to include daita information
///
/// This is a hotfix meant to upgrade clients shipped with 2024.5 or before that did not have
/// daita information in their representation of `ServerRelay`.
/// If a version <= 2024.5 is installed less than an hour before a new upgrade,
/// no servers will be shown in locations when filtering for daita relays.
///
/// > Info: `relayCacheLock` does not need to be accessed here, this method should be ran from `init` only.
private func hotfixRelaysThatDoNotHaveDaita() throws {
guard let cachedRelays else { return }
let daitaPropertyMissing = cachedRelays.relays.wireguard.relays.first { $0.daita ?? false } == nil
// If the cached relays already have daita information, this fix is not necessary
guard daitaPropertyMissing else { return }

let preBundledRelays = try cache.readPrebundledRelays()
let preBundledDaitaRelays = preBundledRelays.relays.wireguard.relays.filter { $0.daita == true }
var cachedRelaysWithFixedDaita = cachedRelays.relays.wireguard.relays

// For each daita enabled relay in the prebundled relays
// Find the corresponding relay in the cache by matching relay hostnames
// Then update it to toggle daita
for index in 0 ..< cachedRelaysWithFixedDaita.endIndex {
let relay = cachedRelaysWithFixedDaita[index]
preBundledDaitaRelays.forEach {
if $0.hostname == relay.hostname {
cachedRelaysWithFixedDaita[index] = relay.override(daita: true)
}
}
}

let wireguard = REST.ServerWireguardTunnels(
ipv4Gateway:
cachedRelays.relays.wireguard.ipv4Gateway,
ipv6Gateway: cachedRelays.relays.wireguard.ipv6Gateway,
portRanges: cachedRelays.relays.wireguard.portRanges,
relays: cachedRelaysWithFixedDaita
)

let updatedRelays = REST.ServerRelaysResponse(
locations: cachedRelays.relays.locations,
wireguard: wireguard,
bridge: cachedRelays.relays.bridge
)

let updatedCachedRelays = CachedRelays(relays: updatedRelays, updatedAt: Date())
try cache.write(record: updatedCachedRelays)
self.cachedRelays = updatedCachedRelays
}

func startPeriodicUpdates() {
nslock.lock()
defer { nslock.unlock() }
relayCacheLock.lock()
defer { relayCacheLock.unlock() }

guard !isPeriodicUpdatesEnabled else { return }

Expand All @@ -90,8 +140,8 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol {
}

func stopPeriodicUpdates() {
nslock.lock()
defer { nslock.unlock() }
relayCacheLock.lock()
defer { relayCacheLock.unlock() }

guard isPeriodicUpdatesEnabled else { return }

Expand Down Expand Up @@ -135,8 +185,8 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol {
}

func getCachedRelays() throws -> CachedRelays {
nslock.lock()
defer { nslock.unlock() }
relayCacheLock.lock()
defer { relayCacheLock.unlock() }

if let cachedRelays {
return cachedRelays
Expand All @@ -148,9 +198,9 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol {
func refreshCachedRelays() throws {
let newCachedRelays = try cache.read()

nslock.lock()
relayCacheLock.lock()
cachedRelays = newCachedRelays
nslock.unlock()
relayCacheLock.unlock()

DispatchQueue.main.async {
self.observerList.notify { observer in
Expand All @@ -160,8 +210,8 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol {
}

func getNextUpdateDate() -> Date {
nslock.lock()
defer { nslock.unlock() }
relayCacheLock.lock()
defer { relayCacheLock.unlock() }

return _getNextUpdateDate()
}
Expand Down

0 comments on commit 1afb6ad

Please sign in to comment.