-
Notifications
You must be signed in to change notification settings - Fork 360
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
821d36f
commit acc777d
Showing
24 changed files
with
1,556 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
ios/MullvadREST/Transport/Socks5/AnyIPEndpoint+Socks5.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// | ||
// AnyIPEndpoint+Socks5.swift | ||
// MullvadTransport | ||
// | ||
// Created by pronebird on 23/10/2023. | ||
// Copyright © 2023 Mullvad VPN AB. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import MullvadTypes | ||
import Network | ||
|
||
extension AnyIPEndpoint { | ||
/// Convert `AnyIPEndpoint` to `Socks5Endpoint`. | ||
var socksEndpoint: Socks5Endpoint { | ||
switch self { | ||
case let .ipv4(endpoint): | ||
.ipv4(endpoint) | ||
case let .ipv6(endpoint): | ||
.ipv6(endpoint) | ||
} | ||
} | ||
|
||
/// Convert `AnyIPEndpoint` to `NWEndpoint`. | ||
var nwEndpoint: NWEndpoint { | ||
switch self { | ||
case let .ipv4(endpoint): | ||
.hostPort(host: .ipv4(endpoint.ip), port: NWEndpoint.Port(integerLiteral: endpoint.port)) | ||
case let .ipv6(endpoint): | ||
.hostPort(host: .ipv6(endpoint.ip), port: NWEndpoint.Port(integerLiteral: endpoint.port)) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// | ||
// CancellableChain.swift | ||
// MullvadTransport | ||
// | ||
// Created by pronebird on 23/10/2023. | ||
// Copyright © 2023 Mullvad VPN AB. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import MullvadTypes | ||
|
||
/// Cancellable object that cancels all cancellable objects linked to it. | ||
final class CancellableChain: Cancellable { | ||
private let stateLock = NSLock() | ||
private var isCancelled = false | ||
private var linkedTokens: [Cancellable] = [] | ||
|
||
init() {} | ||
|
||
/// Link cancellation token with some other. | ||
/// | ||
/// The token is cancelled immediately, if the chain is already cancelled. | ||
func link(_ token: Cancellable) { | ||
stateLock.withLock { | ||
if isCancelled { | ||
token.cancel() | ||
} else { | ||
linkedTokens.append(token) | ||
} | ||
} | ||
} | ||
|
||
/// Request cancellation. | ||
/// | ||
/// Cancels and releases any of the connected tokens. | ||
func cancel() { | ||
stateLock.withLock { | ||
isCancelled = true | ||
linkedTokens.forEach { $0.cancel() } | ||
linkedTokens.removeAll() | ||
} | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
ios/MullvadREST/Transport/Socks5/NWConnection+Extensions.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// | ||
// NWConnection+Extensions.swift | ||
// MullvadTransport | ||
// | ||
// Created by pronebird on 20/10/2023. | ||
// | ||
|
||
import Foundation | ||
import Network | ||
|
||
extension NWConnection { | ||
/** | ||
Read exact number of bytes from connection. | ||
|
||
- Parameters: | ||
- exactLength: exact number of bytes to read. | ||
- completion: a completion handler. | ||
*/ | ||
func receive(exactLength: Int, completion: @escaping (Data?, ContentContext?, Bool, NWError?) -> Void) { | ||
receive(minimumIncompleteLength: exactLength, maximumLength: exactLength, completion: completion) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// | ||
// Socks5AddressType.swift | ||
// MullvadTransport | ||
// | ||
// Created by pronebird on 19/10/2023. | ||
// | ||
|
||
import Foundation | ||
|
||
/// Address type supported by socks protocol | ||
enum Socks5AddressType: UInt8 { | ||
case ipv4 = 0x01 | ||
case domainName = 0x03 | ||
case ipv6 = 0x04 | ||
} |
14 changes: 14 additions & 0 deletions
14
ios/MullvadREST/Transport/Socks5/Socks5Authentication.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// | ||
// Socks5Authentication.swift | ||
// MullvadTransport | ||
// | ||
// Created by pronebird on 19/10/2023. | ||
// | ||
|
||
import Foundation | ||
|
||
/// Authentication methods supported by socks protocol. | ||
enum Socks5AuthenticationMethod: UInt8 { | ||
case notRequired = 0x00 | ||
case usernamePassword = 0x02 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// | ||
// Socks5Command.swift | ||
// MullvadTransport | ||
// | ||
// Created by pronebird on 21/10/2023. | ||
// | ||
|
||
import Foundation | ||
|
||
/// Commands supported in socks protocol. | ||
enum Socks5Command: UInt8 { | ||
case connect = 0x01 | ||
case bind = 0x02 | ||
case udpAssociate = 0x03 | ||
} |
21 changes: 21 additions & 0 deletions
21
ios/MullvadREST/Transport/Socks5/Socks5Configuration.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// | ||
// Socks5Configuration.swift | ||
// MullvadTransport | ||
// | ||
// Created by pronebird on 23/10/2023. | ||
// Copyright © 2023 Mullvad VPN AB. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import MullvadTypes | ||
|
||
/// Socks5 configuration. | ||
/// - See: ``URLSessionSocks5Transport`` | ||
public struct Socks5Configuration { | ||
/// The socks proxy endpoint. | ||
public var proxyEndpoint: AnyIPEndpoint | ||
|
||
public init(proxyEndpoint: AnyIPEndpoint) { | ||
self.proxyEndpoint = proxyEndpoint | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
ios/MullvadREST/Transport/Socks5/Socks5ConnectCommand.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// | ||
// Socks5ConnectCommand.swift | ||
// MullvadTransport | ||
// | ||
// Created by pronebird on 19/10/2023. | ||
// | ||
|
||
import Foundation | ||
import Network | ||
|
||
/// The connect command message. | ||
struct Socks5ConnectCommand { | ||
/// The remote endpoint to which the client wants to establish connection over the socks proxy. | ||
var endpoint: Socks5Endpoint | ||
|
||
/// The byte representation in socks protocol. | ||
var rawData: Data { | ||
var data = Data() | ||
|
||
// Socks version. | ||
data.append(Socks5Constants.socksVersion) | ||
|
||
// Command code. | ||
data.append(Socks5Command.connect.rawValue) | ||
|
||
// Reserved. | ||
data.append(0) | ||
|
||
// Address type. | ||
data.append(endpoint.addressType.rawValue) | ||
|
||
// Endpoint address. | ||
data.append(endpoint.rawData) | ||
|
||
return data | ||
} | ||
} | ||
|
||
/// The connect command reply message. | ||
struct Socks5ConnectReply { | ||
/// The server status code. | ||
var status: Socks5StatusCode | ||
|
||
/// The server bound endpoint. | ||
var serverBoundEndpoint: Socks5Endpoint | ||
} |
109 changes: 109 additions & 0 deletions
109
ios/MullvadREST/Transport/Socks5/Socks5ConnectNegotiation.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// | ||
// Socks5ConnectNegotiation.swift | ||
// MullvadTransport | ||
// | ||
// Created by pronebird on 20/10/2023. | ||
// | ||
|
||
import Foundation | ||
import Network | ||
|
||
/// The object handling a connection negotiation with socks proxy. | ||
struct Socks5ConnectNegotiation { | ||
/// Connection to the socks proxy. | ||
let connection: NWConnection | ||
|
||
/// Endpoint to which the client wants to initiate connection over socks proxy. | ||
let endpoint: Socks5Endpoint | ||
|
||
/// Completion handler invoked on success. | ||
let onComplete: (Socks5ConnectReply) -> Void | ||
|
||
/// Failure handler invoked on error. | ||
let onFailure: (Error) -> Void | ||
|
||
/// Initiate negotiation by sending a connect command to the socks proxy. | ||
func perform() { | ||
let connectCommand = Socks5ConnectCommand(endpoint: endpoint) | ||
|
||
connection.send(content: connectCommand.rawData, completion: .contentProcessed { [self] error in | ||
if let error { | ||
onFailure(Socks5Error.remoteConnectionFailure(error)) | ||
} else { | ||
readPartialReply() | ||
} | ||
}) | ||
} | ||
|
||
/// Read the preamble of the connect reply. | ||
private func readPartialReply() { | ||
// The length of the preamble of the CONNECT reply. | ||
let replyPreambleLength = 4 | ||
|
||
connection.receive(exactLength: replyPreambleLength) { [self] data, _, _, error in | ||
if let error { | ||
onFailure(Socks5Error.remoteConnectionFailure(error)) | ||
} else if let data { | ||
do { | ||
try handlePartialReply(data: data) | ||
} catch { | ||
onFailure(error) | ||
} | ||
} else { | ||
onFailure(Socks5Error.unexpectedEndOfStream) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
Parse the bytes that comprise the preamble of a connect reply. Upon success read the endpoint data to produce the complete reply and finish negotiation. | ||
|
||
The following fields are contained within the first 4 bytes: socks version, status code, reserved field, address type. | ||
*/ | ||
private func handlePartialReply(data: Data) throws { | ||
// Parse partial reply that contains the status code and address type. | ||
let (statusCode, addressType) = try parsePartialReply(data: data) | ||
|
||
// Parse server bound endpoint to produce the complete reply. | ||
let endpointReader = Socks5EndpointReader( | ||
connection: connection, | ||
addressType: addressType, | ||
onComplete: { [self] endpoint in | ||
let reply = Socks5ConnectReply(status: statusCode, serverBoundEndpoint: endpoint) | ||
onComplete(reply) | ||
}, | ||
onFailure: onFailure | ||
) | ||
endpointReader.perform() | ||
} | ||
|
||
/// Parse the bytes that comprise the preamble of reply without endpoint data. | ||
private func parsePartialReply(data: Data) throws -> (Socks5StatusCode, Socks5AddressType) { | ||
var iterator = data.makeIterator() | ||
|
||
// Read the protocol version. | ||
guard let version = iterator.next() else { throw Socks5Error.unexpectedEndOfStream } | ||
|
||
// Verify the protocol version. | ||
guard version == Socks5Constants.socksVersion else { throw Socks5Error.invalidSocksVersion } | ||
|
||
// Read status code, reserved field and address type from reply. | ||
guard let rawStatusCode = iterator.next(), | ||
iterator.next() != nil, // skip reserved field | ||
let rawAddressType = iterator.next() else { | ||
throw Socks5Error.unexpectedEndOfStream | ||
} | ||
|
||
// Parse the status code. | ||
guard let status = Socks5StatusCode(rawValue: rawStatusCode) else { | ||
throw Socks5Error.invalidStatusCode(rawStatusCode) | ||
} | ||
|
||
// Parse the address type. | ||
guard let addressType = Socks5AddressType(rawValue: rawAddressType) else { | ||
throw Socks5Error.invalidAddressType | ||
} | ||
|
||
return (status, addressType) | ||
} | ||
} |
Oops, something went wrong.