Skip to content

[Kestrel] HTTPS with ServerCertificateSelector, build full chain every connection, who to cache it? #46117

@WhitePhoera

Description

@WhitePhoera

Hello, issue happened with YARP, but code itself is Kestrel actually.
I had implemented dynamic certificate loading(by reload command),

            ServerCertificateSelector = (ctx, sni) =>
            {
                if (!string.IsNullOrWhiteSpace(sni) && KnownCertificates.GetCertificate(sni) is { } cert)
                {
                    return cert;
                }
                ctx.Abort();
                return KnownCertificates.DefaultCertificate;
            }
        });

which worked fine so far with Let's Encrypt certificates and others, where CA is known to system.
But we added other certificate which have it's own CA, which is not known to system.
Under load handshake time increases dramaticaly, by doing dotnet-stack report, i found that most thread where stuck with building full chain:

Thread (0x682D):
  [Native Frames]
  System.Private.CoreLib!System.Threading.ManualResetEventSlim.Wait(int32,value class System.Threading.CancellationToken)
  System.Private.CoreLib!System.Threading.Tasks.Task.SpinThenBlockingWait(int32,value class System.Threading.CancellationToken)
  System.Private.CoreLib!System.Threading.Tasks.Task.InternalWaitCore(int32,value class System.Threading.CancellationToken)
  System.Private.CoreLib!System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(class System.Threading.Tasks.Task)
  System.Net.Http!System.Threading.Tasks.TaskCompletionSourceWithCancellation`1[System.__Canon].WaitWithCancellation(value class System.Threading.CancellationToken)
  System.Net.Http!System.Net.Http.HttpConnectionPool+<GetHttp11ConnectionAsync>d__75.MoveNext()
  System.Private.CoreLib!System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&)
  System.Net.Http!System.Net.Http.HttpConnectionPool.GetHttp11ConnectionAsync(class System.Net.Http.HttpRequestMessage,bool,value class System.Threading.CancellationToken)
  System.Net.Http!System.Net.Http.HttpConnectionPool+<SendWithVersionDetectionAndRetryAsync>d__83.MoveNext()
  System.Private.CoreLib!System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&)
  System.Net.Http!System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(class System.Net.Http.HttpRequestMessage,bool,bool,value class System.Threading.CancellationToken)
  System.Net.Http!System.Net.Http.HttpConnectionPoolManager.SendAsyncCore(class System.Net.Http.HttpRequestMessage,class System.Uri,bool,bool,bool,value class System.Threading.CancellationToken)
  System.Net.Http!System.Net.Http.HttpConnectionPoolManager.SendAsync(class System.Net.Http.HttpRequestMessage,bool,bool,value class System.Threading.CancellationToken)
  System.Net.Http!System.Net.Http.HttpConnectionHandler.SendAsync(class System.Net.Http.HttpRequestMessage,bool,value class System.Threading.CancellationToken)
  System.Net.Http!System.Net.Http.HttpMessageHandlerStage.Send(class System.Net.Http.HttpRequestMessage,value class System.Threading.CancellationToken)
  System.Net.Http!System.Net.Http.DiagnosticsHandler.SendAsync(class System.Net.Http.HttpRequestMessage,bool,value class System.Threading.CancellationToken)
  System.Net.Http!System.Net.Http.HttpMessageHandlerStage.Send(class System.Net.Http.HttpRequestMessage,value class System.Threading.CancellationToken)
  System.Net.Http!System.Net.Http.SocketsHttpHandler.Send(class System.Net.Http.HttpRequestMessage,value class System.Threading.CancellationToken)
  System.Net.Http!System.Net.Http.HttpMessageInvoker.Send(class System.Net.Http.HttpRequestMessage,value class System.Threading.CancellationToken)
  System.Net.Http!System.Net.Http.HttpClient.Send(class System.Net.Http.HttpRequestMessage,value class System.Net.Http.HttpCompletionOption,value class System.Threading.CancellationToken)
  System.Net.Http!System.Net.Http.HttpClient.Send(class System.Net.Http.HttpRequestMessage,value class System.Threading.CancellationToken)
  ?!?
  System.Private.CoreLib!System.Reflection.RuntimeMethodInfo.Invoke(class System.Object,value class System.Reflection.BindingFlags,class System.Reflection.Binder,class System.Object[],class System.Globalization.CultureInfo)
  System.Security.Cryptography.X509Certificates!Internal.Cryptography.Pal.CertificateAssetDownloader+<>c__DisplayClass5_0.<CreateDownloadBytesFunc>b__0(class System.String,value class System.Threading.CancellationToken)
  System.Security.Cryptography.X509Certificates!Internal.Cryptography.Pal.CertificateAssetDownloader.DownloadAsset(class System.String,value class System.TimeSpan)
  System.Security.Cryptography.X509Certificates!Internal.Cryptography.Pal.CertificateAssetDownloader.DownloadCertificate(class System.String,value class System.TimeSpan)
  System.Security.Cryptography.X509Certificates!Internal.Cryptography.Pal.OpenSslX509ChainProcessor.FindChainViaAia(class System.Collections.Generic.List`1<class System.Security.Cryptography.X509Certificates.X509Certificate2>&)
  System.Security.Cryptography.X509Certificates!Internal.Cryptography.Pal.ChainPal.BuildChain(bool,class Internal.Cryptography.ICertificatePal,class System.Security.Cryptography.X509Certificates.X509Certificate2Collection,class System.Security.Cryptography.OidCollection,class System.Security.Cryptography.OidCollection,value class System.Security.Cryptography.X509Certificates.X509RevocationMode,value class System.Security.Cryptography.X509Certificates.X509RevocationFlag,class System.Security.Cryptography.X509Certificates.X509Certificate2Collection,value class System.Security.Cryptography.X509Certificates.X509ChainTrustMode,value class System.DateTime,value class System.TimeSpan,bool)
  System.Security.Cryptography.X509Certificates!System.Security.Cryptography.X509Certificates.X509Chain.Build(class System.Security.Cryptography.X509Certificates.X509Certificate2,bool)
  System.Net.Security!System.Net.Security.SslStreamCertificateContext.Create(class System.Security.Cryptography.X509Certificates.X509Certificate2,class System.Security.Cryptography.X509Certificates.X509Certificate2Collection,bool,class System.Net.Security.SslCertificateTrust)
  System.Net.Security!System.Net.Security.SecureChannel.AcquireServerCredentials(unsigned int8[]&)
  System.Net.Security!System.Net.Security.SecureChannel.GenerateToken(value class System.ReadOnlySpan`1<unsigned int8>,unsigned int8[]&)
  System.Net.Security!System.Net.Security.SecureChannel.NextMessage(value class System.ReadOnlySpan`1<unsigned int8>)
  System.Net.Security!System.Net.Security.SslStream.ProcessBlob(int32)
  System.Net.Security!System.Net.Security.SslStream+<ReceiveBlobAsync>d__176`1[System.Net.Security.AsyncReadWriteAdapter].MoveNext()
  System.Private.CoreLib!System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&)
  System.Net.Security!System.Net.Security.SslStream.ReceiveBlobAsync(!!0)
  System.Net.Security!System.Net.Security.SslStream+<ForceAuthenticationAsync>d__175`1[System.Net.Security.AsyncReadWriteAdapter].MoveNext()
  System.Private.CoreLib!System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&)
  System.Net.Security!System.Net.Security.SslStream.ProcessAuthenticationAsync(bool,bool,value class System.Threading.CancellationToken)
  System.Net.Security!System.Net.Security.SslStream.AuthenticateAsServerAsync(class System.Net.Security.SslServerAuthenticationOptions,value class System.Threading.CancellationToken)
  Microsoft.AspNetCore.Server.Kestrel.Core!Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.DoOptionsBasedHandshakeAsync(class Microsoft.AspNetCore.Connections.ConnectionContext,class System.Net.Security.SslStream,class Microsoft.AspNetCore.Server.Kestrel.Core.Internal.TlsConnectionFeature,value class System.Threading.CancellationToken)
  Microsoft.AspNetCore.Server.Kestrel.Core!Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware+<OnConnectionAsync>d__17.MoveNext()
  System.Private.CoreLib!System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&)
  Microsoft.AspNetCore.Server.Kestrel.Core!Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(class Microsoft.AspNetCore.Connections.ConnectionContext)
  Eternalhost.L7Proxy!Program+<>c__DisplayClass0_2+<<<Main>$>b__21>d.MoveNext()
  System.Private.CoreLib!System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&)
  Eternalhost.L7Proxy!Program+<>c__DisplayClass0_2.<<Main>$>b__21(class Microsoft.AspNetCore.Connections.ConnectionContext)
  Microsoft.AspNetCore.Server.Kestrel.Core!Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1+<ExecuteAsync>d__6[System.__Canon].MoveNext()
  System.Private.CoreLib!System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&)
  Microsoft.AspNetCore.Server.Kestrel.Core!Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1[System.__Canon].ExecuteAsync()
  Microsoft.AspNetCore.Server.Kestrel.Core!Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection`1[System.__Canon].System.Threading.IThreadPoolWorkItem.Execute()
  System.Private.CoreLib!System.Threading.ThreadPoolWorkQueue.Dispatch()
  System.Private.CoreLib!System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart()

since simple load is handled nicely, but still connection time was bigger in comparison with other domains, i assume that any https-connection is doing that code.
After i added that CA into system, handshake does not hangs any more.

But looks like every connection does building full chain.
Is there is the way to precache it? so i can do that once when i load certificate into memory?

I took stacktrace from .net 7, but .net 6 had same bottleneck.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ✔️ Resolution: AnsweredResolved because the question asked by the original author has been answered.Status: Resolvedarea-networkingIncludes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions