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;
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
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 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 this.Information("Connected to " + this.host + ":" + this.port.ToString());
120
121 this.state = Socks5State.Initializing;
122 await this.SendPacket(true, 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 await this.DisposeAsync();
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, bool ConstantBuffer, byte[] Buffer, int Offset, int Count)
158 {
159 if (this.HasSniffers)
160 this.TransmitBinary(ConstantBuffer, Buffer, Offset, Count);
161
162 return Task.CompletedTask;
163 }
164
165 private async Task<bool> Client_OnReceived(object Sender, bool ConstantBuffer, byte[] Buffer, int Offset, int Count)
166 {
167 if (this.HasSniffers)
168 this.ReceiveBinary(ConstantBuffer, 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 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 [Obsolete("Use DisposeAsync()")]
237 public void Dispose()
238 {
239 this.DisposeAsync().Wait();
240 }
241
245 public async Task DisposeAsync()
246 {
247 if (!this.disposed)
248 {
249 this.disposed = true;
250 await this.SetState(Socks5State.Offline);
251
252 if (!(this.client is null))
253 {
254 await this.client.DisposeAsync();
255 this.client = null;
256 }
257 }
258 }
259
265 [Obsolete("Use an overload with a ConstantBuffer argument. This increases performance, as the buffer will not be unnecessarily cloned if queued.")]
266 public Task<bool> Send(byte[] Data)
267 {
268 return this.Send(false, Data);
269 }
270
278 public Task<bool> Send(bool ConstantBuffer, byte[] Data)
279 {
280 if (this.state != Socks5State.Connected)
281 throw new IOException("SOCKS5 connection not open.");
282
283 return this.SendPacket(ConstantBuffer, Data);
284 }
285
286 private Task<bool> SendPacket(bool ConstantBuffer, byte[] Data)
287 {
288 lock (this.synchObj)
289 {
290 this.isWriting = true;
291 }
292
293 return this.client.SendAsync(ConstantBuffer, Data);
294 }
295
300
304 public Task CloseWhenDone()
305 {
306 lock (this.synchObj)
307 {
308 if (this.isWriting)
309 {
310 this.closeWhenDone = true;
311 return Task.CompletedTask;
312 }
313 }
314
315 return this.DisposeAsync();
316 }
317
318 private async Task ParseIncoming(byte[] Buffer, int Offset, int Count)
319 {
320 if (this.state == Socks5State.Connected)
321 await this.OnDataReceived.Raise(this, new DataReceivedEventArgs(Buffer, Offset, Count, this, this.callbackState), false);
322 else if (this.state == Socks5State.Initializing)
323 {
324 if (Count < 2 || Buffer[Offset++] < 5)
325 {
326 await this.ToError();
327 return;
328 }
329
330 byte Method = Buffer[Offset++];
331
332 switch (Method)
333 {
334 case 0: // No authentication.
335 await this.SetState(Socks5State.Authenticated);
336 break;
337
338 default:
339 await this.ToError();
340 return;
341 }
342 }
343 else
344 {
345 int c = Offset + Count;
346
347 if (Count < 5 || Buffer[Offset++] < 5)
348 {
349 await this.ToError();
350 return;
351 }
352
353 byte REP = Buffer[Offset++];
354
355 switch (REP)
356 {
357 case 0: // Succeeded
358 await this.SetState(Socks5State.Connected);
359 break;
360
361 case 1:
362 this.Error("General SOCKS server failure.");
363 await this.ToError();
364 break;
365
366 case 2:
367 this.Error("Connection not allowed by ruleset.");
368 await this.ToError();
369 break;
370
371 case 3:
372 this.Error("Network unreachable.");
373 await this.ToError();
374 break;
375
376 case 4:
377 this.Error("Host unreachable.");
378 await this.ToError();
379 break;
380
381 case 5:
382 this.Error("Connection refused.");
383 await this.ToError();
384 break;
385
386 case 6:
387 this.Error("TTL expired.");
388 await this.ToError();
389 break;
390
391 case 7:
392 this.Error("Command not supported.");
393 await this.ToError();
394 break;
395
396 case 8:
397 this.Error("Address type not supported.");
398 await this.ToError();
399 break;
400
401 default:
402 this.Error("Unrecognized error code returned: " + REP.ToString());
403 await this.ToError();
404 break;
405 }
406
407 Offset++;
408
409 byte ATYP = Buffer[Offset++];
410 IPAddress Addr = null;
411 string DomainName = null;
412
413 switch (ATYP)
414 {
415 case 1: // IPv4.
416 if (Offset + 4 > c)
417 {
418 this.Error("Expected more bytes.");
419 await this.ToError();
420 return;
421 }
422
423 byte[] A = new byte[4];
424 System.Buffer.BlockCopy(Buffer, Offset, A, 0, 4);
425 Offset += 4;
426 Addr = new IPAddress(A);
427 break;
428
429 case 3: // Domain name.
430 byte NrBytes = Buffer[Offset++];
431 if (Offset + NrBytes > c)
432 {
433 this.Error("Expected more bytes.");
434 await this.ToError();
435 return;
436 }
437
438 DomainName = Encoding.ASCII.GetString(Buffer, Offset, NrBytes);
439 Offset += NrBytes;
440 break;
441
442 case 4: // IPv6.
443 if (Offset + 16 > c)
444 {
445 this.Error("Expected more bytes.");
446 await this.ToError();
447 return;
448 }
449
450 A = new byte[16];
451 System.Buffer.BlockCopy(Buffer, Offset, A, 0, 16);
452 Offset += 16;
453 Addr = new IPAddress(A);
454 break;
455
456 default:
457 await this.ToError();
458 return;
459 }
460
461 if (Offset + 2 != c)
462 {
463 this.Error("Invalid number of bytes received.");
464 await this.ToError();
465 return;
466 }
467
468 int Port = Buffer[Offset++];
469 Port <<= 8;
470 Port |= Buffer[Offset++];
471
472 await this.OnResponse.Raise(this, new ResponseEventArgs(REP, Addr, DomainName, Port), false);
473 }
474 }
475
479 public event EventHandlerAsync<ResponseEventArgs> OnResponse = null;
480
484 public event EventHandlerAsync<DataReceivedEventArgs> OnDataReceived = null;
485
486 private async Task ToError()
487 {
488 await this.SetState(Socks5State.Error);
489 await this.client.DisposeAsync();
490 }
491
492 private Task Request(Command Command, IPAddress DestinationAddress, int Port)
493 {
494 using MemoryStream Req = new MemoryStream();
495
496 Req.WriteByte(5);
497 Req.WriteByte((byte)Command);
498 Req.WriteByte(0);
499
500 if (DestinationAddress.AddressFamily == AddressFamily.InterNetwork)
501 Req.WriteByte(1);
502 else if (DestinationAddress.AddressFamily == AddressFamily.InterNetworkV6)
503 Req.WriteByte(4);
504 else
505 throw new ArgumentException("Invalid address family.", nameof(DestinationAddress));
506
507 byte[] Addr = DestinationAddress.GetAddressBytes();
508 Req.Write(Addr, 0, Addr.Length);
509 Req.WriteByte((byte)(Port >> 8));
510 Req.WriteByte((byte)Port);
511
512 return this.SendPacket(true, Req.ToArray());
513 }
514
515 private Task Request(Command Command, string DestinationDomainName, int Port)
516 {
517 using MemoryStream Req = new MemoryStream();
518
519 Req.WriteByte(5);
520 Req.WriteByte((byte)Command);
521 Req.WriteByte(0);
522 Req.WriteByte(3);
523
524 byte[] Bytes = Encoding.ASCII.GetBytes(DestinationDomainName);
525 int c = Bytes.Length;
526 if (c > 255)
527 throw new IOException("Domain name too long.");
528
529 Req.WriteByte((byte)c);
530 Req.Write(Bytes, 0, Bytes.Length);
531 Req.WriteByte((byte)(Port >> 8));
532 Req.WriteByte((byte)Port);
533
534 return this.SendPacket(true, Req.ToArray());
535 }
536
543 public Task CONNECT(IPAddress DestinationAddress, int Port)
544 {
545 return this.Request(Command.CONNECT, DestinationAddress, Port);
546 }
547
554 public Task CONNECT(string DestinationDomainName, int Port)
555 {
556 return this.Request(Command.CONNECT, DestinationDomainName, Port);
557 }
558
566 public Task CONNECT(string StreamID, string RequesterJID, string TargetJID)
567 {
568 string s = StreamID + RequesterJID + TargetJID;
569 byte[] Hash = Hashes.ComputeSHA1Hash(Encoding.UTF8.GetBytes(s));
570 StringBuilder sb = new StringBuilder();
571
572 foreach (byte b in Hash)
573 sb.Append(b.ToString("x2"));
574
575 return this.CONNECT(sb.ToString(), 0);
576 }
577
584 public Task BIND(IPAddress DestinationAddress, int Port)
585 {
586 return this.Request(Command.BIND, DestinationAddress, Port);
587 }
588
595 public Task BIND(string DestinationDomainName, int Port)
596 {
597 return this.Request(Command.BIND, DestinationDomainName, Port);
598 }
599
606 public Task UDP_ASSOCIATE(IPAddress DestinationAddress, int Port)
607 {
608 return this.Request(Command.UDP_ASSOCIATE, DestinationAddress, Port);
609 }
610
617 public Task UDP_ASSOCIATE(string DestinationDomainName, int Port)
618 {
619 return this.Request(Command.UDP_ASSOCIATE, DestinationDomainName, Port);
620 }
621
622 }
623}
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
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:1657
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.
virtual Task DisposeAsync()
Disposes of the object asynchronously. The underlying TcpClient is either disposed directly,...
Task< bool > ConnectAsync(string Host, int Port)
Connects to a host using TCP.
Simple base class for classes implementing communication protocols.
void Exception(Exception Exception)
Called to inform the viewer of an exception state.
ISniffer[] Sniffers
Registered sniffers.
bool HasSniffers
If there are sniffers registered on the object.
void Error(string Error)
Called to inform the viewer of an error state.
void TransmitBinary(int Count)
Called when binary data has been transmitted.
void ReceiveBinary(int Count)
Called when binary data has been received.
void Information(string Comment)
Called to inform the viewer of something.
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.
Task CloseWhenDone()
Closes the stream when all bytes have been sent.
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.
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.
async Task DisposeAsync()
IDisposable.Dispose
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.
Task< bool > Send(bool ConstantBuffer, byte[] Data)
Send binary data.
string JID
JID of SOCKS5 stream host.
Contains methods for simple hash calculations.
Definition: Hashes.cs:57
static byte[] ComputeSHA1Hash(byte[] Data)
Computes the SHA-1 hash of a block of binary data.
Definition: Hashes.cs:415
Interface for objects that contain a reference to a host.
Interface for asynchronously disposable objects.
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:10
delegate Task EventHandlerAsync(object Sender, EventArgs e)
Asynchronous version of EventArgs.
Socks5State
SOCKS5 connection state.
Definition: Socks5Client.cs:18