From 41dc4ee72b7632fcc3fc3bbb2b80e20b17d7a0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20N=C3=A4geli?= Date: Tue, 21 Jan 2025 13:53:48 +0100 Subject: [PATCH] Simplify endpoint handling when listening to any IP (#613) --- API/Infrastructure/IEndPoint.cs | 7 +-- API/Infrastructure/IServerBuilder.cs | 12 ++-- .../Infrastructure/Endpoints/EndPoint.cs | 26 ++++----- .../Endpoints/EndpointCollection.cs | 11 ++-- .../Endpoints/InsecureEndPoint.cs | 4 +- .../Endpoints/SecureEndPoint.cs | 4 +- Engine/Kestrel/Hosting/KestrelEndpoint.cs | 6 +- Engine/Kestrel/Hosting/KestrelServer.cs | 56 ++++++++++++------- Engine/Shared/Hosting/ServerHost.cs | 6 +- Engine/Shared/Infrastructure/Configuration.cs | 2 +- Engine/Shared/Infrastructure/ServerBuilder.cs | 17 ++---- .../Inspection/Concern/InspectionConcern.cs | 2 +- 12 files changed, 76 insertions(+), 77 deletions(-) diff --git a/API/Infrastructure/IEndPoint.cs b/API/Infrastructure/IEndPoint.cs index ad58ebbc..d75028b1 100644 --- a/API/Infrastructure/IEndPoint.cs +++ b/API/Infrastructure/IEndPoint.cs @@ -11,11 +11,7 @@ public interface IEndPoint : IDisposable /// /// The IP address the endpoint is bound to. /// - /// - /// Can be a specific IPv4/IPv6 address or a more generic one - /// such as . - /// - IPAddress IPAddress { get; } + IPAddress? Address { get; } /// /// The port the endpoint is listening on. @@ -26,4 +22,5 @@ public interface IEndPoint : IDisposable /// Specifies, whether this is is an endpoint secured via SSL/TLS. /// bool Secure { get; } + } diff --git a/API/Infrastructure/IServerBuilder.cs b/API/Infrastructure/IServerBuilder.cs index 7b773d06..9c30f061 100644 --- a/API/Infrastructure/IServerBuilder.cs +++ b/API/Infrastructure/IServerBuilder.cs @@ -33,33 +33,33 @@ public interface IServerBuilder : IBuilder /// Registers an endpoint for the given address and port the server will /// bind to on startup to listen for incomming HTTP requests. /// - /// The address to bind to + /// The address to bind to (or null, if the server should listen to any IP) /// The port to listen on - T Bind(IPAddress address, ushort port); + T Bind(IPAddress? address, ushort port); /// /// Registers a secure endpoint the server will bind to on /// startup to listen for incoming HTTPS requests. /// - /// The address to bind to + /// The address to bind to (or null, if the server should listen to any IP) /// The port to listen on /// The certificate used to negoiate a connection with /// The SSL/TLS protocl versions which should be supported by the endpoint /// The validator to check the validity of client certificates with /// If enabled, the server will host a HTTP/3 endpoint via QUIC - T Bind(IPAddress address, ushort port, X509Certificate2 certificate, SslProtocols protocols = SslProtocols.Tls12 | SslProtocols.Tls13, ICertificateValidator? certificateValidator = null, bool enableQuic = false); + T Bind(IPAddress? address, ushort port, X509Certificate2 certificate, SslProtocols protocols = SslProtocols.Tls12 | SslProtocols.Tls13, ICertificateValidator? certificateValidator = null, bool enableQuic = false); /// /// Registers a secure endpoint the server will bind to on /// startup to listen for incoming HTTPS requests. /// - /// The address to bind to + /// The address to bind to (or null, if the server should listen to any IP) /// The port to listen on /// The provider to select the certificate used to negoiate a connection with /// The SSL/TLS protocl versions which should be supported by the endpoint /// The validator to check the validity of client certificates with /// If enabled, the server will host a HTTP/3 endpoint via QUIC - T Bind(IPAddress address, ushort port, ICertificateProvider certificateProvider, SslProtocols protocols = SslProtocols.Tls12 | SslProtocols.Tls13, ICertificateValidator? certificateValidator = null, bool enableQuic = false); + T Bind(IPAddress? address, ushort port, ICertificateProvider certificateProvider, SslProtocols protocols = SslProtocols.Tls12 | SslProtocols.Tls13, ICertificateValidator? certificateValidator = null, bool enableQuic = false); #endregion diff --git a/Engine/Internal/Infrastructure/Endpoints/EndPoint.cs b/Engine/Internal/Infrastructure/Endpoints/EndPoint.cs index 20508c84..abe06e0a 100644 --- a/Engine/Internal/Infrastructure/Endpoints/EndPoint.cs +++ b/Engine/Internal/Infrastructure/Endpoints/EndPoint.cs @@ -17,17 +17,11 @@ internal abstract class EndPoint : IEndPoint protected NetworkConfiguration Configuration { get; } - private IPEndPoint Endpoint { get; } - private Task? Task { get; set; } private Socket? Socket { get; set; } - #endregion - - #region Basic Information - - public IPAddress IPAddress { get; } + public IPAddress? Address { get; } public ushort Port { get; } @@ -37,15 +31,14 @@ internal abstract class EndPoint : IEndPoint #region Initialization - protected EndPoint(IServer server, IPEndPoint endPoint, NetworkConfiguration configuration) + protected EndPoint(IServer server, IPAddress? address, ushort port, NetworkConfiguration configuration) { Server = server; - Endpoint = endPoint; Configuration = configuration; - IPAddress = endPoint.Address; - Port = (ushort)endPoint.Port; + Address = address; + Port = port; } #endregion @@ -54,16 +47,21 @@ protected EndPoint(IServer server, IPEndPoint endPoint, NetworkConfiguration con public void Start() { + var address = Address ?? IPAddress.IPv6Any; + try { - Socket = new Socket(Endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + Socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp); + + Socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false); + + Socket.Bind(new IPEndPoint(address, Port)); - Socket.Bind(Endpoint); Socket.Listen(Configuration.Backlog); } catch (Exception e) { - throw new BindingException($"Failed to bind to {Endpoint}.", e); + throw new BindingException($"Failed to bind to {address} on port {Port}.", e); } Task = Task.Run(Listen); diff --git a/Engine/Internal/Infrastructure/Endpoints/EndpointCollection.cs b/Engine/Internal/Infrastructure/Endpoints/EndpointCollection.cs index 651ab34b..1d1a0ede 100644 --- a/Engine/Internal/Infrastructure/Endpoints/EndpointCollection.cs +++ b/Engine/Internal/Infrastructure/Endpoints/EndpointCollection.cs @@ -1,6 +1,4 @@ -using System.Net; - -using GenHTTP.Api.Infrastructure; +using GenHTTP.Api.Infrastructure; using GenHTTP.Engine.Shared.Infrastructure; @@ -28,13 +26,12 @@ public EndPointCollection(IServer server, IEnumerable con private EndPoint Build(EndPointConfiguration configuration) { - var endpoint = new IPEndPoint(configuration.Address, configuration.Port); - if (configuration.Security is null) { - return new InsecureEndPoint(Server, endpoint, NetworkConfiguration); + return new InsecureEndPoint(Server, configuration.Address, configuration.Port, NetworkConfiguration); } - return new SecureEndPoint(Server, endpoint, configuration.Security, NetworkConfiguration); + + return new SecureEndPoint(Server, configuration.Address, configuration.Port, configuration.Security, NetworkConfiguration); } internal void Start() diff --git a/Engine/Internal/Infrastructure/Endpoints/InsecureEndPoint.cs b/Engine/Internal/Infrastructure/Endpoints/InsecureEndPoint.cs index 1e9f6ae5..5f189fe0 100644 --- a/Engine/Internal/Infrastructure/Endpoints/InsecureEndPoint.cs +++ b/Engine/Internal/Infrastructure/Endpoints/InsecureEndPoint.cs @@ -15,8 +15,8 @@ internal sealed class InsecureEndPoint : EndPoint #region Initialization - internal InsecureEndPoint(IServer server, IPEndPoint endPoint, NetworkConfiguration configuration) - : base(server, endPoint, configuration) + internal InsecureEndPoint(IServer server, IPAddress? address, ushort port, NetworkConfiguration configuration) + : base(server, address, port, configuration) { } diff --git a/Engine/Internal/Infrastructure/Endpoints/SecureEndPoint.cs b/Engine/Internal/Infrastructure/Endpoints/SecureEndPoint.cs index cceb39e5..c8366f6d 100644 --- a/Engine/Internal/Infrastructure/Endpoints/SecureEndPoint.cs +++ b/Engine/Internal/Infrastructure/Endpoints/SecureEndPoint.cs @@ -28,8 +28,8 @@ internal sealed class SecureEndPoint : EndPoint #region Initialization - internal SecureEndPoint(IServer server, IPEndPoint endPoint, SecurityConfiguration options, NetworkConfiguration configuration) - : base(server, endPoint, configuration) + internal SecureEndPoint(IServer server, IPAddress? address, ushort port, SecurityConfiguration options, NetworkConfiguration configuration) + : base(server, address, port, configuration) { Options = options; diff --git a/Engine/Kestrel/Hosting/KestrelEndpoint.cs b/Engine/Kestrel/Hosting/KestrelEndpoint.cs index 7d72a8d7..293a9d6a 100644 --- a/Engine/Kestrel/Hosting/KestrelEndpoint.cs +++ b/Engine/Kestrel/Hosting/KestrelEndpoint.cs @@ -8,7 +8,7 @@ public sealed class KestrelEndpoint : IEndPoint #region Get-/Setters - public IPAddress IPAddress { get; } + public IPAddress? Address { get; } public ushort Port { get; } @@ -18,9 +18,9 @@ public sealed class KestrelEndpoint : IEndPoint #region Initialization - public KestrelEndpoint(IPAddress ipAddress, ushort port, bool secure) + public KestrelEndpoint(IPAddress? address, ushort port, bool secure) { - IPAddress = ipAddress; + Address = address; Port = port; Secure = secure; } diff --git a/Engine/Kestrel/Hosting/KestrelServer.cs b/Engine/Kestrel/Hosting/KestrelServer.cs index 2241296f..4549c4c0 100644 --- a/Engine/Kestrel/Hosting/KestrelServer.cs +++ b/Engine/Kestrel/Hosting/KestrelServer.cs @@ -2,7 +2,6 @@ using System.Security.Cryptography.X509Certificates; using GenHTTP.Adapters.AspNetCore; - using GenHTTP.Api.Content; using GenHTTP.Api.Infrastructure; @@ -104,35 +103,52 @@ private void Configure(WebApplicationBuilder builder) foreach (var endpoint in Configuration.EndPoints) { - if (endpoint.Security != null) + if (endpoint.Address != null) { - options.Listen(endpoint.Address, endpoint.Port, listenOptions => + if (endpoint.Security != null) + { + options.Listen(endpoint.Address, endpoint.Port, listenOptions => Secure(listenOptions, endpoint, endpoint.Security)); + } + else { - listenOptions.Protocols = (endpoint.EnableQuic) ? HttpProtocols.Http1AndHttp2AndHttp3 : HttpProtocols.Http1AndHttp2; - listenOptions.UseHttps(httpsOptions => - { - httpsOptions.SslProtocols = endpoint.Security.Protocols; - httpsOptions.ServerCertificateSelector = (_, hostName) => endpoint.Security.CertificateProvider.Provide(hostName); - - var validator = endpoint.Security.CertificateValidator; - - if (validator != null) - { - httpsOptions.ClientCertificateMode = validator.RequireCertificate ? ClientCertificateMode.RequireCertificate : ClientCertificateMode.AllowCertificate; - httpsOptions.ClientCertificateValidation = validator.Validate; - httpsOptions.CheckCertificateRevocation = (validator.RevocationCheck != X509RevocationMode.NoCheck); - } - }); - }); + options.Listen(endpoint.Address, endpoint.Port); + } } else { - options.Listen(endpoint.Address, endpoint.Port); + if (endpoint.Security != null) + { + options.ListenAnyIP(endpoint.Port, listenOptions => Secure(listenOptions, endpoint, endpoint.Security)); + } + else + { + options.ListenAnyIP(endpoint.Port); + } } } }); } + private static void Secure(ListenOptions options, EndPointConfiguration endpoint, SecurityConfiguration security) + { + options.Protocols = (endpoint.EnableQuic) ? HttpProtocols.Http1AndHttp2AndHttp3 : HttpProtocols.Http1AndHttp2; + + options.UseHttps(httpsOptions => + { + httpsOptions.SslProtocols = security.Protocols; + httpsOptions.ServerCertificateSelector = (_, hostName) => security.CertificateProvider.Provide(hostName); + + var validator = security.CertificateValidator; + + if (validator != null) + { + httpsOptions.ClientCertificateMode = validator.RequireCertificate ? ClientCertificateMode.RequireCertificate : ClientCertificateMode.AllowCertificate; + httpsOptions.ClientCertificateValidation = validator.Validate; + httpsOptions.CheckCertificateRevocation = (validator.RevocationCheck != X509RevocationMode.NoCheck); + } + }); + } + #endregion #region Lifecycle diff --git a/Engine/Shared/Hosting/ServerHost.cs b/Engine/Shared/Hosting/ServerHost.cs index 88a70dc1..ea116670 100644 --- a/Engine/Shared/Hosting/ServerHost.cs +++ b/Engine/Shared/Hosting/ServerHost.cs @@ -35,19 +35,19 @@ public IServerHost Backlog(ushort backlog) return this; } - public IServerHost Bind(IPAddress address, ushort port) + public IServerHost Bind(IPAddress? address, ushort port) { _Builder.Bind(address, port); return this; } - public IServerHost Bind(IPAddress address, ushort port, X509Certificate2 certificate, SslProtocols protocols = SslProtocols.Tls12 | SslProtocols.Tls13, ICertificateValidator? certificateValidator = null, bool enableQuic = false) + public IServerHost Bind(IPAddress? address, ushort port, X509Certificate2 certificate, SslProtocols protocols = SslProtocols.Tls12 | SslProtocols.Tls13, ICertificateValidator? certificateValidator = null, bool enableQuic = false) { _Builder.Bind(address, port, certificate, protocols, certificateValidator, enableQuic); return this; } - public IServerHost Bind(IPAddress address, ushort port, ICertificateProvider certificateProvider, SslProtocols protocols = SslProtocols.Tls12 | SslProtocols.Tls13, ICertificateValidator? certificateValidator = null, bool enableQuic = false) + public IServerHost Bind(IPAddress? address, ushort port, ICertificateProvider certificateProvider, SslProtocols protocols = SslProtocols.Tls12 | SslProtocols.Tls13, ICertificateValidator? certificateValidator = null, bool enableQuic = false) { _Builder.Bind(address, port, certificateProvider, protocols, certificateValidator, enableQuic); return this; diff --git a/Engine/Shared/Infrastructure/Configuration.cs b/Engine/Shared/Infrastructure/Configuration.cs index d251c2c9..f95ad345 100644 --- a/Engine/Shared/Infrastructure/Configuration.cs +++ b/Engine/Shared/Infrastructure/Configuration.cs @@ -11,6 +11,6 @@ public record ServerConfiguration(bool DevelopmentMode, IEnumerable Bind(address, port, new SimpleCertificateProvider(certificate), protocols, certificateValidator, enableQuic); + public IServerBuilder Bind(IPAddress? address, ushort port, X509Certificate2 certificate, SslProtocols protocols = SslProtocols.Tls12 | SslProtocols.Tls13, ICertificateValidator? certificateValidator = null, bool enableQuic = false) => Bind(address, port, new SimpleCertificateProvider(certificate), protocols, certificateValidator, enableQuic); - public IServerBuilder Bind(IPAddress address, ushort port, ICertificateProvider certificateProvider, SslProtocols protocols = SslProtocols.Tls12 | SslProtocols.Tls13, ICertificateValidator? certificateValidator = null, bool enableQuic = false) + public IServerBuilder Bind(IPAddress? address, ushort port, ICertificateProvider certificateProvider, SslProtocols protocols = SslProtocols.Tls12 | SslProtocols.Tls13, ICertificateValidator? certificateValidator = null, bool enableQuic = false) { _EndPoints.Add(new EndPointConfiguration(address, port, new SecurityConfiguration(certificateProvider, protocols, certificateValidator), enableQuic)); return this; @@ -146,15 +145,7 @@ public IServer Build() if (endpoints.Count == 0) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - endpoints.Add(new EndPointConfiguration(IPAddress.Any, _Port, null, false)); - endpoints.Add(new EndPointConfiguration(IPAddress.IPv6Any, _Port, null, false)); - } - else - { - endpoints.Add(new EndPointConfiguration(IPAddress.Any, _Port, null, false)); - } + endpoints.Add(new EndPointConfiguration(null, _Port, null, false)); } var config = new ServerConfiguration(_Development, endpoints, network); diff --git a/Modules/Inspection/Concern/InspectionConcern.cs b/Modules/Inspection/Concern/InspectionConcern.cs index ba0bae3c..bb3f3087 100644 --- a/Modules/Inspection/Concern/InspectionConcern.cs +++ b/Modules/Inspection/Concern/InspectionConcern.cs @@ -53,7 +53,7 @@ public InspectionConcern(IHandler content, SerializationRegistry serialization) Endpoints = server.EndPoints.Select(e => new { Port = e.Port, - IPAddress = e.IPAddress.ToString(), + IPAddress = e.Address?.ToString(), Secure = e.Secure, RequestSource = e == request.EndPoint })