diff options
| author | Bug Magnet <marco.nikic@mullvad.net> | 2024-10-30 17:10:43 +0100 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2024-11-13 15:41:48 +0100 |
| commit | 26b75ba407b6099ae815775ac921024c6708d055 (patch) | |
| tree | 3cf4c06914d66baa6acce0f8298fb77fdcd21c85 /ios/MullvadRustRuntimeTests | |
| parent | e763bf54a1d49f1452d6328acb2b2aaea26beae9 (diff) | |
| download | mullvadvpn-26b75ba407b6099ae815775ac921024c6708d055.tar.xz mullvadvpn-26b75ba407b6099ae815775ac921024c6708d055.zip | |
Add shadowsocks obfuscation as an option
Diffstat (limited to 'ios/MullvadRustRuntimeTests')
| -rw-r--r-- | ios/MullvadRustRuntimeTests/TCPConnection.swift | 6 | ||||
| -rw-r--r-- | ios/MullvadRustRuntimeTests/TunnelObfuscationTests.swift | 48 | ||||
| -rw-r--r-- | ios/MullvadRustRuntimeTests/UDPConnection.swift | 35 | ||||
| -rw-r--r-- | ios/MullvadRustRuntimeTests/UnsafeListener.swift (renamed from ios/MullvadRustRuntimeTests/TCPUnsafeListener.swift) | 21 |
4 files changed, 90 insertions, 20 deletions
diff --git a/ios/MullvadRustRuntimeTests/TCPConnection.swift b/ios/MullvadRustRuntimeTests/TCPConnection.swift index c8543b4879..dd9b58c0d0 100644 --- a/ios/MullvadRustRuntimeTests/TCPConnection.swift +++ b/ios/MullvadRustRuntimeTests/TCPConnection.swift @@ -11,14 +11,16 @@ import Network /// Minimal implementation of TCP connection capable of receiving data. /// > Warning: Do not use this implementation in production code. See the warning in `start()`. -class TCPConnection { +class TCPConnection: Connection { private let dispatchQueue = DispatchQueue(label: "TCPConnection") private let nwConnection: NWConnection - init(nwConnection: NWConnection) { + required init(nwConnection: NWConnection) { self.nwConnection = nwConnection } + static var connectionParameters: NWParameters { .tcp } + deinit { cancel() } diff --git a/ios/MullvadRustRuntimeTests/TunnelObfuscationTests.swift b/ios/MullvadRustRuntimeTests/TunnelObfuscationTests.swift index 7372842c8d..84c9ca8f4f 100644 --- a/ios/MullvadRustRuntimeTests/TunnelObfuscationTests.swift +++ b/ios/MullvadRustRuntimeTests/TunnelObfuscationTests.swift @@ -11,16 +11,20 @@ import Network import XCTest final class TunnelObfuscationTests: XCTestCase { - func testRunningObfuscatorProxy() async throws { + func testRunningUdpOverTcpObfuscatorProxy() async throws { // Each packet is prefixed with u16 that contains a payload length. let preambleLength = MemoryLayout<UInt16>.size let markerData = Data([109, 117, 108, 108, 118, 97, 100]) let packetLength = markerData.count + preambleLength - let tcpListener = try TCPUnsafeListener() + let tcpListener = try UnsafeListener<TCPConnection>() try await tcpListener.start() - let obfuscator = UDPOverTCPObfuscator(remoteAddress: IPv4Address.loopback, tcpPort: tcpListener.listenPort) + let obfuscator = TunnelObfuscator( + remoteAddress: IPv4Address.loopback, + tcpPort: tcpListener.listenPort, + obfuscationProtocol: .udpOverTcp + ) obfuscator.start() // Accept incoming connections @@ -46,4 +50,42 @@ final class TunnelObfuscationTests: XCTestCase { XCTAssert(receivedData.count == packetLength) XCTAssertEqual(receivedData[preambleLength...], markerData) } + + func testRunningShadowsocksObfuscatorProxy() async throws { + let markerData = Data([109, 117, 108, 108, 118, 97, 100]) + + let localUdpListener = try UnsafeListener<UDPConnection>() + try await localUdpListener.start() + + let localObfuscator = TunnelObfuscator( + remoteAddress: IPv4Address.loopback, + tcpPort: localUdpListener.listenPort, + obfuscationProtocol: .shadowsocks + ) + localObfuscator.start() + + // Accept incoming connections + let localConnectionDataTask = Task { + for await obfuscatedConnection in localUdpListener.newConnections { + try await obfuscatedConnection.start() + + let readDatagram = try await obfuscatedConnection.readSingleDatagram() + // Write into the connection the unencrypted payload that was just read + try await obfuscatedConnection.sendData(readDatagram) + return readDatagram + } + throw POSIXError(.ECANCELED) + } + + // Send marker data over UDP + let connection = UDPConnection(remote: IPv4Address.loopback, port: localObfuscator.localUdpPort) + try await connection.start() + try await connection.sendData(markerData) + let readDataFromObfuscator = try await connection.readSingleDatagram() + + // As the connection data is encrypted and this test does not run a shadowsocks server to decrypt the payload + // The connection from the local UDP listener writes back what it read from the obfuscator, unencrypted + _ = try await localConnectionDataTask.value + XCTAssertEqual(readDataFromObfuscator, markerData) + } } diff --git a/ios/MullvadRustRuntimeTests/UDPConnection.swift b/ios/MullvadRustRuntimeTests/UDPConnection.swift index f0886e5fb5..563f3e6c4c 100644 --- a/ios/MullvadRustRuntimeTests/UDPConnection.swift +++ b/ios/MullvadRustRuntimeTests/UDPConnection.swift @@ -9,20 +9,31 @@ import Foundation import Network +protocol Connection { + init(nwConnection: NWConnection) + static var connectionParameters: NWParameters { get } +} + /// Minimal implementation of UDP connection capable of sending data. /// > Warning: Do not use this implementation in production code. See the warning in `start()`. -class UDPConnection { +class UDPConnection: Connection { private let dispatchQueue = DispatchQueue(label: "UDPConnection") private let nwConnection: NWConnection - init(remote: IPAddress, port: UInt16) { - nwConnection = NWConnection( + convenience init(remote: IPAddress, port: UInt16) { + self.init(nwConnection: NWConnection( host: NWEndpoint.Host("\(remote)"), port: NWEndpoint.Port(integerLiteral: port), using: .udp - ) + )) + } + + required init(nwConnection: NWConnection) { + self.nwConnection = nwConnection } + static var connectionParameters: NWParameters { .udp } + deinit { cancel() } @@ -58,6 +69,22 @@ class UDPConnection { nwConnection.cancel() } + func readSingleDatagram() async throws -> Data { + return try await withCheckedThrowingContinuation { continuation in + nwConnection.receiveMessage { data, _, _, error in + guard let data else { + continuation.resume(throwing: POSIXError(.EIO)) + return + } + if let error { + continuation.resume(throwing: error) + return + } + continuation.resume(with: .success(data)) + } + } + } + func sendData(_ data: Data) async throws { return try await withCheckedThrowingContinuation { continuation in nwConnection.send(content: data, completion: .contentProcessed { error in diff --git a/ios/MullvadRustRuntimeTests/TCPUnsafeListener.swift b/ios/MullvadRustRuntimeTests/UnsafeListener.swift index d01fc29dbb..2ccf0c1aaf 100644 --- a/ios/MullvadRustRuntimeTests/TCPUnsafeListener.swift +++ b/ios/MullvadRustRuntimeTests/UnsafeListener.swift @@ -1,5 +1,5 @@ // -// TCPUnsafeListener.swift +// UnsafeListener.swift // MullvadRustRuntimeTests // // Created by pronebird on 27/06/2023. @@ -8,25 +8,24 @@ import Network -/// Minimal implementation of a TCP listener. /// > Warning: Do not use this implementation in production code. See the warning in `start()`. -class TCPUnsafeListener { - private let dispatchQueue = DispatchQueue(label: "TCPListener") +class UnsafeListener<T: Connection> { + private let dispatchQueue = DispatchQueue(label: "com.test.unsafeListener") private let listener: NWListener - /// A stream of new TCP connections. - /// The caller may iterate over this stream to accept new TCP connections. + /// A stream of new connections. + /// The caller may iterate over this stream to accept new connections. /// - /// `TCPConnection` objects are returned unopen, so the caller has to call `TCPConnection.start()` to accept the + /// `Connection` objects are returned unopen, so the caller has to call `Connection.start()` to accept the /// connection before initiating the data exchange. - let newConnections: AsyncStream<TCPConnection> + let newConnections: AsyncStream<T> init() throws { - let listener = try NWListener(using: .tcp) + let listener = try NWListener(using: T.connectionParameters) newConnections = AsyncStream { continuation in listener.newConnectionHandler = { nwConnection in - continuation.yield(TCPConnection(nwConnection: nwConnection)) + continuation.yield(T(nwConnection: nwConnection)) } continuation.onTermination = { @Sendable _ in listener.newConnectionHandler = nil @@ -40,7 +39,7 @@ class TCPUnsafeListener { cancel() } - /// Local TCP port bound by listener on which it accepts new connections. + /// Local port bound by listener on which it accepts new connections. var listenPort: UInt16 { return listener.port?.rawValue ?? 0 } |
