File: WebHostBuilderDirectTlsExtensions.cs
Web Access
Project: src\aspnetcore\src\Servers\Kestrel\Transport.DirectTls\src\Microsoft.AspNetCore.Server.Kestrel.Transport.DirectTls.csproj (Microsoft.AspNetCore.Server.Kestrel.Transport.DirectTls)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.Versioning;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Transport.DirectTls;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
 
namespace Microsoft.AspNetCore.Hosting;
 
/// <summary>
/// <see cref="IWebHostBuilder"/> extension methods to register the DirectTls transport with Kestrel.
/// </summary>
[Experimental("ASPNETCORE_DIRECTTLS_001", UrlFormat = "https://aka.ms/aspnetcore/directtls")]
public static class WebHostBuilderDirectTlsExtensions
{
    /// <summary>
    /// Registers the DirectTls transport (native, file-descriptor-bound OpenSSL TLS) alongside the default
    /// Kestrel transport. Endpoints bound with a <see cref="DirectTlsEndpoint"/> are served by this transport;
    /// all other endpoints continue to use the default transport.
    /// </summary>
    /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
    /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
    /// <remarks>
    /// Call this <b>after</b> <c>UseKestrel()</c> so the DirectTls transport is offered a
    /// <see cref="DirectTlsEndpoint"/> before the default transport.
    /// </remarks>
    [SupportedOSPlatform("linux")]
    public static IWebHostBuilder UseDirectTls(this IWebHostBuilder hostBuilder)
    {
        ArgumentNullException.ThrowIfNull(hostBuilder);
        EnsureCompatibleKestrelCoreVersion();
        if (!OperatingSystem.IsLinux())
        {
            throw new PlatformNotSupportedException("The DirectTls transport requires a Linux operating system.");
        }
 
        return hostBuilder.ConfigureServices(services =>
        {
            // Registered additively (not TryAdd): the default transport is already registered by UseKestrel.
            // KestrelServerImpl reverses the factory list, so this last-registered factory is tried first and
            // its CanBind claims only DirectTlsEndpoint; every other endpoint falls through to the default.
            services.AddSingleton<IConnectionListenerFactory, DirectTlsTransportFactory>();
 
            services.TryAddSingleton<IMemoryPoolFactory<byte>, DefaultSimpleMemoryPoolFactory>();
            services.AddOptions<DirectTlsTransportOptions>().Configure((DirectTlsTransportOptions options, IMemoryPoolFactory<byte> factory) =>
            {
                // UseKestrelCore normally registers PinnedBlockMemoryPoolFactory. The fallback supports
                // standalone transport registration, matching the sockets transport.
                options.MemoryPoolFactory = factory;
            });
 
            // Copies each DirectTlsEndpoint's ListenOptions.Protocols onto the endpoint after all endpoints
            // are configured, so the transport can read the ALPN protocols off the endpoint itself.
            services.AddSingleton<IPostConfigureOptions<KestrelServerOptions>, DirectTlsEndpointProtocolsSetup>();
 
            // Defaults MaxConcurrentHandshakes from KestrelServerLimits.MaxConcurrentConnections when the
            // transport option was not set explicitly, so the pre-handshake flood cap tracks the server's
            // configured connection limit without a separate knob.
            services.AddSingleton<IPostConfigureOptions<DirectTlsTransportOptions>, DirectTlsHandshakeLimitSetup>();
        });
    }
 
    /// <summary>
    /// Registers the DirectTls transport and configures its transport-wide options.
    /// </summary>
    /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
    /// <param name="configureOptions">A callback to configure <see cref="DirectTlsTransportOptions"/>.</param>
    /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
    [SupportedOSPlatform("linux")]
    public static IWebHostBuilder UseDirectTls(this IWebHostBuilder hostBuilder, Action<DirectTlsTransportOptions> configureOptions)
    {
        ArgumentNullException.ThrowIfNull(hostBuilder);
        ArgumentNullException.ThrowIfNull(configureOptions);
 
        return hostBuilder.UseDirectTls().ConfigureServices(services =>
        {
            services.Configure(configureOptions);
        });
    }
 
    // DirectTls is a standalone package that reaches into Kestrel.Core internals via InternalsVisibleTo, which
    // carry no compatibility guarantee across product versions. Its NuGet package only takes an unversioned
    // Microsoft.AspNetCore.App framework reference, so a DirectTls built for one major could load against a
    // different Kestrel.Core major and fail obscurely (e.g. MissingMethodException). Fail fast and clearly.
    private static void EnsureCompatibleKestrelCoreVersion()
    {
        var directTlsAssembly = typeof(WebHostBuilderDirectTlsExtensions).Assembly;
        var kestrelCoreAssembly = typeof(KestrelServerOptions).Assembly;
 
        // The product (informational) version is compared, not AssemblyName.Version: shared-framework assemblies
        // like Kestrel.Core pin AssemblyVersion to major.0.0.0, while this standalone package keeps the build's
        // dev sentinel, so only the informational version reflects the real product major on both.
        var error = GetKestrelCoreVersionMismatchError(
            directTlsAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion,
            kestrelCoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion,
            directTlsAssembly.GetName().Name,
            kestrelCoreAssembly.GetName().Name);
 
        if (error is not null)
        {
            throw new InvalidOperationException(error);
        }
    }
 
    // Returns null when the assemblies are compatible, otherwise a clear error message. Pure (no reflection) so
    // the rule is unit-testable. Fail-open: only a confirmed major difference is reported; an unreadable version
    // (e.g. the informational-version attribute trimmed away) is not evidence of a mismatch, so startup proceeds.
    internal static string? GetKestrelCoreVersionMismatchError(string? directTlsVersion, string? kestrelCoreVersion, string? directTlsName, string? kestrelCoreName)
    {
        if (TryGetMajorProductVersion(directTlsVersion, out int directTlsMajor) &&
            TryGetMajorProductVersion(kestrelCoreVersion, out int kestrelCoreMajor) &&
            directTlsMajor != kestrelCoreMajor)
        {
            return $"The DirectTls transport ({directTlsName} {directTlsVersion}) requires a matching major version of " +
                $"Kestrel.Core ({kestrelCoreName} {kestrelCoreVersion}); it consumes Kestrel.Core internals that are not " +
                "compatible across product versions. Reference a DirectTls package that matches your Microsoft.AspNetCore.App version.";
        }
 
        return null;
    }
 
    // Parses the leading major integer from an informational/product version such as "11.0.0-dev" or
    // "11.0.0-preview.6.25361.1+sha". Everything from the first '.' on (including prerelease/metadata) is ignored.
    private static bool TryGetMajorProductVersion(string? informationalVersion, out int major)
    {
        major = 0;
        if (string.IsNullOrEmpty(informationalVersion))
        {
            return false;
        }
 
        int dot = informationalVersion.IndexOf('.');
        var head = dot < 0 ? informationalVersion : informationalVersion[..dot];
        return int.TryParse(head, out major);
    }
}