KestrelHttpServer 279 B

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. commit 89d1862f211e15d8aae367a6d86de26b173bf6b0
  2. Author: Chris Ross (ASP.NET) <[email protected]>
  3. Date: Tue Nov 14 15:54:30 2017 -0800
  4. #2139 Add ListenLocalhost and ListenAnyIP
  5. diff --git a/samples/SampleApp/Startup.cs b/samples/SampleApp/Startup.cs
  6. index 562f3a020dc..bf8ce0e1a38 100644
  7. --- a/samples/SampleApp/Startup.cs
  8. +++ b/samples/SampleApp/Startup.cs
  9. @@ -76,6 +76,13 @@ namespace SampleApp
  10. listenOptions.UseConnectionLogging();
  11. });
  12. + options.ListenLocalhost(basePort + 2, listenOptions =>
  13. + {
  14. + listenOptions.UseHttps("testCert.pfx", "testPassword");
  15. + });
  16. +
  17. + options.ListenAnyIP(basePort + 3);
  18. +
  19. options.UseSystemd();
  20. // The following section should be used to demo sockets
  21. diff --git a/src/Kestrel.Core/AnyIPListenOptions.cs b/src/Kestrel.Core/AnyIPListenOptions.cs
  22. new file mode 100644
  23. index 00000000000..2639337dd72
  24. --- /dev/null
  25. +++ b/src/Kestrel.Core/AnyIPListenOptions.cs
  26. @@ -0,0 +1,37 @@
  27. +// Copyright (c) .NET Foundation. All rights reserved.
  28. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  29. +
  30. +using System;
  31. +using System.IO;
  32. +using System.Net;
  33. +using System.Threading.Tasks;
  34. +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
  35. +using Microsoft.Extensions.Logging;
  36. +
  37. +namespace Microsoft.AspNetCore.Server.Kestrel.Core
  38. +{
  39. + internal class AnyIPListenOptions : ListenOptions
  40. + {
  41. + internal AnyIPListenOptions(int port)
  42. + : base(new IPEndPoint(IPAddress.IPv6Any, port))
  43. + {
  44. + }
  45. +
  46. + internal override async Task BindAsync(AddressBindContext context)
  47. + {
  48. + // when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
  49. + try
  50. + {
  51. + await base.BindAsync(context).ConfigureAwait(false);
  52. + }
  53. + catch (Exception ex) when (!(ex is IOException))
  54. + {
  55. + context.Logger.LogDebug(CoreStrings.FormatFallbackToIPv4Any(IPEndPoint.Port));
  56. +
  57. + // for machines that do not support IPv6
  58. + IPEndPoint = new IPEndPoint(IPAddress.Any, IPEndPoint.Port);
  59. + await base.BindAsync(context).ConfigureAwait(false);
  60. + }
  61. + }
  62. + }
  63. +}
  64. diff --git a/src/Kestrel.Core/Internal/AddressBindContext.cs b/src/Kestrel.Core/Internal/AddressBindContext.cs
  65. new file mode 100644
  66. index 00000000000..e2f46e60257
  67. --- /dev/null
  68. +++ b/src/Kestrel.Core/Internal/AddressBindContext.cs
  69. @@ -0,0 +1,21 @@
  70. +// Copyright (c) .NET Foundation. All rights reserved.
  71. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  72. +
  73. +using System;
  74. +using System.Collections.Generic;
  75. +using System.Threading.Tasks;
  76. +using Microsoft.Extensions.Logging;
  77. +
  78. +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
  79. +{
  80. + internal class AddressBindContext
  81. + {
  82. + public ICollection<string> Addresses { get; set; }
  83. + public List<ListenOptions> ListenOptions { get; set; }
  84. + public KestrelServerOptions ServerOptions { get; set; }
  85. + public ILogger Logger { get; set; }
  86. + public IDefaultHttpsProvider DefaultHttpsProvider { get; set; }
  87. +
  88. + public Func<ListenOptions, Task> CreateBinding { get; set; }
  89. + }
  90. +}
  91. diff --git a/src/Kestrel.Core/Internal/AddressBinder.cs b/src/Kestrel.Core/Internal/AddressBinder.cs
  92. index 808675c0fb8..5334308ad83 100644
  93. --- a/src/Kestrel.Core/Internal/AddressBinder.cs
  94. +++ b/src/Kestrel.Core/Internal/AddressBinder.cs
  95. @@ -47,17 +47,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
  96. await strategy.BindAsync(context).ConfigureAwait(false);
  97. }
  98. - private class AddressBindContext
  99. - {
  100. - public ICollection<string> Addresses { get; set; }
  101. - public List<ListenOptions> ListenOptions { get; set; }
  102. - public KestrelServerOptions ServerOptions { get; set; }
  103. - public ILogger Logger { get; set; }
  104. - public IDefaultHttpsProvider DefaultHttpsProvider { get; set; }
  105. -
  106. - public Func<ListenOptions, Task> CreateBinding { get; set; }
  107. - }
  108. -
  109. private static IStrategy CreateStrategy(ListenOptions[] listenOptions, string[] addresses, bool preferAddresses)
  110. {
  111. var hasListenOptions = listenOptions.Length > 0;
  112. @@ -109,10 +98,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
  113. return true;
  114. }
  115. - private static Task BindEndpointAsync(IPEndPoint endpoint, AddressBindContext context)
  116. - => BindEndpointAsync(new ListenOptions(endpoint), context);
  117. -
  118. - private static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindContext context)
  119. + internal static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindContext context)
  120. {
  121. try
  122. {
  123. @@ -126,60 +112,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
  124. context.ListenOptions.Add(endpoint);
  125. }
  126. - private static async Task BindLocalhostAsync(ServerAddress address, AddressBindContext context, bool https)
  127. - {
  128. - if (address.Port == 0)
  129. - {
  130. - throw new InvalidOperationException(CoreStrings.DynamicPortOnLocalhostNotSupported);
  131. - }
  132. -
  133. - var exceptions = new List<Exception>();
  134. -
  135. - try
  136. - {
  137. - var options = new ListenOptions(new IPEndPoint(IPAddress.Loopback, address.Port));
  138. - await BindEndpointAsync(options, context).ConfigureAwait(false);
  139. -
  140. - if (https)
  141. - {
  142. - options.KestrelServerOptions = context.ServerOptions;
  143. - context.DefaultHttpsProvider.ConfigureHttps(options);
  144. - }
  145. - }
  146. - catch (Exception ex) when (!(ex is IOException))
  147. - {
  148. - context.Logger.LogWarning(0, CoreStrings.NetworkInterfaceBindingFailed, address, "IPv4 loopback", ex.Message);
  149. - exceptions.Add(ex);
  150. - }
  151. -
  152. - try
  153. - {
  154. - var options = new ListenOptions(new IPEndPoint(IPAddress.IPv6Loopback, address.Port));
  155. - await BindEndpointAsync(options, context).ConfigureAwait(false);
  156. -
  157. - if (https)
  158. - {
  159. - options.KestrelServerOptions = context.ServerOptions;
  160. - context.DefaultHttpsProvider.ConfigureHttps(options);
  161. - }
  162. - }
  163. - catch (Exception ex) when (!(ex is IOException))
  164. - {
  165. - context.Logger.LogWarning(0, CoreStrings.NetworkInterfaceBindingFailed, address, "IPv6 loopback", ex.Message);
  166. - exceptions.Add(ex);
  167. - }
  168. -
  169. - if (exceptions.Count == 2)
  170. - {
  171. - throw new IOException(CoreStrings.FormatAddressBindingFailed(address), new AggregateException(exceptions));
  172. - }
  173. -
  174. - // If StartLocalhost doesn't throw, there is at least one listener.
  175. - // The port cannot change for "localhost".
  176. - context.Addresses.Add(address.ToString());
  177. - }
  178. -
  179. - private static async Task BindAddressAsync(string address, AddressBindContext context)
  180. + internal static ListenOptions ParseAddress(string address, KestrelServerOptions serverOptions, IDefaultHttpsProvider defaultHttpsProvider)
  181. {
  182. var parsedAddress = ServerAddress.FromUrl(address);
  183. var https = false;
  184. @@ -202,47 +135,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
  185. if (parsedAddress.IsUnixPipe)
  186. {
  187. options = new ListenOptions(parsedAddress.UnixPipePath);
  188. - await BindEndpointAsync(options, context).ConfigureAwait(false);
  189. - context.Addresses.Add(options.GetDisplayName());
  190. }
  191. else if (string.Equals(parsedAddress.Host, "localhost", StringComparison.OrdinalIgnoreCase))
  192. {
  193. // "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
  194. - await BindLocalhostAsync(parsedAddress, context, https).ConfigureAwait(false);
  195. + options = new LocalhostListenOptions(parsedAddress.Port);
  196. + }
  197. + else if (TryCreateIPEndPoint(parsedAddress, out var endpoint))
  198. + {
  199. + options = new ListenOptions(endpoint);
  200. }
  201. else
  202. {
  203. - if (TryCreateIPEndPoint(parsedAddress, out var endpoint))
  204. - {
  205. - options = new ListenOptions(endpoint);
  206. - await BindEndpointAsync(options, context).ConfigureAwait(false);
  207. - }
  208. - else
  209. - {
  210. - // when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
  211. - try
  212. - {
  213. - options = new ListenOptions(new IPEndPoint(IPAddress.IPv6Any, parsedAddress.Port));
  214. - await BindEndpointAsync(options, context).ConfigureAwait(false);
  215. - }
  216. - catch (Exception ex) when (!(ex is IOException))
  217. - {
  218. - context.Logger.LogDebug(CoreStrings.FormatFallbackToIPv4Any(parsedAddress.Port));
  219. -
  220. - // for machines that do not support IPv6
  221. - options = new ListenOptions(new IPEndPoint(IPAddress.Any, parsedAddress.Port));
  222. - await BindEndpointAsync(options, context).ConfigureAwait(false);
  223. - }
  224. - }
  225. -
  226. - context.Addresses.Add(options.GetDisplayName());
  227. + // when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
  228. + options = new AnyIPListenOptions(parsedAddress.Port);
  229. }
  230. - if (https && options != null)
  231. + if (https)
  232. {
  233. - options.KestrelServerOptions = context.ServerOptions;
  234. - context.DefaultHttpsProvider.ConfigureHttps(options);
  235. + options.KestrelServerOptions = serverOptions;
  236. + defaultHttpsProvider.ConfigureHttps(options);
  237. }
  238. +
  239. + return options;
  240. }
  241. private interface IStrategy
  242. @@ -256,7 +171,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
  243. {
  244. context.Logger.LogDebug(CoreStrings.BindingToDefaultAddress, Constants.DefaultServerAddress);
  245. - await BindLocalhostAsync(ServerAddress.FromUrl(Constants.DefaultServerAddress), context, https: false).ConfigureAwait(false);
  246. + await ParseAddress(Constants.DefaultServerAddress, context.ServerOptions, context.DefaultHttpsProvider)
  247. + .BindAsync(context).ConfigureAwait(false);
  248. }
  249. }
  250. @@ -308,9 +224,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
  251. {
  252. foreach (var endpoint in _endpoints)
  253. {
  254. - await BindEndpointAsync(endpoint, context).ConfigureAwait(false);
  255. -
  256. - context.Addresses.Add(endpoint.GetDisplayName());
  257. + await endpoint.BindAsync(context).ConfigureAwait(false);
  258. }
  259. }
  260. }
  261. @@ -328,7 +242,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
  262. {
  263. foreach (var address in _addresses)
  264. {
  265. - await BindAddressAsync(address, context).ConfigureAwait(false);
  266. + await ParseAddress(address, context.ServerOptions, context.DefaultHttpsProvider)
  267. + .BindAsync(context).ConfigureAwait(false);
  268. }
  269. }
  270. }
  271. diff --git a/src/Kestrel.Core/KestrelServerOptions.cs b/src/Kestrel.Core/KestrelServerOptions.cs
  272. index 136b983533b..3d70664cbe7 100644
  273. --- a/src/Kestrel.Core/KestrelServerOptions.cs
  274. +++ b/src/Kestrel.Core/KestrelServerOptions.cs
  275. @@ -105,6 +105,40 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
  276. ListenOptions.Add(listenOptions);
  277. }
  278. + public void ListenLocalhost(int port) => ListenLocalhost(port, options => { });
  279. +
  280. + public void ListenLocalhost(int port, Action<ListenOptions> configure)
  281. + {
  282. + if (configure == null)
  283. + {
  284. + throw new ArgumentNullException(nameof(configure));
  285. + }
  286. +
  287. + var listenOptions = new LocalhostListenOptions(port)
  288. + {
  289. + KestrelServerOptions = this,
  290. + };
  291. + configure(listenOptions);
  292. + ListenOptions.Add(listenOptions);
  293. + }
  294. +
  295. + public void ListenAnyIP(int port) => ListenAnyIP(port, options => { });
  296. +
  297. + public void ListenAnyIP(int port, Action<ListenOptions> configure)
  298. + {
  299. + if (configure == null)
  300. + {
  301. + throw new ArgumentNullException(nameof(configure));
  302. + }
  303. +
  304. + var listenOptions = new AnyIPListenOptions(port)
  305. + {
  306. + KestrelServerOptions = this,
  307. + };
  308. + configure(listenOptions);
  309. + ListenOptions.Add(listenOptions);
  310. + }
  311. +
  312. /// <summary>
  313. /// Bind to given Unix domain socket path.
  314. /// </summary>
  315. diff --git a/src/Kestrel.Core/ListenOptions.cs b/src/Kestrel.Core/ListenOptions.cs
  316. index 19e94c43a6e..08e728a35cf 100644
  317. --- a/src/Kestrel.Core/ListenOptions.cs
  318. +++ b/src/Kestrel.Core/ListenOptions.cs
  319. @@ -8,6 +8,7 @@ using System.Net;
  320. using System.Threading.Tasks;
  321. using Microsoft.AspNetCore.Protocols;
  322. using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
  323. +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
  324. using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
  325. namespace Microsoft.AspNetCore.Server.Kestrel.Core
  326. @@ -140,7 +141,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
  327. /// <summary>
  328. /// Gets the name of this endpoint to display on command-line when the web server starts.
  329. /// </summary>
  330. - internal string GetDisplayName()
  331. + internal virtual string GetDisplayName()
  332. {
  333. var scheme = ConnectionAdapters.Any(f => f.IsHttps)
  334. ? "https"
  335. @@ -182,5 +183,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
  336. return app;
  337. }
  338. +
  339. + internal virtual async Task BindAsync(AddressBindContext context)
  340. + {
  341. + await AddressBinder.BindEndpointAsync(this, context).ConfigureAwait(false);
  342. + context.Addresses.Add(GetDisplayName());
  343. + }
  344. }
  345. }
  346. diff --git a/src/Kestrel.Core/LocalhostListenOptions.cs b/src/Kestrel.Core/LocalhostListenOptions.cs
  347. new file mode 100644
  348. index 00000000000..39c49abbfc4
  349. --- /dev/null
  350. +++ b/src/Kestrel.Core/LocalhostListenOptions.cs
  351. @@ -0,0 +1,88 @@
  352. +// Copyright (c) .NET Foundation. All rights reserved.
  353. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  354. +
  355. +using System;
  356. +using System.Collections.Generic;
  357. +using System.IO;
  358. +using System.Linq;
  359. +using System.Net;
  360. +using System.Threading.Tasks;
  361. +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
  362. +using Microsoft.Extensions.Logging;
  363. +
  364. +namespace Microsoft.AspNetCore.Server.Kestrel.Core
  365. +{
  366. + internal class LocalhostListenOptions : ListenOptions
  367. + {
  368. + internal LocalhostListenOptions(int port)
  369. + : base(new IPEndPoint(IPAddress.Loopback, port))
  370. + {
  371. + if (port == 0)
  372. + {
  373. + throw new InvalidOperationException(CoreStrings.DynamicPortOnLocalhostNotSupported);
  374. + }
  375. + }
  376. +
  377. + /// <summary>
  378. + /// Gets the name of this endpoint to display on command-line when the web server starts.
  379. + /// </summary>
  380. + internal override string GetDisplayName()
  381. + {
  382. + var scheme = ConnectionAdapters.Any(f => f.IsHttps)
  383. + ? "https"
  384. + : "http";
  385. +
  386. + return $"{scheme}://localhost:{IPEndPoint.Port}";
  387. + }
  388. +
  389. + internal override async Task BindAsync(AddressBindContext context)
  390. + {
  391. + var exceptions = new List<Exception>();
  392. +
  393. + try
  394. + {
  395. + var v4Options = Clone(IPAddress.Loopback);
  396. + await AddressBinder.BindEndpointAsync(v4Options, context).ConfigureAwait(false);
  397. + }
  398. + catch (Exception ex) when (!(ex is IOException))
  399. + {
  400. + context.Logger.LogWarning(0, CoreStrings.NetworkInterfaceBindingFailed, GetDisplayName(), "IPv4 loopback", ex.Message);
  401. + exceptions.Add(ex);
  402. + }
  403. +
  404. + try
  405. + {
  406. + var v6Options = Clone(IPAddress.IPv6Loopback);
  407. + await AddressBinder.BindEndpointAsync(v6Options, context).ConfigureAwait(false);
  408. + }
  409. + catch (Exception ex) when (!(ex is IOException))
  410. + {
  411. + context.Logger.LogWarning(0, CoreStrings.NetworkInterfaceBindingFailed, GetDisplayName(), "IPv6 loopback", ex.Message);
  412. + exceptions.Add(ex);
  413. + }
  414. +
  415. + if (exceptions.Count == 2)
  416. + {
  417. + throw new IOException(CoreStrings.FormatAddressBindingFailed(GetDisplayName()), new AggregateException(exceptions));
  418. + }
  419. +
  420. + // If StartLocalhost doesn't throw, there is at least one listener.
  421. + // The port cannot change for "localhost".
  422. + context.Addresses.Add(GetDisplayName());
  423. + }
  424. +
  425. + // used for cloning to two IPEndpoints
  426. + private ListenOptions Clone(IPAddress address)
  427. + {
  428. + var options = new ListenOptions(new IPEndPoint(address, IPEndPoint.Port))
  429. + {
  430. + HandleType = HandleType,
  431. + KestrelServerOptions = KestrelServerOptions,
  432. + NoDelay = NoDelay,
  433. + Protocols = Protocols,
  434. + };
  435. + options.ConnectionAdapters.AddRange(ConnectionAdapters);
  436. + return options;
  437. + }
  438. + }
  439. +}
  440. diff --git a/test/Kestrel.Core.Tests/AddressBinderTests.cs b/test/Kestrel.Core.Tests/AddressBinderTests.cs
  441. index d42a598e9c4..f2580353db4 100644
  442. --- a/test/Kestrel.Core.Tests/AddressBinderTests.cs
  443. +++ b/test/Kestrel.Core.Tests/AddressBinderTests.cs
  444. @@ -2,12 +2,12 @@
  445. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  446. using System;
  447. -using System.Collections.Generic;
  448. using System.IO;
  449. using System.Net;
  450. using System.Threading.Tasks;
  451. using Microsoft.AspNetCore.Protocols;
  452. using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
  453. +using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
  454. using Microsoft.AspNetCore.Testing;
  455. using Microsoft.Extensions.Logging.Abstractions;
  456. using Moq;
  457. @@ -51,24 +51,49 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
  458. [InlineData("randomhost")]
  459. [InlineData("+")]
  460. [InlineData("contoso.com")]
  461. - public async Task DefaultsToIPv6AnyOnInvalidIPAddress(string host)
  462. + public void ParseAddressDefaultsToAnyIPOnInvalidIPAddress(string host)
  463. {
  464. - var addresses = new ServerAddressesFeature();
  465. - addresses.Addresses.Add($"http://{host}");
  466. var options = new KestrelServerOptions();
  467. + var listenOptions = AddressBinder.ParseAddress($"http://{host}", options, Mock.Of<IDefaultHttpsProvider>());
  468. + Assert.IsType<AnyIPListenOptions>(listenOptions);
  469. + Assert.Equal(ListenType.IPEndPoint, listenOptions.Type);
  470. + Assert.Equal(IPAddress.IPv6Any, listenOptions.IPEndPoint.Address);
  471. + Assert.Equal(80, listenOptions.IPEndPoint.Port);
  472. + }
  473. - var tcs = new TaskCompletionSource<ListenOptions>();
  474. - await AddressBinder.BindAsync(addresses,
  475. - options,
  476. - NullLogger.Instance,
  477. - Mock.Of<IDefaultHttpsProvider>(),
  478. - endpoint =>
  479. - {
  480. - tcs.TrySetResult(endpoint);
  481. - return Task.CompletedTask;
  482. - });
  483. - var result = await tcs.Task;
  484. - Assert.Equal(IPAddress.IPv6Any, result.IPEndPoint.Address);
  485. + [Fact]
  486. + public void ParseAddressLocalhost()
  487. + {
  488. + var options = new KestrelServerOptions();
  489. + var listenOptions = AddressBinder.ParseAddress("http://localhost", options, Mock.Of<IDefaultHttpsProvider>());
  490. + Assert.IsType<LocalhostListenOptions>(listenOptions);
  491. + Assert.Equal(ListenType.IPEndPoint, listenOptions.Type);
  492. + Assert.Equal(IPAddress.Loopback, listenOptions.IPEndPoint.Address);
  493. + Assert.Equal(80, listenOptions.IPEndPoint.Port);
  494. + }
  495. +
  496. + [Fact]
  497. + public void ParseAddressUnixPipe()
  498. + {
  499. + var options = new KestrelServerOptions();
  500. + var listenOptions = AddressBinder.ParseAddress("http://unix:/tmp/kestrel-test.sock", options, Mock.Of<IDefaultHttpsProvider>());
  501. + Assert.Equal(ListenType.SocketPath, listenOptions.Type);
  502. + Assert.Equal("/tmp/kestrel-test.sock", listenOptions.SocketPath);
  503. + }
  504. +
  505. + [Theory]
  506. + [InlineData("http://10.10.10.10:5000/", "10.10.10.10", 5000)]
  507. + [InlineData("http://[::1]:5000", "::1", 5000)]
  508. + [InlineData("http://[::1]", "::1", 80)]
  509. + [InlineData("http://127.0.0.1", "127.0.0.1", 80)]
  510. + [InlineData("https://127.0.0.1", "127.0.0.1", 443)]
  511. + public void ParseAddressIP(string address, string ip, int port)
  512. + {
  513. + var options = new KestrelServerOptions();
  514. + var listenOptions = AddressBinder.ParseAddress(address, options, Mock.Of<IDefaultHttpsProvider>());
  515. + Assert.Equal(ListenType.IPEndPoint, listenOptions.Type);
  516. + Assert.Equal(IPAddress.Parse(ip), listenOptions.IPEndPoint.Address);
  517. + Assert.Equal(port, listenOptions.IPEndPoint.Port);
  518. }
  519. [Fact]
  520. diff --git a/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs b/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs
  521. index 446a201f71d..58d7369b500 100644
  522. --- a/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs
  523. +++ b/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs
  524. @@ -187,7 +187,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
  525. private Task RegisterAddresses_Success(string addressInput, string testUrl, int testPort = 0)
  526. => RegisterAddresses_Success(addressInput, new[] { testUrl }, testPort);
  527. - private async Task RegisterAddresses_StaticPort_Success(string addressInput, string[] testUrls)
  528. + private Task RegisterAddresses_StaticPort_Success(string addressInput, string[] testUrls) =>
  529. + RunTestWithStaticPort(port => RegisterAddresses_Success($"{addressInput}:{port}", testUrls, port));
  530. +
  531. + private async Task RunTestWithStaticPort(Func<int, Task> test)
  532. {
  533. var retryCount = 0;
  534. var errors = new List<Exception>();
  535. @@ -197,7 +200,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
  536. try
  537. {
  538. var port = GetNextPort();
  539. - await RegisterAddresses_Success($"{addressInput}:{port}", testUrls, port);
  540. + await test(port);
  541. return;
  542. }
  543. catch (XunitException)
  544. @@ -254,34 +257,93 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests
  545. }
  546. }
  547. - private async Task RegisterIPEndPoint_StaticPort_Success(IPAddress address, string testUrl)
  548. + private Task RegisterIPEndPoint_StaticPort_Success(IPAddress address, string testUrl)
  549. + => RunTestWithStaticPort(port => RegisterIPEndPoint_Success(new IPEndPoint(address, port), testUrl, port));
  550. +
  551. + [ConditionalFact]
  552. + public async Task ListenAnyIP_IPv4_Success()
  553. {
  554. - var retryCount = 0;
  555. - var errors = new List<Exception>();
  556. + await ListenAnyIP_Success(new[] { "http://localhost", "http://127.0.0.1" });
  557. + }
  558. - while (retryCount < MaxRetries)
  559. - {
  560. - try
  561. - {
  562. - var port = GetNextPort();
  563. - await RegisterIPEndPoint_Success(new IPEndPoint(address, port), testUrl, port);
  564. - return;
  565. - }
  566. - catch (XunitException)
  567. + [ConditionalFact]
  568. + [IPv6SupportedCondition]
  569. + public async Task ListenAnyIP_IPv6_Success()
  570. + {
  571. + await ListenAnyIP_Success(new[] { "http://[::1]", "http://localhost", "http://127.0.0.1" });
  572. + }
  573. +
  574. + [ConditionalFact]
  575. + [NetworkIsReachable]
  576. + public async Task ListenAnyIP_HostName_Success()
  577. + {
  578. + var hostName = Dns.GetHostName();
  579. + await ListenAnyIP_Success(new[] { $"http://{hostName}" });
  580. + }
  581. +
  582. + private async Task ListenAnyIP_Success(string[] testUrls, int testPort = 0)
  583. + {
  584. + var hostBuilder = TransportSelector.GetWebHostBuilder()
  585. + .UseKestrel(options =>
  586. {
  587. - throw;
  588. - }
  589. - catch (Exception ex)
  590. + options.ListenAnyIP(testPort);
  591. + })
  592. + .ConfigureLogging(_configureLoggingDelegate)
  593. + .Configure(ConfigureEchoAddress);
  594. +
  595. + using (var host = hostBuilder.Build())
  596. + {
  597. + host.Start();
  598. +
  599. + foreach (var testUrl in testUrls.Select(testUrl => $"{testUrl}:{(testPort == 0 ? host.GetPort() : testPort)}"))
  600. {
  601. - errors.Add(ex);
  602. - }
  603. + var response = await HttpClientSlim.GetStringAsync(testUrl, validateCertificate: false);
  604. - retryCount++;
  605. + // Compare the response with Uri.ToString(), rather than testUrl directly.
  606. + // Required to handle IPv6 addresses with zone index, like "fe80::3%1"
  607. + Assert.Equal(new Uri(testUrl).ToString(), response);
  608. + }
  609. }
  610. + }
  611. - if (errors.Any())
  612. + [ConditionalFact]
  613. + public async Task ListenLocalhost_IPv4LocalhostStaticPort_Success()
  614. + {
  615. + await ListenLocalhost_StaticPort_Success(new[] { "http://localhost", "http://127.0.0.1" });
  616. + }
  617. +
  618. + [ConditionalFact]
  619. + [IPv6SupportedCondition]
  620. + public async Task ListenLocalhost_IPv6LocalhostStaticPort_Success()
  621. + {
  622. + await ListenLocalhost_StaticPort_Success(new[] { "http://localhost", "http://127.0.0.1", "http://[::1]" });
  623. + }
  624. +
  625. + private Task ListenLocalhost_StaticPort_Success(string[] testUrls) =>
  626. + RunTestWithStaticPort(port => ListenLocalhost_Success(testUrls, port));
  627. +
  628. + private async Task ListenLocalhost_Success(string[] testUrls, int testPort = 0)
  629. + {
  630. + var hostBuilder = TransportSelector.GetWebHostBuilder()
  631. + .UseKestrel(options =>
  632. + {
  633. + options.ListenLocalhost(testPort);
  634. + })
  635. + .ConfigureLogging(_configureLoggingDelegate)
  636. + .Configure(ConfigureEchoAddress);
  637. +
  638. + using (var host = hostBuilder.Build())
  639. {
  640. - throw new AggregateException(errors);
  641. + host.Start();
  642. +
  643. + foreach (var testUrl in testUrls.Select(testUrl => $"{testUrl}:{(testPort == 0 ? host.GetPort() : testPort)}"))
  644. + {
  645. + var response = await HttpClientSlim.GetStringAsync(testUrl, validateCertificate: false);
  646. +
  647. + // Compare the response with Uri.ToString(), rather than testUrl directly.
  648. + // Required to handle IPv6 addresses with zone index, like "fe80::3%1"
  649. + Assert.Equal(new Uri(testUrl).ToString(), response);
  650. + }
  651. }
  652. }
  653. diff --git a/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs b/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs
  654. index 2a7b0ee4c17..54dd7ed0132 100644
  655. --- a/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs
  656. +++ b/test/Kestrel.Transport.Libuv.Tests/LibuvOutputConsumerTests.cs
  657. @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
  658. _bufferPool = new MemoryPool();
  659. _mockLibuv = new MockLibuv();
  660. - var libuvTransport = new LibuvTransport(_mockLibuv, new TestLibuvTransportContext(), new ListenOptions(0));
  661. + var libuvTransport = new LibuvTransport(_mockLibuv, new TestLibuvTransportContext(), new ListenOptions((ulong)0));
  662. _libuvThread = new LibuvThread(libuvTransport, maxLoops: 1);
  663. _libuvThread.StartAsync().Wait();
  664. }