summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadREST/Relay/ObfuscationMethodSelector.swift
blob: 6bae2823689682fe30d506a896ee4813b26b0a53 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//
//  ObfuscationMethodSelector.swift
//  MullvadREST
//
//  Created by Jon Petersson on 2024-11-01.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import MullvadSettings
import MullvadTypes

public protocol ObfuscationProviding {
    func bypassUnsupportedObfuscation(_: WireGuardObfuscationState) -> WireGuardObfuscationState
}

public struct ObfuscationMethodSelector {
    /// This retry logic used is explained at the following link:
    /// https://github.com/mullvad/mullvadvpn-app/blob/main/docs/relay-selector.md#default-constraints-for-tunnel-endpoints
    ///
    /// - Note: This method should never return `.automatic`.
    public static func obfuscationMethodBy(
        connectionAttemptCount: UInt,
        tunnelSettings: LatestTunnelSettings,
        obfuscationBypass: any ObfuscationProviding
    ) -> WireGuardObfuscationState {
        if tunnelSettings.wireGuardObfuscation.state == .automatic {
            let selectedObfuscation: WireGuardObfuscationState =
                if connectionAttemptCount.isOrdered(
                    nth: 2,
                    forEverySetOf: 4
                ) {
                    .shadowsocks
                } else if connectionAttemptCount.isOrdered(nth: 3, forEverySetOf: 4) {
                    .quic
                } else if connectionAttemptCount.isOrdered(nth: 4, forEverySetOf: 4) {
                    .udpOverTcp
                } else {
                    .off
                }
            return obfuscationBypass.bypassUnsupportedObfuscation(selectedObfuscation)
        }
        return tunnelSettings.wireGuardObfuscation.state
    }
}

public struct UnsupportedObfuscationProvider: ObfuscationProviding {
    let relayConstraint: RelayConstraint<UserSelectedRelays>
    let relays: REST.ServerRelaysResponse
    let filterConstraint: RelayConstraint<RelayFilter>
    let daitaEnabled: Bool

    public init(
        relayConstraint: RelayConstraint<UserSelectedRelays>,
        relays: REST.ServerRelaysResponse,
        filterConstraint: RelayConstraint<RelayFilter>,
        daitaEnabled: Bool
    ) {
        self.relayConstraint = relayConstraint
        self.relays = relays
        self.filterConstraint = filterConstraint
        self.daitaEnabled = daitaEnabled
    }

    public func bypassUnsupportedObfuscation(_ obfuscation: WireGuardObfuscationState) -> WireGuardObfuscationState {
        guard obfuscation != .off else { return .off }
        do {
            let candidates = try RelaySelector.WireGuard.findCandidates(
                by: relayConstraint,
                in: relays,
                filterConstraint: filterConstraint,
                daitaEnabled: daitaEnabled
            )
            return candidates.isEmpty ? .udpOverTcp : obfuscation
        } catch {
            return .udpOverTcp
        }
    }
}

public struct IdentityObfuscationProvider: ObfuscationProviding {
    public init() {}
    public func bypassUnsupportedObfuscation(_ obfuscation: WireGuardObfuscationState) -> WireGuardObfuscationState {
        obfuscation
    }
}