Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit 2dc4b2b

Browse files
author
Cesar Blum Silveira
committed
Bind to both IPv4 and IPv6 when localhost is specified (#231).
1 parent 3f4e232 commit 2dc4b2b

File tree

11 files changed

+125
-73
lines changed

11 files changed

+125
-73
lines changed

src/Microsoft.AspNetCore.Server.Kestrel/Infrastructure/Constants.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ internal class Constants
1111

1212
public const int EOF = -4095;
1313
public static readonly int? ECONNRESET = GetECONNRESET();
14+
public static readonly int? EADDRINUSE = GetEADDRINUSE();
1415

1516
/// <summary>
1617
/// Prefix of host name used to specify Unix sockets in the configuration.
@@ -39,5 +40,20 @@ internal class Constants
3940
return null;
4041
}
4142
}
43+
44+
private static int? GetEADDRINUSE()
45+
{
46+
switch (PlatformServices.Default.Runtime.OperatingSystemPlatform)
47+
{
48+
case Platform.Windows:
49+
return -4091;
50+
case Platform.Linux:
51+
return -98;
52+
case Platform.Darwin:
53+
return -48;
54+
default:
55+
return null;
56+
}
57+
}
4258
}
4359
}

src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Linq;
7+
using System.Net;
78
using System.Reflection;
89
using Microsoft.AspNetCore.Hosting;
910
using Microsoft.AspNetCore.Hosting.Server;
1011
using Microsoft.AspNetCore.Hosting.Server.Features;
1112
using Microsoft.AspNetCore.Http.Features;
1213
using Microsoft.AspNetCore.Server.Kestrel.Http;
1314
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
15+
using Microsoft.AspNetCore.Server.Kestrel.Networking;
1416
using Microsoft.Extensions.Logging;
1517
using Microsoft.Extensions.Options;
1618

@@ -94,6 +96,10 @@ public void Start<TContext>(IHttpApplication<TContext> application)
9496
{
9597
_logger.LogWarning("Unable to determine ECONNRESET value on this platform.");
9698
}
99+
if (!Constants.EADDRINUSE.HasValue)
100+
{
101+
_logger.LogWarning("Unable to determine EADDRINUSE value on this platform.");
102+
}
97103

98104
engine.Start(threadCount);
99105
var atLeastOneListener = false;
@@ -108,8 +114,48 @@ public void Start<TContext>(IHttpApplication<TContext> application)
108114
else
109115
{
110116
atLeastOneListener = true;
111-
_disposables.Push(engine.CreateServer(
112-
parsedAddress));
117+
118+
if (parsedAddress.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase))
119+
{
120+
var exceptionCount = 0;
121+
var originalHost = parsedAddress.Host;
122+
123+
try
124+
{
125+
parsedAddress.Host = IPAddress.Loopback.ToString();
126+
_disposables.Push(engine.CreateServer(
127+
parsedAddress));
128+
}
129+
catch (UvException ex) when (ex.StatusCode != Constants.EADDRINUSE)
130+
{
131+
_logger.LogWarning(0, ex, $"Unable to bind {address} to loopback IPv4 address.");
132+
exceptionCount++;
133+
}
134+
135+
try
136+
{
137+
parsedAddress.Host = IPAddress.IPv6Loopback.ToString();
138+
_disposables.Push(engine.CreateServer(
139+
parsedAddress));
140+
}
141+
catch (UvException ex) when (ex.StatusCode != Constants.EADDRINUSE)
142+
{
143+
_logger.LogWarning(0, ex, $"Unable to bind {address} to loopback IPv6 address.");
144+
exceptionCount++;
145+
}
146+
147+
if (exceptionCount == 2)
148+
{
149+
throw new InvalidOperationException("Binding to localhost failed to bind to any loopback address.");
150+
}
151+
152+
parsedAddress.Host = originalHost;
153+
}
154+
else
155+
{
156+
_disposables.Push(engine.CreateServer(
157+
parsedAddress));
158+
}
113159

114160
// If requested port was "0", replace with assigned dynamic port.
115161
_serverAddresses.Addresses.Remove(address);

src/Microsoft.AspNetCore.Server.Kestrel/Networking/Libuv.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public int Check(int statusCode, out Exception error)
7878
{
7979
var errorName = err_name(statusCode);
8080
var errorDescription = strerror(statusCode);
81-
error = new UvException("Error " + statusCode + " " + errorName + " " + errorDescription);
81+
error = new UvException("Error " + statusCode + " " + errorName + " " + errorDescription, statusCode);
8282
}
8383
else
8484
{

src/Microsoft.AspNetCore.Server.Kestrel/Networking/UvException.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Networking
77
{
88
public class UvException : Exception
99
{
10-
public UvException(string message) : base(message) { }
10+
public UvException(string message, int statusCode) : base(message)
11+
{
12+
StatusCode = statusCode;
13+
}
14+
15+
public int StatusCode { get; }
1116
}
1217
}

src/Microsoft.AspNetCore.Server.Kestrel/Networking/UvTcpHandle.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ public void NoDelay(bool enable)
8181
/// </summary>
8282
public static IPEndPoint CreateIPEndpoint(ServerAddress address)
8383
{
84-
// TODO: IPv6 support
8584
IPAddress ip;
8685

8786
if (!IPAddress.TryParse(address.Host, out ip))

src/Microsoft.AspNetCore.Server.Kestrel/ServerAddress.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel
1111
{
1212
public class ServerAddress
1313
{
14-
public string Host { get; private set; }
14+
public string Host { get; internal set; }
1515
public string PathBase { get; private set; }
1616
public int Port { get; internal set; }
1717
public string Scheme { get; private set; }

test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Collections.Generic;
65
using System.Linq;
76
using System.Net;
87
using System.Net.Http;
@@ -13,8 +12,8 @@
1312
using Microsoft.AspNetCore.Hosting.Server.Features;
1413
using Microsoft.AspNetCore.Http;
1514
using Microsoft.AspNetCore.Http.Extensions;
15+
using Microsoft.AspNetCore.Server.Kestrel.Networking;
1616
using Microsoft.AspNetCore.Testing.xunit;
17-
using Microsoft.Extensions.Configuration;
1817
using Xunit;
1918

2019
namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
@@ -52,16 +51,9 @@ public async Task RegisterAddresses_IPv6ScopeId_Success(string addressInput, Fun
5251

5352
public async Task RegisterAddresses_Success(string addressInput, Func<IServerAddressesFeature, string[]> testUrls)
5453
{
55-
var config = new ConfigurationBuilder()
56-
.AddInMemoryCollection(new Dictionary<string, string>
57-
{
58-
{ "server.urls", addressInput }
59-
})
60-
.Build();
61-
6254
var hostBuilder = new WebHostBuilder()
63-
.UseConfiguration(config)
6455
.UseKestrel()
56+
.UseUrls(addressInput)
6557
.Configure(ConfigureEchoAddress);
6658

6759
using (var host = hostBuilder.Build())
@@ -84,6 +76,39 @@ public async Task RegisterAddresses_Success(string addressInput, Func<IServerAdd
8476
}
8577
}
8678

79+
[Fact]
80+
public void ThrowsWhenBindingLocalhostToIPv4AddressInUse()
81+
{
82+
ThrowsWhenBindingLocalhostToAddressInUse(AddressFamily.InterNetwork, IPAddress.Loopback);
83+
}
84+
85+
[ConditionalFact]
86+
[IPv6SupportedCondition]
87+
public void ThrowsWhenBindingLocalhostToIPv6AddressInUse()
88+
{
89+
ThrowsWhenBindingLocalhostToAddressInUse(AddressFamily.InterNetworkV6, IPAddress.IPv6Loopback);
90+
}
91+
92+
private void ThrowsWhenBindingLocalhostToAddressInUse(AddressFamily addressFamily, IPAddress address)
93+
{
94+
using (var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp))
95+
{
96+
var port = GetNextPort();
97+
socket.Bind(new IPEndPoint(address, port));
98+
99+
var hostBuilder = new WebHostBuilder()
100+
.UseKestrel()
101+
.UseUrls($"http://localhost:{port}")
102+
.Configure(ConfigureEchoAddress);
103+
104+
using (var host = hostBuilder.Build())
105+
{
106+
var exception = Assert.Throws<AggregateException>(() => host.Start());
107+
Assert.Contains(exception.InnerExceptions, ex => ex is UvException);
108+
}
109+
}
110+
}
111+
87112
public static TheoryData<string, Func<IServerAddressesFeature, string[]>> AddressRegistrationDataIPv4
88113
{
89114
get
@@ -147,11 +172,8 @@ public static TheoryData<string, Func<IServerAddressesFeature, string[]>> Addres
147172
// Static port
148173
var port = GetNextPort();
149174
dataset.Add($"http://*:{port}/", _ => new[] { $"http://localhost:{port}/", $"http://127.0.0.1:{port}/", $"http://[::1]:{port}/" });
150-
dataset.Add($"http://localhost:{port}/", _ => new[] { $"http://localhost:{port}/", $"http://127.0.0.1:{port}/",
151-
/* // https://github.com/aspnet/KestrelHttpServer/issues/231
152-
$"http://[::1]:{port}/"
153-
*/ });
154-
dataset.Add($"http://[::1]:{port}/", _ => new[] { $"http://[::1]:{port}/", });
175+
dataset.Add($"http://localhost:{port}/", _ => new[] { $"http://localhost:{port}/", $"http://127.0.0.1:{port}/", $"http://[::1]:{port}/" });
176+
dataset.Add($"http://[::1]:{port}/", _ => new[] { $"http://[::1]:{port}/", $"http://localhost:{port}" });
155177
dataset.Add($"http://127.0.0.1:{port}/;http://[::1]:{port}/", _ => new[] { $"http://127.0.0.1:{port}/", $"http://[::1]:{port}/" });
156178

157179
// Dynamic port

test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/PathBaseTests.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System.Collections.Generic;
54
using System.Net.Http;
65
using System.Threading.Tasks;
76
using Microsoft.AspNetCore.Builder;
87
using Microsoft.AspNetCore.Hosting;
98
using Microsoft.AspNetCore.Http;
10-
using Microsoft.Extensions.Configuration;
119
using Newtonsoft.Json;
1210
using Newtonsoft.Json.Linq;
1311
using Xunit;
@@ -68,14 +66,9 @@ public Task PathBaseCanHaveUTF8Characters()
6866

6967
private async Task TestPathBase(string registerPathBase, string requestPath, string expectedPathBase, string expectedPath)
7068
{
71-
var config = new ConfigurationBuilder().AddInMemoryCollection(
72-
new Dictionary<string, string> {
73-
{ "server.urls", $"http://localhost:0{registerPathBase}" }
74-
}).Build();
75-
7669
var builder = new WebHostBuilder()
77-
.UseConfiguration(config)
7870
.UseKestrel()
71+
.UseUrls($"http://localhost:0{registerPathBase}")
7972
.Configure(app =>
8073
{
8174
app.Run(async context =>

test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/RequestTests.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public async Task LargeUpload()
2424
{
2525
var builder = new WebHostBuilder()
2626
.UseKestrel()
27-
.UseUrls($"http://localhost:0/")
27+
.UseUrls("http://localhost:0/")
2828
.Configure(app =>
2929
{
3030
app.Run(async context =>
@@ -73,7 +73,7 @@ public async Task LargeMultipartUpload()
7373
{
7474
var builder = new WebHostBuilder()
7575
.UseKestrel()
76-
.UseUrls($"http://localhost:0/")
76+
.UseUrls("http://localhost:0/")
7777
.Configure(app =>
7878
{
7979
app.Run(async context =>
@@ -118,18 +118,20 @@ public async Task LargeMultipartUpload()
118118
}
119119

120120
[Theory]
121-
[InlineData("127.0.0.1", "127.0.0.1")]
122-
[InlineData("localhost", "127.0.0.1")]
123-
public Task RemoteIPv4Address(string requestAddress, string expectAddress)
121+
[InlineData("127.0.0.1", "127.0.0.1", "127.0.0.1")]
122+
[InlineData("localhost", "127.0.0.1", "127.0.0.1")]
123+
public Task RemoteIPv4Address(string registerAddress, string requestAddress, string expectAddress)
124124
{
125-
return TestRemoteIPAddress("localhost", requestAddress, expectAddress);
125+
return TestRemoteIPAddress(registerAddress, requestAddress, expectAddress);
126126
}
127127

128-
[ConditionalFact]
128+
[ConditionalTheory]
129129
[IPv6SupportedCondition]
130-
public Task RemoteIPv6Address()
130+
[InlineData("[::1]", "[::1]", "::1")]
131+
[InlineData("localhost", "[::1]", "::1")]
132+
public Task RemoteIPv6Address(string registerAddress, string requestAddress, string expectAddress)
131133
{
132-
return TestRemoteIPAddress("[::1]", "[::1]", "::1");
134+
return TestRemoteIPAddress(registerAddress, requestAddress, expectAddress);
133135
}
134136

135137
[Fact]

test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/ResponseTests.cs

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Collections.Generic;
65
using System.Linq;
76
using System.Net;
87
using System.Net.Http;
98
using System.Threading.Tasks;
109
using Microsoft.AspNetCore.Builder;
1110
using Microsoft.AspNetCore.Hosting;
1211
using Microsoft.AspNetCore.Http;
13-
using Microsoft.Extensions.Configuration;
1412
using Microsoft.Extensions.Primitives;
1513
using Xunit;
1614

@@ -21,16 +19,9 @@ public class ResponseTests
2119
[Fact]
2220
public async Task LargeDownload()
2321
{
24-
var config = new ConfigurationBuilder()
25-
.AddInMemoryCollection(new Dictionary<string, string>
26-
{
27-
{ "server.urls", $"http://localhost:0/" }
28-
})
29-
.Build();
30-
3122
var hostBuilder = new WebHostBuilder()
32-
.UseConfiguration(config)
3323
.UseKestrel()
24+
.UseUrls("http://localhost:0/")
3425
.Configure(app =>
3526
{
3627
app.Run(async context =>
@@ -80,16 +71,9 @@ public async Task LargeDownload()
8071
[Theory, MemberData(nameof(NullHeaderData))]
8172
public async Task IgnoreNullHeaderValues(string headerName, StringValues headerValue, string expectedValue)
8273
{
83-
var config = new ConfigurationBuilder()
84-
.AddInMemoryCollection(new Dictionary<string, string>
85-
{
86-
{ "server.urls", $"http://localhost:0/" }
87-
})
88-
.Build();
89-
9074
var hostBuilder = new WebHostBuilder()
91-
.UseConfiguration(config)
9275
.UseKestrel()
76+
.UseUrls("http://localhost:0/")
9377
.Configure(app =>
9478
{
9579
app.Run(async context =>
@@ -127,19 +111,12 @@ public async Task IgnoreNullHeaderValues(string headerName, StringValues headerV
127111
[Fact]
128112
public async Task OnCompleteCalledEvenWhenOnStartingNotCalled()
129113
{
130-
var config = new ConfigurationBuilder()
131-
.AddInMemoryCollection(new Dictionary<string, string>
132-
{
133-
{ "server.urls", $"http://localhost:0/" }
134-
})
135-
.Build();
136-
137114
var onStartingCalled = false;
138115
var onCompletedCalled = false;
139116

140117
var hostBuilder = new WebHostBuilder()
141-
.UseConfiguration(config)
142118
.UseKestrel()
119+
.UseUrls("http://localhost:0/")
143120
.Configure(app =>
144121
{
145122
app.Run(context =>

0 commit comments

Comments
 (0)