From f367be8d1761ee7ab196448ff304066df054863e Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Sun, 12 May 2024 19:26:45 -0700 Subject: [PATCH] DeflateMiddleware: Add deflate compression to game responses --- Refresh.Common/Refresh.Common.csproj | 4 +- .../Configuration/GameServerConfig.cs | 7 ++- .../Middlewares/DeflateMiddleware.cs | 61 +++++++++++++++++++ .../Middlewares/DigestMiddleware.cs | 2 +- Refresh.GameServer/Refresh.GameServer.csproj | 12 ++-- Refresh.GameServer/RefreshGameServer.cs | 1 + 6 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 Refresh.GameServer/Middlewares/DeflateMiddleware.cs diff --git a/Refresh.Common/Refresh.Common.csproj b/Refresh.Common/Refresh.Common.csproj index be271166..d86ddc9a 100644 --- a/Refresh.Common/Refresh.Common.csproj +++ b/Refresh.Common/Refresh.Common.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/Refresh.GameServer/Configuration/GameServerConfig.cs b/Refresh.GameServer/Configuration/GameServerConfig.cs index d2d4bd21..9213021f 100644 --- a/Refresh.GameServer/Configuration/GameServerConfig.cs +++ b/Refresh.GameServer/Configuration/GameServerConfig.cs @@ -9,7 +9,7 @@ namespace Refresh.GameServer.Configuration; [SuppressMessage("ReSharper", "RedundantDefaultMemberInitializer")] public class GameServerConfig : Config { - public override int CurrentConfigVersion => 13; + public override int CurrentConfigVersion => 14; public override int Version { get; set; } = 0; protected override void Migrate(int oldVer, dynamic oldConfig) {} @@ -27,6 +27,11 @@ protected override void Migrate(int oldVer, dynamic oldConfig) {} public bool MaintenanceMode { get; set; } = false; public bool RequireGameLoginToRegister { get; set; } = false; public bool TrackRequestStatistics { get; set; } = false; + /// + /// Whether to use deflate compression for responses. + /// If this is disabled, large enough responses will cause LBP to overflow its read buffer and eventually corrupt its own memory to the point of crashing. + /// + public bool UseDeflateCompression { get; set; } = true; public string WebExternalUrl { get; set; } = "https://refresh.example.com"; /// /// The base URL that LBP3 uses to grab config files like `network_settings.nws`. diff --git a/Refresh.GameServer/Middlewares/DeflateMiddleware.cs b/Refresh.GameServer/Middlewares/DeflateMiddleware.cs new file mode 100644 index 00000000..50656802 --- /dev/null +++ b/Refresh.GameServer/Middlewares/DeflateMiddleware.cs @@ -0,0 +1,61 @@ +using Bunkum.Core.Database; +using Bunkum.Core.Endpoints.Middlewares; +using Bunkum.Listener.Request; +using Org.BouncyCastle.Utilities.Zlib; +using Refresh.GameServer.Configuration; +using Refresh.GameServer.Endpoints; + +namespace Refresh.GameServer.Middlewares; + +/// +/// Adds deflate encoding to LBP game server responses if they reach a certain size. +/// The game will eventually corrupt its own memory if we do not do this, +/// since non-deflate requests only get a very small request buffer, and if you send too big of a request, +/// it overruns that buffer and starts corrupting random heap memory. +/// Requests encoded with deflate get a much larger read buffer, so they avoid this problem until much larger buffer sizes. +/// +public class DeflateMiddleware(GameServerConfig config) : IMiddleware +{ + /// + /// After this many bytes, start deflating request bodies + /// + private const int DeflateCutoff = 1024; + + public void HandleRequest(ListenerContext context, Lazy database, Action next) + { + next(); + + if (!config.UseDeflateCompression) + return; + + // If this isn't a game request or it's a resource request, don't deflate, since resource requests will be corrupted if deflated + if (!context.Uri.AbsolutePath.StartsWith(GameEndpointAttribute.BaseRoute) || context.Uri.AbsolutePath.StartsWith($"{GameEndpointAttribute.BaseRoute}r/")) + return; + + string? encodings = context.RequestHeaders.Get("Accept-Encoding"); + // If the accepted encodings aren't specified, or they don't contain deflate, or we don't need to use deflate on the data, do nothing. + if (encodings == null || !encodings.Contains("deflate") || context.ResponseStream.Length <= DeflateCutoff) + return; + + // Update the headers marking that we are sending encoded data + context.ResponseHeaders["X-Original-Content-Length"] = context.ResponseStream.Length.ToString(); + context.ResponseHeaders["Vary"] = "Accept-Encoding"; + context.ResponseHeaders["Content-Encoding"] = "deflate"; + + // Create a copy of our uncompressed data + byte[] uncompressed = context.ResponseStream.ToArray(); + + // Reset the response stream position and length to 0, so we can start writing to it again + context.ResponseStream.Position = 0; + context.ResponseStream.SetLength(0); + + // Compress our source data into the response stream + using ZOutputStreamLeaveOpen zlibStream = new(context.ResponseStream, 6); + zlibStream.Write(uncompressed); + zlibStream.Finish(); + zlibStream.Close(); + + // Seek back to the start + context.ResponseStream.Position = 0; + } +} \ No newline at end of file diff --git a/Refresh.GameServer/Middlewares/DigestMiddleware.cs b/Refresh.GameServer/Middlewares/DigestMiddleware.cs index 32bb4f55..40a5af97 100644 --- a/Refresh.GameServer/Middlewares/DigestMiddleware.cs +++ b/Refresh.GameServer/Middlewares/DigestMiddleware.cs @@ -84,7 +84,7 @@ private void SetDigestResponse(ListenerContext context) public void HandleRequest(ListenerContext context, Lazy database, Action next) { - //If this isnt an LBP endpoint, dont do digest + //If this isn't an LBP endpoint, dont do digest if (!context.Uri.AbsolutePath.StartsWith(GameEndpointAttribute.BaseRoute)) { next(); diff --git a/Refresh.GameServer/Refresh.GameServer.csproj b/Refresh.GameServer/Refresh.GameServer.csproj index ae711ea8..8784cd97 100644 --- a/Refresh.GameServer/Refresh.GameServer.csproj +++ b/Refresh.GameServer/Refresh.GameServer.csproj @@ -52,12 +52,12 @@ - - - - - - + + + + + + diff --git a/Refresh.GameServer/RefreshGameServer.cs b/Refresh.GameServer/RefreshGameServer.cs index 47ae1199..d3c68f7b 100644 --- a/Refresh.GameServer/RefreshGameServer.cs +++ b/Refresh.GameServer/RefreshGameServer.cs @@ -87,6 +87,7 @@ protected override void SetupMiddlewares() { this.Server.AddMiddleware(); this.Server.AddMiddleware(); + this.Server.AddMiddleware(new DeflateMiddleware(this._config!)); this.Server.AddMiddleware(); this.Server.AddMiddleware(); this.Server.AddMiddleware();