Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
Socks5Client.cs
1using System;
2using System.IO;
3using System.Net;
4using System.Net.Sockets;
5using System.Text;
6using System.Threading.Tasks;
7using Waher.Content;
8using Waher.Events;
10using Waher.Security;
11
13{
17 public enum Socks5State
18 {
22 Offline,
23
27 Connecting,
28
32 Initializing,
33
37 Authenticating,
38
42 Authenticated,
43
47 Connected,
48
52 Error
53 }
54
60 public class Socks5Client : CommunicationLayer, IDisposable, IHostReference
61 {
62 private BinaryTcpClient client;
63 private Socks5State state = Socks5State.Offline;
64 private readonly object synchObj = new object();
65 private readonly string host;
66 private readonly int port;
67 private readonly string jid;
68 private bool closeWhenDone = false;
69 private bool disposed = false;
70 private object callbackState;
71 private object tag = null;
72 private bool isWriting = false;
73
81 public Socks5Client(string Host, int Port, string JID, params ISniffer[] Sniffers)
82 : base(false, Sniffers)
83 {
84 this.host = Host;
85 this.port = Port;
86 this.jid = JID;
87
88 Task.Run(async () =>
89 {
90 try
91 {
92 await this.SetState(Socks5State.Connecting);
93 await this.Information("Connecting to " + this.host + ":" + this.port.ToString());
94 }
95 catch (Exception ex)
96 {
97 Log.Exception(ex);
98 }
99 });
100
101 this.client = new BinaryTcpClient(false);
102 this.Connect();
103 }
104
105 private async void Connect()
106 {
107 try
108 {
109 this.client.OnReceived += this.Client_OnReceived;
110 this.client.OnSent += this.Client_OnSent;
111 this.client.OnError += this.Client_OnError;
112 this.client.OnDisconnected += this.Client_OnDisconnected;
113 this.client.OnWriteQueueEmpty += this.Client_OnWriteQueueEmpty;
114
115 await this.client.ConnectAsync(this.host, this.port);
116 if (this.disposed)
117 return;
118
119 await this.Information("Connected to " + this.host + ":" + this.port.ToString());
120
121 this.state = Socks5State.Initializing;
122 await this.SendPacket(new byte[] { 5, 1, 0 });
123 }
124 catch (Exception ex)
125 {
126 Log.Exception(ex);
127 await this.SetState(Socks5State.Error);
128 }
129 }
130
131 private async Task Client_OnWriteQueueEmpty(object Sender, EventArgs e)
132 {
133 bool DoDispose;
134
135 lock (this.synchObj)
136 {
137 this.isWriting = false;
138 DoDispose = this.closeWhenDone;
139 }
140
141 if (DoDispose)
142 this.Dispose();
143 else
144 await this.OnWriteQueueEmpty.Raise(this, e);
145 }
146
147 private Task Client_OnDisconnected(object Sender, EventArgs e)
148 {
149 return this.SetState(Socks5State.Offline);
150 }
151
152 private Task Client_OnError(object Sender, Exception Exception)
153 {
154 return this.SetState(Socks5State.Error);
155 }
156
157 private Task Client_OnSent(object Sender, byte[] Buffer, int Offset, int Count)
158 {
159 if (this.HasSniffers)
160 return this.TransmitBinary(BinaryTcpClient.ToArray(Buffer, Offset, Count));
161 else
162 return Task.CompletedTask;
163 }
164
165 private async Task<bool> Client_OnReceived(object Sender, byte[] Buffer, int Offset, int Count)
166 {
167 if (this.HasSniffers)
168 await this.ReceiveBinary(BinaryTcpClient.ToArray(Buffer, Offset, Count));
169
170 try
171 {
172 await this.ParseIncoming(Buffer, Offset, Count);
173 return true;
174 }
175 catch (Exception ex)
176 {
177 Log.Exception(ex);
178 return false;
179 }
180 }
181
185 public Socks5State State => this.state;
186
187 internal async Task SetState(Socks5State NewState)
188 {
189 if (this.state != NewState)
190 {
191 this.state = NewState;
192 await this.Information("State changed to " + this.state.ToString());
193
194 await this.OnStateChange.Raise(this, EventArgs.Empty);
195 }
196 }
197
198 internal object CallbackState
199 {
200 get => this.callbackState;
201 set => this.callbackState = value;
202 }
203
207 public object Tag
208 {
209 get => this.tag;
210 set => this.tag = value;
211 }
212
216 public event EventHandlerAsync OnStateChange = null;
217
221 public string Host => this.host;
222
226 public int Port => this.port;
227
231 public string JID => this.jid;
232
236 public void Dispose()
237 {
238 if (!this.disposed)
239 {
240 this.disposed = true;
241 Task _ = this.SetState(Socks5State.Offline);
242
243 this.client?.Dispose();
244 this.client = null;
245 }
246 }
247
253 public Task<bool> Send(byte[] Data)
254 {
255 if (this.state != Socks5State.Connected)
256 throw new IOException("SOCKS5 connection not open.");
257
258 return this.SendPacket(Data);
259 }
260
261 private Task<bool> SendPacket(byte[] Data)
262 {
263 lock (this.synchObj)
264 {
265 this.isWriting = true;
266 }
267
268 return this.client.SendAsync(Data);
269 }
270
275
279 public void CloseWhenDone()
280 {
281 lock (this.synchObj)
282 {
283 if (this.isWriting)
284 {
285 this.closeWhenDone = true;
286 return;
287 }
288 }
289
290 this.Dispose();
291 }
292
293 private async Task ParseIncoming(byte[] Buffer, int Offset, int Count)
294 {
295 if (this.state == Socks5State.Connected)
296 await this.OnDataReceived.Raise(this, new DataReceivedEventArgs(Buffer, Offset, Count, this, this.callbackState), false);
297 else if (this.state == Socks5State.Initializing)
298 {
299 if (Count < 2 || Buffer[Offset++] < 5)
300 {
301 await this.ToError();
302 return;
303 }
304
305 byte Method = Buffer[Offset++];
306
307 switch (Method)
308 {
309 case 0: // No authentication.
310 await this.SetState(Socks5State.Authenticated);
311 break;
312
313 default:
314 await this.ToError();
315 return;
316 }
317 }
318 else
319 {
320 int c = Offset + Count;
321
322 if (Count < 5 || Buffer[Offset++] < 5)
323 {
324 await this.ToError();
325 return;
326 }
327
328 byte REP = Buffer[Offset++];
329
330 switch (REP)
331 {
332 case 0: // Succeeded
333 await this.SetState(Socks5State.Connected);
334 break;
335
336 case 1:
337 await this.Error("General SOCKS server failure.");
338 await this.ToError();
339 break;
340
341 case 2:
342 await this.Error("Connection not allowed by ruleset.");
343 await this.ToError();
344 break;
345
346 case 3:
347 await this.Error("Network unreachable.");
348 await this.ToError();
349 break;
350
351 case 4:
352 await this.Error("Host unreachable.");
353 await this.ToError();
354 break;
355
356 case 5:
357 await this.Error("Connection refused.");
358 await this.ToError();
359 break;
360
361 case 6:
362 await this.Error("TTL expired.");
363 await this.ToError();
364 break;
365
366 case 7:
367 await this.Error("Command not supported.");
368 await this.ToError();
369 break;
370
371 case 8:
372 await this.Error("Address type not supported.");
373 await this.ToError();
374 break;
375
376 default:
377 await this.Error("Unrecognized error code returned: " + REP.ToString());
378 await this.ToError();
379 break;
380 }
381
382 Offset++;
383
384 byte ATYP = Buffer[Offset++];
385 IPAddress Addr = null;
386 string DomainName = null;
387
388 switch (ATYP)
389 {
390 case 1: // IPv4.
391 if (Offset + 4 > c)
392 {
393 await this.Error("Expected more bytes.");
394 await this.ToError();
395 return;
396 }
397
398 byte[] A = new byte[4];
399 Array.Copy(Buffer, Offset, A, 0, 4);
400 Offset += 4;
401 Addr = new IPAddress(A);
402 break;
403
404 case 3: // Domain name.
405 byte NrBytes = Buffer[Offset++];
406 if (Offset + NrBytes > c)
407 {
408 await this.Error("Expected more bytes.");
409 await this.ToError();
410 return;
411 }
412
413 DomainName = Encoding.ASCII.GetString(Buffer, Offset, NrBytes);
414 Offset += NrBytes;
415 break;
416
417 case 4: // IPv6.
418 if (Offset + 16 > c)
419 {
420 await this.Error("Expected more bytes.");
421 await this.ToError();
422 return;
423 }
424
425 A = new byte[16];
426 Array.Copy(Buffer, Offset, A, 0, 16);
427 Offset += 16;
428 Addr = new IPAddress(A);
429 break;
430
431 default:
432 await this.ToError();
433 return;
434 }
435
436 if (Offset + 2 != c)
437 {
438 await this.Error("Invalid number of bytes received.");
439 await this.ToError();
440 return;
441 }
442
443 int Port = Buffer[Offset++];
444 Port <<= 8;
445 Port |= Buffer[Offset++];
446
447 await this.OnResponse.Raise(this, new ResponseEventArgs(REP, Addr, DomainName, Port), false);
448 }
449 }
450
454 public event EventHandlerAsync<ResponseEventArgs> OnResponse = null;
455
459 public event EventHandlerAsync<DataReceivedEventArgs> OnDataReceived = null;
460
461 private async Task ToError()
462 {
463 await this.SetState(Socks5State.Error);
464 this.client.Dispose();
465 }
466
467 private Task Request(Command Command, IPAddress DestinationAddress, int Port)
468 {
469 using (MemoryStream Req = new MemoryStream())
470 {
471 Req.WriteByte(5);
472 Req.WriteByte((byte)Command);
473 Req.WriteByte(0);
474
475 if (DestinationAddress.AddressFamily == AddressFamily.InterNetwork)
476 Req.WriteByte(1);
477 else if (DestinationAddress.AddressFamily == AddressFamily.InterNetworkV6)
478 Req.WriteByte(4);
479 else
480 throw new ArgumentException("Invalid address family.", nameof(DestinationAddress));
481
482 byte[] Addr = DestinationAddress.GetAddressBytes();
483 Req.Write(Addr, 0, Addr.Length);
484 Req.WriteByte((byte)(Port >> 8));
485 Req.WriteByte((byte)Port);
486
487 return this.SendPacket(Req.ToArray());
488 }
489 }
490
491 private Task Request(Command Command, string DestinationDomainName, int Port)
492 {
493 using (MemoryStream Req = new MemoryStream())
494 {
495 Req.WriteByte(5);
496 Req.WriteByte((byte)Command);
497 Req.WriteByte(0);
498 Req.WriteByte(3);
499
500 byte[] Bytes = Encoding.ASCII.GetBytes(DestinationDomainName);
501 int c = Bytes.Length;
502 if (c > 255)
503 throw new IOException("Domain name too long.");
504
505 Req.WriteByte((byte)c);
506 Req.Write(Bytes, 0, Bytes.Length);
507 Req.WriteByte((byte)(Port >> 8));
508 Req.WriteByte((byte)Port);
509
510 return this.SendPacket(Req.ToArray());
511 }
512 }
513
520 public Task CONNECT(IPAddress DestinationAddress, int Port)
521 {
522 return this.Request(Command.CONNECT, DestinationAddress, Port);
523 }
524
531 public Task CONNECT(string DestinationDomainName, int Port)
532 {
533 return this.Request(Command.CONNECT, DestinationDomainName, Port);
534 }
535
543 public Task CONNECT(string StreamID, string RequesterJID, string TargetJID)
544 {
545 string s = StreamID + RequesterJID + TargetJID;
546 byte[] Hash = Hashes.ComputeSHA1Hash(Encoding.UTF8.GetBytes(s));
547 StringBuilder sb = new StringBuilder();
548
549 foreach (byte b in Hash)
550 sb.Append(b.ToString("x2"));
551
552 return this.CONNECT(sb.ToString(), 0);
553 }
554
561 public Task BIND(IPAddress DestinationAddress, int Port)
562 {
563 return this.Request(Command.BIND, DestinationAddress, Port);
564 }
565
572 public Task BIND(string DestinationDomainName, int Port)
573 {
574 return this.Request(Command.BIND, DestinationDomainName, Port);
575 }
576
583 public Task UDP_ASSOCIATE(IPAddress DestinationAddress, int Port)
584 {
585 return this.Request(Command.UDP_ASSOCIATE, DestinationAddress, Port);
586 }
587
594 public Task UDP_ASSOCIATE(string DestinationDomainName, int Port)
595 {
596 return this.Request(Command.UDP_ASSOCIATE, DestinationDomainName, Port);
597 }
598
599 }
600}
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1647
Implements a binary TCP Client, by encapsulating a TcpClient. It also makes the use of TcpClient safe...
Task< bool > SendAsync(byte[] Packet)
Sends a binary packet.
static byte[] ToArray(byte[] Buffer, int Offset, int Count)
Converts a binary subset of a buffer into an array.
Task< bool > ConnectAsync(string Host, int Port)
Connects to a host using TCP.
virtual void Dispose()
Disposes of the object. The underlying TcpClient is either disposed directly, or when asynchronous op...
Simple base class for classes implementing communication protocols.
Task TransmitBinary(byte[] Data)
Called when binary data has been transmitted.
Task Error(string Error)
Called to inform the viewer of an error state.
Task Exception(Exception Exception)
Called to inform the viewer of an exception state.
ISniffer[] Sniffers
Registered sniffers.
Task Information(string Comment)
Called to inform the viewer of something.
bool HasSniffers
If there are sniffers registered on the object.
Task ReceiveBinary(byte[] Data)
Called when binary data has been received.
Event arguments for data reception events.
Client used for SOCKS5 communication.
Definition: Socks5Client.cs:61
Task CONNECT(string StreamID, string RequesterJID, string TargetJID)
XMPP-specific SOCKS5 connection, as described in XEP-0065: https://xmpp.org/extensions/xep-0065....
Task CONNECT(string DestinationDomainName, int Port)
Connects to the target.
EventHandlerAsync< ResponseEventArgs > OnResponse
Event raised when a response has been returned.
Task UDP_ASSOCIATE(string DestinationDomainName, int Port)
Establish an association within the UDP relay process.
EventHandlerAsync OnWriteQueueEmpty
Event raised when the write queue is empty.
EventHandlerAsync OnStateChange
Event raised whenever the state changes.
string Host
Host of SOCKS5 stream host.
Task BIND(IPAddress DestinationAddress, int Port)
Binds to the target.
Task< bool > Send(byte[] Data)
Send binary data.
EventHandlerAsync< DataReceivedEventArgs > OnDataReceived
Event raised when binary data has been received over an established connection.
void CloseWhenDone()
Closes the stream when all bytes have been sent.
Socks5Client(string Host, int Port, string JID, params ISniffer[] Sniffers)
Client used for SOCKS5 communication.
Definition: Socks5Client.cs:81
int Port
Port of SOCKS5 stream host.
Task BIND(string DestinationDomainName, int Port)
Binds to the target.
Task CONNECT(IPAddress DestinationAddress, int Port)
Connects to the target.
Task UDP_ASSOCIATE(IPAddress DestinationAddress, int Port)
Establish an association within the UDP relay process.
string JID
JID of SOCKS5 stream host.
Contains methods for simple hash calculations.
Definition: Hashes.cs:59
static byte[] ComputeSHA1Hash(byte[] Data)
Computes the SHA-1 hash of a block of binary data.
Definition: Hashes.cs:294
Interface for objects that contain a reference to a host.
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:11
delegate Task EventHandlerAsync(object Sender, EventArgs e)
Asynchronous version of EventArgs.
Socks5State
SOCKS5 connection state.
Definition: Socks5Client.cs:18