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

Commit 89d1862

Browse files
committed
#2139 Add ListenLocalhost and ListenAnyIP
1 parent 186e980 commit 89d1862

File tree

10 files changed

+340
-144
lines changed

10 files changed

+340
-144
lines changed

samples/SampleApp/Startup.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ public static Task Main(string[] args)
7676
listenOptions.UseConnectionLogging();
7777
});
7878

79+
options.ListenLocalhost(basePort + 2, listenOptions =>
80+
{
81+
listenOptions.UseHttps("testCert.pfx", "testPassword");
82+
});
83+
84+
options.ListenAnyIP(basePort + 3);
85+
7986
options.UseSystemd();
8087

8188
// The following section should be used to demo sockets
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.IO;
6+
using System.Net;
7+
using System.Threading.Tasks;
8+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
9+
using Microsoft.Extensions.Logging;
10+
11+
namespace Microsoft.AspNetCore.Server.Kestrel.Core
12+
{
13+
internal class AnyIPListenOptions : ListenOptions
14+
{
15+
internal AnyIPListenOptions(int port)
16+
: base(new IPEndPoint(IPAddress.IPv6Any, port))
17+
{
18+
}
19+
20+
internal override async Task BindAsync(AddressBindContext context)
21+
{
22+
// when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
23+
try
24+
{
25+
await base.BindAsync(context).ConfigureAwait(false);
26+
}
27+
catch (Exception ex) when (!(ex is IOException))
28+
{
29+
context.Logger.LogDebug(CoreStrings.FormatFallbackToIPv4Any(IPEndPoint.Port));
30+
31+
// for machines that do not support IPv6
32+
IPEndPoint = new IPEndPoint(IPAddress.Any, IPEndPoint.Port);
33+
await base.BindAsync(context).ConfigureAwait(false);
34+
}
35+
}
36+
}
37+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Threading.Tasks;
7+
using Microsoft.Extensions.Logging;
8+
9+
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
10+
{
11+
internal class AddressBindContext
12+
{
13+
public ICollection<string> Addresses { get; set; }
14+
public List<ListenOptions> ListenOptions { get; set; }
15+
public KestrelServerOptions ServerOptions { get; set; }
16+
public ILogger Logger { get; set; }
17+
public IDefaultHttpsProvider DefaultHttpsProvider { get; set; }
18+
19+
public Func<ListenOptions, Task> CreateBinding { get; set; }
20+
}
21+
}

src/Kestrel.Core/Internal/AddressBinder.cs

Lines changed: 19 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,6 @@ public static async Task BindAsync(IServerAddressesFeature addresses,
4747
await strategy.BindAsync(context).ConfigureAwait(false);
4848
}
4949

50-
private class AddressBindContext
51-
{
52-
public ICollection<string> Addresses { get; set; }
53-
public List<ListenOptions> ListenOptions { get; set; }
54-
public KestrelServerOptions ServerOptions { get; set; }
55-
public ILogger Logger { get; set; }
56-
public IDefaultHttpsProvider DefaultHttpsProvider { get; set; }
57-
58-
public Func<ListenOptions, Task> CreateBinding { get; set; }
59-
}
60-
6150
private static IStrategy CreateStrategy(ListenOptions[] listenOptions, string[] addresses, bool preferAddresses)
6251
{
6352
var hasListenOptions = listenOptions.Length > 0;
@@ -109,10 +98,7 @@ protected internal static bool TryCreateIPEndPoint(ServerAddress address, out IP
10998
return true;
11099
}
111100

112-
private static Task BindEndpointAsync(IPEndPoint endpoint, AddressBindContext context)
113-
=> BindEndpointAsync(new ListenOptions(endpoint), context);
114-
115-
private static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindContext context)
101+
internal static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindContext context)
116102
{
117103
try
118104
{
@@ -126,60 +112,7 @@ private static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindC
126112
context.ListenOptions.Add(endpoint);
127113
}
128114

129-
private static async Task BindLocalhostAsync(ServerAddress address, AddressBindContext context, bool https)
130-
{
131-
if (address.Port == 0)
132-
{
133-
throw new InvalidOperationException(CoreStrings.DynamicPortOnLocalhostNotSupported);
134-
}
135-
136-
var exceptions = new List<Exception>();
137-
138-
try
139-
{
140-
var options = new ListenOptions(new IPEndPoint(IPAddress.Loopback, address.Port));
141-
await BindEndpointAsync(options, context).ConfigureAwait(false);
142-
143-
if (https)
144-
{
145-
options.KestrelServerOptions = context.ServerOptions;
146-
context.DefaultHttpsProvider.ConfigureHttps(options);
147-
}
148-
}
149-
catch (Exception ex) when (!(ex is IOException))
150-
{
151-
context.Logger.LogWarning(0, CoreStrings.NetworkInterfaceBindingFailed, address, "IPv4 loopback", ex.Message);
152-
exceptions.Add(ex);
153-
}
154-
155-
try
156-
{
157-
var options = new ListenOptions(new IPEndPoint(IPAddress.IPv6Loopback, address.Port));
158-
await BindEndpointAsync(options, context).ConfigureAwait(false);
159-
160-
if (https)
161-
{
162-
options.KestrelServerOptions = context.ServerOptions;
163-
context.DefaultHttpsProvider.ConfigureHttps(options);
164-
}
165-
}
166-
catch (Exception ex) when (!(ex is IOException))
167-
{
168-
context.Logger.LogWarning(0, CoreStrings.NetworkInterfaceBindingFailed, address, "IPv6 loopback", ex.Message);
169-
exceptions.Add(ex);
170-
}
171-
172-
if (exceptions.Count == 2)
173-
{
174-
throw new IOException(CoreStrings.FormatAddressBindingFailed(address), new AggregateException(exceptions));
175-
}
176-
177-
// If StartLocalhost doesn't throw, there is at least one listener.
178-
// The port cannot change for "localhost".
179-
context.Addresses.Add(address.ToString());
180-
}
181-
182-
private static async Task BindAddressAsync(string address, AddressBindContext context)
115+
internal static ListenOptions ParseAddress(string address, KestrelServerOptions serverOptions, IDefaultHttpsProvider defaultHttpsProvider)
183116
{
184117
var parsedAddress = ServerAddress.FromUrl(address);
185118
var https = false;
@@ -202,47 +135,29 @@ private static async Task BindAddressAsync(string address, AddressBindContext co
202135
if (parsedAddress.IsUnixPipe)
203136
{
204137
options = new ListenOptions(parsedAddress.UnixPipePath);
205-
await BindEndpointAsync(options, context).ConfigureAwait(false);
206-
context.Addresses.Add(options.GetDisplayName());
207138
}
208139
else if (string.Equals(parsedAddress.Host, "localhost", StringComparison.OrdinalIgnoreCase))
209140
{
210141
// "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
211-
await BindLocalhostAsync(parsedAddress, context, https).ConfigureAwait(false);
142+
options = new LocalhostListenOptions(parsedAddress.Port);
143+
}
144+
else if (TryCreateIPEndPoint(parsedAddress, out var endpoint))
145+
{
146+
options = new ListenOptions(endpoint);
212147
}
213148
else
214149
{
215-
if (TryCreateIPEndPoint(parsedAddress, out var endpoint))
216-
{
217-
options = new ListenOptions(endpoint);
218-
await BindEndpointAsync(options, context).ConfigureAwait(false);
219-
}
220-
else
221-
{
222-
// when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
223-
try
224-
{
225-
options = new ListenOptions(new IPEndPoint(IPAddress.IPv6Any, parsedAddress.Port));
226-
await BindEndpointAsync(options, context).ConfigureAwait(false);
227-
}
228-
catch (Exception ex) when (!(ex is IOException))
229-
{
230-
context.Logger.LogDebug(CoreStrings.FormatFallbackToIPv4Any(parsedAddress.Port));
231-
232-
// for machines that do not support IPv6
233-
options = new ListenOptions(new IPEndPoint(IPAddress.Any, parsedAddress.Port));
234-
await BindEndpointAsync(options, context).ConfigureAwait(false);
235-
}
236-
}
237-
238-
context.Addresses.Add(options.GetDisplayName());
150+
// when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
151+
options = new AnyIPListenOptions(parsedAddress.Port);
239152
}
240153

241-
if (https && options != null)
154+
if (https)
242155
{
243-
options.KestrelServerOptions = context.ServerOptions;
244-
context.DefaultHttpsProvider.ConfigureHttps(options);
156+
options.KestrelServerOptions = serverOptions;
157+
defaultHttpsProvider.ConfigureHttps(options);
245158
}
159+
160+
return options;
246161
}
247162

248163
private interface IStrategy
@@ -256,7 +171,8 @@ public async Task BindAsync(AddressBindContext context)
256171
{
257172
context.Logger.LogDebug(CoreStrings.BindingToDefaultAddress, Constants.DefaultServerAddress);
258173

259-
await BindLocalhostAsync(ServerAddress.FromUrl(Constants.DefaultServerAddress), context, https: false).ConfigureAwait(false);
174+
await ParseAddress(Constants.DefaultServerAddress, context.ServerOptions, context.DefaultHttpsProvider)
175+
.BindAsync(context).ConfigureAwait(false);
260176
}
261177
}
262178

@@ -308,9 +224,7 @@ public virtual async Task BindAsync(AddressBindContext context)
308224
{
309225
foreach (var endpoint in _endpoints)
310226
{
311-
await BindEndpointAsync(endpoint, context).ConfigureAwait(false);
312-
313-
context.Addresses.Add(endpoint.GetDisplayName());
227+
await endpoint.BindAsync(context).ConfigureAwait(false);
314228
}
315229
}
316230
}
@@ -328,7 +242,8 @@ public virtual async Task BindAsync(AddressBindContext context)
328242
{
329243
foreach (var address in _addresses)
330244
{
331-
await BindAddressAsync(address, context).ConfigureAwait(false);
245+
await ParseAddress(address, context.ServerOptions, context.DefaultHttpsProvider)
246+
.BindAsync(context).ConfigureAwait(false);
332247
}
333248
}
334249
}

src/Kestrel.Core/KestrelServerOptions.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,40 @@ public void Listen(IPEndPoint endPoint, Action<ListenOptions> configure)
105105
ListenOptions.Add(listenOptions);
106106
}
107107

108+
public void ListenLocalhost(int port) => ListenLocalhost(port, options => { });
109+
110+
public void ListenLocalhost(int port, Action<ListenOptions> configure)
111+
{
112+
if (configure == null)
113+
{
114+
throw new ArgumentNullException(nameof(configure));
115+
}
116+
117+
var listenOptions = new LocalhostListenOptions(port)
118+
{
119+
KestrelServerOptions = this,
120+
};
121+
configure(listenOptions);
122+
ListenOptions.Add(listenOptions);
123+
}
124+
125+
public void ListenAnyIP(int port) => ListenAnyIP(port, options => { });
126+
127+
public void ListenAnyIP(int port, Action<ListenOptions> configure)
128+
{
129+
if (configure == null)
130+
{
131+
throw new ArgumentNullException(nameof(configure));
132+
}
133+
134+
var listenOptions = new AnyIPListenOptions(port)
135+
{
136+
KestrelServerOptions = this,
137+
};
138+
configure(listenOptions);
139+
ListenOptions.Add(listenOptions);
140+
}
141+
108142
/// <summary>
109143
/// Bind to given Unix domain socket path.
110144
/// </summary>

src/Kestrel.Core/ListenOptions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Threading.Tasks;
99
using Microsoft.AspNetCore.Protocols;
1010
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
11+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
1112
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
1213

1314
namespace Microsoft.AspNetCore.Server.Kestrel.Core
@@ -140,7 +141,7 @@ public FileHandleType HandleType
140141
/// <summary>
141142
/// Gets the name of this endpoint to display on command-line when the web server starts.
142143
/// </summary>
143-
internal string GetDisplayName()
144+
internal virtual string GetDisplayName()
144145
{
145146
var scheme = ConnectionAdapters.Any(f => f.IsHttps)
146147
? "https"
@@ -182,5 +183,11 @@ public ConnectionDelegate Build()
182183

183184
return app;
184185
}
186+
187+
internal virtual async Task BindAsync(AddressBindContext context)
188+
{
189+
await AddressBinder.BindEndpointAsync(this, context).ConfigureAwait(false);
190+
context.Addresses.Add(GetDisplayName());
191+
}
185192
}
186193
}

0 commit comments

Comments
 (0)