summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadRustRuntime/TunnelObfuscator.swift
blob: 6f74089265161ebca93847acb2b8f84c9766f0f5 (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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//
//  TunnelObfuscator.swift
//  TunnelObfuscation
//
//  Created by pronebird on 19/06/2023.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadRustRuntimeProxy
import MullvadTypes
import Network

public enum TunnelObfuscationProtocol {
    case udpOverTcp
    case shadowsocks
    case quic(hostname: String, token: String)
}

public protocol TunnelObfuscation {
    init(remoteAddress: IPAddress, tcpPort: UInt16, obfuscationProtocol: TunnelObfuscationProtocol)
    func start()
    func stop()
    var localUdpPort: UInt16 { get }
    var remotePort: UInt16 { get }

    var transportLayer: TransportLayer { get }
}

/// Class that implements obfuscation by accepting traffic on a local port and proxying it to the remote endpoint.
///
/// The obfuscation happens either by wrapping UDP traffic into TCP traffic, or by using a local shadowsocks server
/// to encrypt the UDP traffic sent.
public final class TunnelObfuscator: TunnelObfuscation {
    private let stateLock = NSLock()
    private let remoteAddress: IPAddress
    internal let tcpPort: UInt16
    internal let obfuscationProtocol: TunnelObfuscationProtocol

    private var proxyHandle = ProxyHandle(context: nil, port: 0)
    private var isStarted = false

    /// Returns local UDP port used by proxy and bound to 127.0.0.1 (IPv4).
    /// The returned value can be zero if obfuscator hasn't started yet.
    public var localUdpPort: UInt16 {
        return stateLock.withLock { proxyHandle.port }
    }

    public var remotePort: UInt16 { tcpPort }

    public var transportLayer: TransportLayer {
        switch obfuscationProtocol {
        case .udpOverTcp:
            .tcp
        case .shadowsocks:
            .udp
        case .quic:
            .udp
        }
    }

    /// Initialize tunnel obfuscator with remote server address and TCP port where udp2tcp is running.
    public init(remoteAddress: IPAddress, tcpPort: UInt16, obfuscationProtocol: TunnelObfuscationProtocol) {
        self.remoteAddress = remoteAddress
        self.tcpPort = tcpPort
        self.obfuscationProtocol = obfuscationProtocol
    }

    deinit {
        stop()
    }

    public func start() {
        stateLock.withLock {
            guard !isStarted else { return }

            let result = withUnsafeMutablePointer(to: &proxyHandle) { proxyHandlePointer in
                let addressData = remoteAddress.rawValue

                return switch obfuscationProtocol {
                case .udpOverTcp:
                    start_udp2tcp_obfuscator_proxy(
                        addressData.map { $0 },
                        UInt(addressData.count),
                        tcpPort,
                        proxyHandlePointer
                    )
                case .shadowsocks:
                    start_shadowsocks_obfuscator_proxy(
                        addressData.map { $0 },
                        UInt(addressData.count),
                        tcpPort,
                        proxyHandlePointer
                    )
                case let .quic(hostname, token):
                    start_quic_obfuscator_proxy(
                        addressData.map { $0 },
                        UInt(addressData.count),
                        tcpPort,
                        hostname,
                        token,
                        proxyHandlePointer
                    )
                }
            }

            assert(result == 0)

            isStarted = true
        }
    }

    public func stop() {
        stateLock.withLock {
            guard isStarted else { return }

            let result = withUnsafeMutablePointer(to: &proxyHandle) {
                stop_tunnel_obfuscator_proxy($0)
            }

            assert(result == 0)

            isStarted = false
        }
    }
}