@@ -75,17 +75,24 @@ internal static class Socks5Helper
75
75
// | 1 | 1 | X'00' | 1 | Variable | 2 |
76
76
// +----+-----+-------+------+----------+----------+
77
77
78
+ //General use constants
78
79
private const byte ProtocolVersion5 = 0x05 ;
79
- private const byte SubnegotiationVersion = 0x01 ;
80
+ private const byte Socks5Success = 0x00 ;
81
+ private const byte Reserved = 0x00 ;
80
82
private const byte CmdConnect = 0x01 ;
83
+
84
+ //Auth constants
81
85
private const byte MethodNoAuth = 0x00 ;
82
86
private const byte MethodUsernamePassword = 0x02 ;
87
+ private const byte SubnegotiationVersion = 0x01 ;
88
+
89
+ //Address type constants
83
90
private const byte AddressTypeIPv4 = 0x01 ;
84
- private const byte AddressTypeDomain = 0x03 ;
85
91
private const byte AddressTypeIPv6 = 0x04 ;
86
- private const byte Socks5Success = 0x00 ;
92
+ private const byte AddressTypeDomain = 0x03 ;
87
93
88
- private const int BufferSize = 512 ;
94
+ // Largest possible message size when using username and password auth.
95
+ private const int BufferSize = 513 ;
89
96
90
97
public static void PerformSocks5Handshake ( Stream stream , EndPoint endPoint , Socks5AuthenticationSettings authenticationSettings , CancellationToken cancellationToken )
91
98
{
@@ -101,9 +108,10 @@ public static void PerformSocks5Handshake(Stream stream, EndPoint endPoint, Sock
101
108
stream . ReadBytes ( buffer , 0 , 2 , cancellationToken ) ;
102
109
var acceptsUsernamePasswordAuth = ProcessGreetingResponse ( buffer , useAuth ) ;
103
110
111
+ // If we have username and password, but the proxy doesn't need them, we skip.
104
112
if ( useAuth && acceptsUsernamePasswordAuth )
105
113
{
106
- var authenticationRequestLength = CreateAuthenticationRequest ( buffer , authenticationSettings , cancellationToken ) ;
114
+ var authenticationRequestLength = CreateAuthenticationRequest ( buffer , authenticationSettings ) ;
107
115
stream . Write ( buffer , 0 , authenticationRequestLength ) ;
108
116
109
117
stream . ReadBytes ( buffer , 0 , 2 , cancellationToken ) ;
@@ -137,9 +145,10 @@ public static async Task PerformSocks5HandshakeAsync(Stream stream, EndPoint end
137
145
await stream . ReadBytesAsync ( buffer , 0 , 2 , cancellationToken ) . ConfigureAwait ( false ) ;
138
146
var acceptsUsernamePasswordAuth = ProcessGreetingResponse ( buffer , useAuth ) ;
139
147
148
+ // If we have username and password, but the proxy doesn't need them, we skip.
140
149
if ( useAuth && acceptsUsernamePasswordAuth )
141
150
{
142
- var authenticationRequestLength = CreateAuthenticationRequest ( buffer , authenticationSettings , cancellationToken ) ;
151
+ var authenticationRequestLength = CreateAuthenticationRequest ( buffer , authenticationSettings ) ;
143
152
await stream . WriteAsync ( buffer , 0 , authenticationRequestLength , cancellationToken ) . ConfigureAwait ( false ) ;
144
153
145
154
await stream . ReadBytesAsync ( buffer , 0 , 2 , cancellationToken ) . ConfigureAwait ( false ) ;
@@ -163,6 +172,7 @@ private static int CreateGreetingRequest(byte[] buffer, bool useAuth)
163
172
{
164
173
buffer [ 0 ] = ProtocolVersion5 ;
165
174
175
+ //buffer[1] is the number of methods supported by the client.
166
176
if ( ! useAuth )
167
177
{
168
178
buffer [ 1 ] = 1 ;
@@ -179,8 +189,8 @@ private static int CreateGreetingRequest(byte[] buffer, bool useAuth)
179
189
private static bool ProcessGreetingResponse ( byte [ ] buffer , bool useAuth )
180
190
{
181
191
VerifyProtocolVersion ( buffer [ 0 ] ) ;
182
- var method = buffer [ 1 ] ;
183
- if ( method == MethodUsernamePassword )
192
+ var acceptedMethod = buffer [ 1 ] ;
193
+ if ( acceptedMethod == MethodUsernamePassword )
184
194
{
185
195
if ( ! useAuth )
186
196
{
@@ -190,20 +200,21 @@ private static bool ProcessGreetingResponse(byte[] buffer, bool useAuth)
190
200
return true ;
191
201
}
192
202
193
- if ( method != MethodNoAuth )
203
+ if ( acceptedMethod != MethodNoAuth )
194
204
{
195
205
throw new IOException ( "SOCKS5 proxy requires unsupported authentication method." ) ;
196
206
}
197
207
198
208
return false ;
199
209
}
200
210
201
- private static int CreateAuthenticationRequest ( byte [ ] buffer , Socks5AuthenticationSettings authenticationSettings , CancellationToken cancellationToken )
211
+ private static int CreateAuthenticationRequest ( byte [ ] buffer , Socks5AuthenticationSettings authenticationSettings )
202
212
{
203
213
var usernamePasswordAuthenticationSettings = ( Socks5AuthenticationSettings . UsernamePasswordAuthenticationSettings ) authenticationSettings ;
204
214
var proxyUsername = usernamePasswordAuthenticationSettings . Username ;
205
215
var proxyPassword = usernamePasswordAuthenticationSettings . Password ;
206
216
217
+ // We need to add version, username.length, username, password.length, password (in this order)
207
218
buffer [ 0 ] = SubnegotiationVersion ;
208
219
var usernameLength = EncodeString ( proxyUsername , buffer , 2 , nameof ( proxyUsername ) ) ;
209
220
buffer [ 1 ] = usernameLength ;
@@ -221,56 +232,12 @@ private static void ProcessAuthenticationResponse(byte[] buffer)
221
232
}
222
233
}
223
234
224
- private static void PerformUsernamePasswordAuth ( Stream stream , byte [ ] buffer , Socks5AuthenticationSettings authenticationSettings , CancellationToken cancellationToken )
225
- {
226
- var usernamePasswordAuthenticationSettings = ( Socks5AuthenticationSettings . UsernamePasswordAuthenticationSettings ) authenticationSettings ;
227
- var proxyUsername = usernamePasswordAuthenticationSettings . Username ;
228
- var proxyPassword = usernamePasswordAuthenticationSettings . Password ;
229
-
230
- buffer [ 0 ] = SubnegotiationVersion ;
231
- var usernameLength = EncodeString ( proxyUsername , buffer , 2 , nameof ( proxyUsername ) ) ;
232
- buffer [ 1 ] = usernameLength ;
233
- var passwordLength = EncodeString ( proxyPassword , buffer , 3 + usernameLength , nameof ( proxyPassword ) ) ;
234
- buffer [ 2 + usernameLength ] = ( byte ) passwordLength ;
235
-
236
- var authLength = 3 + usernameLength + passwordLength ;
237
- stream . Write ( buffer , 0 , authLength ) ;
238
-
239
- stream . ReadBytes ( buffer , 0 , 2 , cancellationToken ) ;
240
- if ( buffer [ 0 ] != SubnegotiationVersion || buffer [ 1 ] != Socks5Success )
241
- {
242
- throw new IOException ( "SOCKS5 authentication failed." ) ;
243
- }
244
- }
245
-
246
- private static async Task PerformUsernamePasswordAuthAsync ( Stream stream , byte [ ] buffer , Socks5AuthenticationSettings authenticationSettings , CancellationToken cancellationToken )
247
- {
248
- var usernamePasswordAuthenticationSettings = ( Socks5AuthenticationSettings . UsernamePasswordAuthenticationSettings ) authenticationSettings ;
249
- var proxyUsername = usernamePasswordAuthenticationSettings . Username ;
250
- var proxyPassword = usernamePasswordAuthenticationSettings . Password ;
251
-
252
- buffer [ 0 ] = SubnegotiationVersion ;
253
- var usernameLength = EncodeString ( proxyUsername , buffer , 2 , nameof ( proxyUsername ) ) ;
254
- buffer [ 1 ] = usernameLength ;
255
- var passwordLength = EncodeString ( proxyPassword , buffer , 3 + usernameLength , nameof ( proxyPassword ) ) ;
256
- buffer [ 2 + usernameLength ] = passwordLength ;
257
-
258
- var authLength = 3 + usernameLength + passwordLength ;
259
- await stream . WriteAsync ( buffer , 0 , authLength , cancellationToken ) . ConfigureAwait ( false ) ;
260
-
261
- await stream . ReadBytesAsync ( buffer , 0 , 2 , cancellationToken ) . ConfigureAwait ( false ) ;
262
- if ( buffer [ 0 ] != SubnegotiationVersion || buffer [ 1 ] != Socks5Success )
263
- {
264
- throw new IOException ( "SOCKS5 authentication failed." ) ;
265
- }
266
- }
267
-
268
235
private static int CreateConnectRequest ( byte [ ] buffer , string targetHost , int targetPort )
269
236
{
270
237
buffer [ 0 ] = ProtocolVersion5 ;
271
238
buffer [ 1 ] = CmdConnect ;
272
- buffer [ 2 ] = 0x00 ;
273
- var addressLength = 0 ;
239
+ buffer [ 2 ] = Reserved ;
240
+ int addressLength ;
274
241
275
242
if ( IPAddress . TryParse ( targetHost , out var ip ) )
276
243
{
@@ -310,9 +277,13 @@ private static int ProcessConnectResponse(byte[] buffer)
310
277
311
278
if ( buffer [ 1 ] != Socks5Success )
312
279
{
313
- throw new IOException ( $ "SOCKS5 connect failed") ;
280
+ throw new IOException ( $ "SOCKS5 connect failed") ; //TODO Need to add the reason here.
314
281
}
315
282
283
+ // We skip the last bytes of the response as we do not need them.
284
+ // We skip length(dst.address) + length(dst.port) - 1 --- length(dst.port) is always 2
285
+ // -1 because we already ready the first byte of the address type
286
+ // (used for the variable length domain-type addresses)
316
287
return buffer [ 3 ] switch
317
288
{
318
289
AddressTypeIPv4 => 5 ,
0 commit comments