summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrew Bulhak <andrew.bulhak@mullvad.net>2025-06-13 12:06:00 +0200
committerAndrew Bulhak <andrew.bulhak@mullvad.net>2025-06-17 15:29:09 +0200
commit6abe9daec6327e0961fc3f7291f51a4ba6bf22a5 (patch)
tree8674c6087af1f18918a9170b96556e785c47df8a
parentc1f8a8d6975c175dadfa53cbef5259dd82debbf2 (diff)
downloadmullvadvpn-6abe9daec6327e0961fc3f7291f51a4ba6bf22a5.tar.xz
mullvadvpn-6abe9daec6327e0961fc3f7291f51a4ba6bf22a5.zip
Add QUIC/Masque fields and Features structure to ServerRelay
-rw-r--r--ios/MullvadMockData/MullvadREST/ServerRelaysResponse+Stubs.swift35
-rw-r--r--ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift56
-rw-r--r--ios/MullvadRESTTests/ServerRelayTests.swift80
-rw-r--r--ios/MullvadVPN/RelayCacheTracker/RelayCacheTracker.swift4
-rw-r--r--ios/MullvadVPN/View controllers/RelayFilter/FilterDescriptor.swift2
-rw-r--r--ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorTests.swift5
-rw-r--r--ios/MullvadVPNTests/MullvadSettings/IPOverrideWrapperTests.swift5
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
)
}