Skip to content

Commit 6376e7e

Browse files
authored
HTTP/3: Complete support for UseHttps (#42774)
1 parent 5c79e41 commit 6376e7e

28 files changed

+1145
-103
lines changed

src/Servers/Connections.Abstractions/src/Microsoft.AspNetCore.Connections.Abstractions.csproj

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<Description>Core components of ASP.NET Core networking protocol stack.</Description>
@@ -19,6 +19,11 @@
1919
<Compile Include="$(SharedSourceRoot)CodeAnalysis\*.cs" />
2020
</ItemGroup>
2121

22+
<ItemGroup>
23+
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Shipped.txt" />
24+
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Unshipped.txt" />
25+
</ItemGroup>
26+
2227
<!-- Special case building from source because Microsoft.Bcl.AsyncInterfaces isn't available for source builds. -->
2328
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' OR
2429
'$(TargetFramework)' == '$(DefaultNetFxTargetFramework)' OR

src/Servers/Connections.Abstractions/src/PublicAPI/net7.0/PublicAPI.Shipped.txt

Lines changed: 182 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#nullable enable
2+
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature
3+
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature.OnClosed(System.Action<object?>! callback, object? state) -> void
4+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext
5+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.ClientHelloInfo.get -> System.Net.Security.SslClientHelloInfo
6+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.ClientHelloInfo.set -> void
7+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.Connection.get -> Microsoft.AspNetCore.Connections.BaseConnectionContext!
8+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.Connection.set -> void
9+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.State.get -> object?
10+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.State.set -> void
11+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext.TlsConnectionCallbackContext() -> void
12+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackOptions
13+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackOptions.ApplicationProtocols.get -> System.Collections.Generic.List<System.Net.Security.SslApplicationProtocol>!
14+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackOptions.ApplicationProtocols.set -> void
15+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackOptions.OnConnection.get -> System.Func<Microsoft.AspNetCore.Connections.TlsConnectionCallbackContext!, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<System.Net.Security.SslServerAuthenticationOptions!>>!
16+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackOptions.OnConnection.set -> void
17+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackOptions.OnConnectionState.get -> object?
18+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackOptions.OnConnectionState.set -> void
19+
Microsoft.AspNetCore.Connections.TlsConnectionCallbackOptions.TlsConnectionCallbackOptions() -> void

src/Servers/Connections.Abstractions/src/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt

Lines changed: 182 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#nullable enable
2+
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature
3+
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature.OnClosed(System.Action<object?>! callback, object? state) -> void

src/Servers/Connections.Abstractions/src/PublicAPI/netstandard2.1/PublicAPI.Shipped.txt

Lines changed: 182 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#nullable enable
2+
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature
3+
Microsoft.AspNetCore.Connections.Features.IStreamClosedFeature.OnClosed(System.Action<object?>! callback, object? state) -> void
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#if NET7_0_OR_GREATER
5+
using System.Net.Security;
6+
using System.Threading;
7+
using Microsoft.AspNetCore.Connections;
8+
using Microsoft.AspNetCore.Http.Features;
9+
10+
namespace Microsoft.AspNetCore.Connections;
11+
12+
/// <summary>
13+
/// Per connection state used to determine the TLS options.
14+
/// </summary>
15+
public class TlsConnectionCallbackContext
16+
{
17+
/// <summary>
18+
/// Information from the Client Hello message.
19+
/// </summary>
20+
public SslClientHelloInfo ClientHelloInfo { get; set; }
21+
22+
/// <summary>
23+
/// The information that was passed when registering the callback.
24+
/// </summary>
25+
public object? State { get; set; }
26+
27+
/// <summary>
28+
/// Information about an individual connection.
29+
/// </summary>
30+
public BaseConnectionContext Connection { get; set; } = default!;
31+
}
32+
#endif
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#if NET7_0_OR_GREATER
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Net.Security;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
11+
namespace Microsoft.AspNetCore.Connections;
12+
13+
/// <summary>
14+
/// Options used to configure a per connection callback for TLS configuration.
15+
/// </summary>
16+
public class TlsConnectionCallbackOptions
17+
{
18+
/// <summary>
19+
/// The callback to invoke per connection. This property is required.
20+
/// </summary>
21+
public Func<TlsConnectionCallbackContext, CancellationToken, ValueTask<SslServerAuthenticationOptions>> OnConnection { get; set; } = default!;
22+
23+
/// <summary>
24+
/// Optional application state to flow to the <see cref="OnConnection"/> callback.
25+
/// </summary>
26+
public object? OnConnectionState { get; set; }
27+
28+
/// <summary>
29+
/// Gets or sets a list of ALPN protocols.
30+
/// </summary>
31+
public List<SslApplicationProtocol> ApplicationProtocols { get; set; } = default!;
32+
}
33+
#endif

src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportManager.cs

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33

44
#nullable enable
55

6+
using System.IO.Pipelines;
67
using System.Linq;
78
using System.Net;
9+
using System.Net.Security;
810
using Microsoft.AspNetCore.Connections;
911
using Microsoft.AspNetCore.Http.Features;
12+
using Microsoft.AspNetCore.Server.Kestrel.Https;
1013
using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
1114

1215
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@@ -53,18 +56,85 @@ public async Task<EndPoint> BindAsync(EndPoint endPoint, MultiplexedConnectionDe
5356

5457
var features = new FeatureCollection();
5558

56-
// This should always be set in production, but it's not set for InMemory tests.
57-
// The transport will check if the feature is missing.
59+
// HttpsOptions or HttpsCallbackOptions should always be set in production, but it's not set for InMemory tests.
60+
// The QUIC transport will check if TlsConnectionCallbackOptions is missing.
5861
if (listenOptions.HttpsOptions != null)
5962
{
60-
features.Set(HttpsConnectionMiddleware.CreateHttp3Options(listenOptions.HttpsOptions));
63+
var sslServerAuthenticationOptions = HttpsConnectionMiddleware.CreateHttp3Options(listenOptions.HttpsOptions);
64+
features.Set(new TlsConnectionCallbackOptions
65+
{
66+
ApplicationProtocols = sslServerAuthenticationOptions.ApplicationProtocols ?? new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 },
67+
OnConnection = (context, cancellationToken) => ValueTask.FromResult(sslServerAuthenticationOptions),
68+
OnConnectionState = null,
69+
});
70+
}
71+
else if (listenOptions.HttpsCallbackOptions != null)
72+
{
73+
features.Set(new TlsConnectionCallbackOptions
74+
{
75+
ApplicationProtocols = new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 },
76+
OnConnection = (context, cancellationToken) =>
77+
{
78+
return listenOptions.HttpsCallbackOptions.OnConnection(new TlsHandshakeCallbackContext
79+
{
80+
ClientHelloInfo = context.ClientHelloInfo,
81+
CancellationToken = cancellationToken,
82+
State = context.State,
83+
Connection = new ConnectionContextAdapter(context.Connection),
84+
});
85+
},
86+
OnConnectionState = listenOptions.HttpsCallbackOptions.OnConnectionState,
87+
});
6188
}
6289

6390
var transport = await _multiplexedTransportFactory.BindAsync(endPoint, features, cancellationToken).ConfigureAwait(false);
6491
StartAcceptLoop(new GenericMultiplexedConnectionListener(transport), c => multiplexedConnectionDelegate(c), listenOptions.EndpointConfig);
6592
return transport.EndPoint;
6693
}
6794

95+
/// <summary>
96+
/// TlsHandshakeCallbackContext.Connection is ConnectionContext but QUIC connection only implements BaseConnectionContext.
97+
/// </summary>
98+
private sealed class ConnectionContextAdapter : ConnectionContext
99+
{
100+
private readonly BaseConnectionContext _inner;
101+
102+
public ConnectionContextAdapter(BaseConnectionContext inner) => _inner = inner;
103+
104+
public override IDuplexPipe Transport
105+
{
106+
get => throw new NotSupportedException("Not supported by HTTP/3 connections.");
107+
set => throw new NotSupportedException("Not supported by HTTP/3 connections.");
108+
}
109+
public override string ConnectionId
110+
{
111+
get => _inner.ConnectionId;
112+
set => _inner.ConnectionId = value;
113+
}
114+
public override IFeatureCollection Features => _inner.Features;
115+
public override IDictionary<object, object?> Items
116+
{
117+
get => _inner.Items;
118+
set => _inner.Items = value;
119+
}
120+
public override EndPoint? LocalEndPoint
121+
{
122+
get => _inner.LocalEndPoint;
123+
set => _inner.LocalEndPoint = value;
124+
}
125+
public override EndPoint? RemoteEndPoint
126+
{
127+
get => _inner.RemoteEndPoint;
128+
set => _inner.RemoteEndPoint = value;
129+
}
130+
public override CancellationToken ConnectionClosed
131+
{
132+
get => _inner.ConnectionClosed;
133+
set => _inner.ConnectionClosed = value;
134+
}
135+
public override ValueTask DisposeAsync() => _inner.DisposeAsync();
136+
}
137+
68138
private void StartAcceptLoop<T>(IConnectionListener<T> connectionListener, Func<T, Task> connectionDelegate, EndpointConfig? endpointConfig) where T : BaseConnectionContext
69139
{
70140
var transportConnectionManager = new TransportConnectionManager(_serviceContext.ConnectionManager);

src/Servers/Kestrel/Core/src/ListenOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ internal string Scheme
109109

110110
internal bool IsTls { get; set; }
111111
internal HttpsConnectionAdapterOptions? HttpsOptions { get; set; }
112+
internal TlsHandshakeCallbackOptions? HttpsCallbackOptions { get; set; }
112113

113114
/// <summary>
114115
/// Gets the name of this endpoint to display on command-line when the web server starts.

src/Servers/Kestrel/Core/src/ListenOptionsHttpsExtensions.cs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -254,10 +254,6 @@ public static ListenOptions UseHttps(this ListenOptions listenOptions, ServerOpt
254254
/// <returns>The <see cref="ListenOptions"/>.</returns>
255255
public static ListenOptions UseHttps(this ListenOptions listenOptions, ServerOptionsSelectionCallback serverOptionsSelectionCallback, object state, TimeSpan handshakeTimeout)
256256
{
257-
if (listenOptions.Protocols.HasFlag(HttpProtocols.Http3))
258-
{
259-
throw new NotSupportedException($"{nameof(UseHttps)} with {nameof(ServerOptionsSelectionCallback)} is not supported with HTTP/3.");
260-
}
261257
return listenOptions.UseHttps(new TlsHandshakeCallbackOptions()
262258
{
263259
OnConnection = context => serverOptionsSelectionCallback(context.SslStream, context.ClientHelloInfo, context.State, context.CancellationToken),
@@ -285,18 +281,17 @@ public static ListenOptions UseHttps(this ListenOptions listenOptions, TlsHandsh
285281
throw new ArgumentException($"{nameof(TlsHandshakeCallbackOptions.OnConnection)} must not be null.");
286282
}
287283

288-
if (listenOptions.Protocols.HasFlag(HttpProtocols.Http3))
289-
{
290-
throw new NotSupportedException($"{nameof(UseHttps)} with {nameof(TlsHandshakeCallbackOptions)} is not supported with HTTP/3.");
291-
}
292-
293284
var loggerFactory = listenOptions.KestrelServerOptions?.ApplicationServices.GetRequiredService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
294285

295286
listenOptions.IsTls = true;
287+
listenOptions.HttpsCallbackOptions = callbackOptions;
288+
296289
listenOptions.Use(next =>
297290
{
298-
// Set the list of protocols from listen options
291+
// Set the list of protocols from listen options.
292+
// Set it inside Use delegate so Protocols and UseHttps can be called out of order.
299293
callbackOptions.HttpProtocols = listenOptions.Protocols;
294+
300295
var middleware = new HttpsConnectionMiddleware(next, callbackOptions, loggerFactory);
301296
return middleware.OnConnectionAsync;
302297
});

src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ internal ListenOptions Clone(IPAddress address)
7272
DisableAltSvcHeader = DisableAltSvcHeader,
7373
IsTls = IsTls,
7474
HttpsOptions = HttpsOptions,
75+
HttpsCallbackOptions = HttpsCallbackOptions,
7576
EndpointConfig = EndpointConfig
7677
};
7778

src/Servers/Kestrel/Kestrel.slnf

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22
"solution": {
33
"path": "..\\..\\..\\AspNetCore.sln",
44
"projects": [
5+
"src\\DataProtection\\Abstractions\\src\\Microsoft.AspNetCore.DataProtection.Abstractions.csproj",
6+
"src\\DataProtection\\Cryptography.Internal\\src\\Microsoft.AspNetCore.Cryptography.Internal.csproj",
7+
"src\\DataProtection\\DataProtection\\src\\Microsoft.AspNetCore.DataProtection.csproj",
8+
"src\\DefaultBuilder\\src\\Microsoft.AspNetCore.csproj",
59
"src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj",
610
"src\\Extensions\\Features\\test\\Microsoft.Extensions.Features.Tests.csproj",
711
"src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj",
812
"src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj",
913
"src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj",
14+
"src\\Http\\Authentication.Abstractions\\src\\Microsoft.AspNetCore.Authentication.Abstractions.csproj",
15+
"src\\Http\\Authentication.Core\\src\\Microsoft.AspNetCore.Authentication.Core.csproj",
1016
"src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj",
1117
"src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj",
1218
"src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj",
@@ -21,8 +27,12 @@
2127
"src\\Middleware\\HostFiltering\\src\\Microsoft.AspNetCore.HostFiltering.csproj",
2228
"src\\Middleware\\HttpOverrides\\src\\Microsoft.AspNetCore.HttpOverrides.csproj",
2329
"src\\ObjectPool\\src\\Microsoft.Extensions.ObjectPool.csproj",
30+
"src\\Security\\Authentication\\Core\\src\\Microsoft.AspNetCore.Authentication.csproj",
2431
"src\\Security\\Authorization\\Core\\src\\Microsoft.AspNetCore.Authorization.csproj",
32+
"src\\Security\\Authorization\\Policy\\src\\Microsoft.AspNetCore.Authorization.Policy.csproj",
2533
"src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj",
34+
"src\\Servers\\IIS\\IISIntegration\\src\\Microsoft.AspNetCore.Server.IISIntegration.csproj",
35+
"src\\Servers\\IIS\\IIS\\src\\Microsoft.AspNetCore.Server.IIS.csproj",
2636
"src\\Servers\\Kestrel\\Core\\src\\Microsoft.AspNetCore.Server.Kestrel.Core.csproj",
2737
"src\\Servers\\Kestrel\\Core\\test\\Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj",
2838
"src\\Servers\\Kestrel\\Kestrel\\src\\Microsoft.AspNetCore.Server.Kestrel.csproj",
@@ -47,7 +57,8 @@
4757
"src\\Servers\\Kestrel\\test\\Sockets.BindTests\\Sockets.BindTests.csproj",
4858
"src\\Servers\\Kestrel\\test\\Sockets.FunctionalTests\\Sockets.FunctionalTests.csproj",
4959
"src\\Servers\\Kestrel\\tools\\CodeGenerator\\CodeGenerator.csproj",
50-
"src\\Testing\\src\\Microsoft.AspNetCore.Testing.csproj"
60+
"src\\Testing\\src\\Microsoft.AspNetCore.Testing.csproj",
61+
"src\\WebEncoders\\src\\Microsoft.Extensions.WebEncoders.csproj"
5162
]
5263
}
5364
}

src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,11 @@ internal bool TryReturnStream(QuicStreamContext stream)
258258
return false;
259259
}
260260

261+
internal QuicConnection GetInnerConnection()
262+
{
263+
return _connection;
264+
}
265+
261266
private void RemoveExpiredStreams()
262267
{
263268
lock (_poolLock)

0 commit comments

Comments
 (0)