From ea7bcfa652d43d2fefcba081869994864d9040fb Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Sun, 12 Jan 2025 08:26:17 +0100 Subject: [PATCH] [backend/core] Add HTTP proxy support --- .../Core/Configuration/Config.cs | 8 +- .../Core/Extensions/ServiceExtensions.cs | 1 + .../Core/Services/CustomHttpClient.cs | 85 +++++++++++-------- Iceshrimp.Backend/configuration.ini | 4 + 4 files changed, 60 insertions(+), 38 deletions(-) diff --git a/Iceshrimp.Backend/Core/Configuration/Config.cs b/Iceshrimp.Backend/Core/Configuration/Config.cs index 9635ea15..76e70971 100644 --- a/Iceshrimp.Backend/Core/Configuration/Config.cs +++ b/Iceshrimp.Backend/Core/Configuration/Config.cs @@ -17,6 +17,7 @@ public sealed class Config public required InstanceSection Instance { get; init; } = new(); public required DatabaseSection Database { get; init; } = new(); public required SecuritySection Security { get; init; } = new(); + public required NetworkSection Network { get; init; } = new(); public required StorageSection Storage { get; init; } = new(); public required PerformanceSection Performance { get; init; } = new(); public required QueueSection Queue { get; init; } = new(); @@ -67,6 +68,11 @@ public sealed class Config public Enums.PublicPreview PublicPreview { get; init; } = Enums.PublicPreview.Public; } + public sealed class NetworkSection + { + public string? HttpProxy { get; init; } = null; + } + public sealed class DatabaseSection { [Required] public string Host { get; init; } = "localhost"; @@ -423,4 +429,4 @@ public sealed class Config _ => throw new Exception("Unsupported suffix, use one of: [s]econds, [m]inutes, [h]ours, [d]ays, [w]eeks") }; } -} \ No newline at end of file +} diff --git a/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs b/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs index 5481188c..e84deabe 100644 --- a/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs @@ -88,6 +88,7 @@ public static class ServiceExtensions services.ConfigureWithValidation(configuration) .ConfigureWithValidation(configuration, "Instance") .ConfigureWithValidation(configuration, "Security") + .ConfigureWithValidation(configuration, "Network") .ConfigureWithValidation(configuration, "Performance") .ConfigureWithValidation(configuration, "Performance:QueueConcurrency") .ConfigureWithValidation(configuration, "Backfill") diff --git a/Iceshrimp.Backend/Core/Services/CustomHttpClient.cs b/Iceshrimp.Backend/Core/Services/CustomHttpClient.cs index a958802a..77d9175f 100644 --- a/Iceshrimp.Backend/Core/Services/CustomHttpClient.cs +++ b/Iceshrimp.Backend/Core/Services/CustomHttpClient.cs @@ -3,32 +3,23 @@ using System.Net; using System.Net.Sockets; using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Extensions; +using JetBrains.Annotations; using Microsoft.Extensions.Options; namespace Iceshrimp.Backend.Core.Services; +[UsedImplicitly] public class CustomHttpClient : HttpClient, IService, ISingletonService { - private static readonly FastFallback FastFallbackHandler = new(); - - private static readonly HttpMessageHandler InnerHandler = new SocketsHttpHandler - { - AutomaticDecompression = DecompressionMethods.All, - ConnectCallback = FastFallbackHandler.ConnectCallbackAsync, - PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5), - PooledConnectionLifetime = TimeSpan.FromMinutes(60) - }; - - private static readonly HttpMessageHandler Handler = new RedirectHandler(InnerHandler); - public CustomHttpClient( - IOptions options, + IOptions instance, + IOptions network, IOptionsMonitor security, ILoggerFactory loggerFactory - ) : base(Handler) + ) : base(BuildHandler(network, security, loggerFactory)) { // Configure HTTP client options - DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", options.Value.UserAgent); + DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", instance.Value.UserAgent); Timeout = TimeSpan.FromSeconds(30); // Default to HTTP/2, but allow for down-negotiation to HTTP/1.1 or HTTP/1.0 @@ -37,21 +28,41 @@ public class CustomHttpClient : HttpClient, IService, ISingletonServ // Protect against DoS attacks MaxResponseContentBufferSize = 1024 * 1024; // 1MiB + } - // Configure FastFallback - FastFallbackHandler.Logger = loggerFactory.CreateLogger(); - FastFallbackHandler.Security = security; + // This needs to be a static method so we can call the base constructor with varying input based on the configuration + private static RedirectHandler BuildHandler( + IOptions network, + IOptionsMonitor security, + ILoggerFactory loggerFactory + ) + { + var proxy = network.Value.HttpProxy != null ? new WebProxy(network.Value.HttpProxy) : null; + var fastFallback = new FastFallback(loggerFactory.CreateLogger(), security, proxy != null); + var innerHandler = new SocketsHttpHandler + { + AutomaticDecompression = DecompressionMethods.All, + ConnectCallback = fastFallback.ConnectCallbackAsync, + PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5), + PooledConnectionLifetime = TimeSpan.FromMinutes(60), + UseProxy = proxy != null, + Proxy = proxy, + }; + + return new RedirectHandler(innerHandler); } // Adapted from https://github.com/KazWolfe/Dalamud/blob/767cc49ecb80e29dbdda2fa8329d3c3341c964fe/Dalamud/Networking/Http/HappyEyeballsCallback.cs - private class FastFallback(int connectionBackoff = 75) + private class FastFallback( + ILogger logger, + IOptionsMonitor security, + bool proxy, + int connectionBackoff = 75 + ) { - public ILogger? Logger { private get; set; } - public IOptionsMonitor? Security { private get; set; } - - private bool AllowLoopback => Security?.CurrentValue.AllowLoopback ?? false; - private bool AllowLocalIPv4 => Security?.CurrentValue.AllowLocalIPv4 ?? false; - private bool AllowLocalIPv6 => Security?.CurrentValue.AllowLocalIPv6 ?? false; + private bool AllowLoopback => security.CurrentValue.AllowLoopback || proxy; + private bool AllowLocalIPv4 => security.CurrentValue.AllowLocalIPv4 || proxy; + private bool AllowLocalIPv6 => security.CurrentValue.AllowLocalIPv6 || proxy; public async ValueTask ConnectCallbackAsync( SocketsHttpConnectionContext context, CancellationToken token @@ -72,22 +83,22 @@ public class CustomHttpClient : HttpClient, IService, ISingletonServ if (!AllowLoopback && record.IsLoopback()) { - Logger?.LogWarning("Refusing to connect to loopback address {address} due to possible SSRF", - record.ToString()); + logger.LogWarning("Refusing to connect to loopback address {address} due to possible SSRF", + record.ToString()); continue; } if (!AllowLocalIPv6 && record.IsLocalIPv6()) { - Logger?.LogWarning("Refusing to connect to local IPv6 address {address} due to possible SSRF", - record.ToString()); + logger.LogWarning("Refusing to connect to local IPv6 address {address} due to possible SSRF", + record.ToString()); continue; } if (!AllowLocalIPv4 && record.IsLocalIPv4()) { - Logger?.LogWarning("Refusing to connect to local IPv4 address {address} due to possible SSRF", - record.ToString()); + logger.LogWarning("Refusing to connect to local IPv4 address {address} due to possible SSRF", + record.ToString()); continue; } @@ -118,8 +129,8 @@ public class CustomHttpClient : HttpClient, IService, ISingletonServ if (stream == null) { - throw lastException ?? - new Exception("An unknown exception occured during fast fallback connection attempt"); + throw lastException + ?? new Exception("An unknown exception occured during fast fallback connection attempt"); } await linkedToken.CancelAsync(); @@ -279,9 +290,9 @@ public class CustomHttpClient : HttpClient, IService, ISingletonServ //Manual Redirect //https://github.com/dotnet/runtime/blob/ccfe21882e4a2206ce49cd5b32d3eb3cab3e530f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RedirectHandler.cs Uri? redirectUri; - while (IsRedirect(response) && - IsRedirectAllowed(request) && - (redirectUri = GetUriForRedirect(request.RequestUri!, response)) != null) + while (IsRedirect(response) + && IsRedirectAllowed(request) + && (redirectUri = GetUriForRedirect(request.RequestUri!, response)) != null) { redirectCount++; if (redirectCount > MaxAutomaticRedirections) @@ -371,4 +382,4 @@ public class CustomHttpClient : HttpClient, IService, ISingletonServ } } } -} \ No newline at end of file +} diff --git a/Iceshrimp.Backend/configuration.ini b/Iceshrimp.Backend/configuration.ini index d6008cca..04ac8a87 100644 --- a/Iceshrimp.Backend/configuration.ini +++ b/Iceshrimp.Backend/configuration.ini @@ -77,6 +77,10 @@ ExposeBlockReasons = Registered ;; Options: [Public, Restricted, RestrictedNoMedia, Lockdown] PublicPreview = Public +[Network] +;; Uncomment to use the specified HTTP proxy for all outgoing requests. Backend restart is required to apply changes. +;HttpProxy = 127.0.0.1:8080 + [Performance] ;; Maximum number of incoming federation requests to handle concurrently. ;; When exceeded, incoming requests are buffered in memory until they can be executed.