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
|
//
// 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: @Sendable (Socks5ConnectReply) -> Void
/// Failure handler invoked on error.
let onFailure: @Sendable (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)
}
}
|