diff --git a/AspNetCore.sln b/AspNetCore.sln index 197f5c7b7e02..95547f7df0a3 100644 --- a/AspNetCore.sln +++ b/AspNetCore.sln @@ -1636,6 +1636,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Logging.W3C.Sample", "src\M EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Razor.Internal.SourceGenerator.Transport", "src\Razor\Microsoft.AspNetCore.Razor.Internal.SourceGenerator.Transport\Microsoft.AspNetCore.Razor.Internal.SourceGenerator.Transport.csproj", "{247E7B6F-FBA2-41A9-BA03-C7C4DF28091C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpClientApp", "src\Servers\Kestrel\samples\HttpClientApp\HttpClientApp.csproj", "{514726D2-3D2E-44C1-B056-163E37DE3E8B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -7803,6 +7805,18 @@ Global {247E7B6F-FBA2-41A9-BA03-C7C4DF28091C}.Release|x64.Build.0 = Release|Any CPU {247E7B6F-FBA2-41A9-BA03-C7C4DF28091C}.Release|x86.ActiveCfg = Release|Any CPU {247E7B6F-FBA2-41A9-BA03-C7C4DF28091C}.Release|x86.Build.0 = Release|Any CPU + {514726D2-3D2E-44C1-B056-163E37DE3E8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {514726D2-3D2E-44C1-B056-163E37DE3E8B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {514726D2-3D2E-44C1-B056-163E37DE3E8B}.Debug|x64.ActiveCfg = Debug|Any CPU + {514726D2-3D2E-44C1-B056-163E37DE3E8B}.Debug|x64.Build.0 = Debug|Any CPU + {514726D2-3D2E-44C1-B056-163E37DE3E8B}.Debug|x86.ActiveCfg = Debug|Any CPU + {514726D2-3D2E-44C1-B056-163E37DE3E8B}.Debug|x86.Build.0 = Debug|Any CPU + {514726D2-3D2E-44C1-B056-163E37DE3E8B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {514726D2-3D2E-44C1-B056-163E37DE3E8B}.Release|Any CPU.Build.0 = Release|Any CPU + {514726D2-3D2E-44C1-B056-163E37DE3E8B}.Release|x64.ActiveCfg = Release|Any CPU + {514726D2-3D2E-44C1-B056-163E37DE3E8B}.Release|x64.Build.0 = Release|Any CPU + {514726D2-3D2E-44C1-B056-163E37DE3E8B}.Release|x86.ActiveCfg = Release|Any CPU + {514726D2-3D2E-44C1-B056-163E37DE3E8B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -8613,6 +8627,7 @@ Global {F599EAA6-399F-4A91-9B1F-D311305B43D9} = {323C3EB6-1D15-4B3D-918D-699D7F64DED9} {17459B97-1AA3-4154-83D3-C6BDC9FA3F85} = {022B4B80-E813-4256-8034-11A68146F4EF} {247E7B6F-FBA2-41A9-BA03-C7C4DF28091C} = {B27FBAC2-ADA3-4A05-B232-64011B6B2DA3} + {514726D2-3D2E-44C1-B056-163E37DE3E8B} = {7B976D8F-EA31-4C0B-97BD-DFD9B3CC86FB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F} diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportManager.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportManager.cs index 9b136e35778a..d271b777ff6b 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportManager.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportManager.cs @@ -62,7 +62,8 @@ public async Task BindAsync(EndPoint endPoint, MultiplexedConnectionDe // TODO Set other relevant values on options var sslServerAuthenticationOptions = new SslServerAuthenticationOptions { - ServerCertificate = listenOptions.HttpsOptions.ServerCertificate + ServerCertificate = listenOptions.HttpsOptions.ServerCertificate, + ApplicationProtocols = new List() { new SslApplicationProtocol("h3") } }; features.Set(sslServerAuthenticationOptions); diff --git a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs index 704f5ca8e4ca..de1eab76de9e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs @@ -160,9 +160,36 @@ public async Task StartAsync(IHttpApplication application, C async Task OnBind(ListenOptions options, CancellationToken onBindCancellationToken) { + var hasHttp1 = options.Protocols.HasFlag(HttpProtocols.Http1); + var hasHttp2 = options.Protocols.HasFlag(HttpProtocols.Http2); + var hasHttp3 = options.Protocols.HasFlag(HttpProtocols.Http3); + var hasTls = options.IsTls; + + // Filter out invalid combinations. + + if (!hasTls) + { + // Http/1 without TLS, no-op HTTP/2 and 3. + if (hasHttp1) + { + hasHttp2 = false; + hasHttp3 = false; + } + // Http/3 requires TLS. Note we only let it fall back to HTTP/1, not HTTP/2 + else if (hasHttp3) + { + throw new InvalidOperationException("HTTP/3 requires https."); + } + } + + // Quic isn't registered if it's not supported, throw if we can't fall back to 1 or 2 + if (hasHttp3 && _multiplexedTransportFactory is null && !(hasHttp1 || hasHttp2)) + { + throw new InvalidOperationException("This platform doesn't support QUIC or HTTP/3."); + } + // Add the HTTP middleware as the terminal connection middleware - if ((options.Protocols & HttpProtocols.Http1) == HttpProtocols.Http1 - || (options.Protocols & HttpProtocols.Http2) == HttpProtocols.Http2 + if (hasHttp1 || hasHttp2 || options.Protocols == HttpProtocols.None) // TODO a test fails because it doesn't throw an exception in the right place // when there is no HttpProtocols in KestrelServer, can we remove/change the test? { @@ -180,13 +207,8 @@ async Task OnBind(ListenOptions options, CancellationToken onBindCancellationTok options.EndPoint = await _transportManager.BindAsync(options.EndPoint, connectionDelegate, options.EndpointConfig, onBindCancellationToken).ConfigureAwait(false); } - if ((options.Protocols & HttpProtocols.Http3) == HttpProtocols.Http3) + if (hasHttp3 && _multiplexedTransportFactory is not null) { - if (_multiplexedTransportFactory is null) - { - throw new InvalidOperationException($"Cannot start HTTP/3 server if no {nameof(IMultiplexedConnectionListenerFactory)} is registered."); - } - options.UseHttp3Server(ServiceContext, application, options.Protocols, !options.DisableAltSvcHeader); var multiplexedConnectionDelegate = ((IMultiplexedConnectionBuilder)options).Build(); diff --git a/src/Servers/Kestrel/Kestrel.slnf b/src/Servers/Kestrel/Kestrel.slnf index d67c7fa2697a..f5809a62e74b 100644 --- a/src/Servers/Kestrel/Kestrel.slnf +++ b/src/Servers/Kestrel/Kestrel.slnf @@ -2,11 +2,11 @@ "solution": { "path": "..\\..\\..\\AspNetCore.sln", "projects": [ + "src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj", + "src\\Extensions\\Features\\test\\Microsoft.Extensions.Features.Tests.csproj", "src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj", "src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj", "src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj", - "src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj", - "src\\Extensions\\Features\\test\\Microsoft.Extensions.Features.Tests.csproj", "src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj", "src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj", "src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj", @@ -27,6 +27,7 @@ "src\\Servers\\Kestrel\\perf\\Microbenchmarks\\Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj", "src\\Servers\\Kestrel\\samples\\Http2SampleApp\\Http2SampleApp.csproj", "src\\Servers\\Kestrel\\samples\\Http3SampleApp\\Http3SampleApp.csproj", + "src\\Servers\\Kestrel\\samples\\HttpClientApp\\HttpClientApp.csproj", "src\\Servers\\Kestrel\\samples\\LargeResponseApp\\LargeResponseApp.csproj", "src\\Servers\\Kestrel\\samples\\PlaintextApp\\PlaintextApp.csproj", "src\\Servers\\Kestrel\\samples\\SampleApp\\Kestrel.SampleApp.csproj", diff --git a/src/Servers/Kestrel/Kestrel/src/Microsoft.AspNetCore.Server.Kestrel.csproj b/src/Servers/Kestrel/Kestrel/src/Microsoft.AspNetCore.Server.Kestrel.csproj index a2b65d0bfe2f..106684e5cdf6 100644 --- a/src/Servers/Kestrel/Kestrel/src/Microsoft.AspNetCore.Server.Kestrel.csproj +++ b/src/Servers/Kestrel/Kestrel/src/Microsoft.AspNetCore.Server.Kestrel.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs b/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs index 0ce7802b6cae..0a008b0fd817 100644 --- a/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs +++ b/src/Servers/Kestrel/Kestrel/src/WebHostBuilderKestrelExtensions.cs @@ -29,6 +29,7 @@ public static class WebHostBuilderKestrelExtensions /// public static IWebHostBuilder UseKestrel(this IWebHostBuilder hostBuilder) { + hostBuilder.UseQuic(); return hostBuilder.ConfigureServices(services => { // Don't override an already-configured transport diff --git a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs index 76239f9067ba..83f5340dd3a6 100644 --- a/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs +++ b/src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs @@ -553,6 +553,7 @@ public void DefaultConfigSectionCanSetProtocols_MacAndWin7(string input, HttpPro [InlineData("http1", HttpProtocols.Http1)] [InlineData("http2", HttpProtocols.Http2)] [InlineData("http1AndHttp2", HttpProtocols.Http1AndHttp2)] + [InlineData("http1AndHttp2andHttp3", HttpProtocols.Http1AndHttp2AndHttp3)] [OSSkipCondition(OperatingSystems.MacOSX)] [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81)] public void DefaultConfigSectionCanSetProtocols_NonMacAndWin7(string input, HttpProtocols expected) diff --git a/src/Servers/Kestrel/Transport.Quic/src/AssemblyInfo.cs b/src/Servers/Kestrel/Transport.Quic/src/AssemblyInfo.cs index 05d0d764bb1c..5b8e3c25a766 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/AssemblyInfo.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/AssemblyInfo.cs @@ -6,6 +6,4 @@ [assembly: InternalsVisibleTo("Quic.FunctionalTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] -[assembly: SupportedOSPlatform("windows")] -[assembly: SupportedOSPlatform("macos")] -[assembly: SupportedOSPlatform("linux")] + diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs index 0422dff759a1..975aa8bf3946 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs @@ -31,18 +31,10 @@ public QuicConnectionListener(QuicTransportOptions options, IQuicTrace log, EndP throw new NotSupportedException("QUIC is not supported or enabled on this platform. See https://aka.ms/aspnet/kestrel/http3reqs for details."); } - if (options.Alpn == null) - { - throw new InvalidOperationException("QuicTransportOptions.Alpn must be configured with a value."); - } - _log = log; _context = new QuicTransportContext(_log, options); var quicListenerOptions = new QuicListenerOptions(); - // TODO Should HTTP/3 specific ALPN still be global? Revisit whether it can be statically set once HTTP/3 is finalized. - sslServerAuthenticationOptions.ApplicationProtocols = new List() { new SslApplicationProtocol(options.Alpn) }; - quicListenerOptions.ServerAuthenticationOptions = sslServerAuthenticationOptions; quicListenerOptions.ListenEndPoint = endpoint as IPEndPoint; quicListenerOptions.IdleTimeout = options.IdleTimeout; diff --git a/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj b/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj index 8028e7abb55f..f9a3b6d57c35 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj +++ b/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj @@ -7,7 +7,7 @@ true aspnetcore;kestrel true - CS1591;CS0436;$(NoWarn) + CA1416;CS1591;CS0436;$(NoWarn) false enable diff --git a/src/Servers/Kestrel/Transport.Quic/src/PublicAPI.Unshipped.txt b/src/Servers/Kestrel/Transport.Quic/src/PublicAPI.Unshipped.txt index 84251ae229c3..4ab5cd10661e 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/PublicAPI.Unshipped.txt +++ b/src/Servers/Kestrel/Transport.Quic/src/PublicAPI.Unshipped.txt @@ -26,8 +26,6 @@ *REMOVED*~static Microsoft.AspNetCore.Hosting.WebHostBuilderMsQuicExtensions.UseQuic(this Microsoft.AspNetCore.Hosting.IWebHostBuilder hostBuilder) -> Microsoft.AspNetCore.Hosting.IWebHostBuilder Microsoft.AspNetCore.Hosting.WebHostBuilderQuicExtensions Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions -Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions.Alpn.get -> string? -Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions.Alpn.set -> void Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions.IdleTimeout.get -> System.TimeSpan Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions.IdleTimeout.set -> void Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions.MaxBidirectionalStreamCount.get -> ushort diff --git a/src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs b/src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs index 4e3b0741c7c6..638c49eac73f 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs @@ -40,13 +40,8 @@ public async ValueTask ConnectAsync(EndPoint endPo { throw new NotSupportedException($"{endPoint} is not supported"); } - if (_transportContext.Options.Alpn == null) - { - throw new InvalidOperationException("QuicTransportOptions.Alpn must be configured with a value."); - } - var sslOptions = new SslClientAuthenticationOptions(); - sslOptions.ApplicationProtocols = new List() { new SslApplicationProtocol(_transportContext.Options.Alpn) }; + var sslOptions = features?.Get(); var connection = new QuicConnection(QuicImplementationProviders.MsQuic, (IPEndPoint)endPoint, sslOptions); await connection.ConnectAsync(cancellationToken); diff --git a/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs index 789d3c806fdb..8283fed106a2 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs @@ -23,15 +23,10 @@ public class QuicTransportOptions /// public ushort MaxUnidirectionalStreamCount { get; set; } = 10; - /// - /// The Application Layer Protocol Negotiation string. - /// - public string? Alpn { get; set; } - /// /// Sets the idle timeout for connections and streams. /// - public TimeSpan IdleTimeout { get; set; } + public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromSeconds(130); // Matches KestrelServerLimits.KeepAliveTimeout. /// /// The maximum read size. diff --git a/src/Servers/Kestrel/Transport.Quic/src/WebHostBuilderQuicExtensions.cs b/src/Servers/Kestrel/Transport.Quic/src/WebHostBuilderQuicExtensions.cs index bd9657b955ef..1619e8b3956c 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/WebHostBuilderQuicExtensions.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/WebHostBuilderQuicExtensions.cs @@ -16,14 +16,15 @@ public static class WebHostBuilderQuicExtensions { public static IWebHostBuilder UseQuic(this IWebHostBuilder hostBuilder) { - if (!QuicImplementationProviders.Default.IsSupported) + if (QuicImplementationProviders.Default.IsSupported) { - throw new NotSupportedException("QUIC is not supported or enabled on this platform. See https://aka.ms/aspnet/kestrel/http3reqs for details."); + return hostBuilder.ConfigureServices(services => + { + services.AddSingleton(); + }); } - return hostBuilder.ConfigureServices(services => - { - services.AddSingleton(); - }); + + return hostBuilder; } public static IWebHostBuilder UseQuic(this IWebHostBuilder hostBuilder, Action configureOptions) diff --git a/src/Servers/Kestrel/Transport.Quic/test/QuicTestHelpers.cs b/src/Servers/Kestrel/Transport.Quic/test/QuicTestHelpers.cs index 816dfbfab852..a359d7c449c0 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/QuicTestHelpers.cs +++ b/src/Servers/Kestrel/Transport.Quic/test/QuicTestHelpers.cs @@ -31,7 +31,6 @@ internal static class QuicTestHelpers public static QuicTransportFactory CreateTransportFactory(ILoggerFactory loggerFactory = null, ISystemClock systemClock = null) { var quicTransportOptions = new QuicTransportOptions(); - quicTransportOptions.Alpn = Alpn; quicTransportOptions.IdleTimeout = TimeSpan.FromMinutes(1); quicTransportOptions.MaxBidirectionalStreamCount = 200; quicTransportOptions.MaxUnidirectionalStreamCount = 200; @@ -59,6 +58,7 @@ public static FeatureCollection CreateBindAsyncFeatures() var cert = TestResources.GetTestCertificate(); var sslServerAuthenticationOptions = new SslServerAuthenticationOptions(); + sslServerAuthenticationOptions.ApplicationProtocols = new List() { new SslApplicationProtocol(Alpn) }; sslServerAuthenticationOptions.ServerCertificate = cert; sslServerAuthenticationOptions.RemoteCertificateValidationCallback = RemoteCertificateValidationCallback; diff --git a/src/Servers/Kestrel/Transport.Quic/test/WebHostTests.cs b/src/Servers/Kestrel/Transport.Quic/test/WebHostTests.cs index 4e61e1d03aa4..0ebc5f725079 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/WebHostTests.cs +++ b/src/Servers/Kestrel/Transport.Quic/test/WebHostTests.cs @@ -1,25 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Buffers; using System.Net; using System.Net.Http; -using System.Net.Security; -using System.Security.Cryptography.X509Certificates; -using System.Threading; -using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.FunctionalTests; -using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Tests @@ -31,7 +21,7 @@ public class WebHostTests : LoggedTest public async Task UseUrls_HelloWorld_ClientSuccess() { // Arrange - var builder = GetHostBuilder() + var builder = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder @@ -82,7 +72,7 @@ public async Task UseUrls_HelloWorld_ClientSuccess() public async Task Listen_Http3AndSocketsCoexistOnDifferentEndpoints_ClientSuccess(int http3Port, int http1Port) { // Arrange - var builder = GetHostBuilder() + var builder = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder @@ -122,7 +112,7 @@ public async Task Listen_Http3AndSocketsCoexistOnDifferentEndpoints_ClientSucces public async Task Listen_Http3AndSocketsCoexistOnSameEndpoint_ClientSuccess() { // Arrange - var builder = GetHostBuilder() + var builder = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder @@ -157,7 +147,7 @@ public async Task Listen_Http3AndSocketsCoexistOnSameEndpoint_ClientSuccess() public async Task Listen_Http3AndSocketsCoexistOnSameEndpoint_AltSvcEnabled_Upgrade() { // Arrange - var builder = GetHostBuilder() + var builder = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder @@ -221,7 +211,7 @@ public async Task Listen_Http3AndSocketsCoexistOnSameEndpoint_AltSvcEnabled_Upgr public async Task Listen_Http3AndSocketsCoexistOnSameEndpoint_AltSvcDisabled_NoUpgrade() { // Arrange - var builder = GetHostBuilder() + var builder = new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder @@ -321,20 +311,5 @@ private static HttpClient CreateClient() return new HttpClient(httpHandler); } - - public static IHostBuilder GetHostBuilder(long? maxReadBufferSize = null) - { - return new HostBuilder() - .ConfigureWebHost(webHostBuilder => - { - webHostBuilder - .UseQuic(options => - { - options.MaxReadBufferSize = maxReadBufferSize; - options.Alpn = QuicTestHelpers.Alpn; - options.IdleTimeout = TimeSpan.FromSeconds(20); - }); - }); - } } } diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs index 5d2b57f6e0ee..f66f6a010331 100644 --- a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs @@ -1,15 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Net; +using System.Net.Security; using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Https; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; namespace Http3SampleApp { @@ -17,8 +13,6 @@ public class Program { public static void Main(string[] args) { - var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, false); - var hostBuilder = new HostBuilder() .ConfigureLogging((_, factory) => { @@ -28,23 +22,59 @@ public static void Main(string[] args) .ConfigureWebHost(webHost => { webHost.UseKestrel() - .UseQuic(options => - { - options.Alpn = "h3"; // Shouldn't need to populate this as well. - options.IdleTimeout = TimeSpan.FromHours(1); - }) .ConfigureKestrel((context, options) => { - var basePort = 5557; + var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, false); + options.ConfigureHttpsDefaults(httpsOptions => + { + httpsOptions.ServerCertificate = cert; + }); + + options.ListenAnyIP(5000, listenOptions => + { + listenOptions.UseConnectionLogging(); + listenOptions.Protocols = HttpProtocols.Http1AndHttp2; + }); + + options.ListenAnyIP(5001, listenOptions => + { + listenOptions.UseHttps(); + listenOptions.UseConnectionLogging(); + listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; + }); - options.Listen(IPAddress.Any, basePort, listenOptions => + options.ListenAnyIP(5002, listenOptions => + { + listenOptions.UseHttps(StoreName.My, "localhost"); + listenOptions.UseConnectionLogging(); + listenOptions.Protocols = HttpProtocols.Http3; + }); + + options.ListenAnyIP(5003, listenOptions => { listenOptions.UseHttps(httpsOptions => { - httpsOptions.ServerCertificate = cert; + httpsOptions.ServerCertificateSelector = (_, _) => cert; }); listenOptions.UseConnectionLogging(); - listenOptions.Protocols = HttpProtocols.Http3; + listenOptions.Protocols = HttpProtocols.Http1AndHttp2; // TODO: http3 + }); + + options.ListenAnyIP(5004, listenOptions => + { + listenOptions.UseHttps(new TlsHandshakeCallbackOptions() + { + OnConnection = context => + { + var options = new SslServerAuthenticationOptions() + { + ServerCertificate = cert, + }; + return new ValueTask(options); + }, + }); + listenOptions.UseConnectionLogging(); + listenOptions.Protocols = HttpProtocols.Http1AndHttp2; // TODO: http3 }); }) .UseStartup(); diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/Properties/launchSettings.json b/src/Servers/Kestrel/samples/Http3SampleApp/Properties/launchSettings.json index c68927104793..792849fe5788 100644 --- a/src/Servers/Kestrel/samples/Http3SampleApp/Properties/launchSettings.json +++ b/src/Servers/Kestrel/samples/Http3SampleApp/Properties/launchSettings.json @@ -2,11 +2,10 @@ "profiles": { "Http3SampleApp": { "commandName": "Project", - "launchBrowser": true, + "launchBrowser": false, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" + } } } } diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/Startup.cs b/src/Servers/Kestrel/samples/Http3SampleApp/Startup.cs index 1213dc675226..367fb530dd4c 100644 --- a/src/Servers/Kestrel/samples/Http3SampleApp/Startup.cs +++ b/src/Servers/Kestrel/samples/Http3SampleApp/Startup.cs @@ -1,12 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; - namespace Http3SampleApp { public class Startup diff --git a/src/Servers/Kestrel/samples/HttpClientApp/HttpClientApp.csproj b/src/Servers/Kestrel/samples/HttpClientApp/HttpClientApp.csproj new file mode 100644 index 000000000000..69f7638279de --- /dev/null +++ b/src/Servers/Kestrel/samples/HttpClientApp/HttpClientApp.csproj @@ -0,0 +1,12 @@ + + + $(DefaultNetCoreTargetFramework) + exe + + + + + + + + diff --git a/src/Servers/Kestrel/samples/HttpClientApp/Program.cs b/src/Servers/Kestrel/samples/HttpClientApp/Program.cs new file mode 100644 index 000000000000..68b34c3b8529 --- /dev/null +++ b/src/Servers/Kestrel/samples/HttpClientApp/Program.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net; +using System.Net.Http; + +var handler = new SocketsHttpHandler(); +handler.SslOptions.RemoteCertificateValidationCallback = (_, _, _, _) => true; + +using var client = new HttpClient(handler); +client.DefaultRequestVersion = HttpVersion.Version20; +client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher; + +var response = await client.GetAsync("https://localhost:5001"); +Console.WriteLine(response); + +// Alt-svc enables an upgrade after the first request. +response = await client.GetAsync("https://localhost:5001"); +Console.WriteLine(response); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs index 329ba6d19f20..9f782cce377e 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/HttpsTests.cs @@ -445,10 +445,9 @@ public async Task Http3_UseHttpsNoArgsWithDefaultCertificate_UseDefaultCertifica } [Fact] - public async Task Http3_NoUseHttp3_NoSslServerOptions() + public async Task Http3_ConfigureHttpsDefaults_Works() { var serverOptions = CreateServerOptions(); - serverOptions.DefaultCertificate = _x509Certificate2; IFeatureCollection bindFeatures = null; var multiplexedConnectionListenerFactory = new MockMultiplexedConnectionListenerFactory(); @@ -463,9 +462,14 @@ public async Task Http3_NoUseHttp3_NoSslServerOptions() testContext, serverOptions => { + serverOptions.ConfigureHttpsDefaults(https => + { + https.ServerCertificate = _x509Certificate2; + }); serverOptions.ListenLocalhost(5001, listenOptions => { listenOptions.Protocols = HttpProtocols.Http3; + listenOptions.UseHttps(); }); }, services => @@ -478,7 +482,114 @@ public async Task Http3_NoUseHttp3_NoSslServerOptions() Assert.NotNull(bindFeatures); var sslOptions = bindFeatures.Get(); - Assert.Null(sslOptions); + Assert.NotNull(sslOptions); + Assert.Equal(_x509Certificate2, sslOptions.ServerCertificate); + } + + [Fact] + public async Task Http1And2And3_NoUseHttps_MultiplexBindNotCalled() + { + var serverOptions = CreateServerOptions(); + serverOptions.DefaultCertificate = _x509Certificate2; + + var bindCalled = false; + var multiplexedConnectionListenerFactory = new MockMultiplexedConnectionListenerFactory(); + multiplexedConnectionListenerFactory.OnBindAsync = (ep, features) => + { + bindCalled = true; + }; + + var testContext = new TestServiceContext(LoggerFactory); + testContext.ServerOptions = serverOptions; + await using (var server = new TestServer(context => Task.CompletedTask, + testContext, + serverOptions => + { + serverOptions.ListenLocalhost(5001, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; + }); + }, + services => + { + services.AddSingleton(multiplexedConnectionListenerFactory); + })) + { + } + + Assert.False(bindCalled); + } + + [Fact] + public async Task Http2and3_NoUseHttps_Throws() + { + var serverOptions = CreateServerOptions(); + serverOptions.DefaultCertificate = _x509Certificate2; + + var bindCalled = false; + var multiplexedConnectionListenerFactory = new MockMultiplexedConnectionListenerFactory(); + multiplexedConnectionListenerFactory.OnBindAsync = (ep, features) => + { + bindCalled = true; + }; + + var testContext = new TestServiceContext(LoggerFactory); + testContext.ServerOptions = serverOptions; + var ex = await Assert.ThrowsAsync(async () => + { + await using var server = new TestServer(context => Task.CompletedTask, + testContext, + serverOptions => + { + serverOptions.ListenLocalhost(5001, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http3; + }); + }, + services => + { + services.AddSingleton(multiplexedConnectionListenerFactory); + }); + }); + + Assert.False(bindCalled); + Assert.Equal("HTTP/3 requires https.", ex.InnerException.InnerException.Message); + } + + [Fact] + public async Task Http3_NoUseHttps_Throws() + { + var serverOptions = CreateServerOptions(); + serverOptions.DefaultCertificate = _x509Certificate2; + + var bindCalled = false; + var multiplexedConnectionListenerFactory = new MockMultiplexedConnectionListenerFactory(); + multiplexedConnectionListenerFactory.OnBindAsync = (ep, features) => + { + bindCalled = true; + }; + + var testContext = new TestServiceContext(LoggerFactory); + testContext.ServerOptions = serverOptions; + var ex = await Assert.ThrowsAsync(async () => + { + await using var server = new TestServer(context => Task.CompletedTask, + testContext, + serverOptions => + { + serverOptions.ListenLocalhost(5001, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http3; + }); + }, + services => + { + services.AddSingleton(multiplexedConnectionListenerFactory); + }); + }); + + Assert.False(bindCalled); + Assert.Equal("HTTP/3 requires https.", ex.InnerException.InnerException.Message); } [Fact] diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs index 247bba40218f..187c32053f6b 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/ResponseTests.cs @@ -4124,7 +4124,8 @@ public async Task AltSvc_Http1And2And3EndpointConfigured_AltSvcInResponseHeaders { options.CodeBackedListenOptions.Add(new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) { - Protocols = HttpProtocols.Http1AndHttp2AndHttp3 + Protocols = HttpProtocols.Http1AndHttp2AndHttp3, + IsTls = true }); }, services => { })) @@ -4186,7 +4187,8 @@ public async Task AltSvc_Http3ConfiguredDifferentEndpoint_NoAltSvcInResponseHead }); options.CodeBackedListenOptions.Add(new ListenOptions(new IPEndPoint(IPAddress.Loopback, 1)) { - Protocols = HttpProtocols.Http3 + Protocols = HttpProtocols.Http3, + IsTls = true }); }, services => { })) diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs index c51b48303911..794c8da61211 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs @@ -1,11 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections; -using System.Globalization; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; using System.Net.Quic; using System.Text; using System.Threading.Tasks; @@ -693,7 +690,7 @@ private static HttpClient CreateClient() private IHostBuilder CreateHostBuilder(RequestDelegate requestDelegate, HttpProtocols? protocol = null, Action configureKestrel = null) { - return GetHostBuilder() + return new HostBuilder() .ConfigureWebHost(webHostBuilder => { webHostBuilder @@ -719,20 +716,5 @@ private IHostBuilder CreateHostBuilder(RequestDelegate requestDelegate, HttpProt }) .ConfigureServices(AddTestLogging); } - - public static IHostBuilder GetHostBuilder(long? maxReadBufferSize = null) - { - return new HostBuilder() - .ConfigureWebHost(webHostBuilder => - { - webHostBuilder - .UseQuic(options => - { - options.MaxReadBufferSize = maxReadBufferSize; - options.Alpn = "h3"; - options.IdleTimeout = TimeSpan.FromSeconds(20); - }); - }); - } } }