diff options
| author | Andrew Bulhak <andrew.bulhak@mullvad.net> | 2025-06-13 12:06:00 +0200 |
|---|---|---|
| committer | Andrew Bulhak <andrew.bulhak@mullvad.net> | 2025-06-17 15:29:09 +0200 |
| commit | 6abe9daec6327e0961fc3f7291f51a4ba6bf22a5 (patch) | |
| tree | 8674c6087af1f18918a9170b96556e785c47df8a | |
| parent | c1f8a8d6975c175dadfa53cbef5259dd82debbf2 (diff) | |
| download | mullvadvpn-6abe9daec6327e0961fc3f7291f51a4ba6bf22a5.tar.xz mullvadvpn-6abe9daec6327e0961fc3f7291f51a4ba6bf22a5.zip | |
Add QUIC/Masque fields and Features structure to ServerRelay
7 files changed, 172 insertions, 15 deletions
diff --git a/ios/MullvadMockData/MullvadREST/ServerRelaysResponse+Stubs.swift b/ios/MullvadMockData/MullvadREST/ServerRelaysResponse+Stubs.swift index 3cd20183d2..ab604c7a0a 100644 --- a/ios/MullvadMockData/MullvadREST/ServerRelaysResponse+Stubs.swift +++ b/ios/MullvadMockData/MullvadREST/ServerRelaysResponse+Stubs.swift @@ -88,7 +88,10 @@ public enum ServerRelaysResponseStubs { publicKey: PrivateKey().publicKey.rawValue, includeInCountry: true, daita: true, - shadowsocksExtraAddrIn: ["0.0.0.0"] + shadowsocksExtraAddrIn: ["0.0.0.0"], + quicHostname: nil, + masqueExtraAddrIn: nil, + features: .init(daita: .init(), quic: nil) ), REST.ServerRelay( hostname: "se10-wireguard", @@ -102,7 +105,10 @@ public enum ServerRelaysResponseStubs { publicKey: PrivateKey().publicKey.rawValue, includeInCountry: true, daita: false, - shadowsocksExtraAddrIn: ["0.0.0.0"] + shadowsocksExtraAddrIn: ["0.0.0.0"], + quicHostname: nil, + masqueExtraAddrIn: nil, + features: nil ), REST.ServerRelay( hostname: "se2-wireguard", @@ -116,7 +122,10 @@ public enum ServerRelaysResponseStubs { publicKey: PrivateKey().publicKey.rawValue, includeInCountry: true, daita: false, - shadowsocksExtraAddrIn: ["0.0.0.0"] + shadowsocksExtraAddrIn: ["0.0.0.0"], + quicHostname: nil, + masqueExtraAddrIn: nil, + features: nil ), REST.ServerRelay( hostname: "se6-wireguard", @@ -130,7 +139,10 @@ public enum ServerRelaysResponseStubs { publicKey: PrivateKey().publicKey.rawValue, includeInCountry: true, daita: false, - shadowsocksExtraAddrIn: ["0.0.0.0"] + shadowsocksExtraAddrIn: ["0.0.0.0"], + quicHostname: nil, + masqueExtraAddrIn: nil, + features: nil ), REST.ServerRelay( hostname: "us-dal-wg-001", @@ -144,7 +156,10 @@ public enum ServerRelaysResponseStubs { publicKey: PrivateKey().publicKey.rawValue, includeInCountry: true, daita: false, - shadowsocksExtraAddrIn: ["0.0.0.0"] + shadowsocksExtraAddrIn: ["0.0.0.0"], + quicHostname: nil, + masqueExtraAddrIn: nil, + features: nil ), REST.ServerRelay( hostname: "us-nyc-wg-301", @@ -158,7 +173,10 @@ public enum ServerRelaysResponseStubs { publicKey: PrivateKey().publicKey.rawValue, includeInCountry: true, daita: true, - shadowsocksExtraAddrIn: nil + shadowsocksExtraAddrIn: nil, + quicHostname: nil, + masqueExtraAddrIn: nil, + features: .init(daita: .init(), quic: nil) ), REST.ServerRelay( hostname: "us-nyc-wg-302", @@ -172,7 +190,10 @@ public enum ServerRelaysResponseStubs { publicKey: PrivateKey().publicKey.rawValue, includeInCountry: true, daita: true, - shadowsocksExtraAddrIn: nil + shadowsocksExtraAddrIn: nil, + quicHostname: nil, + masqueExtraAddrIn: nil, + features: .init(daita: .init(), quic: nil) ), ], shadowsocksPortRanges: shadowsocksPortRanges diff --git a/ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift b/ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift index 047f29ba9b..48c292e8e1 100644 --- a/ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift +++ b/ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift @@ -50,7 +50,23 @@ extension REST { } } + // swiftlint:disable nesting public struct ServerRelay: Codable, Equatable, Sendable { + public struct Features: Codable, Equatable, Sendable { + public struct DAITA: Codable, Equatable, Sendable { + // this structure intentionally left blank + } + + public struct QUIC: Codable, Equatable, Sendable { + public let addrIn: [String] + public let domain: String + public let token: String + } + + public let daita: DAITA? + public let quic: QUIC? + } + public let hostname: String public let active: Bool public let owned: Bool @@ -63,6 +79,9 @@ extension REST { public let includeInCountry: Bool public let daita: Bool? public let shadowsocksExtraAddrIn: [String]? + public let quicHostname: String? + public let masqueExtraAddrIn: [String]? + public let features: Features? public func override(ipv4AddrIn: IPv4Address?, ipv6AddrIn: IPv6Address?) -> Self { ServerRelay( @@ -86,10 +105,14 @@ extension REST { default: true } - } + }, + quicHostname: quicHostname, + masqueExtraAddrIn: masqueExtraAddrIn, + features: features ) } + // this is for the legacy DAITA flag, which will be deprecated in favour of a DAITA structure under Features public func override(daita: Bool) -> Self { ServerRelay( hostname: hostname, @@ -103,11 +126,40 @@ extension REST { publicKey: publicKey, includeInCountry: includeInCountry, daita: daita, - shadowsocksExtraAddrIn: shadowsocksExtraAddrIn + shadowsocksExtraAddrIn: shadowsocksExtraAddrIn, + quicHostname: quicHostname, + masqueExtraAddrIn: masqueExtraAddrIn, + features: features + ) + } + + public func override(features: ServerRelay.Features) -> Self { + ServerRelay( + hostname: hostname, + active: active, + owned: owned, + location: location, + provider: provider, + weight: weight, + ipv4AddrIn: ipv4AddrIn, + ipv6AddrIn: ipv6AddrIn, + publicKey: publicKey, + includeInCountry: includeInCountry, + daita: daita, + shadowsocksExtraAddrIn: shadowsocksExtraAddrIn, + quicHostname: quicHostname, + masqueExtraAddrIn: masqueExtraAddrIn, + features: features ) } + + public var hasDaita: Bool { + (features?.daita != nil) || daita == true + } } + // swiftlint:enable nesting + public struct ServerWireguardTunnels: Codable, Equatable, Sendable { public let ipv4Gateway: IPv4Address public let ipv6Gateway: IPv6Address diff --git a/ios/MullvadRESTTests/ServerRelayTests.swift b/ios/MullvadRESTTests/ServerRelayTests.swift index 9dea7f62fc..5c4cd89659 100644 --- a/ios/MullvadRESTTests/ServerRelayTests.swift +++ b/ios/MullvadRESTTests/ServerRelayTests.swift @@ -3,6 +3,81 @@ import Network import XCTest class ServerRelayTests: XCTestCase { + // swiftlint:disable:next function_body_length + func testDecodeFronJSON() throws { + let json = """ + { + "active": true, + "hostname": "us-was-wg-002", + "quic_hostname": "cat.pictures.com", + "include_in_country": true, + "ipv4_addr_in": "185.213.193.127", + "ipv6_addr_in": "2604:980:1002:11::f101", + "location": "us-was", + "owned": false, + "provider": "Zenlayer", + "public_key": "2AvJGG4MJfnJMRSR6kcha9FZMMkhJM/AtktI5DSESSI=", + "shadowsocks_extra_addr_in": [ + "185.213.193.139" + ], + "masque_extra_addr_in": [ + "1.2.3.4", + "::1", + ], + "stboot": true, + "weight": 100, + "daita": true, + "features": { + "daita": {}, + "quic": { + "addr_in": [ + "45.130.118.209" + ], + "domain": "se-got-wg-881.blockerad.eu", + "token": "test" + } + } + } + """ + + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + let value = try decoder.decode(REST.ServerRelay.self, from: json.data(using: .utf8)!) + XCTAssertEqual(value, REST.ServerRelay( + hostname: "us-was-wg-002", + active: true, + owned: false, + location: .init(rawValue: "us-was")!, + provider: "Zenlayer", + weight: 100, + ipv4AddrIn: IPv4Address("185.213.193.127")!, + ipv6AddrIn: IPv6Address("2604:980:1002:11::f101")!, + publicKey: Data(base64Encoded: "2AvJGG4MJfnJMRSR6kcha9FZMMkhJM/AtktI5DSESSI=")!, + includeInCountry: true, + daita: true, + shadowsocksExtraAddrIn: [ + "185.213.193.139", + ], + quicHostname: "cat.pictures.com", + masqueExtraAddrIn: ["1.2.3.4", "::1"], + features: .init( + daita: .init(), + quic: .init( + addrIn: ["45.130.118.209"], + domain: "se-got-wg-881.blockerad.eu", + token: "test" + ) + ) + )) + } + + func testCheckForDaitaWorksFromFeatures() { + let relayWithDaitaFeature = mockServerRelay.override(features: .init(daita: .init(), quic: nil)) + let relayWithoutDaitaFeature = mockServerRelay + XCTAssertTrue(relayWithDaitaFeature.hasDaita) + XCTAssertFalse(relayWithoutDaitaFeature.hasDaita) + } + func testOverrideIPv4AddrIn() throws { let overrideRelay: REST.ServerRelay = self.mockServerRelay.override( ipv4AddrIn: .loopback, @@ -75,7 +150,10 @@ class ServerRelayTests: XCTestCase { publicKey: Data(), includeInCountry: true, daita: false, - shadowsocksExtraAddrIn: shadowSocksExtraAddrIn + shadowsocksExtraAddrIn: shadowSocksExtraAddrIn, + quicHostname: nil, + masqueExtraAddrIn: nil, + features: nil ) } } diff --git a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift index e1fd209f95..f640a34721 100644 --- a/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift +++ b/ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift @@ -85,12 +85,12 @@ final class RelayCacheTracker: RelayCacheTrackerProtocol, @unchecked Sendable { /// > 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 + let daitaPropertyMissing = cachedRelays.relays.wireguard.relays.first { $0.hasDaita } == nil // If the cached relays already have daita information, this fix is not necessary guard daitaPropertyMissing else { return } let preBundledRelays = try cache.readPrebundledRelays().relays - let preBundledDaitaRelays = preBundledRelays.wireguard.relays.filter { $0.daita == true } + let preBundledDaitaRelays = preBundledRelays.wireguard.relays.filter { $0.hasDaita } var cachedRelaysWithFixedDaita = cachedRelays.relays.wireguard.relays // For each daita enabled relay in the prebundled relays diff --git a/ios/MullvadVPN/View controllers/RelayFilter/FilterDescriptor.swift b/ios/MullvadVPN/View controllers/RelayFilter/FilterDescriptor.swift index 666fd12c98..f50dc04c0a 100644 --- a/ios/MullvadVPN/View controllers/RelayFilter/FilterDescriptor.swift +++ b/ios/MullvadVPN/View controllers/RelayFilter/FilterDescriptor.swift @@ -30,7 +30,7 @@ struct FilterDescriptor { return hasSufficientRelays() } else if isSmartRoutingEnabled { // Smart Routing mode: Enabled only if there is NO daita server in the exit relays - let isSmartRoutingNeeded = !relayFilterResult.exitRelays.contains { $0.relay.daita == true } + let isSmartRoutingNeeded = !relayFilterResult.exitRelays.contains { $0.relay.hasDaita } return isSmartRoutingNeeded ? hasSufficientRelays() : true } else { // Single-hop mode: The filter is enabled if at least one available exit relay exists. diff --git a/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorTests.swift b/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorTests.swift index fcc71b4653..11b6702a2e 100644 --- a/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorTests.swift +++ b/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorTests.swift @@ -324,7 +324,10 @@ extension RelaySelectorTests { publicKey: PrivateKey().publicKey.rawValue, includeInCountry: true, daita: true, - shadowsocksExtraAddrIn: nil + shadowsocksExtraAddrIn: nil, + quicHostname: nil, + masqueExtraAddrIn: nil, + features: nil ), ], shadowsocksPortRanges: [] diff --git a/ios/MullvadVPNTests/MullvadSettings/IPOverrideWrapperTests.swift b/ios/MullvadVPNTests/MullvadSettings/IPOverrideWrapperTests.swift index 9b4b2313af..ff45f645cf 100644 --- a/ios/MullvadVPNTests/MullvadSettings/IPOverrideWrapperTests.swift +++ b/ios/MullvadVPNTests/MullvadSettings/IPOverrideWrapperTests.swift @@ -85,7 +85,10 @@ extension IPOverrideWrapperTests { publicKey: Data(), includeInCountry: true, daita: false, - shadowsocksExtraAddrIn: nil + shadowsocksExtraAddrIn: nil, + quicHostname: nil, + masqueExtraAddrIn: nil, + features: nil ) } |
