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