diff --git a/samples/SampleApp/Startup.cs b/samples/SampleApp/Startup.cs index 562f3a020d..bf8ce0e1a3 100644 --- a/samples/SampleApp/Startup.cs +++ b/samples/SampleApp/Startup.cs @@ -76,6 +76,13 @@ namespace SampleApp listenOptions.UseConnectionLogging(); }); + options.ListenLocalhost(basePort + 2, listenOptions => + { + listenOptions.UseHttps("testCert.pfx", "testPassword"); + }); + + options.ListenAnyIP(basePort + 3); + options.UseSystemd(); // The following section should be used to demo sockets diff --git a/src/Kestrel.Core/AnyIPListenOptions.cs b/src/Kestrel.Core/AnyIPListenOptions.cs new file mode 100644 index 0000000000..2639337dd7 --- /dev/null +++ b/src/Kestrel.Core/AnyIPListenOptions.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core +{ + internal class AnyIPListenOptions : ListenOptions + { + internal AnyIPListenOptions(int port) + : base(new IPEndPoint(IPAddress.IPv6Any, port)) + { + } + + internal override async Task BindAsync(AddressBindContext context) + { + // when address is 'http://hostname:port', 'http://*:port', or 'http://+:port' + try + { + await base.BindAsync(context).ConfigureAwait(false); + } + catch (Exception ex) when (!(ex is IOException)) + { + context.Logger.LogDebug(CoreStrings.FormatFallbackToIPv4Any(IPEndPoint.Port)); + + // for machines that do not support IPv6 + IPEndPoint = new IPEndPoint(IPAddress.Any, IPEndPoint.Port); + await base.BindAsync(context).ConfigureAwait(false); + } + } + } +} diff --git a/src/Kestrel.Core/Internal/AddressBindContext.cs b/src/Kestrel.Core/Internal/AddressBindContext.cs new file mode 100644 index 0000000000..e2f46e6025 --- /dev/null +++ b/src/Kestrel.Core/Internal/AddressBindContext.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + internal class AddressBindContext + { + public ICollection Addresses { get; set; } + public List ListenOptions { get; set; } + public KestrelServerOptions ServerOptions { get; set; } + public ILogger Logger { get; set; } + public IDefaultHttpsProvider DefaultHttpsProvider { get; set; } + + public Func CreateBinding { get; set; } + } +} diff --git a/src/Kestrel.Core/Internal/AddressBinder.cs b/src/Kestrel.Core/Internal/AddressBinder.cs index 808675c0fb..5334308ad8 100644 --- a/src/Kestrel.Core/Internal/AddressBinder.cs +++ b/src/Kestrel.Core/Internal/AddressBinder.cs @@ -47,17 +47,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal await strategy.BindAsync(context).ConfigureAwait(false); } - private class AddressBindContext - { - public ICollection Addresses { get; set; } - public List ListenOptions { get; set; } - public KestrelServerOptions ServerOptions { get; set; } - public ILogger Logger { get; set; } - public IDefaultHttpsProvider DefaultHttpsProvider { get; set; } - - public Func CreateBinding { get; set; } - } - private static IStrategy CreateStrategy(ListenOptions[] listenOptions, string[] addresses, bool preferAddresses) { var hasListenOptions = listenOptions.Length > 0; @@ -109,10 +98,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal return true; } - private static Task BindEndpointAsync(IPEndPoint endpoint, AddressBindContext context) - => BindEndpointAsync(new ListenOptions(endpoint), context); - - private static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindContext context) + internal static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindContext context) { try { @@ -126,60 +112,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal context.ListenOptions.Add(endpoint); } - private static async Task BindLocalhostAsync(ServerAddress address, AddressBindContext context, bool https) - { - if (address.Port == 0) - { - throw new InvalidOperationException(CoreStrings.DynamicPortOnLocalhostNotSupported); - } - - var exceptions = new List(); - - try - { - var options = new ListenOptions(new IPEndPoint(IPAddress.Loopback, address.Port)); - await BindEndpointAsync(options, context).ConfigureAwait(false); - - if (https) - { - options.KestrelServerOptions = context.ServerOptions; - context.DefaultHttpsProvider.ConfigureHttps(options); - } - } - catch (Exception ex) when (!(ex is IOException)) - { - context.Logger.LogWarning(0, CoreStrings.NetworkInterfaceBindingFailed, address, "IPv4 loopback", ex.Message); - exceptions.Add(ex); - } - - try - { - var options = new ListenOptions(new IPEndPoint(IPAddress.IPv6Loopback, address.Port)); - await BindEndpointAsync(options, context).ConfigureAwait(false); - - if (https) - { - options.KestrelServerOptions = context.ServerOptions; - context.DefaultHttpsProvider.ConfigureHttps(options); - } - } - catch (Exception ex) when (!(ex is IOException)) - { - context.Logger.LogWarning(0, CoreStrings.NetworkInterfaceBindingFailed, address, "IPv6 loopback", ex.Message); - exceptions.Add(ex); - } - - if (exceptions.Count == 2) - { - throw new IOException(CoreStrings.FormatAddressBindingFailed(address), new AggregateException(exceptions)); - } - - // If StartLocalhost doesn't throw, there is at least one listener. - // The port cannot change for "localhost". - context.Addresses.Add(address.ToString()); - } - - private static async Task BindAddressAsync(string address, AddressBindContext context) + internal static ListenOptions ParseAddress(string address, KestrelServerOptions serverOptions, IDefaultHttpsProvider defaultHttpsProvider) { var parsedAddress = ServerAddress.FromUrl(address); var https = false; @@ -202,47 +135,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal if (parsedAddress.IsUnixPipe) { options = new ListenOptions(parsedAddress.UnixPipePath); - await BindEndpointAsync(options, context).ConfigureAwait(false); - context.Addresses.Add(options.GetDisplayName()); } else if (string.Equals(parsedAddress.Host, "localhost", StringComparison.OrdinalIgnoreCase)) { // "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint. - await BindLocalhostAsync(parsedAddress, context, https).ConfigureAwait(false); + options = new LocalhostListenOptions(parsedAddress.Port); + } + else if (TryCreateIPEndPoint(parsedAddress, out var endpoint)) + { + options = new ListenOptions(endpoint); } else { - if (TryCreateIPEndPoint(parsedAddress, out var endpoint)) - { - options = new ListenOptions(endpoint); - await BindEndpointAsync(options, context).ConfigureAwait(false); - } - else - { - // when address is 'http://hostname:port', 'http://*:port', or 'http://+:port' - try - { - options = new ListenOptions(new IPEndPoint(IPAddress.IPv6Any, parsedAddress.Port)); - await BindEndpointAsync(options, context).ConfigureAwait(false); - } - catch (Exception ex) when (!(ex is IOException)) - { - context.Logger.LogDebug(CoreStrings.FormatFallbackToIPv4Any(parsedAddress.Port)); - - // for machines that do not support IPv6 - options = new ListenOptions(new IPEndPoint(IPAddress.Any, parsedAddress.Port)); - await BindEndpointAsync(options, context).ConfigureAwait(false); - } - } - - context.Addresses.Add(options.GetDisplayName()); + // when address is 'http://hostname:port', 'http://*:port', or 'http://+:port' + options = new AnyIPListenOptions(parsedAddress.Port); } - if (https && options != null) + if (https) { - options.KestrelServerOptions = context.ServerOptions; - context.DefaultHttpsProvider.ConfigureHttps(options); + options.KestrelServerOptions = serverOptions; + defaultHttpsProvider.ConfigureHttps(options); } + + return options; } private interface IStrategy @@ -256,7 +171,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { context.Logger.LogDebug(CoreStrings.BindingToDefaultAddress, Constants.DefaultServerAddress); - await BindLocalhostAsync(ServerAddress.FromUrl(Constants.DefaultServerAddress), context, https: false).ConfigureAwait(false); + await ParseAddress(Constants.DefaultServerAddress, context.ServerOptions, context.DefaultHttpsProvider) + .BindAsync(context).ConfigureAwait(false); } } @@ -308,9 +224,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { foreach (var endpoint in _endpoints) { - await BindEndpointAsync(endpoint, context).ConfigureAwait(false); - - context.Addresses.Add(endpoint.GetDisplayName()); + await endpoint.BindAsync(context).ConfigureAwait(false); } } } @@ -328,7 +242,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { foreach (var address in _addresses) { - await BindAddressAsync(address, context).ConfigureAwait(false); + await ParseAddress(address, context.ServerOptions, context.DefaultHttpsProvider) + .BindAsync(context).ConfigureAwait(false); } } } diff --git a/src/Kestrel.Core/KestrelServerOptions.cs b/src/Kestrel.Core/KestrelServerOptions.cs index 136b983533..3d70664cbe 100644 --- a/src/Kestrel.Core/KestrelServerOptions.cs +++ b/src/Kestrel.Core/KestrelServerOptions.cs @@ -105,6 +105,40 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core ListenOptions.Add(listenOptions); } + public void ListenLocalhost(int port) => ListenLocalhost(port, options => { }); + + public void ListenLocalhost(int port, Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var listenOptions = new LocalhostListenOptions(port) + { + KestrelServerOptions = this, + }; + configure(listenOptions); + ListenOptions.Add(listenOptions); + } + + public void ListenAnyIP(int port) => ListenAnyIP(port, options => { }); + + public void ListenAnyIP(int port, Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var listenOptions = new AnyIPListenOptions(port) + { + KestrelServerOptions = this, + }; + configure(listenOptions); + ListenOptions.Add(listenOptions); + } + /// /// Bind to given Unix domain socket path. /// diff --git a/src/Kestrel.Core/ListenOptions.cs b/src/Kestrel.Core/ListenOptions.cs index 19e94c43a6..08e728a35c 100644 --- a/src/Kestrel.Core/ListenOptions.cs +++ b/src/Kestrel.Core/ListenOptions.cs @@ -8,6 +8,7 @@ using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; namespace Microsoft.AspNetCore.Server.Kestrel.Core @@ -140,7 +141,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core /// /// Gets the name of this endpoint to display on command-line when the web server starts. /// - internal string GetDisplayName() + internal virtual string GetDisplayName() { var scheme = ConnectionAdapters.Any(f => f.IsHttps) ? "https" @@ -182,5 +183,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core return app; } + + internal virtual async Task BindAsync(AddressBindContext context) + { + await AddressBinder.BindEndpointAsync(this, context).ConfigureAwait(false); + context.Addresses.Add(GetDisplayName()); + } } } diff --git a/src/Kestrel.Core/LocalhostListenOptions.cs b/src/Kestrel.Core/LocalhostListenOptions.cs new file mode 100644 index 0000000000..39c49abbfc --- /dev/null +++ b/src/Kestrel.Core/LocalhostListenOptions.cs @@ -0,0 +1,88 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core +{ + internal class LocalhostListenOptions : ListenOptions + { + internal LocalhostListenOptions(int port) + : base(new IPEndPoint(IPAddress.Loopback, port)) + { + if (port == 0) + { + throw new InvalidOperationException(CoreStrings.DynamicPortOnLocalhostNotSupported); + } + } + + /// + /// Gets the name of this endpoint to display on command-line when the web server starts. + /// + internal override string GetDisplayName() + { + var scheme = ConnectionAdapters.Any(f => f.IsHttps) + ? "https" + : "http"; + + return $"{scheme}://localhost:{IPEndPoint.Port}"; + } + + internal override async Task BindAsync(AddressBindContext context) + { + var exceptions = new List(); + + try + { + var v4Options = Clone(IPAddress.Loopback); + await AddressBinder.BindEndpointAsync(v4Options, context).ConfigureAwait(false); + } + catch (Exception ex) when (!(ex is IOException)) + { + context.Logger.LogWarning(0, CoreStrings.NetworkInterfaceBindingFailed, GetDisplayName(), "IPv4 loopback", ex.Message); + exceptions.Add(ex); + } + + try + { + var v6Options = Clone(IPAddress.IPv6Loopback); + await AddressBinder.BindEndpointAsync(v6Options, context).ConfigureAwait(false); + } + catch (Exception ex) when (!(ex is IOException)) + { + context.Logger.LogWarning(0, CoreStrings.NetworkInterfaceBindingFailed, GetDisplayName(), "IPv6 loopback", ex.Message); + exceptions.Add(ex); + } + + if (exceptions.Count == 2) + { + throw new IOException(CoreStrings.FormatAddressBindingFailed(GetDisplayName()), new AggregateException(exceptions)); + } + + // If StartLocalhost doesn't throw, there is at least one listener. + // The port cannot change for "localhost". + context.Addresses.Add(GetDisplayName()); + } + + // used for cloning to two IPEndpoints + private ListenOptions Clone(IPAddress address) + { + var options = new ListenOptions(new IPEndPoint(address, IPEndPoint.Port)) + { + HandleType = HandleType, + KestrelServerOptions = KestrelServerOptions, + NoDelay = NoDelay, + Protocols = Protocols, + }; + options.ConnectionAdapters.AddRange(ConnectionAdapters); + return options; + } + } +} diff --git a/test/Kestrel.Core.Tests/AddressBinderTests.cs b/test/Kestrel.Core.Tests/AddressBinderTests.cs index d42a598e9c..f2580353db 100644 --- a/test/Kestrel.Core.Tests/AddressBinderTests.cs +++ b/test/Kestrel.Core.Tests/AddressBinderTests.cs @@ -2,12 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.IO; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Abstractions; using Moq; @@ -51,24 +51,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [InlineData("randomhost")] [InlineData("+")] [InlineData("contoso.com")] - public async Task DefaultsToIPv6AnyOnInvalidIPAddress(string host) + public void ParseAddressDefaultsToAnyIPOnInvalidIPAddress(string host) { - var addresses = new ServerAddressesFeature(); - addresses.Addresses.Add($"http://{host}"); var options = new KestrelServerOptions(); + var listenOptions = AddressBinder.ParseAddress($"http://{host}", options, Mock.Of()); + Assert.IsType(listenOptions); + Assert.Equal(ListenType.IPEndPoint, listenOptions.Type); + Assert.Equal(IPAddress.IPv6Any, listenOptions.IPEndPoint.Address); + Assert.Equal(80, listenOptions.IPEndPoint.Port); + } - var tcs = new TaskCompletionSource(); - await AddressBinder.BindAsync(addresses, - options, - NullLogger.Instance, - Mock.Of(), - endpoint => - { - tcs.TrySetResult(endpoint); - return Task.CompletedTask; - }); - var result = await tcs.Task; - Assert.Equal(IPAddress.IPv6Any, result.IPEndPoint.Address); + [Fact] + public void ParseAddressLocalhost() + { + var options = new KestrelServerOptions(); + var listenOptions = AddressBinder.ParseAddress("http://localhost", options, Mock.Of()); + Assert.IsType(listenOptions); + Assert.Equal(ListenType.IPEndPoint, listenOptions.Type); + Assert.Equal(IPAddress.Loopback, listenOptions.IPEndPoint.Address); + Assert.Equal(80, listenOptions.IPEndPoint.Port); + } + + [Fact] + public void ParseAddressUnixPipe() + { + var options = new KestrelServerOptions(); + var listenOptions = AddressBinder.ParseAddress("http://unix:/tmp/kestrel-test.sock", options, Mock.Of()); + Assert.Equal(ListenType.SocketPath, listenOptions.Type); + Assert.Equal("/tmp/kestrel-test.sock", listenOptions.SocketPath); + } + + [Theory] + [InlineData("http://10.10.10.10:5000/", "10.10.10.10", 5000)] + [InlineData("http://[::1]:5000", "::1", 5000)] + [InlineData("http://[::1]", "::1", 80)] + [InlineData("http://127.0.0.1", "127.0.0.1", 80)] + [InlineData("https://127.0.0.1", "127.0.0.1", 443)] + public void ParseAddressIP(string address, string ip, int port) + { + var options = new KestrelServerOptions(); + var listenOptions = AddressBinder.ParseAddress(address, options, Mock.Of()); + Assert.Equal(ListenType.IPEndPoint, listenOptions.Type); + Assert.Equal(IPAddress.Parse(ip), listenOptions.IPEndPoint.Address); + Assert.Equal(port, listenOptions.IPEndPoint.Port); } [Fact] diff --git a/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs b/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs index 446a201f71..58d7369b50 100644 --- a/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs +++ b/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs @@ -187,7 +187,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests private Task RegisterAddresses_Success(string addressInput, string testUrl, int testPort = 0) => RegisterAddresses_Success(addressInput, new[] { testUrl }, testPort); - private async Task RegisterAddresses_StaticPort_Success(string addressInput, string[] testUrls) + private Task RegisterAddresses_StaticPort_Success(string addressInput, string[] testUrls) => + RunTestWithStaticPort(port => RegisterAddresses_Success($"{addressInput}:{port}", testUrls, port)); + + private async Task RunTestWithStaticPort(Func test) { var retryCount = 0; var errors = new List(); @@ -197,7 +200,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests try { var port = GetNextPort(); - await RegisterAddresses_Success($"{addressInput}:{port}", testUrls, port); + await test(port); return; } catch (XunitException) @@ -254,34 +257,93 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } - private async Task RegisterIPEndPoint_StaticPort_Success(IPAddress address, string testUrl) + private Task RegisterIPEndPoint_StaticPort_Success(IPAddress address, string testUrl) + => RunTestWithStaticPort(port => RegisterIPEndPoint_Success(new IPEndPoint(address, port), testUrl, port)); + + [ConditionalFact] + public async Task ListenAnyIP_IPv4_Success() { - var retryCount = 0; - var errors = new List(); + await ListenAnyIP_Success(new[] { "http://localhost", "http://127.0.0.1" }); + } - while (retryCount < MaxRetries) + [ConditionalFact] + [IPv6SupportedCondition] + public async Task ListenAnyIP_IPv6_Success() + { + await ListenAnyIP_Success(new[] { "http://[::1]", "http://localhost", "http://127.0.0.1" }); + } + + [ConditionalFact] + [NetworkIsReachable] + public async Task ListenAnyIP_HostName_Success() + { + var hostName = Dns.GetHostName(); + await ListenAnyIP_Success(new[] { $"http://{hostName}" }); + } + + private async Task ListenAnyIP_Success(string[] testUrls, int testPort = 0) + { + var hostBuilder = TransportSelector.GetWebHostBuilder() + .UseKestrel(options => + { + options.ListenAnyIP(testPort); + }) + .ConfigureLogging(_configureLoggingDelegate) + .Configure(ConfigureEchoAddress); + + using (var host = hostBuilder.Build()) { - try - { - var port = GetNextPort(); - await RegisterIPEndPoint_Success(new IPEndPoint(address, port), testUrl, port); - return; - } - catch (XunitException) - { - throw; - } - catch (Exception ex) - { - errors.Add(ex); - } + host.Start(); - retryCount++; + foreach (var testUrl in testUrls.Select(testUrl => $"{testUrl}:{(testPort == 0 ? host.GetPort() : testPort)}")) + { + var response = await HttpClientSlim.GetStringAsync(testUrl, validateCertificate: false); + + // Compare the response with Uri.ToString(), rather than testUrl directly. + // Required to handle IPv6 addresses with zone index, like "fe80::3%1" + Assert.Equal(new Uri(testUrl).ToString(), response); + } } + } - if (errors.Any()) + [ConditionalFact] + public async Task ListenLocalhost_IPv4LocalhostStaticPort_Success() + { + await ListenLocalhost_StaticPort_Success(new[] { "http://localhost", "http://127.0.0.1" }); + } + + [ConditionalFact] + [IPv6SupportedCondition] + public async Task ListenLocalhost_IPv6LocalhostStaticPort_Success() + { + await ListenLocalhost_StaticPort_Success(new[] { "http://localhost", "http://127.0.0.1", "http://[::1]" }); + } + + private Task ListenLocalhost_StaticPort_Success(string[] testUrls) => + RunTestWithStaticPort(port => ListenLocalhost_Success(testUrls, port)); + + private async Task ListenLocalhost_Success(string[] testUrls, int testPort = 0) + { + var hostBuilder = TransportSelector.GetWebHostBuilder() + .UseKestrel(options => + { + options.ListenLocalhost(testPort); + }) + .ConfigureLogging(_configureLoggingDelegate) + .Configure(ConfigureEchoAddress); + + using (var host = hostBuilder.Build()) { - throw new AggregateException(errors); + host.Start(); + + foreach (var testUrl in testUrls.Select(testUrl => $"{testUrl}:{(testPort == 0 ? host.GetPort() : testPort)}")) + { + var response = await HttpClientSlim.GetStringAsync(testUrl, validateCertificate: false); + + // Compare the response with Uri.ToString(), rather than testUrl directly. + // Required to handle IPv6 addresses with zone index, like "fe80::3%1" + Assert.Equal(new Uri(testUrl).ToString(), response); + } } } diff --git a/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs b/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs index 2a7b0ee4c1..54dd7ed013 100644 --- a/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs +++ b/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests _bufferPool = new MemoryPool(); _mockLibuv = new MockLibuv(); - var libuvTransport = new LibuvTransport(_mockLibuv, new TestLibuvTransportContext(), new ListenOptions(0)); + var libuvTransport = new LibuvTransport(_mockLibuv, new TestLibuvTransportContext(), new ListenOptions((ulong)0)); _libuvThread = new LibuvThread(libuvTransport, maxLoops: 1); _libuvThread.StartAsync().Wait(); }