From 09f3a966afbc7e11472624997b13d970f827f7a9 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 11 Aug 2021 23:08:28 +1200 Subject: [PATCH 1/5] HTTP/3: Fix binding listener that shares transports with ListenAnyIP --- .../Core/src/Internal/KestrelServerImpl.cs | 26 +++- .../Kestrel/Core/test/KestrelServerTests.cs | 116 +++++++++++++++++- .../Http3/Http3RequestTests.cs | 14 +-- 3 files changed, 146 insertions(+), 10 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs index 01e8d6a7edf1..90de964f57b6 100644 --- a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.IO.Pipelines; using System.Linq; +using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; @@ -203,6 +204,8 @@ async Task OnBind(ListenOptions options, CancellationToken onBindCancellationTok // multiplexed transport factory, which happens if QUIC isn't supported. var addAltSvcHeader = !options.DisableAltSvcHeader && _multiplexedTransportFactory != null; + var originalEndPoint = options.EndPoint; + // Add the HTTP middleware as the terminal connection middleware if (hasHttp1 || hasHttp2 || options.Protocols == HttpProtocols.None) // TODO a test fails because it doesn't throw an exception in the right place @@ -219,7 +222,7 @@ async Task OnBind(ListenOptions options, CancellationToken onBindCancellationTok // Add the connection limit middleware connectionDelegate = EnforceConnectionLimit(connectionDelegate, Options.Limits.MaxConcurrentConnections, Trace); - options.EndPoint = await _transportManager.BindAsync(options.EndPoint, connectionDelegate, options.EndpointConfig, onBindCancellationToken).ConfigureAwait(false); + options.EndPoint = await _transportManager.BindAsync(ResolveEndPoint(originalEndPoint, options.EndPoint), connectionDelegate, options.EndpointConfig, onBindCancellationToken).ConfigureAwait(false); } if (hasHttp3 && _multiplexedTransportFactory is not null) @@ -230,7 +233,7 @@ async Task OnBind(ListenOptions options, CancellationToken onBindCancellationTok // Add the connection limit middleware multiplexedConnectionDelegate = EnforceConnectionLimit(multiplexedConnectionDelegate, Options.Limits.MaxConcurrentConnections, Trace); - options.EndPoint = await _transportManager.BindAsync(options.EndPoint, multiplexedConnectionDelegate, options, onBindCancellationToken).ConfigureAwait(false); + options.EndPoint = await _transportManager.BindAsync(ResolveEndPoint(originalEndPoint, options.EndPoint), multiplexedConnectionDelegate, options, onBindCancellationToken).ConfigureAwait(false); } } @@ -247,6 +250,25 @@ async Task OnBind(ListenOptions options, CancellationToken onBindCancellationTok // Register the options with the event source so it can be logged (if necessary) KestrelEventSource.Log.AddServerOptions(Options); + + static EndPoint ResolveEndPoint(EndPoint originalEndPoint, EndPoint currentEndPoint) + { + // From the original endpoint we want the address. This address could be a constant. + // For example, IPAddress.IPv4Any or IPAddress.IPv6Any. QUIC connection listener + // has logic that special cases these constants. + // + // However, from the current endpoint we want to get the port. If there is a port of 0 + // then the first listener will resolve it to an actual port number. We want additional + // listeners to use that port number instead of each using 0 and resolving to different + // port numbers. + if (originalEndPoint is IPEndPoint originalIP && originalIP.Port == 0 && + currentEndPoint is IPEndPoint currentIP && currentIP.Port != 0) + { + return new IPEndPoint(originalIP.Address, currentIP.Port); + } + + return originalEndPoint; + } } // Graceful shutdown if possible diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index f7cd3faa0bf7..f9ac1ac18c31 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -9,8 +9,10 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Https.Internal; @@ -247,6 +249,78 @@ public void StartWithMultipleTransportFactoriesDoesNotThrow() StartDummyApplication(server); } + [Fact] + public async Task ListenIPWithStaticPort_TransportsGetIPv6Any() + { + var options = new KestrelServerOptions(); + options.ApplicationServices = new ServiceCollection() + .AddLogging() + .BuildServiceProvider(); + options.ListenAnyIP(5000, options => + { + options.UseHttps(); + options.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; + }); + + var mockTransportFactory = new MockTransportFactory(); + var mockMultiplexedTransportFactory = new MockMultiplexedTransportFactory(); + + using var server = new KestrelServerImpl( + Options.Create(options), + new List() { mockTransportFactory }, + new List() { mockMultiplexedTransportFactory }, + new LoggerFactory(new[] { new KestrelTestLoggerProvider() })); + + await server.StartAsync(new DummyApplication(context => Task.CompletedTask), CancellationToken.None); + + var transportEndPoint = Assert.Single(mockTransportFactory.BoundEndPoints); + var multiplexedTransportEndPoint = Assert.Single(mockMultiplexedTransportFactory.BoundEndPoints); + + // Both transports should get the IPv6Any + Assert.Same(IPAddress.IPv6Any, ((IPEndPoint)transportEndPoint.OriginalEndPoint).Address); + Assert.Same(IPAddress.IPv6Any, ((IPEndPoint)multiplexedTransportEndPoint.OriginalEndPoint).Address); + + Assert.Equal(5000, ((IPEndPoint)transportEndPoint.OriginalEndPoint).Port); + Assert.Equal(5000, ((IPEndPoint)multiplexedTransportEndPoint.OriginalEndPoint).Port); + } + + [Fact] + public async Task ListenIPWithEphemeralPort_TransportsGetIPv6Any() + { + var options = new KestrelServerOptions(); + options.ApplicationServices = new ServiceCollection() + .AddLogging() + .BuildServiceProvider(); + options.ListenAnyIP(0, options => + { + options.UseHttps(); + options.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; + }); + + var mockTransportFactory = new MockTransportFactory(); + var mockMultiplexedTransportFactory = new MockMultiplexedTransportFactory(); + + using var server = new KestrelServerImpl( + Options.Create(options), + new List() { mockTransportFactory }, + new List() { mockMultiplexedTransportFactory }, + new LoggerFactory(new[] { new KestrelTestLoggerProvider() })); + + await server.StartAsync(new DummyApplication(context => Task.CompletedTask), CancellationToken.None); + + var transportEndPoint = Assert.Single(mockTransportFactory.BoundEndPoints); + var multiplexedTransportEndPoint = Assert.Single(mockMultiplexedTransportFactory.BoundEndPoints); + + Assert.Same(IPAddress.IPv6Any, ((IPEndPoint)transportEndPoint.OriginalEndPoint).Address); + Assert.Same(IPAddress.IPv6Any, ((IPEndPoint)multiplexedTransportEndPoint.OriginalEndPoint).Address); + + // Should have been assigned a random value. + Assert.NotEqual(0, ((IPEndPoint)transportEndPoint.BoundEndPoint).Port); + + // Same random value should be used for both transports. + Assert.Equal(((IPEndPoint)transportEndPoint.BoundEndPoint).Port, ((IPEndPoint)multiplexedTransportEndPoint.BoundEndPoint).Port); + } + [Fact] public async Task StopAsyncCallsCompleteWhenFirstCallCompletes() { @@ -692,10 +766,24 @@ private static void StartDummyApplication(IServer server) private class MockTransportFactory : IConnectionListenerFactory { + public List BoundEndPoints { get; } = new List(); + public ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { + EndPoint resolvedEndPoint = endpoint; + if (resolvedEndPoint is IPEndPoint ipEndPoint) + { + var port = ipEndPoint.Port == 0 + ? Random.Shared.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort) + : ipEndPoint.Port; + + resolvedEndPoint = new IPEndPoint(new IPAddress(ipEndPoint.Address.GetAddressBytes()), port); + } + + BoundEndPoints.Add(new BindDetail(endpoint, resolvedEndPoint)); + var mock = new Mock(); - mock.Setup(m => m.EndPoint).Returns(endpoint); + mock.Setup(m => m.EndPoint).Returns(resolvedEndPoint); return new ValueTask(mock.Object); } } @@ -707,5 +795,31 @@ public ValueTask BindAsync(EndPoint endpoint, CancellationT throw new InvalidOperationException(); } } + + private class MockMultiplexedTransportFactory : IMultiplexedConnectionListenerFactory + { + public List BoundEndPoints { get; } = new List(); + + public ValueTask BindAsync(EndPoint endpoint, IFeatureCollection features = null, CancellationToken cancellationToken = default) + { + EndPoint resolvedEndPoint = endpoint; + if (resolvedEndPoint is IPEndPoint ipEndPoint) + { + var port = ipEndPoint.Port == 0 + ? Random.Shared.Next(IPEndPoint.MinPort, IPEndPoint.MaxPort) + : ipEndPoint.Port; + + resolvedEndPoint = new IPEndPoint(new IPAddress(ipEndPoint.Address.GetAddressBytes()), port); + } + + BoundEndPoints.Add(new BindDetail(endpoint, resolvedEndPoint)); + + var mock = new Mock(); + mock.Setup(m => m.EndPoint).Returns(resolvedEndPoint); + return new ValueTask(mock.Object); + } + } + + private record BindDetail(EndPoint OriginalEndPoint, EndPoint BoundEndPoint); } } diff --git a/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs b/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs index 81d705f5f594..9ffe3c2a9f3d 100644 --- a/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs +++ b/src/Servers/Kestrel/test/Interop.FunctionalTests/Http3/Http3RequestTests.cs @@ -277,7 +277,7 @@ public async Task POST_ServerCompletesWithoutReadingRequestBody_ClientGetsRespon using (var host = builder.Build()) using (var client = Http3Helpers.CreateClient()) { - await host.StartAsync(); + await host.StartAsync().DefaultTimeout(); var requestContent = new StreamingHttpContext(); @@ -289,22 +289,22 @@ public async Task POST_ServerCompletesWithoutReadingRequestBody_ClientGetsRespon // Act var responseTask = client.SendAsync(request, CancellationToken.None); - var requestStream = await requestContent.GetStreamAsync(); + var requestStream = await requestContent.GetStreamAsync().DefaultTimeout(); // Send headers - await requestStream.FlushAsync(); + await requestStream.FlushAsync().DefaultTimeout(); // Write content - await requestStream.WriteAsync(TestData); + await requestStream.WriteAsync(TestData).DefaultTimeout(); - var response = await responseTask; + var response = await responseTask.DefaultTimeout(); // Assert response.EnsureSuccessStatusCode(); Assert.Equal(HttpVersion.Version30, response.Version); - var responseText = await response.Content.ReadAsStringAsync(); + var responseText = await response.Content.ReadAsStringAsync().DefaultTimeout(); Assert.Equal("Hello world", responseText); - await host.StopAsync(); + await host.StopAsync().DefaultTimeout(); } } From 6e89957ee3a1dd00ce5a4db52326da8ee4425207 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 12 Aug 2021 11:15:42 +1200 Subject: [PATCH 2/5] Better fix --- .../Core/src/Internal/KestrelServerImpl.cs | 25 ++----------------- .../Kestrel/Core/test/KestrelServerTests.cs | 8 +++--- .../src/Internal/QuicConnectionListener.cs | 15 ++++++++++- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs index 90de964f57b6..cce3a1fdb1ea 100644 --- a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs @@ -204,8 +204,6 @@ async Task OnBind(ListenOptions options, CancellationToken onBindCancellationTok // multiplexed transport factory, which happens if QUIC isn't supported. var addAltSvcHeader = !options.DisableAltSvcHeader && _multiplexedTransportFactory != null; - var originalEndPoint = options.EndPoint; - // Add the HTTP middleware as the terminal connection middleware if (hasHttp1 || hasHttp2 || options.Protocols == HttpProtocols.None) // TODO a test fails because it doesn't throw an exception in the right place @@ -222,7 +220,7 @@ async Task OnBind(ListenOptions options, CancellationToken onBindCancellationTok // Add the connection limit middleware connectionDelegate = EnforceConnectionLimit(connectionDelegate, Options.Limits.MaxConcurrentConnections, Trace); - options.EndPoint = await _transportManager.BindAsync(ResolveEndPoint(originalEndPoint, options.EndPoint), connectionDelegate, options.EndpointConfig, onBindCancellationToken).ConfigureAwait(false); + options.EndPoint = await _transportManager.BindAsync(options.EndPoint, connectionDelegate, options.EndpointConfig, onBindCancellationToken).ConfigureAwait(false); } if (hasHttp3 && _multiplexedTransportFactory is not null) @@ -233,7 +231,7 @@ async Task OnBind(ListenOptions options, CancellationToken onBindCancellationTok // Add the connection limit middleware multiplexedConnectionDelegate = EnforceConnectionLimit(multiplexedConnectionDelegate, Options.Limits.MaxConcurrentConnections, Trace); - options.EndPoint = await _transportManager.BindAsync(ResolveEndPoint(originalEndPoint, options.EndPoint), multiplexedConnectionDelegate, options, onBindCancellationToken).ConfigureAwait(false); + options.EndPoint = await _transportManager.BindAsync(options.EndPoint, multiplexedConnectionDelegate, options, onBindCancellationToken).ConfigureAwait(false); } } @@ -250,25 +248,6 @@ async Task OnBind(ListenOptions options, CancellationToken onBindCancellationTok // Register the options with the event source so it can be logged (if necessary) KestrelEventSource.Log.AddServerOptions(Options); - - static EndPoint ResolveEndPoint(EndPoint originalEndPoint, EndPoint currentEndPoint) - { - // From the original endpoint we want the address. This address could be a constant. - // For example, IPAddress.IPv4Any or IPAddress.IPv6Any. QUIC connection listener - // has logic that special cases these constants. - // - // However, from the current endpoint we want to get the port. If there is a port of 0 - // then the first listener will resolve it to an actual port number. We want additional - // listeners to use that port number instead of each using 0 and resolving to different - // port numbers. - if (originalEndPoint is IPEndPoint originalIP && originalIP.Port == 0 && - currentEndPoint is IPEndPoint currentIP && currentIP.Port != 0) - { - return new IPEndPoint(originalIP.Address, currentIP.Port); - } - - return originalEndPoint; - } } // Graceful shutdown if possible diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index f9ac1ac18c31..6aa2b64f658d 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -277,8 +277,8 @@ public async Task ListenIPWithStaticPort_TransportsGetIPv6Any() var multiplexedTransportEndPoint = Assert.Single(mockMultiplexedTransportFactory.BoundEndPoints); // Both transports should get the IPv6Any - Assert.Same(IPAddress.IPv6Any, ((IPEndPoint)transportEndPoint.OriginalEndPoint).Address); - Assert.Same(IPAddress.IPv6Any, ((IPEndPoint)multiplexedTransportEndPoint.OriginalEndPoint).Address); + Assert.Equal(IPAddress.IPv6Any, ((IPEndPoint)transportEndPoint.OriginalEndPoint).Address); + Assert.Equal(IPAddress.IPv6Any, ((IPEndPoint)multiplexedTransportEndPoint.OriginalEndPoint).Address); Assert.Equal(5000, ((IPEndPoint)transportEndPoint.OriginalEndPoint).Port); Assert.Equal(5000, ((IPEndPoint)multiplexedTransportEndPoint.OriginalEndPoint).Port); @@ -311,8 +311,8 @@ public async Task ListenIPWithEphemeralPort_TransportsGetIPv6Any() var transportEndPoint = Assert.Single(mockTransportFactory.BoundEndPoints); var multiplexedTransportEndPoint = Assert.Single(mockMultiplexedTransportFactory.BoundEndPoints); - Assert.Same(IPAddress.IPv6Any, ((IPEndPoint)transportEndPoint.OriginalEndPoint).Address); - Assert.Same(IPAddress.IPv6Any, ((IPEndPoint)multiplexedTransportEndPoint.OriginalEndPoint).Address); + Assert.Equal(IPAddress.IPv6Any, ((IPEndPoint)transportEndPoint.OriginalEndPoint).Address); + Assert.Equal(IPAddress.IPv6Any, ((IPEndPoint)multiplexedTransportEndPoint.OriginalEndPoint).Address); // Should have been assigned a random value. Assert.NotEqual(0, ((IPEndPoint)transportEndPoint.BoundEndPoint).Port); diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs index 975aa8bf3946..dd91c6cd9846 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs @@ -35,8 +35,21 @@ public QuicConnectionListener(QuicTransportOptions options, IQuicTrace log, EndP _context = new QuicTransportContext(_log, options); var quicListenerOptions = new QuicListenerOptions(); + var listenEndPoint = (IPEndPoint)endpoint; + + // Workaround for issue in System.Net.Quic + // https://github.com/dotnet/runtime/issues/57241 + if (listenEndPoint.Address.Equals(IPAddress.Any) && listenEndPoint.Address != IPAddress.Any) + { + listenEndPoint = new IPEndPoint(IPAddress.Any, listenEndPoint.Port); + } + if (listenEndPoint.Address.Equals(IPAddress.IPv6Any) && listenEndPoint.Address != IPAddress.IPv6Any) + { + listenEndPoint = new IPEndPoint(IPAddress.IPv6Any, listenEndPoint.Port); + } + quicListenerOptions.ServerAuthenticationOptions = sslServerAuthenticationOptions; - quicListenerOptions.ListenEndPoint = endpoint as IPEndPoint; + quicListenerOptions.ListenEndPoint = listenEndPoint; quicListenerOptions.IdleTimeout = options.IdleTimeout; quicListenerOptions.MaxBidirectionalStreams = options.MaxBidirectionalStreamCount; quicListenerOptions.MaxUnidirectionalStreams = options.MaxUnidirectionalStreamCount; From 0bcbf2b67f703d0feb659068a6afafc0ce37214d Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 12 Aug 2021 11:17:15 +1200 Subject: [PATCH 3/5] Update --- src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs | 1 - src/Servers/Kestrel/Core/test/KestrelServerTests.cs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs index cce3a1fdb1ea..01e8d6a7edf1 100644 --- a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.IO.Pipelines; using System.Linq; -using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; diff --git a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs index 6aa2b64f658d..eb976cc4b0b2 100644 --- a/src/Servers/Kestrel/Core/test/KestrelServerTests.cs +++ b/src/Servers/Kestrel/Core/test/KestrelServerTests.cs @@ -258,7 +258,7 @@ public async Task ListenIPWithStaticPort_TransportsGetIPv6Any() .BuildServiceProvider(); options.ListenAnyIP(5000, options => { - options.UseHttps(); + options.UseHttps(TestResources.GetTestCertificate()); options.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; }); @@ -293,7 +293,7 @@ public async Task ListenIPWithEphemeralPort_TransportsGetIPv6Any() .BuildServiceProvider(); options.ListenAnyIP(0, options => { - options.UseHttps(); + options.UseHttps(TestResources.GetTestCertificate()); options.Protocols = HttpProtocols.Http1AndHttp2AndHttp3; }); From 5de4cea25039709024b3c0353a495702e5cc294b Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 12 Aug 2021 11:47:02 +1200 Subject: [PATCH 4/5] Update --- .../src/Internal/QuicConnectionListener.cs | 7 ++- .../src/QuicTransportFactory.cs | 5 ++ .../Transport.Quic/test/WebHostTests.cs | 46 +++++++++++++++++-- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs index dd91c6cd9846..9a9528bc04a9 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs @@ -35,7 +35,12 @@ public QuicConnectionListener(QuicTransportOptions options, IQuicTrace log, EndP _context = new QuicTransportContext(_log, options); var quicListenerOptions = new QuicListenerOptions(); - var listenEndPoint = (IPEndPoint)endpoint; + var listenEndPoint = endpoint as IPEndPoint; + + if (listenEndPoint == null) + { + throw new InvalidOperationException($"QUIC doesn't support listening on the configured endpoint type. Expected {nameof(IPEndPoint)} but got {endpoint.GetType().Name}."); + } // Workaround for issue in System.Net.Quic // https://github.com/dotnet/runtime/issues/57241 diff --git a/src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs index 8a82ae301f9b..4e20ff926b4d 100644 --- a/src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs @@ -48,6 +48,11 @@ public QuicTransportFactory(ILoggerFactory loggerFactory, IOptionsA public ValueTask BindAsync(EndPoint endpoint, IFeatureCollection? features = null, CancellationToken cancellationToken = default) { + if (endpoint == null) + { + throw new ArgumentNullException(nameof(endpoint)); + } + var sslServerAuthenticationOptions = features?.Get(); if (sslServerAuthenticationOptions == null) diff --git a/src/Servers/Kestrel/Transport.Quic/test/WebHostTests.cs b/src/Servers/Kestrel/Transport.Quic/test/WebHostTests.cs index 34b56980b5be..10b5d6d113da 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/WebHostTests.cs +++ b/src/Servers/Kestrel/Transport.Quic/test/WebHostTests.cs @@ -82,12 +82,12 @@ public async Task Listen_Http3AndSocketsCoexistOnDifferentEndpoints_ClientSucces o.Listen(IPAddress.Parse("127.0.0.1"), http3Port, listenOptions => { listenOptions.Protocols = Core.HttpProtocols.Http3; - listenOptions.UseHttps(); + listenOptions.UseHttps(TestResources.GetTestCertificate()); }); o.Listen(IPAddress.Parse("127.0.0.1"), http1Port, listenOptions => { listenOptions.Protocols = Core.HttpProtocols.Http1; - listenOptions.UseHttps(); + listenOptions.UseHttps(TestResources.GetTestCertificate()); }); }) .Configure(app => @@ -122,7 +122,7 @@ public async Task Listen_Http3AndSocketsCoexistOnSameEndpoint_ClientSuccess() o.Listen(IPAddress.Parse("127.0.0.1"), 5005, listenOptions => { listenOptions.Protocols = Core.HttpProtocols.Http1AndHttp2AndHttp3; - listenOptions.UseHttps(); + listenOptions.UseHttps(TestResources.GetTestCertificate()); }); }) .Configure(app => @@ -157,7 +157,7 @@ public async Task Listen_Http3AndSocketsCoexistOnSameEndpoint_AltSvcEnabled_Upgr o.Listen(IPAddress.Parse("127.0.0.1"), 0, listenOptions => { listenOptions.Protocols = Core.HttpProtocols.Http1AndHttp2AndHttp3; - listenOptions.UseHttps(); + listenOptions.UseHttps(TestResources.GetTestCertificate()); }); }) .Configure(app => @@ -225,7 +225,7 @@ public async Task Listen_Http3AndSocketsCoexistOnSameEndpoint_AltSvcDisabled_NoU o.Listen(IPAddress.Parse("127.0.0.1"), 0, listenOptions => { listenOptions.Protocols = Core.HttpProtocols.Http1AndHttp2AndHttp3; - listenOptions.UseHttps(); + listenOptions.UseHttps(TestResources.GetTestCertificate()); }); }) .Configure(app => @@ -305,6 +305,42 @@ private static async Task CallHttp3AndHttp1EndpointsAsync(int http3Port, int htt } } + [ConditionalFact] + [MsQuicSupported] + public async Task StartAsync_Http3WithNonIPListener_ThrowError() + { + // Arrange + var builder = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder + .UseKestrel(o => + { + o.ListenUnixSocket("/test-path", listenOptions => + { + listenOptions.Protocols = Core.HttpProtocols.Http3; + listenOptions.UseHttps(TestResources.GetTestCertificate()); + }); + }) + .Configure(app => + { + app.Run(async context => + { + await context.Response.WriteAsync("hello, world"); + }); + }); + }) + .ConfigureServices(AddTestLogging); + + using var host = builder.Build(); + + // Act + var ex = await Assert.ThrowsAsync(() => host.StartAsync()).DefaultTimeout(); + + // Assert + Assert.Equal("QUIC doesn't support listening on the configured endpoint type. Expected IPEndPoint but got UnixDomainSocketEndPoint.", ex.Message); + } + private static HttpClient CreateClient() { var httpHandler = new HttpClientHandler(); From eef559f6658ebd885e5ffc81de72448f823f8f92 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 12 Aug 2021 12:23:32 +1200 Subject: [PATCH 5/5] Fix test --- .../Kestrel/Transport.Quic/test/QuicTransportFactoryTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Servers/Kestrel/Transport.Quic/test/QuicTransportFactoryTests.cs b/src/Servers/Kestrel/Transport.Quic/test/QuicTransportFactoryTests.cs index 730be811833b..da0760e742dd 100644 --- a/src/Servers/Kestrel/Transport.Quic/test/QuicTransportFactoryTests.cs +++ b/src/Servers/Kestrel/Transport.Quic/test/QuicTransportFactoryTests.cs @@ -49,7 +49,7 @@ public async Task BindAsync_NoServerCertificate_Error() var ex = await Assert.ThrowsAsync(() => quicTransportFactory.BindAsync(new IPEndPoint(0, 0), features: features, cancellationToken: CancellationToken.None).AsTask()).DefaultTimeout(); // Assert - Assert.Equal("SslServerAuthenticationOptions.ServerCertificate must be configured with a value.", ex.Message); + Assert.Equal("SslServerAuthenticationOptions must provide a server certificate using ServerCertificate, ServerCertificateContext, or ServerCertificateSelectionCallback.", ex.Message); } } }