From aeca222e85a9ab8104956307473f338fa8ee87a7 Mon Sep 17 00:00:00 2001 From: Jon Petersson Date: Wed, 24 Jan 2024 15:55:48 +0100 Subject: [PATCH] Relay selector should use overridden IP addresses for relays --- .../ApiHandlers/ServerRelaysResponse.swift | 28 +++++ ios/MullvadREST/Relay/AnyRelay.swift | 29 +++++ ios/MullvadREST/Relay/RelayCache.swift | 57 ++++++++- ios/MullvadREST/Relay/RelaySelector.swift | 13 -- ios/MullvadSettings/IPOverride.swift | 2 +- ios/MullvadVPN.xcodeproj/project.pbxproj | 22 ++-- ios/MullvadVPN/AppDelegate.swift | 2 +- .../IPOverride/IPOverrideViewController.swift | 2 - .../IPOverrideRepositoryStub.swift | 33 ++++++ ios/MullvadVPNTests/RelayCacheTests.swift | 111 ++++++++++++++++-- .../PacketTunnelProvider.swift | 2 +- 11 files changed, 256 insertions(+), 45 deletions(-) create mode 100644 ios/MullvadREST/Relay/AnyRelay.swift create mode 100644 ios/MullvadVPNTests/IPOverrideRepositoryStub.swift diff --git a/ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift b/ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift index 63f0822e63b6..a23289973061 100644 --- a/ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift +++ b/ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift @@ -34,6 +34,19 @@ extension REST { public let ipv4AddrIn: IPv4Address public let weight: UInt64 public let includeInCountry: Bool + + public func copyWith(ipv4AddrIn: IPv4Address?) -> Self { + return BridgeRelay( + hostname: hostname, + active: active, + owned: owned, + location: location, + provider: provider, + ipv4AddrIn: ipv4AddrIn ?? self.ipv4AddrIn, + weight: weight, + includeInCountry: includeInCountry + ) + } } public struct ServerRelay: Codable, Equatable { @@ -47,6 +60,21 @@ extension REST { public let ipv6AddrIn: IPv6Address public let publicKey: Data public let includeInCountry: Bool + + public func copyWith(ipv4AddrIn: IPv4Address?, ipv6AddrIn: IPv6Address?) -> Self { + return ServerRelay( + hostname: hostname, + active: active, + owned: owned, + location: location, + provider: provider, + weight: weight, + ipv4AddrIn: ipv4AddrIn ?? self.ipv4AddrIn, + ipv6AddrIn: ipv6AddrIn ?? self.ipv6AddrIn, + publicKey: publicKey, + includeInCountry: includeInCountry + ) + } } public struct ServerWireguardTunnels: Codable, Equatable { diff --git a/ios/MullvadREST/Relay/AnyRelay.swift b/ios/MullvadREST/Relay/AnyRelay.swift new file mode 100644 index 000000000000..4c03cb071ccb --- /dev/null +++ b/ios/MullvadREST/Relay/AnyRelay.swift @@ -0,0 +1,29 @@ +// +// AnyRelay.swift +// MullvadREST +// +// Created by Jon Petersson on 2024-01-31. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import MullvadTypes +import Network + +public protocol AnyRelay { + var hostname: String { get } + var owned: Bool { get } + var location: String { get } + var provider: String { get } + var weight: UInt64 { get } + var active: Bool { get } + var includeInCountry: Bool { get } + + func copyWith(ipv4AddrIn: IPv4Address?, ipv6AddrIn: IPv6Address?) -> Self +} + +extension REST.ServerRelay: AnyRelay {} +extension REST.BridgeRelay: AnyRelay { + public func copyWith(ipv4AddrIn: IPv4Address?, ipv6AddrIn: IPv6Address?) -> REST.BridgeRelay { + copyWith(ipv4AddrIn: ipv4AddrIn) + } +} diff --git a/ios/MullvadREST/Relay/RelayCache.swift b/ios/MullvadREST/Relay/RelayCache.swift index 6cc9ddc61680..8de45740dc87 100644 --- a/ios/MullvadREST/Relay/RelayCache.swift +++ b/ios/MullvadREST/Relay/RelayCache.swift @@ -6,8 +6,9 @@ // Copyright © 2021 Mullvad VPN AB. All rights reserved. // -import Foundation +import MullvadSettings import MullvadTypes +import Network public protocol RelayCacheProtocol { func read() throws -> CachedRelays @@ -16,22 +17,28 @@ public protocol RelayCacheProtocol { public final class RelayCache: RelayCacheProtocol { private let fileCache: any FileCacheProtocol + private let ipOverrideRepository: any IPOverrideRepositoryProtocol /// Designated initializer - public init(cacheDirectory: URL) { + public init(cacheDirectory: URL, ipOverrideRepository: IPOverrideRepositoryProtocol) { fileCache = FileCache(fileURL: cacheDirectory.appendingPathComponent("relays.json", isDirectory: false)) + self.ipOverrideRepository = ipOverrideRepository } /// Initializer that accepts a custom FileCache implementation. Used in tests. - init(fileCache: some FileCacheProtocol) { + init(fileCache: some FileCacheProtocol, ipOverrideRepository: some IPOverrideRepositoryProtocol) { self.fileCache = fileCache + self.ipOverrideRepository = ipOverrideRepository } /// Safely read the cache file from disk using file coordinator and fallback to prebundled /// relays in case if the relay cache file is missing. public func read() throws -> CachedRelays { do { - return try fileCache.read() + let cache = try fileCache.read() + let relayResponse = apply(overrides: ipOverrideRepository.fetchAll(), to: cache.relays) + + return CachedRelays(relays: relayResponse, updatedAt: cache.updatedAt) } catch { if error is DecodingError || (error as? CocoaError)?.code == .fileReadNoSuchFile { return try readPrebundledRelays() @@ -59,4 +66,46 @@ public final class RelayCache: RelayCacheProtocol { updatedAt: Date(timeIntervalSince1970: 0) ) } + + private func apply( + overrides: [IPOverride], + to relyResponse: REST.ServerRelaysResponse + ) -> REST.ServerRelaysResponse { + let wireguard = relyResponse.wireguard + let bridge = relyResponse.bridge + + let wireguardRelays = wireguard.relays.map { relay in + return apply(overrides: overrides, to: relay) + } + let bridgeRelays = bridge.relays.map { relay in + return apply(overrides: overrides, to: relay) + } + + return REST.ServerRelaysResponse( + locations: relyResponse.locations, + wireguard: REST.ServerWireguardTunnels( + ipv4Gateway: wireguard.ipv4Gateway, + ipv6Gateway: wireguard.ipv6Gateway, + portRanges: wireguard.portRanges, + relays: wireguardRelays + ), + bridge: REST.ServerBridges( + shadowsocks: bridge.shadowsocks, + relays: bridgeRelays + ) + ) + } + + private func apply(overrides: [IPOverride], to relay: T) -> T { + if let override = overrides.first(where: { host in + host.hostname == relay.hostname + }) { + return relay.copyWith( + ipv4AddrIn: override.ipv4Address, + ipv6AddrIn: override.ipv6Address + ) + } + + return relay + } } diff --git a/ios/MullvadREST/Relay/RelaySelector.swift b/ios/MullvadREST/Relay/RelaySelector.swift index bc0378c21789..6fc016d2c7e6 100644 --- a/ios/MullvadREST/Relay/RelaySelector.swift +++ b/ios/MullvadREST/Relay/RelaySelector.swift @@ -306,19 +306,6 @@ public struct RelaySelectorResult: Codable, Equatable { public var location: Location } -public protocol AnyRelay { - var hostname: String { get } - var owned: Bool { get } - var location: String { get } - var provider: String { get } - var weight: UInt64 { get } - var active: Bool { get } - var includeInCountry: Bool { get } -} - -extension REST.ServerRelay: AnyRelay {} -extension REST.BridgeRelay: AnyRelay {} - private struct RelayWithLocation { let relay: T let serverLocation: Location diff --git a/ios/MullvadSettings/IPOverride.swift b/ios/MullvadSettings/IPOverride.swift index 6a1e955c34bd..f5408855ebc7 100644 --- a/ios/MullvadSettings/IPOverride.swift +++ b/ios/MullvadSettings/IPOverride.swift @@ -27,7 +27,7 @@ public struct IPOverride: Codable, Equatable { case ipv6Address = "ipv6_addr_in" } - init(hostname: String, ipv4Address: IPv4Address?, ipv6Address: IPv6Address?) throws { + public init(hostname: String, ipv4Address: IPv4Address?, ipv6Address: IPv6Address?) throws { self.hostname = hostname self.ipv4Address = ipv4Address self.ipv6Address = ipv6Address diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 9910d082236d..3140d356969b 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -508,13 +508,6 @@ 7A6000F92B6273A4001CF0D9 /* AccessMethodViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586C0D7B2B03BDD100E7CDD7 /* AccessMethodViewModel.swift */; }; 7A6000FC2B628DF6001CF0D9 /* ListCellContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6000FB2B628DF6001CF0D9 /* ListCellContentConfiguration.swift */; }; 7A6000FE2B628E9F001CF0D9 /* ListCellContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6000FD2B628E9F001CF0D9 /* ListCellContentView.swift */; }; - 7A5869B72B56B41500640D27 /* IPOverrideTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5869B62B56B41500640D27 /* IPOverrideTextViewController.swift */; }; - 7A5869B92B56E7F000640D27 /* IPOverrideViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5869B82B56E7F000640D27 /* IPOverrideViewControllerDelegate.swift */; }; - 7A5869BC2B56EF3400640D27 /* IPOverrideRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5869BA2B56EE9500640D27 /* IPOverrideRepository.swift */; }; - 7A5869BD2B56EF7300640D27 /* IPOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5869B22B5697AC00640D27 /* IPOverride.swift */; }; - 7A5869BF2B57D0A100640D27 /* IPOverrideStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5869BE2B57D0A100640D27 /* IPOverrideStatus.swift */; }; - 7A5869C12B57D21A00640D27 /* IPOverrideStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5869C02B57D21A00640D27 /* IPOverrideStatusView.swift */; }; - 7A5869C32B5820CE00640D27 /* IPOverrideRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5869C22B5820CE00640D27 /* IPOverrideRepositoryTests.swift */; }; 7A6B4F592AB8412E00123853 /* TunnelMonitorTimings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6B4F582AB8412E00123853 /* TunnelMonitorTimings.swift */; }; 7A6F2FA52AFA3CB2006D0856 /* AccountExpiryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA42AFA3CB2006D0856 /* AccountExpiryTests.swift */; }; 7A6F2FA72AFBB9AE006D0856 /* AccountExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */; }; @@ -567,6 +560,8 @@ 7AD0AA1D2AD6A86700119E10 /* PacketTunnelActorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD0AA192AD69B6E00119E10 /* PacketTunnelActorProtocol.swift */; }; 7AD0AA1F2AD6C8B900119E10 /* URLRequestProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD0AA1E2AD6C8B900119E10 /* URLRequestProxyProtocol.swift */; }; 7AD0AA212AD6CB0000119E10 /* URLRequestProxyStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD0AA202AD6CB0000119E10 /* URLRequestProxyStub.swift */; }; + 7ADCB2D82B6A6EB300C88F89 /* AnyRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADCB2D72B6A6EB300C88F89 /* AnyRelay.swift */; }; + 7ADCB2DA2B6A730400C88F89 /* IPOverrideRepositoryStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADCB2D92B6A730400C88F89 /* IPOverrideRepositoryStub.swift */; }; 7AE044BB2A935726003915D8 /* Routing.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A88DCD02A8FABBE00D2FF0E /* Routing.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7AEF7F1A2AD00F52006FE45D /* AppMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEF7F192AD00F52006FE45D /* AppMessageHandler.swift */; }; 7AF10EB22ADE859200C090B9 /* AlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF10EB12ADE859200C090B9 /* AlertViewController.swift */; }; @@ -1692,13 +1687,6 @@ 7A6000F52B60092F001CF0D9 /* AccessMethodViewModelEditing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessMethodViewModelEditing.swift; sourceTree = ""; }; 7A6000FB2B628DF6001CF0D9 /* ListCellContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCellContentConfiguration.swift; sourceTree = ""; }; 7A6000FD2B628E9F001CF0D9 /* ListCellContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCellContentView.swift; sourceTree = ""; }; - 7A5869B22B5697AC00640D27 /* IPOverride.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverride.swift; sourceTree = ""; }; - 7A5869B62B56B41500640D27 /* IPOverrideTextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideTextViewController.swift; sourceTree = ""; }; - 7A5869B82B56E7F000640D27 /* IPOverrideViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideViewControllerDelegate.swift; sourceTree = ""; }; - 7A5869BA2B56EE9500640D27 /* IPOverrideRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideRepository.swift; sourceTree = ""; }; - 7A5869BE2B57D0A100640D27 /* IPOverrideStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideStatus.swift; sourceTree = ""; }; - 7A5869C02B57D21A00640D27 /* IPOverrideStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideStatusView.swift; sourceTree = ""; }; - 7A5869C22B5820CE00640D27 /* IPOverrideRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideRepositoryTests.swift; sourceTree = ""; }; 7A6B4F582AB8412E00123853 /* TunnelMonitorTimings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorTimings.swift; sourceTree = ""; }; 7A6F2FA42AFA3CB2006D0856 /* AccountExpiryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiryTests.swift; sourceTree = ""; }; 7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiry.swift; sourceTree = ""; }; @@ -1744,6 +1732,8 @@ 7AD0AA1B2AD6A63F00119E10 /* PacketTunnelActorStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelActorStub.swift; sourceTree = ""; }; 7AD0AA1E2AD6C8B900119E10 /* URLRequestProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestProxyProtocol.swift; sourceTree = ""; }; 7AD0AA202AD6CB0000119E10 /* URLRequestProxyStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestProxyStub.swift; sourceTree = ""; }; + 7ADCB2D72B6A6EB300C88F89 /* AnyRelay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyRelay.swift; sourceTree = ""; }; + 7ADCB2D92B6A730400C88F89 /* IPOverrideRepositoryStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideRepositoryStub.swift; sourceTree = ""; }; 7AEF7F192AD00F52006FE45D /* AppMessageHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppMessageHandler.swift; sourceTree = ""; }; 7AF10EB12ADE859200C090B9 /* AlertViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertViewController.swift; sourceTree = ""; }; 7AF10EB32ADE85BC00C090B9 /* RelayFilterCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelayFilterCoordinator.swift; sourceTree = ""; }; @@ -2782,6 +2772,7 @@ F09D04BA2AE95396003D4F89 /* URLSessionStub.swift */, 58165EBD2A262CBB00688EAD /* WgKeyRotationTests.swift */, F0B0E6962AFE6E7E001DC66B /* XCTest+Async.swift */, + 7ADCB2D92B6A730400C88F89 /* IPOverrideRepositoryStub.swift */, ); path = MullvadVPNTests; sourceTree = ""; @@ -3478,6 +3469,7 @@ F0DC779F2B2222D20087F09D /* Relay */ = { isa = PBXGroup; children = ( + 7ADCB2D72B6A6EB300C88F89 /* AnyRelay.swift */, 585DA87626B024A600B8C587 /* CachedRelays.swift */, F0DDE4272B220A15006B57A7 /* Haversine.swift */, F0DDE4292B220A15006B57A7 /* Midpoint.swift */, @@ -4477,6 +4469,7 @@ A90763B42B2857D50045ADF0 /* NWConnection+Extensions.swift in Sources */, F06045EA2B23217E00B2D37A /* ShadowsocksTransport.swift in Sources */, 06799AFC28F98EE300ACD94E /* AddressCache.swift in Sources */, + 7ADCB2D82B6A6EB300C88F89 /* AnyRelay.swift in Sources */, 06799AF028F98E4800ACD94E /* REST.swift in Sources */, 06799ADF28F98E4800ACD94E /* RESTDevicesProxy.swift in Sources */, 06799ADA28F98E4800ACD94E /* RESTResponseHandler.swift in Sources */, @@ -4642,6 +4635,7 @@ A9A5FA2F2ACB05160083449F /* FixedWidthIntegerArithmeticsTests.swift in Sources */, A9A5FA302ACB05160083449F /* InputTextFormatterTests.swift in Sources */, F0B0E6972AFE6E7E001DC66B /* XCTest+Async.swift in Sources */, + 7ADCB2DA2B6A730400C88F89 /* IPOverrideRepositoryStub.swift in Sources */, A9A5FA312ACB05160083449F /* MockFileCache.swift in Sources */, A9A5FA322ACB05160083449F /* RelayCacheTests.swift in Sources */, A9A5FA332ACB05160083449F /* RelaySelectorTests.swift in Sources */, diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift index 7a1ddb1358aa..51eaa0ab05ab 100644 --- a/ios/MullvadVPN/AppDelegate.swift +++ b/ios/MullvadVPN/AppDelegate.swift @@ -66,7 +66,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD setUpProxies(containerURL: containerURL) - let relayCache = RelayCache(cacheDirectory: containerURL) + let relayCache = RelayCache(cacheDirectory: containerURL, ipOverrideRepository: IPOverrideRepository()) relayCacheTracker = RelayCacheTracker(relayCache: relayCache, application: application, apiProxy: apiProxy) addressCacheTracker = AddressCacheTracker(application: application, apiProxy: apiProxy, store: addressCache) diff --git a/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideViewController.swift b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideViewController.swift index acb5d1a14058..f0c0f4e7c67d 100644 --- a/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideViewController.swift +++ b/ios/MullvadVPN/Coordinators/Settings/IPOverride/IPOverrideViewController.swift @@ -16,8 +16,6 @@ class IPOverrideViewController: UIViewController { weak var delegate: IPOverrideViewControllerDelegate? - weak var delegate: IPOverrideViewControllerDelegate? - private lazy var containerView: UIStackView = { let view = UIStackView() view.axis = .vertical diff --git a/ios/MullvadVPNTests/IPOverrideRepositoryStub.swift b/ios/MullvadVPNTests/IPOverrideRepositoryStub.swift new file mode 100644 index 000000000000..633bc44bdb5f --- /dev/null +++ b/ios/MullvadVPNTests/IPOverrideRepositoryStub.swift @@ -0,0 +1,33 @@ +// +// IPOverrideRepositoryStub.swift +// MullvadVPNTests +// +// Created by Jon Petersson on 2024-01-31. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import MullvadSettings + +struct IPOverrideRepositoryStub: IPOverrideRepositoryProtocol { + let overrides: [IPOverride] + + init(overrides: [IPOverride] = []) { + self.overrides = overrides + } + + func add(_ overrides: [IPOverride]) {} + + func fetchAll() -> [IPOverride] { + overrides + } + + func fetchByHostname(_ hostname: String) -> IPOverride? { + nil + } + + func deleteAll() {} + + func parse(data: Data) throws -> [IPOverride] { + overrides + } +} diff --git a/ios/MullvadVPNTests/RelayCacheTests.swift b/ios/MullvadVPNTests/RelayCacheTests.swift index 153f4738ee37..4c21a3116ffe 100644 --- a/ios/MullvadVPNTests/RelayCacheTests.swift +++ b/ios/MullvadVPNTests/RelayCacheTests.swift @@ -7,49 +7,142 @@ // @testable import MullvadREST +import MullvadSettings +import Network import XCTest final class RelayCacheTests: XCTestCase { - func testCanReadCache() throws { + func testReadCache() throws { let fileCache = MockFileCache( initialState: .exists(CachedRelays(relays: .mock(), updatedAt: .distantPast)) ) - let cache = RelayCache(fileCache: fileCache) + let cache = RelayCache(fileCache: fileCache, ipOverrideRepository: IPOverrideRepositoryStub()) let relays = try XCTUnwrap(cache.read()) XCTAssertEqual(fileCache.getState(), .exists(relays)) } - func testCanWriteCache() throws { + func testWriteCache() throws { let fileCache = MockFileCache( initialState: .exists(CachedRelays(relays: .mock(), updatedAt: .distantPast)) ) - let cache = RelayCache(fileCache: fileCache) + let cache = RelayCache(fileCache: fileCache, ipOverrideRepository: IPOverrideRepositoryStub()) let newCachedRelays = CachedRelays(relays: .mock(), updatedAt: Date()) try cache.write(record: newCachedRelays) XCTAssertEqual(fileCache.getState(), .exists(newCachedRelays)) } - func testCanReadPrebundledRelaysWhenNoCacheIsStored() throws { + func testReadPrebundledRelaysWhenNoCacheIsStored() throws { let fileCache = MockFileCache(initialState: .fileNotFound) - let cache = RelayCache(fileCache: fileCache) + let cache = RelayCache(fileCache: fileCache, ipOverrideRepository: IPOverrideRepositoryStub()) XCTAssertNoThrow(try cache.read()) } + + func testOverrideServerRelayInCache() throws { + let relays = [ + mockServerRelay.copyWith(ipv4AddrIn: .loopback, ipv6AddrIn: .broadcast), + mockServerRelay, + ] + + let fileCache = MockFileCache( + initialState: .exists(CachedRelays(relays: .mock(serverRelays: relays), updatedAt: .distantPast)) + ) + + let override = try IPOverride(hostname: "Host 1", ipv4Address: .loopback, ipv6Address: .broadcast) + + let cache = RelayCache( + fileCache: fileCache, + ipOverrideRepository: IPOverrideRepositoryStub(overrides: [override]) + ) + + let storedCache = try cache.read() + + // Assert that relay was overridden. + let host1 = storedCache.relays.wireguard.relays.first + XCTAssertEqual(host1?.ipv4AddrIn, .loopback) + XCTAssertEqual(host1?.ipv6AddrIn, .broadcast) + + // Assert that relay was NOT overridden. + let host2 = storedCache.relays.wireguard.relays.last + XCTAssertEqual(host2?.ipv4AddrIn, .any) + XCTAssertEqual(host2?.ipv6AddrIn, .any) + } + + func testOverrideBridgeRelayInCache() throws { + let relays = [ + mockBridgeRelay.copyWith(ipv4AddrIn: .loopback), + mockBridgeRelay, + ] + + let fileCache = MockFileCache( + initialState: .exists(CachedRelays(relays: .mock(brideRelays: relays), updatedAt: .distantPast)) + ) + + let override = try IPOverride(hostname: "Host 1", ipv4Address: .loopback, ipv6Address: .broadcast) + + let cache = RelayCache( + fileCache: fileCache, + ipOverrideRepository: IPOverrideRepositoryStub(overrides: [override]) + ) + + let storedCache = try cache.read() + + // Assert that relay was overridden. + let host1 = storedCache.relays.bridge.relays.first + XCTAssertEqual(host1?.ipv4AddrIn, .loopback) + + // Assert that relay was NOT overridden. + let host2 = storedCache.relays.bridge.relays.last + XCTAssertEqual(host2?.ipv4AddrIn, .any) + } +} + +extension RelayCacheTests { + var mockServerRelay: REST.ServerRelay { + REST.ServerRelay( + hostname: "", + active: true, + owned: true, + location: "", + provider: "", + weight: 0, + ipv4AddrIn: .any, + ipv6AddrIn: .any, + publicKey: Data(), + includeInCountry: true + ) + } + + var mockBridgeRelay: REST.BridgeRelay { + REST.BridgeRelay( + hostname: "", + active: true, + owned: true, + location: "", + provider: "", + ipv4AddrIn: .any, + weight: 0, + includeInCountry: true + ) + } } private extension REST.ServerRelaysResponse { - static func mock() -> Self { + static func mock( + serverRelays: [REST.ServerRelay] = [], + brideRelays: [REST.BridgeRelay] = [] + ) -> Self { REST.ServerRelaysResponse( locations: [:], wireguard: REST.ServerWireguardTunnels( ipv4Gateway: .loopback, ipv6Gateway: .loopback, portRanges: [], - relays: [] + relays: serverRelays ), - bridge: REST.ServerBridges(shadowsocks: [], relays: []) + bridge: REST.ServerBridges(shadowsocks: [], relays: brideRelays) ) } } diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 3cbf52c6756a..03a44df1c296 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -34,7 +34,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { let addressCache = REST.AddressCache(canWriteToCache: false, cacheDirectory: containerURL) addressCache.loadFromFile() - let relayCache = RelayCache(cacheDirectory: containerURL) + let relayCache = RelayCache(cacheDirectory: containerURL, ipOverrideRepository: IPOverrideRepository()) let urlSession = REST.makeURLSession() let urlSessionTransport = URLSessionTransport(urlSession: urlSession)