Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ProxyPort.cs
1using System;
2using System.Collections.Generic;
3using System.ComponentModel;
4using System.IO;
5using System.Net;
6using System.Net.NetworkInformation;
7using System.Net.Sockets;
8using System.Security.Authentication;
9using System.Security.Cryptography.X509Certificates;
10using System.Text;
11using System.Threading.Tasks;
12using Waher.Events;
18
20{
24 public class ProxyPort : CommunicationLayer, IDisposable
25 {
26 private readonly LinkedList<TcpListener> tcpListeners = new LinkedList<TcpListener>();
27 private readonly Dictionary<Guid, ProxyClientConncetion> connections = new Dictionary<Guid, ProxyClientConncetion>();
28 private readonly IpCidr[] remoteIps;
29 private readonly IpHostPortProxy node;
30 private readonly string host;
31 private readonly int port;
32 private readonly int listeningPort;
33 private readonly bool tls;
34 private readonly bool trustServer;
35 private readonly bool authorizedAccess;
36 private long nrBytesDownlink = 0;
37 private long nrBytesUplink = 0;
38 private bool closed = false;
39
40 private ProxyPort(IpHostPortProxy Node, string Host, int Port, bool Tls, bool TrustServer, int ListeningPort, bool AuthorizedAccess,
41 IpCidr[] RemoteIps, params ISniffer[] Sniffers)
42 : base(false, Sniffers)
43 {
44 this.node = Node;
45 this.host = Host;
46 this.port = Port;
47 this.tls = Tls;
48 this.trustServer = TrustServer;
49 this.listeningPort = ListeningPort;
50 this.authorizedAccess = AuthorizedAccess;
51 this.remoteIps = RemoteIps;
52 }
53
66 public static async Task<ProxyPort> Create(IpHostPortProxy Node, string Host, int Port, bool Tls, bool TrustServer, int ListeningPort,
67 bool AuthorizedAccess, IpCidr[] RemoteIps)
68 {
69 ProxyPort Result = new ProxyPort(Node, Host, Port, Tls, TrustServer, ListeningPort, AuthorizedAccess, RemoteIps);
70 await Result.Open();
71 return Result;
72 }
73
74 private async Task Open()
75 {
76 foreach (NetworkInterface Interface in NetworkInterface.GetAllNetworkInterfaces())
77 {
78 if (Interface.OperationalStatus != OperationalStatus.Up)
79 continue;
80
81 IPInterfaceProperties Properties = Interface.GetIPProperties();
82
83 foreach (UnicastIPAddressInformation UnicastAddress in Properties.UnicastAddresses)
84 {
85 if ((UnicastAddress.Address.AddressFamily == AddressFamily.InterNetwork && Socket.OSSupportsIPv4) ||
86 (UnicastAddress.Address.AddressFamily == AddressFamily.InterNetworkV6 && Socket.OSSupportsIPv6))
87 {
88 IPEndPoint DesiredEndpoint = new IPEndPoint(UnicastAddress.Address, this.listeningPort);
89
90 try
91 {
92 TcpListener Listener = new TcpListener(UnicastAddress.Address, this.listeningPort);
93
94 Listener.Start();
95 Task T = this.ListenForIncomingConnections(Listener);
96
97 lock (this.tcpListeners)
98 {
99 this.tcpListeners.AddLast(Listener);
100 }
101
102 await this.node.RemoveErrorAsync(DesiredEndpoint.ToString());
103 }
104 catch (SocketException)
105 {
106 await this.node.LogErrorAsync(DesiredEndpoint.ToString(), "Unable to open Proxy port for listening.");
107 }
108 catch (Exception ex)
109 {
110 await this.node.LogErrorAsync(DesiredEndpoint.ToString(), ex.Message);
111 }
112 }
113 }
114 }
115 }
116
117 private async Task ListenForIncomingConnections(TcpListener Listener)
118 {
119 try
120 {
121 while (!this.closed)
122 {
123 try
124 {
125 TcpClient Client;
126
127 try
128 {
129 Client = await Listener.AcceptTcpClientAsync();
130 if (this.closed)
131 return;
132 }
133 catch (InvalidOperationException)
134 {
135 lock (this.tcpListeners)
136 {
137 LinkedListNode<TcpListener> Node = this.tcpListeners?.First;
138
139 while (!(Node is null))
140 {
141 if (Node.Value == Listener)
142 {
143 this.tcpListeners.Remove(Node);
144 break;
145 }
146
147 Node = Node.Next;
148 }
149 }
150
151 return;
152 }
153
154 if (!(Client is null))
155 {
156 if (!(this.remoteIps is null))
157 {
158 bool Match = false;
159
160 if (Client.Client.RemoteEndPoint is IPEndPoint IPEndPoint)
161 {
162 foreach (IpCidr Range in this.remoteIps)
163 {
164 if (Range.Matches(IPEndPoint.Address))
165 {
166 Match = true;
167 break;
168 }
169 }
170 }
171
172 if (!Match)
173 {
174 await this.Error("Remote IP not approved. Conncetion reused.");
175 Client.Dispose();
176 continue;
177 }
178 }
179
180 await this.Information("Connection accepted from " + Client.Client.RemoteEndPoint.ToString() + ".");
181
182 BinaryTcpClient Incoming = new BinaryTcpClient(Client, false);
183 BinaryTcpClient Outgoing = null;
184
185 Incoming.Bind(true);
186
187 if (!Types.TryGetModuleParameter("X509", out object Obj) || !(Obj is X509Certificate Certificate))
188 Certificate = null;
189
190 try
191 {
192 Outgoing = new BinaryTcpClient(false);
193 if (!await Outgoing.ConnectAsync(this.host, this.port, true))
194 {
195 await this.node.LogErrorAsync("UnableToConnect", "Unable to connect to remote endpoint.");
196 Incoming.DisposeWhenDone();
197 continue;
198 }
199
200 if (this.tls)
201 await Outgoing.UpgradeToTlsAsClient(Certificate, SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12, this.trustServer);
202 }
203 catch (Exception ex)
204 {
205 await this.node.LogErrorAsync("UnableToConnect", "Unable to connect to remote endpoint: " + ex.Message);
206 await this.Exception(ex);
207 Incoming.DisposeWhenDone();
208 Outgoing?.Dispose();
209 continue;
210 }
211
212 await this.node.RemoveErrorAsync("UnableToConnect");
213
214 if ((this.tls || this.authorizedAccess) && !(Certificate is null))
215 {
216 await this.node.RemoveWarningAsync("NoCertificate");
217
218 Task _ = this.SwitchToTls(Incoming, Outgoing, Certificate);
219 }
220 else
221 {
222 if (this.tls)
223 await this.node.LogWarningAsync("NoCertificate", "No registered certificate found. Listening port is unencrypted.");
224
225 ProxyClientConncetion Connection = new ProxyClientConncetion(this, Incoming, Outgoing, this.Sniffers);
226 Outgoing.Continue();
227 Incoming.Continue();
228
229 lock (this.connections)
230 {
231 this.connections[Connection.Id] = Connection;
232 }
233 }
234 }
235 }
236 catch (SocketException)
237 {
238 // Ignore
239 }
240 catch (ObjectDisposedException)
241 {
242 // Ignore
243 }
244 catch (NullReferenceException)
245 {
246 // Ignore
247 }
248 catch (Exception ex)
249 {
250 if (this.closed || this.tcpListeners is null)
251 break;
252
253 bool Found = false;
254
255 foreach (TcpListener P in this.tcpListeners)
256 {
257 if (P == Listener)
258 {
259 Found = true;
260 break;
261 }
262 }
263
264 if (Found)
265 Log.Exception(ex);
266 else
267 break; // Removed, for instance due to network change
268 }
269 }
270 }
271 catch (Exception ex)
272 {
273 if (this.closed || this.tcpListeners is null)
274 return;
275
276 Log.Exception(ex);
277 }
278 }
279
280 private async Task SwitchToTls(BinaryTcpClient Incoming, BinaryTcpClient Outgoing, X509Certificate Certificate)
281 {
282 string RemoteIpEndpoint;
283 EndPoint EP = Incoming.Client.Client.RemoteEndPoint;
284
285 if (EP is IPEndPoint IpEP)
286 RemoteIpEndpoint = IpEP.Address.ToString();
287 else
288 RemoteIpEndpoint = EP.ToString();
289
290 if (LoginAuditor.CanStartTls(RemoteIpEndpoint))
291 {
292 try
293 {
294 await this.Information("Switching to TLS.");
295
296 await Incoming.UpgradeToTlsAsServer(Certificate, SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12, ClientCertificates.Optional);
297
298 if (this.authorizedAccess)
299 {
300 if (Incoming.RemoteCertificate is null)
301 {
302 await this.Error("No remote certificate found. mTLS is required.");
303 Incoming.Dispose();
304 Outgoing.Dispose();
305 return;
306 }
307
308 if (!Incoming.RemoteCertificateValid)
309 {
310 await this.Error("Remote certificate not valid.");
311 Incoming.Dispose();
312 Outgoing.Dispose();
313 return;
314 }
315
316 string[] Identities = IpHostPortProxy.GetCertificateIdentities(Incoming.RemoteCertificate);
317 User User = null;
318
319 foreach (string Identity in IpHostPortProxy.GetCertificateIdentities(Certificate))
320 {
321 User = await Users.GetUser(Identity, false);
322 if (!(User is null))
323 break;
324 }
325
326 string RemoteEndpoint = Incoming.Client.Client.RemoteEndPoint.ToString();
327 int i = RemoteEndpoint.LastIndexOf(':');
328 if (i > 0 && int.TryParse(RemoteEndpoint.Substring(i + 1), out int _))
329 RemoteEndpoint = RemoteEndpoint.Substring(0, i);
330
331 if (User is null)
332 {
333 string Msg = "Invalid login: No user found matching certificate subject.";
335 await this.Error(Msg);
336 Incoming.Dispose();
337 Outgoing.Dispose();
338 return;
339 }
340 else
341 LoginAuditor.Success("Successful login using remote certificate.", User.UserName, RemoteEndpoint, "PROXY");
342 }
343
344 if (this.HasSniffers)
345 {
346 await this.Information("TLS established" +
347 ". Cipher Strength: " + Incoming.CipherStrength.ToString() +
348 ", Hash Strength: " + Incoming.HashStrength.ToString() +
349 ", Key Exchange Strength: " + Incoming.KeyExchangeStrength.ToString());
350
351 if (!(Incoming.RemoteCertificate is null))
352 {
353 if (this.HasSniffers)
354 {
355 StringBuilder sb = new StringBuilder();
356
357 sb.Append("Remote Certificate received. Valid: ");
358 sb.Append(Incoming.RemoteCertificateValid.ToString());
359 sb.Append(", Subject: ");
360 sb.Append(Incoming.RemoteCertificate.Subject);
361 sb.Append(", Issuer: ");
362 sb.Append(Incoming.RemoteCertificate.Issuer);
363 sb.Append(", S/N: ");
364 sb.Append(Convert.ToBase64String(Incoming.RemoteCertificate.GetSerialNumber()));
365 sb.Append(", Hash: ");
366 sb.Append(Convert.ToBase64String(Incoming.RemoteCertificate.GetCertHash()));
367
368 await this.Information(sb.ToString());
369 }
370 }
371 }
372
373 ProxyClientConncetion Connection = new ProxyClientConncetion(this, Incoming, Outgoing, this.Sniffers);
374 Outgoing.Continue();
375 Incoming.Continue();
376
377 lock (this.connections)
378 {
379 this.connections[Connection.Id] = Connection;
380 }
381 }
382 catch (AuthenticationException ex)
383 {
384 await this.LoginFailure(ex, Incoming, Outgoing, RemoteIpEndpoint);
385 }
386 catch (Win32Exception ex)
387 {
388 if (ex is SocketException)
389 {
390 Incoming.Dispose();
391 Outgoing.Dispose();
392 }
393 else
394 await this.LoginFailure(ex, Incoming, Outgoing, RemoteIpEndpoint);
395 }
396 catch (IOException)
397 {
398 Incoming.Dispose();
399 Outgoing.Dispose();
400 }
401 catch (Exception ex)
402 {
403 Incoming.Dispose();
404 Outgoing.Dispose();
405 Log.Exception(ex);
406 }
407 }
408 else
409 {
410 Incoming.Dispose();
411 Outgoing.Dispose();
412 }
413 }
414
415 private async Task LoginFailure(Exception ex, BinaryTcpClient Incoming, BinaryTcpClient Outgoing, string RemoteIpEndpoint)
416 {
417 Exception ex2 = Log.UnnestException(ex);
418 await LoginAuditor.ReportTlsHackAttempt(RemoteIpEndpoint, "TLS handshake failed: " + ex2.Message, "PROXY");
419
420 Incoming.Dispose();
421 Outgoing.Dispose();
422 }
423
424 private void Close()
425 {
426 TcpListener[] Listeners;
427 ProxyClientConncetion[] Connections;
428
429 this.closed = true;
430
431 lock (this.tcpListeners)
432 {
433 Listeners = new TcpListener[this.tcpListeners.Count];
434 this.tcpListeners.CopyTo(Listeners, 0);
435 this.tcpListeners.Clear();
436 }
437
438 lock (this.connections)
439 {
440 Connections = new ProxyClientConncetion[this.connections.Count];
441 this.connections.Values.CopyTo(Connections, 0);
442 this.connections.Clear();
443 }
444
445 foreach (TcpListener Listener in Listeners)
446 {
447 try
448 {
449 Listener.Stop();
450 }
451 catch (Exception)
452 {
453 // Ignore
454 }
455 }
456
457 foreach (ProxyClientConncetion Connection in Connections)
458 {
459 try
460 {
461 Connection.Dispose();
462 }
463 catch (Exception)
464 {
465 // Ignore
466 }
467 }
468 }
469
473 public void Dispose()
474 {
475 this.Close();
476 }
477
482 public void Remove(ProxyClientConncetion Connection)
483 {
484 lock (this.connections)
485 {
486 this.connections.Remove(Connection.Id);
487 }
488
489 Connection.Dispose();
490 }
491
496 public void IncUplink(int NrBytes)
497 {
498 this.nrBytesUplink += NrBytes;
499 }
500
505 public void IncDownlink(int NrBytes)
506 {
507 this.nrBytesDownlink += NrBytes;
508 }
509
513 public long NrBytesUplink => this.nrBytesUplink;
514
518 public long NrBytesDownlink => this.nrBytesDownlink;
519
523 public int NrConnctions
524 {
525 get
526 {
527 lock (this.connections)
528 {
529 return this.connections.Count;
530 }
531 }
532 }
533
534 }
535}
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
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:818
Implements a binary TCP Client, by encapsulating a TcpClient. It also makes the use of TcpClient safe...
void Bind()
Binds to a TcpClient that was already connected when provided to the constructor.
Task UpgradeToTlsAsClient(SslProtocols Protocols)
Upgrades a client connection to TLS.
bool RemoteCertificateValid
If the remote certificate is valid.
TcpClient Client
Underlying TcpClient object.
int HashStrength
Hash algorithm strength. (Nr bits of brute force complexity required to break algorithm).
void DisposeWhenDone()
Disposes the client when done sending all data.
void Continue()
Continues reading from the socket, if paused in an event handler.
X509Certificate RemoteCertificate
Certificate used by the remote endpoint.
int KeyExchangeStrength
Key Exchange strength. (Nr bits of brute force complexity required to break algorithm).
int CipherStrength
Cipher strength. (Nr bits of brute force complexity required to break algorithm).
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...
Task UpgradeToTlsAsServer(X509Certificate ServerCertificate)
Upgrades a server connection to TLS.
Simple base class for classes implementing communication protocols.
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.
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:14
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Definition: Types.cs:583
Class that monitors login events, and help applications determine malicious intent....
Definition: LoginAuditor.cs:26
static async void Success(string Message, string UserName, string RemoteEndpoint, string Protocol, params KeyValuePair< string, object >[] Tags)
Handles a successful login attempt.
static async void Fail(string Message, string UserName, string RemoteEndpoint, string Protocol, params KeyValuePair< string, object >[] Tags)
Handles a failed login attempt.
static async Task ReportTlsHackAttempt(string RemoteEndpoint, string Message, string Protocol)
Reports a TLS hacking attempt from an endpoint. Can be used to deny TLS negotiation to proceed,...
static bool CanStartTls(string RemoteEndpoint)
Checks if TLS negotiation can start, for a given endpoint. If the endpoint has tries a TLS hack attem...
Login state information relating to a remote endpoint
Corresponds to a user in the system.
Definition: User.cs:21
string UserName
User Name
Definition: User.cs:53
Maintains the collection of all users in the system.
Definition: Users.cs:24
static async Task< User > GetUser(string UserName, bool CreateIfNew)
Gets the User object corresponding to a User Name.
Definition: Users.cs:65
IP Address Rangee, expressed using CIDR format.
Definition: IpCidr.cs:10
Node representing a proxy port node.
Node acting as a TCP/IP proxy opening a port for incoming communication and proxying it to another po...
Definition: ProxyPort.cs:25
long NrBytesUplink
Number of bytes send uplink
Definition: ProxyPort.cs:513
void IncDownlink(int NrBytes)
Increment downlink counter.
Definition: ProxyPort.cs:505
void Remove(ProxyClientConncetion Connection)
Removes a proxy client connection.
Definition: ProxyPort.cs:482
int NrConnctions
Number of connections.
Definition: ProxyPort.cs:524
long NrBytesDownlink
Number of bytes send downlink
Definition: ProxyPort.cs:518
void Dispose()
IDisposable.Dispose
Definition: ProxyPort.cs:473
static async Task< ProxyPort > Create(IpHostPortProxy Node, string Host, int Port, bool Tls, bool TrustServer, int ListeningPort, bool AuthorizedAccess, IpCidr[] RemoteIps)
Creates a port proxy object.
Definition: ProxyPort.cs:66
void IncUplink(int NrBytes)
Increment uplink counter.
Definition: ProxyPort.cs:496
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:11
ClientCertificates
Client Certificate Options