From cefc14fc9ba5d14df822fb753582af958c459aab Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Wed, 12 Jun 2024 17:43:22 -0400 Subject: [PATCH] Split out PumpBuffer and separated HTTP header parser. --- NetProxy.Library/PumpBuffer.cs | 30 ++++ NetProxy.Service/NpServiceManager.cs | 16 +- .../NpServiceManagerMessageHandlerBase.cs | 2 +- .../NpServiceManagerNotificationHandlers.cs | 31 ---- .../NpServiceManagerQueryHandlers.cs | 6 +- .../Proxy/HttpHeaderAugmentation.cs | 93 +++++++++++ NetProxy.Service/Proxy/NpProxyConnection.cs | 156 +++++------------- 7 files changed, 181 insertions(+), 153 deletions(-) create mode 100644 NetProxy.Library/PumpBuffer.cs create mode 100644 NetProxy.Service/Proxy/HttpHeaderAugmentation.cs diff --git a/NetProxy.Library/PumpBuffer.cs b/NetProxy.Library/PumpBuffer.cs new file mode 100644 index 0000000..bf6e90a --- /dev/null +++ b/NetProxy.Library/PumpBuffer.cs @@ -0,0 +1,30 @@ +namespace NetProxy.Library +{ + public class PumpBuffer + { + public byte[] Bytes { get; set; } + public int Length { get; set; } + + public PumpBuffer(int initialSize) + { + Bytes = new byte[initialSize]; + } + + public void AutoResize(int maxBufferSize) + { + if (Length == Bytes.Length && Bytes.Length < maxBufferSize) + { + //If we read as much data as we could fit in the buffer, resize it a bit up to the maximum. + int newBufferSize = (int)(Bytes.Length + (Bytes.Length * 0.20)); + if (newBufferSize > maxBufferSize) + { + newBufferSize = maxBufferSize; + } + + Bytes = new byte[newBufferSize]; + } + } + } +} + + diff --git a/NetProxy.Service/NpServiceManager.cs b/NetProxy.Service/NpServiceManager.cs index dc25de3..38d4be4 100644 --- a/NetProxy.Service/NpServiceManager.cs +++ b/NetProxy.Service/NpServiceManager.cs @@ -13,7 +13,7 @@ public class NpServiceManager public NpConfiguration? Configuration { get; set; } private readonly RmServer _messageServer; private readonly NpProxyCollection _proxies = new(); - public HashSet AuthenticatedConnections { get; set; } = new(); + private readonly HashSet _authenticatedConnections = new(); public NpServiceManager() { @@ -28,10 +28,18 @@ public NpServiceManager() _messageServer.OnDisconnected += _messageServer_OnDisconnected; } + public void RemoveAuthenticated(Guid connectionId) + => _authenticatedConnections.Remove(connectionId); + + public void AddAuthenticated(Guid connectionId) + => _authenticatedConnections.Add(connectionId); + + public bool IsAuthenticated(Guid connectionId) + => _authenticatedConnections.Contains(connectionId); + public void Notify(Guid connectionId, IRmNotification notification) => _messageServer.Notify(connectionId, notification); - public NpProxy? GetProxyById(Guid proxyId) => _proxies.Where(o => o.Configuration.Id == proxyId).SingleOrDefault(); @@ -48,9 +56,9 @@ private void _messageServer_OnDisconnected(RmContext context) { lock (Configuration.EnsureNotNull()) { - AuthenticatedConnections.Remove(context.ConnectionId); + _authenticatedConnections.Remove(context.ConnectionId); } - Console.WriteLine($"Deregistered connection: {context.ConnectionId} (Logged in users {AuthenticatedConnections.Count})."); + Console.WriteLine($"Deregistered connection: {context.ConnectionId} (Logged in users {_authenticatedConnections.Count})."); } public void SaveConfiguration() diff --git a/NetProxy.Service/NpServiceManagerMessageHandlerBase.cs b/NetProxy.Service/NpServiceManagerMessageHandlerBase.cs index 83c4b56..ec6fcc7 100644 --- a/NetProxy.Service/NpServiceManagerMessageHandlerBase.cs +++ b/NetProxy.Service/NpServiceManagerMessageHandlerBase.cs @@ -8,7 +8,7 @@ internal class NpServiceManagerMessageHandlerBase public NpServiceManager EnforceLoginAndGetServiceManager(RmContext context) { var serviceManager = (context.Endpoint.Parameter as NpServiceManager).EnsureNotNull(); - if (serviceManager.AuthenticatedConnections.Contains(context.ConnectionId) == false) + if (serviceManager.IsAuthenticated(context.ConnectionId) == false) { throw new Exception("Login has not been completed."); } diff --git a/NetProxy.Service/NpServiceManagerNotificationHandlers.cs b/NetProxy.Service/NpServiceManagerNotificationHandlers.cs index 1846e9a..3ee930a 100644 --- a/NetProxy.Service/NpServiceManagerNotificationHandlers.cs +++ b/NetProxy.Service/NpServiceManagerNotificationHandlers.cs @@ -1,5 +1,4 @@ using NetProxy.Library.Payloads.ReliableMessages.Notifications; -using NetProxy.Library.Utilities; using NetProxy.Service.Proxy; using NTDLS.NullExtensions; using NTDLS.ReliableMessaging; @@ -8,35 +7,6 @@ namespace NetProxy.Service { internal class NpServiceManagerNotificationHandlers : NpServiceManagerMessageHandlerBase, IRmMessageHandler { - public void OnNotificationRegisterLogin(RmContext context, NotificationRegisterLogin notification) - { - var serviceManager = (context.Endpoint.Parameter as NpServiceManager).EnsureNotNull(); - - try - { - lock (serviceManager.Configuration.EnsureNotNull()) - { - if (serviceManager.Configuration.Users.Collection.Where(o => - o.UserName.ToLower() == notification.UserName.ToLower() && o.PasswordHash.ToLower() == notification.PasswordHash.ToLower()).Any()) - { - serviceManager.AuthenticatedConnections.Add(context.ConnectionId); - - Singletons.Logging.Write(NpLogging.Severity.Verbose, - $"Registered connection: {context.ConnectionId}, User: {notification.UserName} (Logged in users {serviceManager.AuthenticatedConnections.Count})."); - } - else - { - Singletons.Logging.Write(NpLogging.Severity.Verbose, - $"Failed to register connection: {context.ConnectionId}, User: {notification.UserName} (Logged in users {serviceManager.AuthenticatedConnections.Count})."); - } - } - } - catch (Exception ex) - { - Singletons.Logging.Write("An error occurred while logging in.", ex); - } - } - public void OnNotificationPersistUserList(RmContext context, NotificationPersistUserList notification) { var serviceManager = EnforceLoginAndGetServiceManager(context); @@ -67,7 +37,6 @@ public void OnNotificationUpsertProxy(RmContext context, NotificationUpsertProxy try { - lock (serviceManager.Configuration.EnsureNotNull()) { var existingProxy = serviceManager.GetProxyById(notification.ProxyConfiguration.Id); diff --git a/NetProxy.Service/NpServiceManagerQueryHandlers.cs b/NetProxy.Service/NpServiceManagerQueryHandlers.cs index 5d4ca35..5b97101 100644 --- a/NetProxy.Service/NpServiceManagerQueryHandlers.cs +++ b/NetProxy.Service/NpServiceManagerQueryHandlers.cs @@ -22,14 +22,14 @@ public QueryLoginReply OnQueryLogin(RmContext context, QueryLogin query) o.UserName.Equals(query.UserName, StringComparison.CurrentCultureIgnoreCase) && o.PasswordHash.Equals(query.PasswordHash, StringComparison.CurrentCultureIgnoreCase)).Any()) { - serviceManager.AuthenticatedConnections.Add(context.ConnectionId); + serviceManager.AddAuthenticated(context.ConnectionId); Singletons.Logging.Write(NpLogging.Severity.Verbose, - $"Logged in connection: {context.ConnectionId}, User: {query.UserName} (Logged in users {serviceManager.AuthenticatedConnections.Count})."); + $"Logged in connection: {context.ConnectionId}, User: {query.UserName}."); } else { Singletons.Logging.Write(NpLogging.Severity.Verbose, - $"Failed login connection: {context.ConnectionId}, User: {query.UserName} (Logged in users {serviceManager.AuthenticatedConnections.Count})."); + $"Failed login connection: {context.ConnectionId}, User: {query.UserName}."); } reply.Result = true; diff --git a/NetProxy.Service/Proxy/HttpHeaderAugmentation.cs b/NetProxy.Service/Proxy/HttpHeaderAugmentation.cs new file mode 100644 index 0000000..9820980 --- /dev/null +++ b/NetProxy.Service/Proxy/HttpHeaderAugmentation.cs @@ -0,0 +1,93 @@ +using NetProxy.Library; +using NetProxy.Library.Payloads.Routing; +using System.Text; + +namespace NetProxy.Service.Proxy +{ + internal static class HttpHeaderAugmentation + { + public enum HTTPHeaderResult + { + WaitOnData, + Present, + NotPresent + } + + public static HTTPHeaderResult Process(ref StringBuilder httpHeaderBuilder, NpProxyConfiguration proxyConfig, PumpBuffer buffer) + { + try + { + if (httpHeaderBuilder.Length > 0) //We are reconstructing a fragmented HTTP request header. + { + var stringContent = Encoding.UTF8.GetString(buffer.Bytes, 0, buffer.Length); + httpHeaderBuilder.Append(stringContent); + } + else if (HttpUtility.StartsWithHTTPVerb(buffer.Bytes)) + { + var stringContent = Encoding.UTF8.GetString(buffer.Bytes, 0, buffer.Length); + httpHeaderBuilder.Append(stringContent); + } + + string headerDelimiter = string.Empty; + + if (httpHeaderBuilder.Length > 0) + { + var headerType = HttpUtility.IsHttpHeader(httpHeaderBuilder.ToString(), out string? requestVerb); + + if (headerType != HttpHeaderType.None && requestVerb != null) + { + var endOfHeaderIndex = HttpUtility.GetHttpHeaderEnd(httpHeaderBuilder.ToString(), out headerDelimiter); + if (endOfHeaderIndex < 0) + { + return HTTPHeaderResult.WaitOnData; //We have a HTTP header but its a fragment. Wait on the remaining header. + } + else + { + if (buffer.Length > headerDelimiter.Length * 2) // "\r\n" or "\r\n\r\n" + { + //If we received more bytes than just the delimiter then we + // need to determine how many non-header bytes need to be sent to the peer. + + int endOfHeaderInBufferIndex = HttpUtility.FindDelimiterIndexInByteArray(buffer.Bytes, buffer.Length, $"{headerDelimiter}{headerDelimiter}"); + if (endOfHeaderInBufferIndex < 0) + { + throw new Exception("Could not locate HTTP header in receive buffer."); + } + + int bufferEndOfHeaderOffset = endOfHeaderInBufferIndex + (headerDelimiter.Length * 2); + + if (bufferEndOfHeaderOffset > buffer.Length) + { + //We received extra non-header bytes. We need to remove the header bytes from the buffer + // and then send them after we modify and send the header. + int newBufferLength = buffer.Length - bufferEndOfHeaderOffset; + Array.Copy(buffer.Bytes, bufferEndOfHeaderOffset, buffer.Bytes, 0, newBufferLength); + } + + buffer.Length -= bufferEndOfHeaderOffset; + } + else + { + buffer.Length = 0; + } + } + + //Apply the header rules, replacing the original http header builder. + httpHeaderBuilder = new StringBuilder( + HttpUtility.ApplyHttpHeaderRules(proxyConfig, + httpHeaderBuilder.ToString(), headerType, requestVerb, headerDelimiter)); + + return HTTPHeaderResult.Present; + } + } + } + catch (Exception ex) + { + httpHeaderBuilder.Clear(); + Singletons.Logging.Write("An error occurred while parsing the HTTP request header.", ex); + } + + return HTTPHeaderResult.NotPresent; + } + } +} diff --git a/NetProxy.Service/Proxy/NpProxyConnection.cs b/NetProxy.Service/Proxy/NpProxyConnection.cs index ee64c07..9de140e 100644 --- a/NetProxy.Service/Proxy/NpProxyConnection.cs +++ b/NetProxy.Service/Proxy/NpProxyConnection.cs @@ -73,10 +73,10 @@ public void WriteBytesToPeer(byte[] buffer, int length) _stream.Write(buffer, 0, length); } - public bool ReadBytesFromPeer(ref byte[] buffer, out int outBytesRead) + public bool ReadBytesFromPeer(ref PumpBuffer buffer) { LastActivityDateTime = DateTime.UtcNow; - int bytesRead = _stream.Read(buffer, 0, buffer.Length); + int bytesRead = _stream.Read(buffer.Bytes, 0, buffer.Bytes.Length); if (_outboundEndpoint != null) { @@ -90,7 +90,7 @@ public bool ReadBytesFromPeer(ref byte[] buffer, out int outBytesRead) } _listener.Proxy.Statistics.Use((o) => o.BytesRead += (ulong)bytesRead); - outBytesRead = bytesRead; + buffer.Length = bytesRead; return bytesRead > 0; } @@ -271,131 +271,59 @@ internal void DataPumpThreadProc() try { - var buffer = new byte[_listener.Proxy.Configuration.InitialBufferSize]; + var buffer = new PumpBuffer(_listener.Proxy.Configuration.InitialBufferSize); - StringBuilder? httpRequestHeaderBuilder = null; + var httpHeaderBuilder = new StringBuilder(); - while (_keepRunning && ReadBytesFromPeer(ref buffer, out int bufferLength)) + while (_keepRunning && ReadBytesFromPeer(ref buffer)) { - #region HTTP Header augmentation. - - try - { - if ( - //Only parse HTTP headers if the traffic type is HTTP - _listener.Proxy.Configuration.TrafficType == TrafficType.Http - && + #region HTTP Header Augmentation. + + if ( + //Only parse HTTP headers if the traffic type is HTTP. + _listener.Proxy.Configuration.TrafficType == TrafficType.Http + && + ( + // and the direction is inbound and we have request rules. ( - // and the direction is inbound and we have request rules - ( - Direction == ConnectionDirection.Inbound - && _listener.Proxy.Configuration.HttpHeaderRules.Collection - .Where(o => (new[] { HttpHeaderType.Request, HttpHeaderType.Any }).Contains(o.HeaderType)).Any() - ) - // or the direction is outbound and we have response rules. - || ( - Direction == ConnectionDirection.Outbound - && _listener.Proxy.Configuration.HttpHeaderRules.Collection - .Where(o => (new[] { HttpHeaderType.Response, HttpHeaderType.Any }).Contains(o.HeaderType)).Any() - ) + Direction == ConnectionDirection.Inbound + && _listener.Proxy.Configuration.HttpHeaderRules.Collection + .Where(o => (new[] { HttpHeaderType.Request, HttpHeaderType.Any }).Contains(o.HeaderType)).Any() + ) + // or the direction is outbound and we have response rules. + || ( + Direction == ConnectionDirection.Outbound + && _listener.Proxy.Configuration.HttpHeaderRules.Collection + .Where(o => (new[] { HttpHeaderType.Response, HttpHeaderType.Any }).Contains(o.HeaderType)).Any() ) ) + ) + { + switch (HttpHeaderAugmentation.Process(ref httpHeaderBuilder, _listener.Proxy.Configuration, buffer)) { - if (httpRequestHeaderBuilder != null) //We are reconstructing a fragmented HTTP request header. - { - var stringContent = Encoding.UTF8.GetString(buffer, 0, bufferLength); - httpRequestHeaderBuilder.Append(stringContent); - } - else if (HttpUtility.StartsWithHTTPVerb(buffer)) - { - var stringContent = Encoding.UTF8.GetString(buffer, 0, bufferLength); - httpRequestHeaderBuilder = new StringBuilder(stringContent); - } - - string headerDelimiter = string.Empty; - - if (httpRequestHeaderBuilder != null) - { - var headerType = HttpUtility.IsHttpHeader(httpRequestHeaderBuilder.ToString(), out string? requestVerb); - - if (headerType != HttpHeaderType.None && requestVerb != null) - { - var endOfHeaderIndex = HttpUtility.GetHttpHeaderEnd(httpRequestHeaderBuilder.ToString(), out headerDelimiter); - if (endOfHeaderIndex < 0) - { - continue; //We have a HTTP header but its a fragment. Wait on the remaining header. - } - else - { - if (bufferLength > headerDelimiter.Length * 2) // "\r\n" or "\r\n\r\n" - { - //If we received more bytes than just the delimiter then we - // need to determine how many non-header bytes need to be sent to the peer. - - int endOfHeaderInBufferIndex = HttpUtility.FindDelimiterIndexInByteArray(buffer, bufferLength, $"{headerDelimiter}{headerDelimiter}"); - if (endOfHeaderInBufferIndex < 0) - { - throw new Exception("Could not locate HTTP header in receive buffer."); - } - - int bufferEndOfHeaderOffset = endOfHeaderInBufferIndex + (headerDelimiter.Length * 2); - - if (bufferEndOfHeaderOffset > bufferLength) - { - //We received extra non-header bytes. We need to remove the header bytes from the buffer - // and then send them after we modify and send the header. - int newBufferLength = bufferLength - bufferEndOfHeaderOffset; - Array.Copy(buffer, bufferEndOfHeaderOffset, buffer, 0, newBufferLength); - } - - bufferLength -= bufferEndOfHeaderOffset; - } - else - { - bufferLength = 0; - } - } - - //apply the header rules: - string modifiedHttpRequestHeader = HttpUtility.ApplyHttpHeaderRules - (_listener.Proxy.Configuration, httpRequestHeaderBuilder.ToString(), headerType, requestVerb, headerDelimiter); - httpRequestHeaderBuilder = null; //We have completed reconstructing the header and performed modifications. - - //Send the modified header to the peer. - _peer?.WriteBytesToPeer(Encoding.UTF8.GetBytes(modifiedHttpRequestHeader)); - - if (bufferLength == 0) - { - //All we received is the header, so that;s all we have to send at this time. - continue; - } - } - } + case HttpHeaderAugmentation.HTTPHeaderResult.WaitOnData: + //We received a partial HTTP header, wait on more data. + continue; + case HttpHeaderAugmentation.HTTPHeaderResult.Present: + //Found an HTTP header, send it to the peer. There may be bytes remaining + // in the buffer if buffer.Length > 0 so follow though to WriteBytesToPeer. + _peer?.WriteBytesToPeer(Encoding.UTF8.GetBytes(httpHeaderBuilder.ToString())); + httpHeaderBuilder.Clear(); + break; + case HttpHeaderAugmentation.HTTPHeaderResult.NotPresent: + //Didn't find an HTTP header. + break; } } - catch (Exception ex) - { - httpRequestHeaderBuilder = null; - Singletons.Logging.Write("An error occurred while parsing the HTTP request header.", ex); - } #endregion - _peer?.WriteBytesToPeer(buffer, bufferLength); //Send data to remote peer. - - #region Buffer resize. - if (bufferLength == buffer.Length && buffer.Length < _listener.Proxy.Configuration.MaxBufferSize) + if (buffer.Length > 0) { - //If we read as much data as we could fit in the buffer, resize it a bit up to the maximum. - int newBufferSize = (int)(buffer.Length + (buffer.Length * 0.20)); - if (newBufferSize > _listener.Proxy.Configuration.MaxBufferSize) - { - newBufferSize = _listener.Proxy.Configuration.MaxBufferSize; - } - - buffer = new byte[newBufferSize]; + _peer?.WriteBytesToPeer(buffer.Bytes, buffer.Length); //Send data to remote peer. } - #endregion + + buffer.AutoResize(_listener.Proxy.Configuration.MaxBufferSize); } } catch