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
}
}
}
|