diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs b/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs index 0bf4baf9c9..d9844714de 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Hosting; @@ -109,8 +110,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel if (!parsedAddress.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase)) { - _disposables.Push(engine.CreateServer( - parsedAddress)); + try + { + _disposables.Push(engine.CreateServer(parsedAddress)); + } + catch (AggregateException ex) + { + if ((ex.InnerException as UvException)?.StatusCode == Constants.EADDRINUSE) + { + throw new IOException($"Failed to bind to address {parsedAddress}: address already in use.", ex); + } + + throw; + } } else { @@ -120,24 +132,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel } var ipv4Address = parsedAddress.WithHost("127.0.0.1"); - var exceptions = new List(); + var exceptions = new List(); try { _disposables.Push(engine.CreateServer(ipv4Address)); } - catch (AggregateException ex) + catch (AggregateException ex) when (ex.InnerException is UvException) { - var uvException = ex.InnerException as UvException; - - if (uvException != null && uvException.StatusCode != Constants.EADDRINUSE) + if ((ex.InnerException as UvException).StatusCode == Constants.EADDRINUSE) { - _logger.LogWarning(0, ex, $"Unable to bind to {parsedAddress.ToString()} on the IPv4 loopback interface."); - exceptions.Add(uvException); + throw new IOException($"Failed to bind to address {parsedAddress.ToString()} on the IPv4 loopback interface: port already in use.", ex); } else { - throw; + _logger.LogWarning(0, ex, $"Unable to bind to {parsedAddress.ToString()} on the IPv4 loopback interface."); + exceptions.Add(ex.InnerException); } } @@ -147,26 +157,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel { _disposables.Push(engine.CreateServer(ipv6Address)); } - catch (AggregateException ex) + catch (AggregateException ex) when (ex.InnerException is UvException) { - var uvException = ex.InnerException as UvException; - - if (uvException != null && uvException.StatusCode != Constants.EADDRINUSE) + if ((ex.InnerException as UvException).StatusCode == Constants.EADDRINUSE) { - _logger.LogWarning(0, ex, $"Unable to bind to {parsedAddress.ToString()} on the IPv6 loopback interface."); - exceptions.Add(uvException); + throw new IOException($"Failed to bind to address {parsedAddress.ToString()} on the IPv6 loopback interface: port already in use.", ex); } else { - throw; + _logger.LogWarning(0, ex, $"Unable to bind to {parsedAddress.ToString()} on the IPv6 loopback interface."); + exceptions.Add(ex.InnerException); } } if (exceptions.Count == 2) { - var ex = new AggregateException(exceptions); - _logger.LogError(0, ex, $"Unable to bind to {parsedAddress.ToString()} on any loopback interface."); - throw ex; + throw new IOException($"Failed to bind to address {parsedAddress.ToString()}.", new AggregateException(exceptions)); } } diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs index a438309246..be37bf0674 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net; using System.Net.NetworkInformation; @@ -97,6 +98,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } + [Fact] + public void ThrowsWhenBindingToIPv4AddressInUse() + { + using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + var port = GetNextPort(); + socket.Bind(new IPEndPoint(IPAddress.Loopback, port)); + + var hostBuilder = new WebHostBuilder() + .UseKestrel() + .UseUrls($"http://127.0.0.1:{port}") + .Configure(ConfigureEchoAddress); + + using (var host = hostBuilder.Build()) + { + var exception = Assert.Throws(() => host.Start()); + Assert.Equal($"Failed to bind to address http://127.0.0.1:{port}: address already in use.", exception.Message); + } + } + } + + [ConditionalFact] + [IPv6SupportedCondition] + public void ThrowsWhenBindingToIPv6AddressInUse() + { + using (var socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp)) + { + var port = GetNextPort(); + socket.Bind(new IPEndPoint(IPAddress.IPv6Loopback, port)); + + var hostBuilder = new WebHostBuilder() + .UseKestrel() + .UseUrls($"http://[::1]:{port}") + .Configure(ConfigureEchoAddress); + + using (var host = hostBuilder.Build()) + { + var exception = Assert.Throws(() => host.Start()); + Assert.Equal($"Failed to bind to address http://[::1]:{port}: address already in use.", exception.Message); + } + } + } + [Fact] public void ThrowsWhenBindingLocalhostToIPv4AddressInUse() { @@ -138,8 +182,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests using (var host = hostBuilder.Build()) { - var exception = Assert.Throws(() => host.Start()); - Assert.Contains(exception.InnerExceptions, ex => ex is UvException); + var exception = Assert.Throws(() => host.Start()); + Assert.Equal( + $"Failed to bind to address http://localhost:{port} on the {(addressFamily == AddressFamily.InterNetwork ? "IPv4" : "IPv6")} loopback interface: port already in use.", + exception.Message); } } }