diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/serverprocess.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/serverprocess.cxx index 4caf8cd3a3..76da27a99b 100644 --- a/src/AspNetCoreModuleV1/AspNetCore/src/serverprocess.cxx +++ b/src/AspNetCoreModuleV1/AspNetCore/src/serverprocess.cxx @@ -132,7 +132,6 @@ SERVER_PROCESS::SetupListenPort( pEnvironmentVarTable->FindKey(ASPNETCORE_PORT_ENV_STR, &pEntry); if (pEntry != NULL) { - pEntry->Dereference(); if (pEntry->QueryValue() != NULL || pEntry->QueryValue()[0] != L'\0') { m_dwPort = (DWORD)_wtoi(pEntry->QueryValue()); diff --git a/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs b/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs new file mode 100644 index 0000000000..e4c281c467 --- /dev/null +++ b/test/Common.FunctionalTests/OutOfProcess/AspNetCorePortTests.cs @@ -0,0 +1,94 @@ +// 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.Net; +using System.Net.Sockets; +using System.Threading.Tasks; +using Xunit; +using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Testing.xunit; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + [Collection(PublishedSitesCollection.Name)] + public class AspNetCorePortTests : IISFunctionalTestBase + { + private static readonly Random _random = new Random(); + + private readonly PublishedSitesFixture _fixture; + + public AspNetCorePortTests(PublishedSitesFixture fixture) + { + _fixture = fixture; + } + + public static TestMatrix TestVariants + => TestMatrix.ForServers(DeployerSelector.ServerType) + .WithTfms(Tfm.NetCoreApp22) + .WithApplicationTypes(ApplicationType.Portable) + .WithAllAncmVersions(); + + [ConditionalTheory] + [MemberData(nameof(TestVariants))] + public async Task EnvVarInWebConfig(TestVariant variant) + { + // Must publish to set env vars in web.config + var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); + + // Workaround "PublishedSitesFixture.GetBaseDeploymentParameters() overrides too many properties of baseParameters" + // https://github.com/aspnet/IISIntegration/issues/1237 + deploymentParameters.AncmVersion = variant.AncmVersion; + + var port = GetUnusedRandomPort(); + deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_PORT"] = port.ToString(); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.HttpClient.GetAsync("/ServerAddresses"); + var responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal(port, new Uri(responseText).Port); + } + + private static int GetUnusedRandomPort() + { + // Port range allowed by ANCM config + const int minPort = 1025; + const int maxPort = 48000; + + // Large number of retries to prevent test failures due to port collisions, but not infinite + // to prevent infinite loop in case Bind() fails repeatedly for some other reason. + const int retries = 100; + + List exceptions = null; + + for (var i = 0; i < retries; i++) + { + var port = _random.Next(minPort, maxPort); + + using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + try + { + socket.Bind(new IPEndPoint(IPAddress.Loopback, port)); + return port; + } + catch (Exception e) + { + // Bind failed, most likely because port is in use. Save exception and retry. + if (exceptions == null) + { + exceptions = new List(retries); + } + exceptions.Add(e); + } + } + } + + throw new AggregateException($"Unable to find unused random port after {retries} retries.", exceptions); + } + } +} diff --git a/test/WebSites/OutOfProcessWebSite/Startup.cs b/test/WebSites/OutOfProcessWebSite/Startup.cs index 059b5a1264..8a8358d364 100644 --- a/test/WebSites/OutOfProcessWebSite/Startup.cs +++ b/test/WebSites/OutOfProcessWebSite/Startup.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.IISIntegration.FunctionalTests; @@ -22,9 +23,13 @@ namespace TestSites { public class Startup { + private IServerAddressesFeature _serverAddresses; + public void Configure(IApplicationBuilder app) { TestStartup.Register(app, this); + + _serverAddresses = app.ServerFeatures.Get(); } public Task Path(HttpContext ctx) => ctx.Response.WriteAsync(ctx.Request.Path.Value); @@ -120,5 +125,10 @@ namespace TestSites { await context.Response.WriteAsync(Process.GetCurrentProcess().Id.ToString()); } + + private async Task ServerAddresses(HttpContext context) + { + await context.Response.WriteAsync(string.Join(",", _serverAddresses.Addresses)); + } } }