Skip to content

Commit

Permalink
Simplify endpoint handling when listening to any IP (#613)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kaliumhexacyanoferrat authored Jan 21, 2025
1 parent 1b0bdc8 commit 41dc4ee
Show file tree
Hide file tree
Showing 12 changed files with 76 additions and 77 deletions.
7 changes: 2 additions & 5 deletions API/Infrastructure/IEndPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ public interface IEndPoint : IDisposable
/// <summary>
/// The IP address the endpoint is bound to.
/// </summary>
/// <remarks>
/// Can be a specific IPv4/IPv6 address or a more generic one
/// such as <see cref="System.Net.IPAddress.Any" />.
/// </remarks>
IPAddress IPAddress { get; }
IPAddress? Address { get; }

/// <summary>
/// The port the endpoint is listening on.
Expand All @@ -26,4 +22,5 @@ public interface IEndPoint : IDisposable
/// Specifies, whether this is is an endpoint secured via SSL/TLS.
/// </summary>
bool Secure { get; }

}
12 changes: 6 additions & 6 deletions API/Infrastructure/IServerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,33 +33,33 @@ public interface IServerBuilder<out T> : IBuilder<IServer>
/// Registers an endpoint for the given address and port the server will
/// bind to on startup to listen for incomming HTTP requests.
/// </summary>
/// <param name="address">The address to bind to</param>
/// <param name="address">The address to bind to (or null, if the server should listen to any IP)</param>
/// <param name="port">The port to listen on</param>
T Bind(IPAddress address, ushort port);
T Bind(IPAddress? address, ushort port);

/// <summary>
/// Registers a secure endpoint the server will bind to on
/// startup to listen for incoming HTTPS requests.
/// </summary>
/// <param name="address">The address to bind to</param>
/// <param name="address">The address to bind to (or null, if the server should listen to any IP)</param>
/// <param name="port">The port to listen on</param>
/// <param name="certificate">The certificate used to negoiate a connection with</param>
/// <param name="protocols">The SSL/TLS protocl versions which should be supported by the endpoint</param>
/// <param name="certificateValidator">The validator to check the validity of client certificates with</param>
/// <param name="enableQuic">If enabled, the server will host a HTTP/3 endpoint via QUIC</param>
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);

/// <summary>
/// Registers a secure endpoint the server will bind to on
/// startup to listen for incoming HTTPS requests.
/// </summary>
/// <param name="address">The address to bind to</param>
/// <param name="address">The address to bind to (or null, if the server should listen to any IP)</param>
/// <param name="port">The port to listen on</param>
/// <param name="certificateProvider">The provider to select the certificate used to negoiate a connection with</param>
/// <param name="protocols">The SSL/TLS protocl versions which should be supported by the endpoint</param>
/// <param name="certificateValidator">The validator to check the validity of client certificates with</param>
/// <param name="enableQuic">If enabled, the server will host a HTTP/3 endpoint via QUIC</param>
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

Expand Down
26 changes: 12 additions & 14 deletions Engine/Internal/Infrastructure/Endpoints/EndPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand All @@ -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
Expand All @@ -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);
Expand Down
11 changes: 4 additions & 7 deletions Engine/Internal/Infrastructure/Endpoints/EndpointCollection.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Net;

using GenHTTP.Api.Infrastructure;
using GenHTTP.Api.Infrastructure;

using GenHTTP.Engine.Shared.Infrastructure;

Expand Down Expand Up @@ -28,13 +26,12 @@ public EndPointCollection(IServer server, IEnumerable<EndPointConfiguration> 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()
Expand Down
4 changes: 2 additions & 2 deletions Engine/Internal/Infrastructure/Endpoints/InsecureEndPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{

}
Expand Down
4 changes: 2 additions & 2 deletions Engine/Internal/Infrastructure/Endpoints/SecureEndPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
6 changes: 3 additions & 3 deletions Engine/Kestrel/Hosting/KestrelEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public sealed class KestrelEndpoint : IEndPoint

#region Get-/Setters

public IPAddress IPAddress { get; }
public IPAddress? Address { get; }

public ushort Port { get; }

Expand All @@ -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;
}
Expand Down
56 changes: 36 additions & 20 deletions Engine/Kestrel/Hosting/KestrelServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Security.Cryptography.X509Certificates;

using GenHTTP.Adapters.AspNetCore;

using GenHTTP.Api.Content;
using GenHTTP.Api.Infrastructure;

Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions Engine/Shared/Hosting/ServerHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion Engine/Shared/Infrastructure/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ public record ServerConfiguration(bool DevelopmentMode, IEnumerable<EndPointConf
public record NetworkConfiguration(TimeSpan RequestReadTimeout, uint RequestMemoryLimit,
uint TransferBufferSize, ushort Backlog);

public record EndPointConfiguration(IPAddress Address, ushort Port, SecurityConfiguration? Security, bool EnableQuic);
public record EndPointConfiguration(IPAddress? Address, ushort Port, SecurityConfiguration? Security, bool EnableQuic);

public record SecurityConfiguration(ICertificateProvider CertificateProvider, SslProtocols Protocols, ICertificateValidator? CertificateValidator);
17 changes: 4 additions & 13 deletions Engine/Shared/Infrastructure/ServerBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;

Expand Down Expand Up @@ -87,15 +86,15 @@ public IServerBuilder Port(ushort port)
return this;
}

public IServerBuilder Bind(IPAddress address, ushort port)
public IServerBuilder Bind(IPAddress? address, ushort port)
{
_EndPoints.Add(new EndPointConfiguration(address, port, null, false));
return this;
}

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, 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;
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion Modules/Inspection/Concern/InspectionConcern.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
Expand Down

0 comments on commit 41dc4ee

Please sign in to comment.