├── .travis.yml
├── EventArgs
└── CreateConnectionAsyncCompletedEventArgs.cs
├── Exceptions
└── ProxyException.cs
├── HttpProxyClient.cs
├── IProxyClient.cs
├── ProxyClientFactory.cs
├── Socks4ProxyClient.cs
├── Socks4aProxyClient.cs
├── Socks5ProxyClient.cs
├── StarkSoftProxy.csproj
└── Utils.cs
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: c
2 |
3 | install:
4 | - sudo apt-get update > /dev/null
5 | - sudo apt-get install mono-devel > /dev/null
6 |
7 | script:
8 | - xbuild StarkSoftProxy.csproj
9 |
10 | notifications:
11 | irc:
12 | channels: "chat.freenode.net#smuxi-devel"
13 | skip_join: true
14 | template:
15 | - "%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message}"
16 | - "Build details : %{build_url}"
17 |
--------------------------------------------------------------------------------
/EventArgs/CreateConnectionAsyncCompletedEventArgs.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Authors: Benton Stark
3 | *
4 | * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | *
24 | */
25 |
26 | using System;
27 | using System.Net.Sockets;
28 | using System.ComponentModel;
29 |
30 | namespace Starksoft.Net.Proxy
31 | {
32 | ///
33 | /// Event arguments class for the EncryptAsyncCompleted event.
34 | ///
35 | public class CreateConnectionAsyncCompletedEventArgs : AsyncCompletedEventArgs
36 | {
37 | private TcpClient _proxyConnection;
38 |
39 | ///
40 | /// Constructor.
41 | ///
42 | /// Exception information generated by the event.
43 | /// Cancelled event flag. This flag is set to true if the event was cancelled.
44 | /// Proxy Connection. The initialized and open TcpClient proxy connection.
45 | public CreateConnectionAsyncCompletedEventArgs(Exception error, bool cancelled, TcpClient proxyConnection)
46 | : base(error, cancelled, null)
47 | {
48 | _proxyConnection = proxyConnection;
49 | }
50 |
51 | ///
52 | /// The proxy connection.
53 | ///
54 | public TcpClient ProxyConnection
55 | {
56 | get { return _proxyConnection; }
57 | }
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/Exceptions/ProxyException.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Authors: Benton Stark
3 | *
4 | * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | *
24 | */
25 |
26 | using System;
27 | using System.Runtime.Serialization;
28 |
29 | namespace Starksoft.Net.Proxy
30 | {
31 |
32 | ///
33 | /// This exception is thrown when a general, unexpected proxy error.
34 | ///
35 | [Serializable()]
36 | public class ProxyException : Exception
37 | {
38 | ///
39 | /// Constructor.
40 | ///
41 | public ProxyException()
42 | {
43 | }
44 |
45 | ///
46 | /// Constructor.
47 | ///
48 | /// Exception message text.
49 | public ProxyException(string message)
50 | : base(message)
51 | {
52 | }
53 |
54 | ///
55 | /// Constructor.
56 | ///
57 | /// Exception message text.
58 | /// The inner exception object.
59 | public ProxyException(string message, Exception innerException)
60 | :
61 | base(message, innerException)
62 | {
63 | }
64 |
65 | ///
66 | /// Constructor.
67 | ///
68 | /// Serialization information.
69 | /// Stream context information.
70 | protected ProxyException(SerializationInfo info,
71 | StreamingContext context)
72 | : base(info, context)
73 | {
74 | }
75 | }
76 |
77 | }
--------------------------------------------------------------------------------
/HttpProxyClient.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Authors: Benton Stark
3 | *
4 | * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | *
24 | */
25 |
26 | using System;
27 | using System.Text;
28 | using System.Net.Sockets;
29 | using System.Threading;
30 | using System.Globalization;
31 | using System.ComponentModel;
32 |
33 | namespace Starksoft.Net.Proxy
34 | {
35 | ///
36 | /// HTTP connection proxy class. This class implements the HTTP standard proxy protocol.
37 | ///
38 | /// You can use this class to set up a connection to an HTTP proxy server. Calling the
39 | /// CreateConnection() method initiates the proxy connection and returns a standard
40 | /// System.Net.Socks.TcpClient object that can be used as normal. The proxy plumbing
41 | /// is all handled for you.
42 | ///
43 | ///
44 | ///
45 | ///
46 | ///
47 | public class HttpProxyClient : IProxyClient
48 | {
49 | private string _proxyHost;
50 | private int _proxyPort;
51 | private HttpResponseCodes _respCode;
52 | private string _respText;
53 | private TcpClient _tcpClient;
54 |
55 | private const int HTTP_PROXY_DEFAULT_PORT = 8080;
56 | private const string HTTP_PROXY_CONNECT_CMD = "CONNECT {0}:{1} HTTP/1.0\r\nHost: {0}:{1}\r\n\r\n";
57 | private const int WAIT_FOR_DATA_INTERVAL = 50; // 50 ms
58 | private const int WAIT_FOR_DATA_TIMEOUT = 15000; // 15 seconds
59 | private const string PROXY_NAME = "HTTP";
60 |
61 | private enum HttpResponseCodes
62 | {
63 | None = 0,
64 | Continue = 100,
65 | SwitchingProtocols = 101,
66 | OK = 200,
67 | Created = 201,
68 | Accepted = 202,
69 | NonAuthoritiveInformation = 203,
70 | NoContent = 204,
71 | ResetContent = 205,
72 | PartialContent = 206,
73 | MultipleChoices = 300,
74 | MovedPermanetly = 301,
75 | Found = 302,
76 | SeeOther = 303,
77 | NotModified = 304,
78 | UserProxy = 305,
79 | TemporaryRedirect = 307,
80 | BadRequest = 400,
81 | Unauthorized = 401,
82 | PaymentRequired = 402,
83 | Forbidden = 403,
84 | NotFound = 404,
85 | MethodNotAllowed = 405,
86 | NotAcceptable = 406,
87 | ProxyAuthenticantionRequired = 407,
88 | RequestTimeout = 408,
89 | Conflict = 409,
90 | Gone = 410,
91 | PreconditionFailed = 411,
92 | RequestEntityTooLarge = 413,
93 | RequestURITooLong = 414,
94 | UnsupportedMediaType = 415,
95 | RequestedRangeNotSatisfied = 416,
96 | ExpectationFailed = 417,
97 | InternalServerError = 500,
98 | NotImplemented = 501,
99 | BadGateway = 502,
100 | ServiceUnavailable = 503,
101 | GatewayTimeout = 504,
102 | HTTPVersionNotSupported = 505
103 | }
104 |
105 | ///
106 | /// Constructor.
107 | ///
108 | public HttpProxyClient() { }
109 |
110 | ///
111 | /// Creates a HTTP proxy client object using the supplied TcpClient object connection.
112 | ///
113 | /// A TcpClient connection object.
114 | public HttpProxyClient(TcpClient tcpClient)
115 | {
116 | if (tcpClient == null)
117 | throw new ArgumentNullException("tcpClient");
118 |
119 | _tcpClient = tcpClient;
120 | }
121 |
122 |
123 | ///
124 | /// Constructor. The default HTTP proxy port 8080 is used.
125 | ///
126 | /// Host name or IP address of the proxy.
127 | public HttpProxyClient(string proxyHost)
128 | {
129 | if (String.IsNullOrEmpty(proxyHost))
130 | throw new ArgumentNullException("proxyHost");
131 |
132 | _proxyHost = proxyHost;
133 | _proxyPort = HTTP_PROXY_DEFAULT_PORT;
134 | }
135 |
136 | ///
137 | /// Constructor.
138 | ///
139 | /// Host name or IP address of the proxy server.
140 | /// Port number for the proxy server.
141 | public HttpProxyClient(string proxyHost, int proxyPort)
142 | {
143 | if (String.IsNullOrEmpty(proxyHost))
144 | throw new ArgumentNullException("proxyHost");
145 |
146 | if (proxyPort <= 0 || proxyPort > 65535)
147 | throw new ArgumentOutOfRangeException("proxyPort", "port must be greater than zero and less than 65535");
148 |
149 | _proxyHost = proxyHost;
150 | _proxyPort = proxyPort;
151 | }
152 |
153 | ///
154 | /// Gets or sets host name or IP address of the proxy server.
155 | ///
156 | public string ProxyHost
157 | {
158 | get { return _proxyHost; }
159 | set { _proxyHost = value; }
160 | }
161 |
162 | ///
163 | /// Gets or sets port number for the proxy server.
164 | ///
165 | public int ProxyPort
166 | {
167 | get { return _proxyPort; }
168 | set { _proxyPort = value; }
169 | }
170 |
171 | ///
172 | /// Gets String representing the name of the proxy.
173 | ///
174 | /// This property will always return the value 'HTTP'
175 | public string ProxyName
176 | {
177 | get { return PROXY_NAME; }
178 | }
179 |
180 | ///
181 | /// Gets or sets the TcpClient object.
182 | /// This property can be set prior to executing CreateConnection to use an existing TcpClient connection.
183 | ///
184 | public TcpClient TcpClient
185 | {
186 | get { return _tcpClient; }
187 | set { _tcpClient = value; }
188 | }
189 |
190 |
191 | ///
192 | /// Creates a remote TCP connection through a proxy server to the destination host on the destination port.
193 | ///
194 | /// Destination host name or IP address.
195 | /// Port number to connect to on the destination host.
196 | ///
197 | /// Returns an open TcpClient object that can be used normally to communicate
198 | /// with the destination server
199 | ///
200 | ///
201 | /// This method creates a connection to the proxy server and instructs the proxy server
202 | /// to make a pass through connection to the specified destination host on the specified
203 | /// port.
204 | ///
205 | public TcpClient CreateConnection(string destinationHost, int destinationPort)
206 | {
207 | try
208 | {
209 | // if we have no connection, create one
210 | if (_tcpClient == null)
211 | {
212 | if (String.IsNullOrEmpty(_proxyHost))
213 | throw new ProxyException("ProxyHost property must contain a value.");
214 |
215 | if (_proxyPort <= 0 || _proxyPort > 65535)
216 | throw new ProxyException("ProxyPort value must be greater than zero and less than 65535");
217 |
218 | // create new tcp client object to the proxy server
219 | _tcpClient = new TcpClient();
220 |
221 | // attempt to open the connection
222 | _tcpClient.Connect(_proxyHost, _proxyPort);
223 | }
224 |
225 | // send connection command to proxy host for the specified destination host and port
226 | SendConnectionCommand(destinationHost, destinationPort);
227 |
228 | // return the open proxied tcp client object to the caller for normal use
229 | return _tcpClient;
230 | }
231 | catch (SocketException ex)
232 | {
233 | throw new ProxyException(String.Format(CultureInfo.InvariantCulture, "Connection to proxy host {0} on port {1} failed.", Utils.GetHost(_tcpClient), Utils.GetPort(_tcpClient)), ex);
234 | }
235 | }
236 |
237 |
238 | private void SendConnectionCommand(string host, int port)
239 | {
240 | NetworkStream stream = _tcpClient.GetStream();
241 |
242 | // PROXY SERVER REQUEST
243 | // =======================================================================
244 | //CONNECT starksoft.com:443 HTTP/1.0
245 | //HOST starksoft.com:443
246 | //[... other HTTP header lines ending with if required]>
247 | // // Last Empty Line
248 |
249 | string connectCmd = String.Format(CultureInfo.InvariantCulture, HTTP_PROXY_CONNECT_CMD, host, port.ToString(CultureInfo.InvariantCulture));
250 | byte[] request = ASCIIEncoding.ASCII.GetBytes(connectCmd);
251 |
252 | // send the connect request
253 | stream.Write(request, 0, request.Length);
254 |
255 | // wait for the proxy server to respond
256 | WaitForData(stream);
257 |
258 | // PROXY SERVER RESPONSE
259 | // =======================================================================
260 | //HTTP/1.0 200 Connection Established
261 | //[.... other HTTP header lines ending with ..
262 | //ignore all of them]
263 | // // Last Empty Line
264 |
265 | // create an byte response array
266 | byte[] response = new byte[_tcpClient.ReceiveBufferSize];
267 | StringBuilder sbuilder = new StringBuilder();
268 | int bytes = 0;
269 | long total = 0;
270 |
271 | do
272 | {
273 | bytes = stream.Read(response, 0, _tcpClient.ReceiveBufferSize);
274 | total += bytes;
275 | sbuilder.Append(System.Text.ASCIIEncoding.UTF8.GetString(response, 0, bytes));
276 | } while (stream.DataAvailable);
277 |
278 | ParseResponse(sbuilder.ToString());
279 |
280 | // evaluate the reply code for an error condition
281 | if (_respCode != HttpResponseCodes.OK)
282 | HandleProxyCommandError(host, port);
283 | }
284 |
285 | private void HandleProxyCommandError(string host, int port)
286 | {
287 | string msg;
288 |
289 | switch (_respCode)
290 | {
291 | case HttpResponseCodes.None:
292 | msg = String.Format(CultureInfo.InvariantCulture, "Proxy destination {0} on port {1} failed to return a recognized HTTP response code. Server response: {2}", Utils.GetHost(_tcpClient), Utils.GetPort(_tcpClient), _respText);
293 | break;
294 |
295 | case HttpResponseCodes.BadGateway:
296 | //HTTP/1.1 502 Proxy Error (The specified Secure Sockets Layer (SSL) port is not allowed. ISA Server is not configured to allow SSL requests from this port. Most Web browsers use port 443 for SSL requests.)
297 | msg = String.Format(CultureInfo.InvariantCulture, "Proxy destination {0} on port {1} responded with a 502 code - Bad Gateway. If you are connecting to a Microsoft ISA destination please refer to knowledge based article Q283284 for more information. Server response: {2}", Utils.GetHost(_tcpClient), Utils.GetPort(_tcpClient), _respText);
298 | break;
299 |
300 | default:
301 | msg = String.Format(CultureInfo.InvariantCulture, "Proxy destination {0} on port {1} responded with a {2} code - {3}", Utils.GetHost(_tcpClient), Utils.GetPort(_tcpClient), ((int)_respCode).ToString(CultureInfo.InvariantCulture), _respText);
302 | break;
303 | }
304 |
305 | // throw a new application exception
306 | throw new ProxyException(msg);
307 | }
308 |
309 | private void WaitForData(NetworkStream stream)
310 | {
311 | int sleepTime = 0;
312 | while (!stream.DataAvailable)
313 | {
314 | Thread.Sleep(WAIT_FOR_DATA_INTERVAL);
315 | sleepTime += WAIT_FOR_DATA_INTERVAL;
316 | if (sleepTime > WAIT_FOR_DATA_TIMEOUT)
317 | throw new ProxyException(String.Format("A timeout while waiting for the proxy server at {0} on port {1} to respond.", Utils.GetHost(_tcpClient), Utils.GetPort(_tcpClient) ));
318 | }
319 | }
320 |
321 | private void ParseResponse(string response)
322 | {
323 | string[] data = null;
324 |
325 | // get rid of the LF character if it exists and then split the string on all CR
326 | data = response.Replace('\n', ' ').Split('\r');
327 |
328 | ParseCodeAndText(data[0]);
329 | }
330 |
331 | private void ParseCodeAndText(string line)
332 | {
333 | int begin = 0;
334 | int end = 0;
335 | string val = null;
336 |
337 | if (line.IndexOf("HTTP") == -1)
338 | throw new ProxyException(String.Format("No HTTP response received from proxy destination. Server response: {0}.", line));
339 |
340 | begin = line.IndexOf(" ") + 1;
341 | end = line.IndexOf(" ", begin);
342 |
343 | val = line.Substring(begin, end - begin);
344 | Int32 code = 0;
345 |
346 | if (!Int32.TryParse(val, out code))
347 | throw new ProxyException(String.Format("An invalid response code was received from proxy destination. Server response: {0}.", line));
348 |
349 | _respCode = (HttpResponseCodes)code;
350 | _respText = line.Substring(end + 1).Trim();
351 | }
352 |
353 |
354 |
355 | #region "Async Methods"
356 |
357 | private BackgroundWorker _asyncWorker;
358 | private Exception _asyncException;
359 | bool _asyncCancelled;
360 |
361 | ///
362 | /// Gets a value indicating whether an asynchronous operation is running.
363 | ///
364 | /// Returns true if an asynchronous operation is running; otherwise, false.
365 | ///
366 | public bool IsBusy
367 | {
368 | get { return _asyncWorker == null ? false : _asyncWorker.IsBusy; }
369 | }
370 |
371 | ///
372 | /// Gets a value indicating whether an asynchronous operation is cancelled.
373 | ///
374 | /// Returns true if an asynchronous operation is cancelled; otherwise, false.
375 | ///
376 | public bool IsAsyncCancelled
377 | {
378 | get { return _asyncCancelled; }
379 | }
380 |
381 | ///
382 | /// Cancels any asychronous operation that is currently active.
383 | ///
384 | public void CancelAsync()
385 | {
386 | if (_asyncWorker != null && !_asyncWorker.CancellationPending && _asyncWorker.IsBusy)
387 | {
388 | _asyncCancelled = true;
389 | _asyncWorker.CancelAsync();
390 | }
391 | }
392 |
393 | private void CreateAsyncWorker()
394 | {
395 | if (_asyncWorker != null)
396 | _asyncWorker.Dispose();
397 | _asyncException = null;
398 | _asyncWorker = null;
399 | _asyncCancelled = false;
400 | _asyncWorker = new BackgroundWorker();
401 | }
402 |
403 | ///
404 | /// Event handler for CreateConnectionAsync method completed.
405 | ///
406 | public event EventHandler CreateConnectionAsyncCompleted;
407 |
408 | ///
409 | /// Asynchronously creates a remote TCP connection through a proxy server to the destination host on the destination port.
410 | ///
411 | /// Destination host name or IP address.
412 | /// Port number to connect to on the destination host.
413 | ///
414 | /// Returns an open TcpClient object that can be used normally to communicate
415 | /// with the destination server
416 | ///
417 | ///
418 | /// This method creates a connection to the proxy server and instructs the proxy server
419 | /// to make a pass through connection to the specified destination host on the specified
420 | /// port.
421 | ///
422 | public void CreateConnectionAsync(string destinationHost, int destinationPort)
423 | {
424 | if (_asyncWorker != null && _asyncWorker.IsBusy)
425 | throw new InvalidOperationException("The HttpProxy object is already busy executing another asynchronous operation. You can only execute one asychronous method at a time.");
426 |
427 | CreateAsyncWorker();
428 | _asyncWorker.WorkerSupportsCancellation = true;
429 | _asyncWorker.DoWork += new DoWorkEventHandler(CreateConnectionAsync_DoWork);
430 | _asyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(CreateConnectionAsync_RunWorkerCompleted);
431 | Object[] args = new Object[2];
432 | args[0] = destinationHost;
433 | args[1] = destinationPort;
434 | _asyncWorker.RunWorkerAsync(args);
435 | }
436 |
437 | private void CreateConnectionAsync_DoWork(object sender, DoWorkEventArgs e)
438 | {
439 | try
440 | {
441 | Object[] args = (Object[])e.Argument;
442 | e.Result = CreateConnection((string)args[0], (int)args[1]);
443 | }
444 | catch (Exception ex)
445 | {
446 | _asyncException = ex;
447 | }
448 | }
449 |
450 | private void CreateConnectionAsync_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
451 | {
452 | if (CreateConnectionAsyncCompleted != null)
453 | CreateConnectionAsyncCompleted(this, new CreateConnectionAsyncCompletedEventArgs(_asyncException, _asyncCancelled, (TcpClient)e.Result));
454 | }
455 |
456 |
457 |
458 | #endregion
459 |
460 | }
461 | }
462 |
--------------------------------------------------------------------------------
/IProxyClient.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Authors: Benton Stark
3 | *
4 | * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | *
24 | */
25 |
26 | using System;
27 | using System.Collections.Generic;
28 | using System.Text;
29 | using System.Net.Sockets;
30 |
31 | namespace Starksoft.Net.Proxy
32 | {
33 | ///
34 | /// Proxy client interface. This is the interface that all proxy clients must implement.
35 | ///
36 | public interface IProxyClient
37 | {
38 |
39 | ///
40 | /// Event handler for CreateConnectionAsync method completed.
41 | ///
42 | event EventHandler CreateConnectionAsyncCompleted;
43 |
44 | ///
45 | /// Gets or sets proxy host name or IP address.
46 | ///
47 | string ProxyHost { get; set; }
48 |
49 | ///
50 | /// Gets or sets proxy port number.
51 | ///
52 | int ProxyPort { get; set; }
53 |
54 | ///
55 | /// Gets String representing the name of the proxy.
56 | ///
57 | string ProxyName { get; }
58 |
59 | ///
60 | /// Gets or set the TcpClient object if one was specified in the constructor.
61 | ///
62 | TcpClient TcpClient { get; set; }
63 |
64 | ///
65 | /// Creates a remote TCP connection through a proxy server to the destination host on the destination port.
66 | ///
67 | /// Destination host name or IP address.
68 | /// Port number to connect to on the destination host.
69 | ///
70 | /// Returns an open TcpClient object that can be used normally to communicate
71 | /// with the destination server
72 | ///
73 | ///
74 | /// This method creates a connection to the proxy server and instructs the proxy server
75 | /// to make a pass through connection to the specified destination host on the specified
76 | /// port.
77 | ///
78 | TcpClient CreateConnection(string destinationHost, int destinationPort);
79 |
80 | ///
81 | /// Asynchronously creates a remote TCP connection through a proxy server to the destination host on the destination port.
82 | ///
83 | /// Destination host name or IP address.
84 | /// Port number to connect to on the destination host.
85 | ///
86 | /// Returns an open TcpClient object that can be used normally to communicate
87 | /// with the destination server
88 | ///
89 | ///
90 | /// This method creates a connection to the proxy server and instructs the proxy server
91 | /// to make a pass through connection to the specified destination host on the specified
92 | /// port.
93 | ///
94 | void CreateConnectionAsync(string destinationHost, int destinationPort);
95 |
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/ProxyClientFactory.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Authors: Benton Stark
3 | *
4 | * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | *
24 | */
25 |
26 | using System;
27 | using System.Collections.Generic;
28 | using System.Text;
29 | using System.Net.Sockets;
30 |
31 | namespace Starksoft.Net.Proxy
32 | {
33 | ///
34 | /// The type of proxy.
35 | ///
36 | public enum ProxyType
37 | {
38 | ///
39 | /// No Proxy specified. Note this option will cause an exception to be thrown if used to create a proxy object by the factory.
40 | ///
41 | None,
42 | ///
43 | /// HTTP Proxy
44 | ///
45 | Http,
46 | ///
47 | /// SOCKS v4 Proxy
48 | ///
49 | Socks4,
50 | ///
51 | /// SOCKS v4a Proxy
52 | ///
53 | Socks4a,
54 | ///
55 | /// SOCKS v5 Proxy
56 | ///
57 | Socks5
58 | }
59 |
60 | ///
61 | /// Factory class for creating new proxy client objects.
62 | ///
63 | ///
64 | ///
65 | /// // create an instance of the client proxy factory
66 | /// ProxyClientFactory factory = new ProxyClientFactory();
67 | ///
68 | /// // use the proxy client factory to generically specify the type of proxy to create
69 | /// // the proxy factory method CreateProxyClient returns an IProxyClient object
70 | /// IProxyClient proxy = factory.CreateProxyClient(ProxyType.Http, "localhost", 6588);
71 | ///
72 | /// // create a connection through the proxy to www.starksoft.com over port 80
73 | /// System.Net.Sockets.TcpClient tcpClient = proxy.CreateConnection("www.starksoft.com", 80);
74 | ///
75 | ///
76 | public class ProxyClientFactory
77 | {
78 |
79 | ///
80 | /// Factory method for creating new proxy client objects.
81 | ///
82 | /// The type of proxy client to create.
83 | /// Proxy client object.
84 | public IProxyClient CreateProxyClient(ProxyType type)
85 | {
86 | if (type == ProxyType.None)
87 | throw new ArgumentOutOfRangeException("type");
88 |
89 | switch (type)
90 | {
91 | case ProxyType.Http:
92 | return new HttpProxyClient();
93 | case ProxyType.Socks4:
94 | return new Socks4ProxyClient();
95 | case ProxyType.Socks4a:
96 | return new Socks4aProxyClient();
97 | case ProxyType.Socks5:
98 | return new Socks5ProxyClient();
99 | default:
100 | throw new ProxyException(String.Format("Unknown proxy type {0}.", type.ToString()));
101 | }
102 | }
103 |
104 | ///
105 | /// Factory method for creating new proxy client objects using an existing TcpClient connection object.
106 | ///
107 | /// The type of proxy client to create.
108 | /// Open TcpClient object.
109 | /// Proxy client object.
110 | public IProxyClient CreateProxyClient(ProxyType type, TcpClient tcpClient)
111 | {
112 | if (type == ProxyType.None)
113 | throw new ArgumentOutOfRangeException("type");
114 |
115 | switch (type)
116 | {
117 | case ProxyType.Http:
118 | return new HttpProxyClient(tcpClient);
119 | case ProxyType.Socks4:
120 | return new Socks4ProxyClient(tcpClient);
121 | case ProxyType.Socks4a:
122 | return new Socks4aProxyClient(tcpClient);
123 | case ProxyType.Socks5:
124 | return new Socks5ProxyClient(tcpClient);
125 | default:
126 | throw new ProxyException(String.Format("Unknown proxy type {0}.", type.ToString()));
127 | }
128 | }
129 |
130 | ///
131 | /// Factory method for creating new proxy client objects.
132 | ///
133 | /// The type of proxy client to create.
134 | /// The proxy host or IP address.
135 | /// The proxy port number.
136 | /// Proxy client object.
137 | public IProxyClient CreateProxyClient(ProxyType type, string proxyHost, int proxyPort)
138 | {
139 | if (type == ProxyType.None)
140 | throw new ArgumentOutOfRangeException("type");
141 |
142 | switch (type)
143 | {
144 | case ProxyType.Http:
145 | return new HttpProxyClient(proxyHost, proxyPort);
146 | case ProxyType.Socks4:
147 | return new Socks4ProxyClient(proxyHost, proxyPort);
148 | case ProxyType.Socks4a:
149 | return new Socks4aProxyClient(proxyHost, proxyPort);
150 | case ProxyType.Socks5:
151 | return new Socks5ProxyClient(proxyHost, proxyPort);
152 | default:
153 | throw new ProxyException(String.Format("Unknown proxy type {0}.", type.ToString()));
154 | }
155 | }
156 |
157 | ///
158 | /// Factory method for creating new proxy client objects.
159 | ///
160 | /// The type of proxy client to create.
161 | /// The proxy host or IP address.
162 | /// The proxy port number.
163 | /// The proxy username. This parameter is only used by Socks4 and Socks5 proxy objects.
164 | /// The proxy user password. This parameter is only used Socks5 proxy objects.
165 | /// Proxy client object.
166 | public IProxyClient CreateProxyClient(ProxyType type, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword)
167 | {
168 | if (type == ProxyType.None)
169 | throw new ArgumentOutOfRangeException("type");
170 |
171 | switch (type)
172 | {
173 | case ProxyType.Http:
174 | return new HttpProxyClient(proxyHost, proxyPort);
175 | case ProxyType.Socks4:
176 | return new Socks4ProxyClient(proxyHost, proxyPort, proxyUsername);
177 | case ProxyType.Socks4a:
178 | return new Socks4aProxyClient(proxyHost, proxyPort, proxyUsername);
179 | case ProxyType.Socks5:
180 | return new Socks5ProxyClient(proxyHost, proxyPort, proxyUsername, proxyPassword);
181 | default:
182 | throw new ProxyException(String.Format("Unknown proxy type {0}.", type.ToString()));
183 | }
184 | }
185 |
186 | ///
187 | /// Factory method for creating new proxy client objects.
188 | ///
189 | /// The type of proxy client to create.
190 | /// Open TcpClient object.
191 | /// The proxy host or IP address.
192 | /// The proxy port number.
193 | /// The proxy username. This parameter is only used by Socks4 and Socks5 proxy objects.
194 | /// The proxy user password. This parameter is only used Socks5 proxy objects.
195 | /// Proxy client object.
196 | public IProxyClient CreateProxyClient(ProxyType type, TcpClient tcpClient, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword)
197 | {
198 | IProxyClient c = CreateProxyClient(type, proxyHost, proxyPort, proxyUsername, proxyPassword);
199 | c.TcpClient = tcpClient;
200 | return c;
201 | }
202 |
203 |
204 | }
205 |
206 |
207 |
208 | }
209 |
--------------------------------------------------------------------------------
/Socks4ProxyClient.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Authors: Benton Stark
3 | *
4 | * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | *
24 | */
25 |
26 | using System;
27 | using System.Text;
28 | using System.Net;
29 | using System.Net.Sockets;
30 | using System.Globalization;
31 | using System.IO;
32 | using System.Threading;
33 | using System.ComponentModel;
34 |
35 | namespace Starksoft.Net.Proxy
36 | {
37 | ///
38 | /// Socks4 connection proxy class. This class implements the Socks4 standard proxy protocol.
39 | ///
40 | ///
41 | /// This class implements the Socks4 proxy protocol standard for TCP communciations.
42 | ///
43 | public class Socks4ProxyClient : IProxyClient
44 | {
45 | private const int WAIT_FOR_DATA_INTERVAL = 50; // 50 ms
46 | private const int WAIT_FOR_DATA_TIMEOUT = 15000; // 15 seconds
47 | private const string PROXY_NAME = "SOCKS4";
48 | private TcpClient _tcpClient;
49 |
50 | private string _proxyHost;
51 | private int _proxyPort;
52 | private string _proxyUserId;
53 |
54 | ///
55 | /// Default Socks4 proxy port.
56 | ///
57 | internal const int SOCKS_PROXY_DEFAULT_PORT = 1080;
58 | ///
59 | /// Socks4 version number.
60 | ///
61 | internal const byte SOCKS4_VERSION_NUMBER = 4;
62 | ///
63 | /// Socks4 connection command value.
64 | ///
65 | internal const byte SOCKS4_CMD_CONNECT = 0x01;
66 | ///
67 | /// Socks4 bind command value.
68 | ///
69 | internal const byte SOCKS4_CMD_BIND = 0x02;
70 | ///
71 | /// Socks4 reply request grant response value.
72 | ///
73 | internal const byte SOCKS4_CMD_REPLY_REQUEST_GRANTED = 90;
74 | ///
75 | /// Socks4 reply request rejected or failed response value.
76 | ///
77 | internal const byte SOCKS4_CMD_REPLY_REQUEST_REJECTED_OR_FAILED = 91;
78 | ///
79 | /// Socks4 reply request rejected becauase the proxy server can not connect to the IDENTD server value.
80 | ///
81 | internal const byte SOCKS4_CMD_REPLY_REQUEST_REJECTED_CANNOT_CONNECT_TO_IDENTD = 92;
82 | ///
83 | /// Socks4 reply request rejected because of a different IDENTD server.
84 | ///
85 | internal const byte SOCKS4_CMD_REPLY_REQUEST_REJECTED_DIFFERENT_IDENTD = 93;
86 |
87 | ///
88 | /// Create a Socks4 proxy client object. The default proxy port 1080 is used.
89 | ///
90 | public Socks4ProxyClient() { }
91 |
92 | ///
93 | /// Creates a Socks4 proxy client object using the supplied TcpClient object connection.
94 | ///
95 | /// A TcpClient connection object.
96 | public Socks4ProxyClient(TcpClient tcpClient)
97 | {
98 | if (tcpClient == null)
99 | throw new ArgumentNullException("tcpClient");
100 |
101 | _tcpClient = tcpClient;
102 | }
103 |
104 | ///
105 | /// Create a Socks4 proxy client object. The default proxy port 1080 is used.
106 | ///
107 | /// Host name or IP address of the proxy server.
108 | /// Proxy user identification information.
109 | public Socks4ProxyClient(string proxyHost, string proxyUserId)
110 | {
111 | if (String.IsNullOrEmpty(proxyHost))
112 | throw new ArgumentNullException("proxyHost");
113 |
114 | if (proxyUserId == null)
115 | throw new ArgumentNullException("proxyUserId");
116 |
117 | _proxyHost = proxyHost;
118 | _proxyPort = SOCKS_PROXY_DEFAULT_PORT;
119 | _proxyUserId = proxyUserId;
120 | }
121 |
122 | ///
123 | /// Create a Socks4 proxy client object.
124 | ///
125 | /// Host name or IP address of the proxy server.
126 | /// Port used to connect to proxy server.
127 | /// Proxy user identification information.
128 | public Socks4ProxyClient(string proxyHost, int proxyPort, string proxyUserId)
129 | {
130 | if (String.IsNullOrEmpty(proxyHost))
131 | throw new ArgumentNullException("proxyHost");
132 |
133 | if (proxyPort <= 0 || proxyPort > 65535)
134 | throw new ArgumentOutOfRangeException("proxyPort", "port must be greater than zero and less than 65535");
135 |
136 | if (proxyUserId == null)
137 | throw new ArgumentNullException("proxyUserId");
138 |
139 | _proxyHost = proxyHost;
140 | _proxyPort = proxyPort;
141 | _proxyUserId = proxyUserId;
142 | }
143 |
144 | ///
145 | /// Create a Socks4 proxy client object. The default proxy port 1080 is used.
146 | ///
147 | /// Host name or IP address of the proxy server.
148 | public Socks4ProxyClient(string proxyHost)
149 | {
150 | if (String.IsNullOrEmpty(proxyHost))
151 | throw new ArgumentNullException("proxyHost");
152 |
153 | _proxyHost = proxyHost;
154 | _proxyPort = SOCKS_PROXY_DEFAULT_PORT;
155 | }
156 |
157 | ///
158 | /// Create a Socks4 proxy client object.
159 | ///
160 | /// Host name or IP address of the proxy server.
161 | /// Port used to connect to proxy server.
162 | public Socks4ProxyClient(string proxyHost, int proxyPort)
163 | {
164 | if (String.IsNullOrEmpty(proxyHost))
165 | throw new ArgumentNullException("proxyHost");
166 |
167 | if (proxyPort <= 0 || proxyPort > 65535)
168 | throw new ArgumentOutOfRangeException("proxyPort", "port must be greater than zero and less than 65535");
169 |
170 | _proxyHost = proxyHost;
171 | _proxyPort = proxyPort;
172 | }
173 |
174 | ///
175 | /// Gets or sets host name or IP address of the proxy server.
176 | ///
177 | public string ProxyHost
178 | {
179 | get { return _proxyHost; }
180 | set { _proxyHost = value; }
181 | }
182 |
183 | ///
184 | /// Gets or sets port used to connect to proxy server.
185 | ///
186 | public int ProxyPort
187 | {
188 | get { return _proxyPort; }
189 | set { _proxyPort = value; }
190 | }
191 |
192 | ///
193 | /// Gets String representing the name of the proxy.
194 | ///
195 | /// This property will always return the value 'SOCKS4'
196 | virtual public string ProxyName
197 | {
198 | get { return PROXY_NAME; }
199 | }
200 |
201 | ///
202 | /// Gets or sets proxy user identification information.
203 | ///
204 | public string ProxyUserId
205 | {
206 | get { return _proxyUserId; }
207 | set { _proxyUserId = value; }
208 | }
209 |
210 | ///
211 | /// Gets or sets the TcpClient object.
212 | /// This property can be set prior to executing CreateConnection to use an existing TcpClient connection.
213 | ///
214 | public TcpClient TcpClient
215 | {
216 | get { return _tcpClient; }
217 | set { _tcpClient = value; }
218 | }
219 |
220 | ///
221 | /// Creates a TCP connection to the destination host through the proxy server
222 | /// host.
223 | ///
224 | /// Destination host name or IP address of the destination server.
225 | /// Port number to connect to on the destination server.
226 | ///
227 | /// Returns an open TcpClient object that can be used normally to communicate
228 | /// with the destination server
229 | ///
230 | ///
231 | /// This method creates a connection to the proxy server and instructs the proxy server
232 | /// to make a pass through connection to the specified destination host on the specified
233 | /// port.
234 | ///
235 | public TcpClient CreateConnection(string destinationHost, int destinationPort)
236 | {
237 | if (String.IsNullOrEmpty(destinationHost))
238 | throw new ArgumentNullException("destinationHost");
239 |
240 | if (destinationPort <= 0 || destinationPort > 65535)
241 | throw new ArgumentOutOfRangeException("destinationPort", "port must be greater than zero and less than 65535");
242 |
243 | try
244 | {
245 | // if we have no connection, create one
246 | if (_tcpClient == null)
247 | {
248 | if (String.IsNullOrEmpty(_proxyHost))
249 | throw new ProxyException("ProxyHost property must contain a value.");
250 |
251 | if (_proxyPort <= 0 || _proxyPort > 65535)
252 | throw new ProxyException("ProxyPort value must be greater than zero and less than 65535");
253 |
254 | // create new tcp client object to the proxy server
255 | _tcpClient = new TcpClient();
256 |
257 | // attempt to open the connection
258 | _tcpClient.Connect(_proxyHost, _proxyPort);
259 | }
260 |
261 | // send connection command to proxy host for the specified destination host and port
262 | SendCommand(_tcpClient.GetStream(), SOCKS4_CMD_CONNECT, destinationHost, destinationPort, _proxyUserId);
263 |
264 | // return the open proxied tcp client object to the caller for normal use
265 | return _tcpClient;
266 | }
267 | catch (Exception ex)
268 | {
269 | throw new ProxyException(String.Format(CultureInfo.InvariantCulture, "Connection to proxy host {0} on port {1} failed.", Utils.GetHost(_tcpClient), Utils.GetPort(_tcpClient)), ex);
270 | }
271 | }
272 |
273 |
274 | ///
275 | /// Sends a command to the proxy server.
276 | ///
277 | /// Proxy server data stream.
278 | /// Proxy byte command to execute.
279 | /// Destination host name or IP address.
280 | /// Destination port number
281 | /// IDENTD user ID value.
282 | internal virtual void SendCommand(NetworkStream proxy, byte command, string destinationHost, int destinationPort, string userId)
283 | {
284 | // PROXY SERVER REQUEST
285 | // The client connects to the SOCKS server and sends a CONNECT request when
286 | // it wants to establish a connection to an application server. The client
287 | // includes in the request packet the IP address and the port number of the
288 | // destination host, and userid, in the following format.
289 | //
290 | // +----+----+----+----+----+----+----+----+----+----+....+----+
291 | // | VN | CD | DSTPORT | DSTIP | USERID |NULL|
292 | // +----+----+----+----+----+----+----+----+----+----+....+----+
293 | // # of bytes: 1 1 2 4 variable 1
294 | //
295 | // VN is the SOCKS protocol version number and should be 4. CD is the
296 | // SOCKS command code and should be 1 for CONNECT request. NULL is a byte
297 | // of all zero bits.
298 |
299 | // userId needs to be a zero length string so that the GetBytes method
300 | // works properly
301 | if (userId == null)
302 | userId = "";
303 |
304 | byte[] destIp = GetIPAddressBytes(destinationHost);
305 | byte[] destPort = GetDestinationPortBytes(destinationPort);
306 | byte[] userIdBytes = ASCIIEncoding.ASCII.GetBytes(userId);
307 | byte[] request = new byte[9 + userIdBytes.Length];
308 |
309 | // set the bits on the request byte array
310 | request[0] = SOCKS4_VERSION_NUMBER;
311 | request[1] = command;
312 | destPort.CopyTo(request, 2);
313 | destIp.CopyTo(request, 4);
314 | userIdBytes.CopyTo(request, 8);
315 | request[8 + userIdBytes.Length] = 0x00; // null (byte with all zeros) terminator for userId
316 |
317 | // send the connect request
318 | proxy.Write(request, 0, request.Length);
319 |
320 | // wait for the proxy server to respond
321 | WaitForData(proxy);
322 |
323 | // PROXY SERVER RESPONSE
324 | // The SOCKS server checks to see whether such a request should be granted
325 | // based on any combination of source IP address, destination IP address,
326 | // destination port number, the userid, and information it may obtain by
327 | // consulting IDENT, cf. RFC 1413. If the request is granted, the SOCKS
328 | // server makes a connection to the specified port of the destination host.
329 | // A reply packet is sent to the client when this connection is established,
330 | // or when the request is rejected or the operation fails.
331 | //
332 | // +----+----+----+----+----+----+----+----+
333 | // | VN | CD | DSTPORT | DSTIP |
334 | // +----+----+----+----+----+----+----+----+
335 | // # of bytes: 1 1 2 4
336 | //
337 | // VN is the version of the reply code and should be 0. CD is the result
338 | // code with one of the following values:
339 | //
340 | // 90: request granted
341 | // 91: request rejected or failed
342 | // 92: request rejected becuase SOCKS server cannot connect to
343 | // identd on the client
344 | // 93: request rejected because the client program and identd
345 | // report different user-ids
346 | //
347 | // The remaining fields are ignored.
348 | //
349 | // The SOCKS server closes its connection immediately after notifying
350 | // the client of a failed or rejected request. For a successful request,
351 | // the SOCKS server gets ready to relay traffic on both directions. This
352 | // enables the client to do I/O on its connection as if it were directly
353 | // connected to the application server.
354 |
355 | // create an 8 byte response array
356 | byte[] response = new byte[8];
357 |
358 | // read the resonse from the network stream
359 | proxy.Read(response, 0, 8);
360 |
361 | // evaluate the reply code for an error condition
362 | if (response[1] != SOCKS4_CMD_REPLY_REQUEST_GRANTED)
363 | HandleProxyCommandError(response, destinationHost, destinationPort);
364 | }
365 |
366 | ///
367 | /// Translate the host name or IP address to a byte array.
368 | ///
369 | /// Host name or IP address.
370 | /// Byte array representing IP address in bytes.
371 | internal byte[] GetIPAddressBytes(string destinationHost)
372 | {
373 | IPAddress ipAddr = null;
374 |
375 | // if the address doesn't parse then try to resolve with dns
376 | if (!IPAddress.TryParse(destinationHost, out ipAddr))
377 | {
378 | try
379 | {
380 | ipAddr = Dns.GetHostEntry(destinationHost).AddressList[0];
381 | }
382 | catch (Exception ex)
383 | {
384 | throw new ProxyException(String.Format(CultureInfo.InvariantCulture, "A error occurred while attempting to DNS resolve the host name {0}.", destinationHost), ex);
385 | }
386 | }
387 |
388 | // return address bytes
389 | return ipAddr.GetAddressBytes();
390 | }
391 |
392 | ///
393 | /// Translate the destination port value to a byte array.
394 | ///
395 | /// Destination port.
396 | /// Byte array representing an 16 bit port number as two bytes.
397 | internal byte[] GetDestinationPortBytes(int value)
398 | {
399 | byte[] array = new byte[2];
400 | array[0] = Convert.ToByte(value / 256);
401 | array[1] = Convert.ToByte(value % 256);
402 | return array;
403 | }
404 |
405 | ///
406 | /// Receive a byte array from the proxy server and determine and handle and errors that may have occurred.
407 | ///
408 | /// Proxy server command response as a byte array.
409 | /// Destination host.
410 | /// Destination port number.
411 | internal void HandleProxyCommandError(byte[] response, string destinationHost, int destinationPort)
412 | {
413 |
414 | if (response == null)
415 | throw new ArgumentNullException("response");
416 |
417 | // extract the reply code
418 | byte replyCode = response[1];
419 |
420 | // extract the ip v4 address (4 bytes)
421 | byte[] ipBytes = new byte[4];
422 | for (int i = 0; i < 4; i++)
423 | ipBytes[i] = response[i + 4];
424 |
425 | // convert the ip address to an IPAddress object
426 | IPAddress ipAddr = new IPAddress(ipBytes);
427 |
428 | // extract the port number big endian (2 bytes)
429 | byte[] portBytes = new byte[2];
430 | portBytes[0] = response[3];
431 | portBytes[1] = response[2];
432 | Int16 port = BitConverter.ToInt16(portBytes, 0);
433 |
434 | // translate the reply code error number to human readable text
435 | string proxyErrorText;
436 | switch (replyCode)
437 | {
438 | case SOCKS4_CMD_REPLY_REQUEST_REJECTED_OR_FAILED:
439 | proxyErrorText = "connection request was rejected or failed";
440 | break;
441 | case SOCKS4_CMD_REPLY_REQUEST_REJECTED_CANNOT_CONNECT_TO_IDENTD:
442 | proxyErrorText = "connection request was rejected because SOCKS destination cannot connect to identd on the client";
443 | break;
444 | case SOCKS4_CMD_REPLY_REQUEST_REJECTED_DIFFERENT_IDENTD:
445 | proxyErrorText = "connection request rejected because the client program and identd report different user-ids";
446 | break;
447 | default:
448 | proxyErrorText = String.Format(CultureInfo.InvariantCulture, "proxy client received an unknown reply with the code value '{0}' from the proxy destination", replyCode.ToString(CultureInfo.InvariantCulture));
449 | break;
450 | }
451 |
452 | // build the exeception message string
453 | string exceptionMsg = String.Format(CultureInfo.InvariantCulture, "The {0} concerning destination host {1} port number {2}. The destination reported the host as {3} port {4}.", proxyErrorText, destinationHost, destinationPort, ipAddr.ToString(), port.ToString(CultureInfo.InvariantCulture));
454 |
455 | // throw a new application exception
456 | throw new ProxyException(exceptionMsg);
457 | }
458 |
459 | internal void WaitForData(NetworkStream stream)
460 | {
461 | int sleepTime = 0;
462 | while (!stream.DataAvailable)
463 | {
464 | Thread.Sleep(WAIT_FOR_DATA_INTERVAL);
465 | sleepTime += WAIT_FOR_DATA_INTERVAL;
466 | if (sleepTime > WAIT_FOR_DATA_TIMEOUT)
467 | throw new ProxyException("A timeout while waiting for the proxy destination to respond.");
468 | }
469 | }
470 |
471 |
472 | #region "Async Methods"
473 |
474 | private BackgroundWorker _asyncWorker;
475 | private Exception _asyncException;
476 | bool _asyncCancelled;
477 |
478 | ///
479 | /// Gets a value indicating whether an asynchronous operation is running.
480 | ///
481 | /// Returns true if an asynchronous operation is running; otherwise, false.
482 | ///
483 | public bool IsBusy
484 | {
485 | get { return _asyncWorker == null ? false : _asyncWorker.IsBusy; }
486 | }
487 |
488 | ///
489 | /// Gets a value indicating whether an asynchronous operation is cancelled.
490 | ///
491 | /// Returns true if an asynchronous operation is cancelled; otherwise, false.
492 | ///
493 | public bool IsAsyncCancelled
494 | {
495 | get { return _asyncCancelled; }
496 | }
497 |
498 | ///
499 | /// Cancels any asychronous operation that is currently active.
500 | ///
501 | public void CancelAsync()
502 | {
503 | if (_asyncWorker != null && !_asyncWorker.CancellationPending && _asyncWorker.IsBusy)
504 | {
505 | _asyncCancelled = true;
506 | _asyncWorker.CancelAsync();
507 | }
508 | }
509 |
510 | private void CreateAsyncWorker()
511 | {
512 | if (_asyncWorker != null)
513 | _asyncWorker.Dispose();
514 | _asyncException = null;
515 | _asyncWorker = null;
516 | _asyncCancelled = false;
517 | _asyncWorker = new BackgroundWorker();
518 | }
519 |
520 | ///
521 | /// Event handler for CreateConnectionAsync method completed.
522 | ///
523 | public event EventHandler CreateConnectionAsyncCompleted;
524 |
525 | ///
526 | /// Asynchronously creates a remote TCP connection through a proxy server to the destination host on the destination port
527 | /// using the supplied open TcpClient object with an open connection to proxy server.
528 | ///
529 | /// Destination host name or IP address.
530 | /// Port number to connect to on the destination host.
531 | ///
532 | /// Returns TcpClient object that can be used normally to communicate
533 | /// with the destination server.
534 | ///
535 | ///
536 | /// This instructs the proxy server to make a pass through connection to the specified destination host on the specified
537 | /// port.
538 | ///
539 | public void CreateConnectionAsync(string destinationHost, int destinationPort)
540 | {
541 | if (_asyncWorker != null && _asyncWorker.IsBusy)
542 | throw new InvalidOperationException("The Socks4/4a object is already busy executing another asynchronous operation. You can only execute one asychronous method at a time.");
543 |
544 | CreateAsyncWorker();
545 | _asyncWorker.WorkerSupportsCancellation = true;
546 | _asyncWorker.DoWork += new DoWorkEventHandler(CreateConnectionAsync_DoWork);
547 | _asyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(CreateConnectionAsync_RunWorkerCompleted);
548 | Object[] args = new Object[2];
549 | args[0] = destinationHost;
550 | args[1] = destinationPort;
551 | _asyncWorker.RunWorkerAsync(args);
552 | }
553 |
554 | private void CreateConnectionAsync_DoWork(object sender, DoWorkEventArgs e)
555 | {
556 | try
557 | {
558 | Object[] args = (Object[])e.Argument;
559 | e.Result = CreateConnection((string)args[0], (int)args[1]);
560 | }
561 | catch (Exception ex)
562 | {
563 | _asyncException = ex;
564 | }
565 | }
566 |
567 | private void CreateConnectionAsync_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
568 | {
569 | if (CreateConnectionAsyncCompleted != null)
570 | CreateConnectionAsyncCompleted(this, new CreateConnectionAsyncCompletedEventArgs(_asyncException, _asyncCancelled, (TcpClient)e.Result));
571 | }
572 |
573 | #endregion
574 |
575 | }
576 |
577 | }
578 |
--------------------------------------------------------------------------------
/Socks4aProxyClient.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Authors: Benton Stark
3 | *
4 | * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | *
24 | */
25 |
26 | using System;
27 | using System.Text;
28 | using System.Net;
29 | using System.Net.Sockets;
30 | using System.IO;
31 |
32 | namespace Starksoft.Net.Proxy
33 | {
34 | ///
35 | /// Socks4a connection proxy class. This class implements the Socks4a standard proxy protocol
36 | /// which is an extension of the Socks4 protocol
37 | ///
38 | ///
39 | /// In Socks version 4A if the client cannot resolve the destination host's domain name
40 | /// to find its IP address the server will attempt to resolve it.
41 | ///
42 | public class Socks4aProxyClient : Socks4ProxyClient
43 | {
44 | private const string PROXY_NAME = "SOCKS4a";
45 |
46 | ///
47 | /// Default constructor.
48 | ///
49 | public Socks4aProxyClient()
50 | : base()
51 | { }
52 |
53 | ///
54 | /// Creates a Socks4 proxy client object using the supplied TcpClient object connection.
55 | ///
56 | /// An open TcpClient object with an established connection.
57 | public Socks4aProxyClient(TcpClient tcpClient)
58 | : base(tcpClient)
59 | { }
60 |
61 | ///
62 | /// Create a Socks4a proxy client object. The default proxy port 1080 is used.
63 | ///
64 | /// Host name or IP address of the proxy server.
65 | /// Proxy user identification information for an IDENTD server.
66 | public Socks4aProxyClient(string proxyHost, string proxyUserId)
67 | : base(proxyHost, proxyUserId)
68 | { }
69 |
70 | ///
71 | /// Create a Socks4a proxy client object.
72 | ///
73 | /// Host name or IP address of the proxy server.
74 | /// Port used to connect to proxy server.
75 | /// Proxy user identification information.
76 | public Socks4aProxyClient(string proxyHost, int proxyPort, string proxyUserId)
77 | : base(proxyHost, proxyPort, proxyUserId)
78 | { }
79 |
80 | ///
81 | /// Create a Socks4 proxy client object. The default proxy port 1080 is used.
82 | ///
83 | /// Host name or IP address of the proxy server.
84 | public Socks4aProxyClient(string proxyHost) : base(proxyHost)
85 | { }
86 |
87 | ///
88 | /// Create a Socks4a proxy client object.
89 | ///
90 | /// Host name or IP address of the proxy server.
91 | /// Port used to connect to proxy server.
92 | public Socks4aProxyClient(string proxyHost, int proxyPort)
93 | : base(proxyHost, proxyPort)
94 | { }
95 |
96 | ///
97 | /// Gets String representing the name of the proxy.
98 | ///
99 | /// This property will always return the value 'SOCKS4a'
100 | public override string ProxyName
101 | {
102 | get { return PROXY_NAME; }
103 | }
104 |
105 |
106 | ///
107 | /// Sends a command to the proxy server.
108 | ///
109 | /// Proxy server data stream.
110 | /// Proxy byte command to execute.
111 | /// Destination host name or IP address.
112 | /// Destination port number
113 | /// IDENTD user ID value.
114 | ///
115 | /// This method override the SendCommand message in the Sock4ProxyClient object. The override adds support for the
116 | /// Socks4a extensions which allow the proxy client to optionally command the proxy server to resolve the
117 | /// destination host IP address.
118 | ///
119 | internal override void SendCommand(NetworkStream proxy, byte command, string destinationHost, int destinationPort, string userId)
120 | {
121 | // PROXY SERVER REQUEST
122 | //Please read SOCKS4.protocol first for an description of the version 4
123 | //protocol. This extension is intended to allow the use of SOCKS on hosts
124 | //which are not capable of resolving all domain names.
125 | //
126 | //In version 4, the client sends the following packet to the SOCKS server
127 | //to request a CONNECT or a BIND operation:
128 | //
129 | // +----+----+----+----+----+----+----+----+----+----+....+----+
130 | // | VN | CD | DSTPORT | DSTIP | USERID |NULL|
131 | // +----+----+----+----+----+----+----+----+----+----+....+----+
132 | // # of bytes: 1 1 2 4 variable 1
133 | //
134 | //VN is the SOCKS protocol version number and should be 4. CD is the
135 | //SOCKS command code and should be 1 for CONNECT or 2 for BIND. NULL
136 | //is a byte of all zero bits.
137 | //
138 | //For version 4A, if the client cannot resolve the destination host's
139 | //domain name to find its IP address, it should set the first three bytes
140 | //of DSTIP to NULL and the last byte to a non-zero value. (This corresponds
141 | //to IP address 0.0.0.x, with x nonzero. As decreed by IANA -- The
142 | //Internet Assigned Numbers Authority -- such an address is inadmissible
143 | //as a destination IP address and thus should never occur if the client
144 | //can resolve the domain name.) Following the NULL byte terminating
145 | //USERID, the client must sends the destination domain name and termiantes
146 | //it with another NULL byte. This is used for both CONNECT and BIND requests.
147 | //
148 | //A server using protocol 4A must check the DSTIP in the request packet.
149 | //If it represent address 0.0.0.x with nonzero x, the server must read
150 | //in the domain name that the client sends in the packet. The server
151 | //should resolve the domain name and make connection to the destination
152 | //host if it can.
153 | //
154 | //SOCKSified sockd may pass domain names that it cannot resolve to
155 | //the next-hop SOCKS server.
156 |
157 | // userId needs to be a zero length string so that the GetBytes method
158 | // works properly
159 | if (userId == null)
160 | userId = "";
161 |
162 | byte[] destIp = {0,0,0,1}; // build the invalid ip address as specified in the 4a protocol
163 | byte[] destPort = GetDestinationPortBytes(destinationPort);
164 | byte[] userIdBytes = ASCIIEncoding.ASCII.GetBytes(userId);
165 | byte[] hostBytes = ASCIIEncoding.ASCII.GetBytes(destinationHost);
166 | byte[] request = new byte[10 + userIdBytes.Length + hostBytes.Length];
167 |
168 | // set the bits on the request byte array
169 | request[0] = SOCKS4_VERSION_NUMBER;
170 | request[1] = command;
171 | destPort.CopyTo(request, 2);
172 | destIp.CopyTo(request, 4);
173 | userIdBytes.CopyTo(request, 8); // copy the userid to the request byte array
174 | request[8 + userIdBytes.Length] = 0x00; // null (byte with all zeros) terminator for userId
175 | hostBytes.CopyTo(request, 9 + userIdBytes.Length); // copy the host name to the request byte array
176 | request[9 + userIdBytes.Length + hostBytes.Length] = 0x00; // null (byte with all zeros) terminator for userId
177 |
178 | // send the connect request
179 | proxy.Write(request, 0, request.Length);
180 |
181 | // wait for the proxy server to send a response
182 | base.WaitForData(proxy);
183 |
184 | // PROXY SERVER RESPONSE
185 | // The SOCKS server checks to see whether such a request should be granted
186 | // based on any combination of source IP address, destination IP address,
187 | // destination port number, the userid, and information it may obtain by
188 | // consulting IDENT, cf. RFC 1413. If the request is granted, the SOCKS
189 | // server makes a connection to the specified port of the destination host.
190 | // A reply packet is sent to the client when this connection is established,
191 | // or when the request is rejected or the operation fails.
192 | //
193 | // +----+----+----+----+----+----+----+----+
194 | // | VN | CD | DSTPORT | DSTIP |
195 | // +----+----+----+----+----+----+----+----+
196 | // # of bytes: 1 1 2 4
197 | //
198 | // VN is the version of the reply code and should be 0. CD is the result
199 | // code with one of the following values:
200 | //
201 | // 90: request granted
202 | // 91: request rejected or failed
203 | // 92: request rejected becuase SOCKS server cannot connect to
204 | // identd on the client
205 | // 93: request rejected because the client program and identd
206 | // report different user-ids
207 | //
208 | // The remaining fields are ignored.
209 | //
210 | // The SOCKS server closes its connection immediately after notifying
211 | // the client of a failed or rejected request. For a successful request,
212 | // the SOCKS server gets ready to relay traffic on both directions. This
213 | // enables the client to do I/O on its connection as if it were directly
214 | // connected to the application server.
215 |
216 | // create an 8 byte response array
217 | byte[] response = new byte[8];
218 |
219 | // read the resonse from the network stream
220 | proxy.Read(response, 0, 8);
221 |
222 | // evaluate the reply code for an error condition
223 | if (response[1] != SOCKS4_CMD_REPLY_REQUEST_GRANTED)
224 | HandleProxyCommandError(response, destinationHost, destinationPort);
225 | }
226 |
227 |
228 |
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/Socks5ProxyClient.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Authors: Benton Stark
3 | *
4 | * Copyright (c) 2007-2009 Starksoft, LLC (http://www.starksoft.com)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | *
24 | */
25 |
26 | using System;
27 | using System.Text;
28 | using System.Net;
29 | using System.Net.Sockets;
30 | using System.Collections.Generic;
31 | using System.Globalization;
32 | using System.ComponentModel;
33 |
34 | namespace Starksoft.Net.Proxy
35 | {
36 | ///
37 | /// Socks5 connection proxy class. This class implements the Socks5 standard proxy protocol.
38 | ///
39 | ///
40 | /// This implementation supports TCP proxy connections with a Socks v5 server.
41 | ///
42 | public class Socks5ProxyClient : IProxyClient
43 | {
44 | private string _proxyHost;
45 | private int _proxyPort;
46 | private string _proxyUserName;
47 | private string _proxyPassword;
48 | private SocksAuthentication _proxyAuthMethod;
49 | private TcpClient _tcpClient;
50 |
51 | private const string PROXY_NAME = "SOCKS5";
52 | private const int SOCKS5_DEFAULT_PORT = 1080;
53 |
54 | private const byte SOCKS5_VERSION_NUMBER = 5;
55 | private const byte SOCKS5_RESERVED = 0x00;
56 | private const byte SOCKS5_AUTH_METHOD_NO_AUTHENTICATION_REQUIRED = 0x00;
57 | private const byte SOCKS5_AUTH_METHOD_GSSAPI = 0x01;
58 | private const byte SOCKS5_AUTH_METHOD_USERNAME_PASSWORD = 0x02;
59 | private const byte SOCKS5_AUTH_METHOD_IANA_ASSIGNED_RANGE_BEGIN = 0x03;
60 | private const byte SOCKS5_AUTH_METHOD_IANA_ASSIGNED_RANGE_END = 0x7f;
61 | private const byte SOCKS5_AUTH_METHOD_RESERVED_RANGE_BEGIN = 0x80;
62 | private const byte SOCKS5_AUTH_METHOD_RESERVED_RANGE_END = 0xfe;
63 | private const byte SOCKS5_AUTH_METHOD_REPLY_NO_ACCEPTABLE_METHODS = 0xff;
64 | private const byte SOCKS5_CMD_CONNECT = 0x01;
65 | private const byte SOCKS5_CMD_BIND = 0x02;
66 | private const byte SOCKS5_CMD_UDP_ASSOCIATE = 0x03;
67 | private const byte SOCKS5_CMD_REPLY_SUCCEEDED = 0x00;
68 | private const byte SOCKS5_CMD_REPLY_GENERAL_SOCKS_SERVER_FAILURE = 0x01;
69 | private const byte SOCKS5_CMD_REPLY_CONNECTION_NOT_ALLOWED_BY_RULESET = 0x02;
70 | private const byte SOCKS5_CMD_REPLY_NETWORK_UNREACHABLE = 0x03;
71 | private const byte SOCKS5_CMD_REPLY_HOST_UNREACHABLE = 0x04;
72 | private const byte SOCKS5_CMD_REPLY_CONNECTION_REFUSED = 0x05;
73 | private const byte SOCKS5_CMD_REPLY_TTL_EXPIRED = 0x06;
74 | private const byte SOCKS5_CMD_REPLY_COMMAND_NOT_SUPPORTED = 0x07;
75 | private const byte SOCKS5_CMD_REPLY_ADDRESS_TYPE_NOT_SUPPORTED = 0x08;
76 | private const byte SOCKS5_ADDRTYPE_IPV4 = 0x01;
77 | private const byte SOCKS5_ADDRTYPE_DOMAIN_NAME = 0x03;
78 | private const byte SOCKS5_ADDRTYPE_IPV6 = 0x04;
79 |
80 | ///
81 | /// Authentication itemType.
82 | ///
83 | private enum SocksAuthentication
84 | {
85 | ///
86 | /// No authentication used.
87 | ///
88 | None,
89 | ///
90 | /// Username and password authentication.
91 | ///
92 | UsernamePassword
93 | }
94 |
95 | ///
96 | /// Create a Socks5 proxy client object.
97 | ///
98 | public Socks5ProxyClient() { }
99 |
100 | ///
101 | /// Creates a Socks5 proxy client object using the supplied TcpClient object connection.
102 | ///
103 | /// A TcpClient connection object.
104 | public Socks5ProxyClient(TcpClient tcpClient)
105 | {
106 | if (tcpClient == null)
107 | throw new ArgumentNullException("tcpClient");
108 |
109 | _tcpClient = tcpClient;
110 | }
111 |
112 | ///
113 | /// Create a Socks5 proxy client object. The default proxy port 1080 is used.
114 | ///
115 | /// Host name or IP address of the proxy server.
116 | public Socks5ProxyClient(string proxyHost)
117 | {
118 | if (String.IsNullOrEmpty(proxyHost))
119 | throw new ArgumentNullException("proxyHost");
120 |
121 | _proxyHost = proxyHost;
122 | _proxyPort = SOCKS5_DEFAULT_PORT;
123 | }
124 |
125 | ///
126 | /// Create a Socks5 proxy client object.
127 | ///
128 | /// Host name or IP address of the proxy server.
129 | /// Port used to connect to proxy server.
130 | public Socks5ProxyClient(string proxyHost, int proxyPort)
131 | {
132 | if (String.IsNullOrEmpty(proxyHost))
133 | throw new ArgumentNullException("proxyHost");
134 |
135 | if (proxyPort <= 0 || proxyPort > 65535)
136 | throw new ArgumentOutOfRangeException("proxyPort", "port must be greater than zero and less than 65535");
137 |
138 | _proxyHost = proxyHost;
139 | _proxyPort = proxyPort;
140 | }
141 |
142 | ///
143 | /// Create a Socks5 proxy client object. The default proxy port 1080 is used.
144 | ///
145 | /// Host name or IP address of the proxy server.
146 | /// Proxy authentication user name.
147 | /// Proxy authentication password.
148 | public Socks5ProxyClient(string proxyHost, string proxyUserName, string proxyPassword)
149 | {
150 | if (String.IsNullOrEmpty(proxyHost))
151 | throw new ArgumentNullException("proxyHost");
152 |
153 | if (proxyUserName == null)
154 | throw new ArgumentNullException("proxyUserName");
155 |
156 | if (proxyPassword == null)
157 | throw new ArgumentNullException("proxyPassword");
158 |
159 | _proxyHost = proxyHost;
160 | _proxyPort = SOCKS5_DEFAULT_PORT;
161 | _proxyUserName = proxyUserName;
162 | _proxyPassword = proxyPassword;
163 | }
164 |
165 | ///
166 | /// Create a Socks5 proxy client object.
167 | ///
168 | /// Host name or IP address of the proxy server.
169 | /// Port used to connect to proxy server.
170 | /// Proxy authentication user name.
171 | /// Proxy authentication password.
172 | public Socks5ProxyClient(string proxyHost, int proxyPort, string proxyUserName, string proxyPassword)
173 | {
174 | if (String.IsNullOrEmpty(proxyHost))
175 | throw new ArgumentNullException("proxyHost");
176 |
177 | if (proxyPort <= 0 || proxyPort > 65535)
178 | throw new ArgumentOutOfRangeException("proxyPort", "port must be greater than zero and less than 65535");
179 |
180 | if (proxyUserName == null)
181 | throw new ArgumentNullException("proxyUserName");
182 |
183 | if (proxyPassword == null)
184 | throw new ArgumentNullException("proxyPassword");
185 |
186 | _proxyHost = proxyHost;
187 | _proxyPort = proxyPort;
188 | _proxyUserName = proxyUserName;
189 | _proxyPassword = proxyPassword;
190 | }
191 |
192 | ///
193 | /// Gets or sets host name or IP address of the proxy server.
194 | ///
195 | public string ProxyHost
196 | {
197 | get { return _proxyHost; }
198 | set { _proxyHost = value; }
199 | }
200 |
201 | ///
202 | /// Gets or sets port used to connect to proxy server.
203 | ///
204 | public int ProxyPort
205 | {
206 | get { return _proxyPort; }
207 | set { _proxyPort = value; }
208 | }
209 |
210 | ///
211 | /// Gets String representing the name of the proxy.
212 | ///
213 | /// This property will always return the value 'SOCKS5'
214 | public string ProxyName
215 | {
216 | get { return PROXY_NAME; }
217 | }
218 |
219 | ///
220 | /// Gets or sets proxy authentication user name.
221 | ///
222 | public string ProxyUserName
223 | {
224 | get { return _proxyUserName; }
225 | set { _proxyUserName = value; }
226 | }
227 |
228 | ///
229 | /// Gets or sets proxy authentication password.
230 | ///
231 | public string ProxyPassword
232 | {
233 | get { return _proxyPassword; }
234 | set { _proxyPassword = value; }
235 | }
236 |
237 | ///
238 | /// Gets or sets the TcpClient object.
239 | /// This property can be set prior to executing CreateConnection to use an existing TcpClient connection.
240 | ///
241 | public TcpClient TcpClient
242 | {
243 | get { return _tcpClient; }
244 | set { _tcpClient = value; }
245 | }
246 |
247 | ///
248 | /// Creates a remote TCP connection through a proxy server to the destination host on the destination port.
249 | ///
250 | /// Destination host name or IP address of the destination server.
251 | /// Port number to connect to on the destination host.
252 | ///
253 | /// Returns an open TcpClient object that can be used normally to communicate
254 | /// with the destination server
255 | ///
256 | ///
257 | /// This method creates a connection to the proxy server and instructs the proxy server
258 | /// to make a pass through connection to the specified destination host on the specified
259 | /// port.
260 | ///
261 | public TcpClient CreateConnection(string destinationHost, int destinationPort)
262 | {
263 | if (String.IsNullOrEmpty(destinationHost))
264 | throw new ArgumentNullException("destinationHost");
265 |
266 | if (destinationPort <= 0 || destinationPort > 65535)
267 | throw new ArgumentOutOfRangeException("destinationPort", "port must be greater than zero and less than 65535");
268 |
269 | try
270 | {
271 | // if we have no connection, create one
272 | if (_tcpClient == null)
273 | {
274 | if (String.IsNullOrEmpty(_proxyHost))
275 | throw new ProxyException("ProxyHost property must contain a value.");
276 |
277 | if (_proxyPort <= 0 || _proxyPort > 65535)
278 | throw new ProxyException("ProxyPort value must be greater than zero and less than 65535");
279 |
280 | // create new tcp client object to the proxy server
281 | _tcpClient = new TcpClient();
282 |
283 | // attempt to open the connection
284 | _tcpClient.Connect(_proxyHost, _proxyPort);
285 | }
286 |
287 | // determine which authentication method the client would like to use
288 | DetermineClientAuthMethod();
289 |
290 | // negotiate which authentication methods are supported / accepted by the server
291 | NegotiateServerAuthMethod();
292 |
293 | // send a connect command to the proxy server for destination host and port
294 | SendCommand(SOCKS5_CMD_CONNECT, destinationHost, destinationPort);
295 |
296 | // return the open proxied tcp client object to the caller for normal use
297 | return _tcpClient;
298 | }
299 | catch (Exception ex)
300 | {
301 | throw new ProxyException(String.Format(CultureInfo.InvariantCulture, "Connection to proxy host {0} on port {1} failed.", Utils.GetHost(_tcpClient), Utils.GetPort(_tcpClient)), ex);
302 | }
303 | }
304 |
305 |
306 | private void DetermineClientAuthMethod()
307 | {
308 | // set the authentication itemType used based on values inputed by the user
309 | if (_proxyUserName != null && _proxyPassword != null)
310 | _proxyAuthMethod = SocksAuthentication.UsernamePassword;
311 | else
312 | _proxyAuthMethod = SocksAuthentication.None;
313 | }
314 |
315 | private void NegotiateServerAuthMethod()
316 | {
317 | // get a reference to the network stream
318 | NetworkStream stream = _tcpClient.GetStream();
319 |
320 | // SERVER AUTHENTICATION REQUEST
321 | // The client connects to the server, and sends a version
322 | // identifier/method selection message:
323 | //
324 | // +----+----------+----------+
325 | // |VER | NMETHODS | METHODS |
326 | // +----+----------+----------+
327 | // | 1 | 1 | 1 to 255 |
328 | // +----+----------+----------+
329 |
330 | var haveUserPass = !String.IsNullOrEmpty(_proxyUserName) &&
331 | !String.IsNullOrEmpty(_proxyPassword);
332 |
333 | var authRequest = new List();
334 | authRequest.Add(SOCKS5_VERSION_NUMBER);
335 | if (haveUserPass) {
336 | authRequest.Add(2);
337 | authRequest.Add(SOCKS5_AUTH_METHOD_NO_AUTHENTICATION_REQUIRED);
338 | authRequest.Add(SOCKS5_AUTH_METHOD_USERNAME_PASSWORD);
339 | } else {
340 | authRequest.Add(1);
341 | authRequest.Add(SOCKS5_AUTH_METHOD_NO_AUTHENTICATION_REQUIRED);
342 | }
343 |
344 | // send the request to the server specifying authentication types supported by the client.
345 | stream.Write(authRequest.ToArray(), 0, authRequest.Count);
346 |
347 | // SERVER AUTHENTICATION RESPONSE
348 | // The server selects from one of the methods given in METHODS, and
349 | // sends a METHOD selection message:
350 | //
351 | // +----+--------+
352 | // |VER | METHOD |
353 | // +----+--------+
354 | // | 1 | 1 |
355 | // +----+--------+
356 | //
357 | // If the selected METHOD is X'FF', none of the methods listed by the
358 | // client are acceptable, and the client MUST close the connection.
359 | //
360 | // The values currently defined for METHOD are:
361 | // * X'00' NO AUTHENTICATION REQUIRED
362 | // * X'01' GSSAPI
363 | // * X'02' USERNAME/PASSWORD
364 | // * X'03' to X'7F' IANA ASSIGNED
365 | // * X'80' to X'FE' RESERVED FOR PRIVATE METHODS
366 | // * X'FF' NO ACCEPTABLE METHODS
367 |
368 | // receive the server response
369 | byte[] response = new byte[2];
370 | stream.Read(response, 0, response.Length);
371 |
372 | // the first byte contains the socks version number (e.g. 5)
373 | // the second byte contains the auth method acceptable to the proxy server
374 | byte acceptedAuthMethod = response[1];
375 |
376 | // if the server does not accept any of our supported authenication methods then throw an error
377 | if (acceptedAuthMethod == SOCKS5_AUTH_METHOD_REPLY_NO_ACCEPTABLE_METHODS)
378 | {
379 | _tcpClient.Close();
380 | throw new ProxyException("The proxy destination does not accept the supported proxy client authentication methods.");
381 | }
382 |
383 | // if the server accepts a username and password authentication and none is provided by the user then throw an error
384 | if (acceptedAuthMethod == SOCKS5_AUTH_METHOD_USERNAME_PASSWORD && _proxyAuthMethod == SocksAuthentication.None)
385 | {
386 | _tcpClient.Close();
387 | throw new ProxyException("The proxy destination requires a username and password for authentication.");
388 | }
389 |
390 | if (acceptedAuthMethod == SOCKS5_AUTH_METHOD_USERNAME_PASSWORD)
391 | {
392 |
393 | // USERNAME / PASSWORD SERVER REQUEST
394 | // Once the SOCKS V5 server has started, and the client has selected the
395 | // Username/Password Authentication protocol, the Username/Password
396 | // subnegotiation begins. This begins with the client producing a
397 | // Username/Password request:
398 | //
399 | // +----+------+----------+------+----------+
400 | // |VER | ULEN | UNAME | PLEN | PASSWD |
401 | // +----+------+----------+------+----------+
402 | // | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
403 | // +----+------+----------+------+----------+
404 |
405 | byte[] credentials = new byte[_proxyUserName.Length + _proxyPassword.Length + 3];
406 | credentials[0] = 1;
407 | credentials[1] = (byte)_proxyUserName.Length;
408 | Array.Copy(ASCIIEncoding.ASCII.GetBytes(_proxyUserName), 0, credentials, 2, _proxyUserName.Length);
409 | credentials[_proxyUserName.Length + 2] = (byte)_proxyPassword.Length;
410 | Array.Copy(ASCIIEncoding.ASCII.GetBytes(_proxyPassword), 0, credentials, _proxyUserName.Length + 3, _proxyPassword.Length);
411 |
412 | // USERNAME / PASSWORD SERVER RESPONSE
413 | // The server verifies the supplied UNAME and PASSWD, and sends the
414 | // following response:
415 | //
416 | // +----+--------+
417 | // |VER | STATUS |
418 | // +----+--------+
419 | // | 1 | 1 |
420 | // +----+--------+
421 | //
422 | // A STATUS field of X'00' indicates success. If the server returns a
423 | // `failure' (STATUS value other than X'00') status, it MUST close the
424 | // connection.
425 | stream.Write(credentials, 0, credentials.Length);
426 | byte[] crResponse = new byte[2];
427 | stream.Read(crResponse, 0, crResponse.Length);
428 |
429 | if (crResponse[1] != 0)
430 | {
431 | _tcpClient.Close();
432 | throw new ProxyException("Proxy authentification failure!");
433 | }
434 | }
435 | }
436 |
437 | private byte GetDestAddressType(string host)
438 | {
439 | IPAddress ipAddr = null;
440 |
441 | bool result = IPAddress.TryParse(host, out ipAddr);
442 |
443 | if (!result)
444 | return SOCKS5_ADDRTYPE_DOMAIN_NAME;
445 |
446 | switch (ipAddr.AddressFamily)
447 | {
448 | case AddressFamily.InterNetwork:
449 | return SOCKS5_ADDRTYPE_IPV4;
450 | case AddressFamily.InterNetworkV6:
451 | return SOCKS5_ADDRTYPE_IPV6;
452 | default:
453 | throw new ProxyException(String.Format(CultureInfo.InvariantCulture, "The host addess {0} of type '{1}' is not a supported address type. The supported types are InterNetwork and InterNetworkV6.", host, Enum.GetName(typeof(AddressFamily), ipAddr.AddressFamily)));
454 | }
455 |
456 | }
457 |
458 | private byte[] GetDestAddressBytes(byte addressType, string host)
459 | {
460 | switch (addressType)
461 | {
462 | case SOCKS5_ADDRTYPE_IPV4:
463 | case SOCKS5_ADDRTYPE_IPV6:
464 | return IPAddress.Parse(host).GetAddressBytes();
465 | case SOCKS5_ADDRTYPE_DOMAIN_NAME:
466 | // create a byte array to hold the host name bytes plus one byte to store the length
467 | byte[] bytes = new byte[host.Length + 1];
468 | // if the address field contains a fully-qualified domain name. The first
469 | // octet of the address field contains the number of octets of name that
470 | // follow, there is no terminating NUL octet.
471 | bytes[0] = Convert.ToByte(host.Length);
472 | Encoding.ASCII.GetBytes(host).CopyTo(bytes, 1);
473 | return bytes;
474 | default:
475 | return null;
476 | }
477 | }
478 |
479 | private byte[] GetDestPortBytes(int value)
480 | {
481 | byte[] array = new byte[2];
482 | array[0] = Convert.ToByte(value / 256);
483 | array[1] = Convert.ToByte(value % 256);
484 | return array;
485 | }
486 |
487 | private void SendCommand(byte command, string destinationHost, int destinationPort)
488 | {
489 | NetworkStream stream = _tcpClient.GetStream();
490 |
491 | byte addressType = GetDestAddressType(destinationHost);
492 | byte[] destAddr = GetDestAddressBytes(addressType, destinationHost);
493 | byte[] destPort = GetDestPortBytes(destinationPort);
494 |
495 | // The connection request is made up of 6 bytes plus the
496 | // length of the variable address byte array
497 | //
498 | // +----+-----+-------+------+----------+----------+
499 | // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
500 | // +----+-----+-------+------+----------+----------+
501 | // | 1 | 1 | X'00' | 1 | Variable | 2 |
502 | // +----+-----+-------+------+----------+----------+
503 | //
504 | // * VER protocol version: X'05'
505 | // * CMD
506 | // * CONNECT X'01'
507 | // * BIND X'02'
508 | // * UDP ASSOCIATE X'03'
509 | // * RSV RESERVED
510 | // * ATYP address itemType of following address
511 | // * IP V4 address: X'01'
512 | // * DOMAINNAME: X'03'
513 | // * IP V6 address: X'04'
514 | // * DST.ADDR desired destination address
515 | // * DST.PORT desired destination port in network octet order
516 |
517 | byte[] request = new byte[4 + destAddr.Length + 2];
518 | request[0] = SOCKS5_VERSION_NUMBER;
519 | request[1] = command;
520 | request[2] = SOCKS5_RESERVED;
521 | request[3] = addressType;
522 | destAddr.CopyTo(request, 4);
523 | destPort.CopyTo(request, 4 + destAddr.Length);
524 |
525 | // send connect request.
526 | stream.Write(request, 0, request.Length);
527 |
528 | // PROXY SERVER RESPONSE
529 | // +----+-----+-------+------+----------+----------+
530 | // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
531 | // +----+-----+-------+------+----------+----------+
532 | // | 1 | 1 | X'00' | 1 | Variable | 2 |
533 | // +----+-----+-------+------+----------+----------+
534 | //
535 | // * VER protocol version: X'05'
536 | // * REP Reply field:
537 | // * X'00' succeeded
538 | // * X'01' general SOCKS server failure
539 | // * X'02' connection not allowed by ruleset
540 | // * X'03' Network unreachable
541 | // * X'04' Host unreachable
542 | // * X'05' Connection refused
543 | // * X'06' TTL expired
544 | // * X'07' Command not supported
545 | // * X'08' Address itemType not supported
546 | // * X'09' to X'FF' unassigned
547 | //* RSV RESERVED
548 | //* ATYP address itemType of following address
549 |
550 | byte[] response = new byte[255];
551 |
552 | // read proxy server response
553 | var responseSize = stream.Read(response, 0, response.Length);
554 |
555 | byte replyCode = response[1];
556 |
557 | // evaluate the reply code for an error condition
558 | if (responseSize < 2 || replyCode != SOCKS5_CMD_REPLY_SUCCEEDED)
559 | HandleProxyCommandError(response, destinationHost, destinationPort );
560 | }
561 |
562 | private void HandleProxyCommandError(byte[] response, string destinationHost, int destinationPort)
563 | {
564 | string proxyErrorText;
565 | byte replyCode = response[1];
566 | byte addrType = response[3];
567 | string addr = "";
568 | Int16 port = 0;
569 |
570 | switch (addrType)
571 | {
572 | case SOCKS5_ADDRTYPE_DOMAIN_NAME:
573 | int addrLen = Convert.ToInt32(response[4]);
574 | byte[] addrBytes = new byte[addrLen];
575 | for (int i = 0; i < addrLen; i++)
576 | addrBytes[i] = response[i + 5];
577 | addr = System.Text.ASCIIEncoding.ASCII.GetString(addrBytes);
578 | byte[] portBytesDomain = new byte[2];
579 | portBytesDomain[0] = response[6 + addrLen];
580 | portBytesDomain[1] = response[5 + addrLen];
581 | port = BitConverter.ToInt16(portBytesDomain, 0);
582 | break;
583 |
584 | case SOCKS5_ADDRTYPE_IPV4:
585 | byte[] ipv4Bytes = new byte[4];
586 | for (int i = 0; i < 4; i++)
587 | ipv4Bytes[i] = response[i + 4];
588 | IPAddress ipv4 = new IPAddress(ipv4Bytes);
589 | addr = ipv4.ToString();
590 | byte[] portBytesIpv4 = new byte[2];
591 | portBytesIpv4[0] = response[9];
592 | portBytesIpv4[1] = response[8];
593 | port = BitConverter.ToInt16(portBytesIpv4, 0);
594 | break;
595 |
596 | case SOCKS5_ADDRTYPE_IPV6:
597 | byte[] ipv6Bytes = new byte[16];
598 | for (int i = 0; i < 16; i++)
599 | ipv6Bytes[i] = response[i + 4];
600 | IPAddress ipv6 = new IPAddress(ipv6Bytes);
601 | addr = ipv6.ToString();
602 | byte[] portBytesIpv6 = new byte[2];
603 | portBytesIpv6[0] = response[21];
604 | portBytesIpv6[1] = response[20];
605 | port = BitConverter.ToInt16(portBytesIpv6, 0);
606 | break;
607 | }
608 |
609 |
610 | switch (replyCode)
611 | {
612 | case SOCKS5_CMD_REPLY_GENERAL_SOCKS_SERVER_FAILURE:
613 | proxyErrorText = "a general socks destination failure occurred";
614 | break;
615 | case SOCKS5_CMD_REPLY_CONNECTION_NOT_ALLOWED_BY_RULESET:
616 | proxyErrorText = "the connection is not allowed by proxy destination rule set";
617 | break;
618 | case SOCKS5_CMD_REPLY_NETWORK_UNREACHABLE:
619 | proxyErrorText = "the network was unreachable";
620 | break;
621 | case SOCKS5_CMD_REPLY_HOST_UNREACHABLE:
622 | proxyErrorText = "the host was unreachable";
623 | break;
624 | case SOCKS5_CMD_REPLY_CONNECTION_REFUSED:
625 | proxyErrorText = "the connection was refused by the remote network";
626 | break;
627 | case SOCKS5_CMD_REPLY_TTL_EXPIRED:
628 | proxyErrorText = "the time to live (TTL) has expired";
629 | break;
630 | case SOCKS5_CMD_REPLY_COMMAND_NOT_SUPPORTED:
631 | proxyErrorText = "the command issued by the proxy client is not supported by the proxy destination";
632 | break;
633 | case SOCKS5_CMD_REPLY_ADDRESS_TYPE_NOT_SUPPORTED:
634 | proxyErrorText = "the address type specified is not supported";
635 | break;
636 | default:
637 | proxyErrorText = String.Format(CultureInfo.InvariantCulture, "that an unknown reply with the code value '{0}' was received by the destination", replyCode.ToString(CultureInfo.InvariantCulture));
638 | break;
639 | }
640 | string exceptionMsg = String.Format(CultureInfo.InvariantCulture, "The {0} concerning destination host {1} port number {2}. The destination reported the host as {3} port {4}.", proxyErrorText, destinationHost, destinationPort, addr, port.ToString(CultureInfo.InvariantCulture));
641 |
642 | throw new ProxyException(exceptionMsg);
643 |
644 | }
645 |
646 |
647 | #region "Async Methods"
648 |
649 | private BackgroundWorker _asyncWorker;
650 | private Exception _asyncException;
651 | bool _asyncCancelled;
652 |
653 | ///
654 | /// Gets a value indicating whether an asynchronous operation is running.
655 | ///
656 | /// Returns true if an asynchronous operation is running; otherwise, false.
657 | ///
658 | public bool IsBusy
659 | {
660 | get { return _asyncWorker == null ? false : _asyncWorker.IsBusy; }
661 | }
662 |
663 | ///
664 | /// Gets a value indicating whether an asynchronous operation is cancelled.
665 | ///
666 | /// Returns true if an asynchronous operation is cancelled; otherwise, false.
667 | ///
668 | public bool IsAsyncCancelled
669 | {
670 | get { return _asyncCancelled; }
671 | }
672 |
673 | ///
674 | /// Cancels any asychronous operation that is currently active.
675 | ///
676 | public void CancelAsync()
677 | {
678 | if (_asyncWorker != null && !_asyncWorker.CancellationPending && _asyncWorker.IsBusy)
679 | {
680 | _asyncCancelled = true;
681 | _asyncWorker.CancelAsync();
682 | }
683 | }
684 |
685 | private void CreateAsyncWorker()
686 | {
687 | if (_asyncWorker != null)
688 | _asyncWorker.Dispose();
689 | _asyncException = null;
690 | _asyncWorker = null;
691 | _asyncCancelled = false;
692 | _asyncWorker = new BackgroundWorker();
693 | }
694 |
695 | ///
696 | /// Event handler for CreateConnectionAsync method completed.
697 | ///
698 | public event EventHandler CreateConnectionAsyncCompleted;
699 |
700 |
701 | ///
702 | /// Asynchronously creates a remote TCP connection through a proxy server to the destination host on the destination port.
703 | ///
704 | /// Destination host name or IP address.
705 | /// Port number to connect to on the destination host.
706 | ///
707 | /// Returns TcpClient object that can be used normally to communicate
708 | /// with the destination server.
709 | ///
710 | ///
711 | /// This method instructs the proxy server
712 | /// to make a pass through connection to the specified destination host on the specified
713 | /// port.
714 | ///
715 | public void CreateConnectionAsync(string destinationHost, int destinationPort)
716 | {
717 | if (_asyncWorker != null && _asyncWorker.IsBusy)
718 | throw new InvalidOperationException("The Socks4 object is already busy executing another asynchronous operation. You can only execute one asychronous method at a time.");
719 |
720 | CreateAsyncWorker();
721 | _asyncWorker.WorkerSupportsCancellation = true;
722 | _asyncWorker.DoWork += new DoWorkEventHandler(CreateConnectionAsync_DoWork);
723 | _asyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(CreateConnectionAsync_RunWorkerCompleted);
724 | Object[] args = new Object[2];
725 | args[0] = destinationHost;
726 | args[1] = destinationPort;
727 | _asyncWorker.RunWorkerAsync(args);
728 | }
729 |
730 | private void CreateConnectionAsync_DoWork(object sender, DoWorkEventArgs e)
731 | {
732 | try
733 | {
734 | Object[] args = (Object[])e.Argument;
735 | e.Result = CreateConnection((string)args[0], (int)args[1]);
736 | }
737 | catch (Exception ex)
738 | {
739 | _asyncException = ex;
740 | }
741 | }
742 |
743 | private void CreateConnectionAsync_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
744 | {
745 | if (CreateConnectionAsyncCompleted != null)
746 | CreateConnectionAsyncCompleted(this, new CreateConnectionAsyncCompletedEventArgs(_asyncException, _asyncCancelled, (TcpClient)e.Result));
747 | }
748 |
749 |
750 |
751 | #endregion
752 | }
753 | }
754 |
--------------------------------------------------------------------------------
/StarkSoftProxy.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 10.0.0
7 | 2.0
8 | {3F8CF2C1-EA37-444F-8693-A3A00B1131D2}
9 | Library
10 | StarkSoftProxy
11 | StarkSoftProxy
12 | v4.5
13 |
14 |
15 | true
16 | full
17 | false
18 | bin\Debug
19 | DEBUG;
20 | prompt
21 | 4
22 | false
23 |
24 |
25 | none
26 | false
27 | bin\Release
28 | prompt
29 | 4
30 | false
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/Utils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using System.Globalization;
4 | using System.Net.Sockets;
5 |
6 | namespace Starksoft.Net.Proxy
7 | {
8 | internal static class Utils
9 | {
10 | internal static string GetHost(TcpClient client)
11 | {
12 | if (client == null)
13 | throw new ArgumentNullException("client");
14 |
15 | string host = "";
16 | try
17 | {
18 | host = ((System.Net.IPEndPoint)client.Client.RemoteEndPoint).Address.ToString();
19 | }
20 | catch
21 | { };
22 |
23 | return host;
24 | }
25 |
26 | internal static string GetPort(TcpClient client)
27 | {
28 | if (client == null)
29 | throw new ArgumentNullException("client");
30 |
31 | string port = "";
32 | try
33 | {
34 | port = ((System.Net.IPEndPoint)client.Client.RemoteEndPoint).Port.ToString(CultureInfo.InvariantCulture);
35 | }
36 | catch
37 | { };
38 |
39 | return port;
40 | }
41 |
42 | }
43 | }
44 |
--------------------------------------------------------------------------------