Skip to content

Commit

Permalink
Fix duplicate host port binding (#1138)
Browse files Browse the repository at this point in the history
* Fix issues with duplicate ports with wildcard and non wildcard ports
* Allow wildcards to override specific Ips
* Add test for server.urls
* Remove interaction with ASPNET_CORE_URLS environment variable
  • Loading branch information
hananiel authored Jul 19, 2023
1 parent 6c9fb5d commit ea9c195
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 50 deletions.
104 changes: 64 additions & 40 deletions src/Common/src/Common.Hosting/HostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;

namespace Steeltoe.Common.Hosting;

public static class HostBuilderExtensions
{
public const string DEFAULT_URL = "http://*:8080";
private const string DeprecatedServerUrlsKey = "server.urls";
internal const string DeprecatedServerUrlsKey = "server.urls";

/// <summary>
/// Configure the application to listen on port(s) provided by the environment at runtime. Defaults to port 8080.
Expand Down Expand Up @@ -84,23 +88,15 @@ private static IWebHostBuilder BindToPorts(this IWebHostBuilder webHostBuilder,
var urls = new HashSet<string>();

var portStr = Environment.GetEnvironmentVariable("PORT") ?? Environment.GetEnvironmentVariable("SERVER_PORT");
var aspnetUrls = Environment.GetEnvironmentVariable("ASPNETCORE_URLS");
var serverUrlSetting = webHostBuilder.GetSetting(DeprecatedServerUrlsKey); // check for deprecated setting
var urlSetting = webHostBuilder.GetSetting(WebHostDefaults.ServerUrlsKey);

if (!string.IsNullOrEmpty(serverUrlSetting))
{
urls.Add(GetCanonical(serverUrlSetting));
}

if (!string.IsNullOrEmpty(urlSetting))
{
urls.Add(GetCanonical(urlSetting));
}
AddServerUrls(urlSetting, urls);
AddServerUrls(serverUrlSetting, urls);

if (!string.IsNullOrWhiteSpace(portStr))
{
AddPortAndAspNetCoreUrls(urls, portStr, aspnetUrls);
AddPortAndAspNetCoreUrls(urls, portStr);
}
else if (Platform.IsKubernetes)
{
Expand All @@ -111,36 +107,74 @@ private static IWebHostBuilder BindToPorts(this IWebHostBuilder webHostBuilder,
AddRunLocalPorts(urls, runLocalHttpPort, runLocalHttpsPort);
}

if (urls.Any())
if (!urls.Any())
{
urls.Add(DEFAULT_URL);
}

urls = RemoveDuplicates(urls);

return webHostBuilder.UseSetting(WebHostDefaults.ServerUrlsKey, string.Join(";", urls));
}

private static HashSet<string> RemoveDuplicates(HashSet<string> urls)
{
HashSet<UrlEntry> entries = new ();
HashSet<string> uniqueUrls = new HashSet<string>();

foreach (var url in urls)
{
// setting ASPNETCORE_URLS should only be needed to override launchSettings.json
if (string.IsNullOrWhiteSpace(portStr) && !Platform.IsKubernetes)
var bindingAddress = BindingAddress.Parse(url);
var host = bindingAddress.Host;

if (!IPAddress.TryParse(bindingAddress.Host, out var address) || address.ToString() == "::")
{
Environment.SetEnvironmentVariable("ASPNETCORE_URLS", string.Join(";", urls));
host = "*";
}

entries.Add(new UrlEntry() { Host = host, Scheme = bindingAddress.Scheme, Port = bindingAddress.Port });
}
else

foreach (IGrouping<int, UrlEntry> group in entries.GroupBy(entry => entry.Port))
{
Environment.SetEnvironmentVariable("ASPNETCORE_URLS", DEFAULT_URL);
urls.Add(DEFAULT_URL);
var wildCardEntry = group.FirstOrDefault(entry => entry.Host == "*");
if (!wildCardEntry.Equals(default(UrlEntry)))
{
uniqueUrls.Add(wildCardEntry.ToString());
}
else
{
foreach (var entry in group)
{
uniqueUrls.Add(entry.ToString());
}
}
}

return webHostBuilder.BindToPorts(urls);
return uniqueUrls;
}

private static IWebHostBuilder BindToPorts(this IWebHostBuilder webHostBuilder, HashSet<string> urls)
private struct UrlEntry
{
string currentSetting = webHostBuilder.GetSetting(WebHostDefaults.ServerUrlsKey);
public string Scheme;
public string Host;
public int Port;

if (!string.IsNullOrEmpty(currentSetting))
public override string ToString()
{
foreach (var url in currentSetting.Split(';'))
return $"{Scheme}://{Host}:{Port}";
}
}

private static void AddServerUrls(string serverUrlSetting, HashSet<string> urls)
{
if (!string.IsNullOrEmpty(serverUrlSetting))
{
foreach (var url in serverUrlSetting.Split(';'))
{
urls.Add(GetCanonical(url));
}
}

return webHostBuilder.UseSetting(WebHostDefaults.ServerUrlsKey, string.Join(";", urls));
}

private static string GetCanonical(string serverUrlSetting)
Expand All @@ -150,27 +184,17 @@ private static string GetCanonical(string serverUrlSetting)
return canonicalUrl;
}

private static void AddPortAndAspNetCoreUrls(HashSet<string> urls, string portStr, string aspnetUrls)
private static void AddPortAndAspNetCoreUrls(HashSet<string> urls, string portStr)
{
if (int.TryParse(portStr, out var port))
{
urls.Add($"http://*:{port}");
}
else if (portStr?.Contains(";") == true)
{
if (!string.IsNullOrEmpty(aspnetUrls))
{
foreach (var url in aspnetUrls.Split(';'))
{
urls.Add(GetCanonical(url));
}
}
else
{
var ports = portStr.Split(';');
urls.Add($"http://*:{ports[0]}");
urls.Add($"https://*:{ports[1]}");
}
var ports = portStr.Split(';');
urls.Add($"http://*:{ports[0]}");
urls.Add($"https://*:{ports[1]}");
}
}

Expand Down
87 changes: 77 additions & 10 deletions src/Common/test/Common.Hosting.Test/HostBuilderExtensionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,12 @@ public void UseCloudHosting_UsesLocalPortSettings()
}

[Fact]
public void UseCloudHosting_UsesCommandLine_ServerUrls()
public void UseCloudHosting_UsesCommandLine_Urls()
{
var config = new ConfigurationBuilder().AddCommandLine(new[]
{
"--server.urls",
"http://*:8081"
"--urls",
"http://*:8099"
}).Build();

var hostBuilder = new WebHostBuilder().UseConfiguration(config).UseStartup<TestServerStartup>().UseKestrel();
Expand All @@ -169,17 +169,38 @@ public void UseCloudHosting_UsesCommandLine_ServerUrls()

var addresses = server.ServerFeatures.Get<IServerAddressesFeature>();

Assert.Single(addresses.Addresses);
Assert.Contains("http://*:8081", addresses.Addresses);
Assert.Single(addresses.Addresses, "http://*:8099");
Assert.Contains("http://*:8099", addresses.Addresses);
}

[Fact]
public void UseCloudHosting_UsesCommandLine_Urls()
public void UseCloudHosting_UsesCommandLine_ServerUrls()
{
var config = new ConfigurationBuilder().AddCommandLine(new[]
var config = new ConfigurationBuilder().AddCommandLine(new string[]
{
$"--{HostBuilderExtensions.DeprecatedServerUrlsKey}",
"http://*:8088"
}).Build();

var hostBuilder = new WebHostBuilder().UseConfiguration(config).UseStartup<TestServerStartup>().UseKestrel();

hostBuilder.UseCloudHosting();
var server = hostBuilder.Build();

var addresses = server.ServerFeatures.Get<IServerAddressesFeature>();

Assert.Contains(addresses.Addresses, address => address == "http://*:8088");
}

[Fact]
public void UseCloudHosting_UsesCommandLine_ServerUrls_Handles_Duplicates()
{
var config = new ConfigurationBuilder().AddCommandLine(new string[]
{
"--server.urls",
"http://*:8088",
"--urls",
"http://*:8081"
"http://*:8088"
}).Build();

var hostBuilder = new WebHostBuilder().UseConfiguration(config).UseStartup<TestServerStartup>().UseKestrel();
Expand All @@ -189,8 +210,54 @@ public void UseCloudHosting_UsesCommandLine_Urls()

var addresses = server.ServerFeatures.Get<IServerAddressesFeature>();

Assert.Single(addresses.Addresses);
Assert.Contains("http://*:8081", addresses.Addresses);
Assert.Collection(addresses.Addresses, (address) => Assert.Equal("http://*:8088", address));
}

[Fact]
public void UseCloudHosting_AnyWildCard_Overrides_SpecificIps()
{
try
{
Environment.SetEnvironmentVariable("ASPNETCORE_URLS", "http://192.168.1.2:8085;http://*:8085");

var hostBuilder = new WebHostBuilder().UseStartup<TestServerStartup>().UseKestrel();

hostBuilder.UseCloudHosting();
var server = hostBuilder.Build();

var addresses = server.ServerFeatures.Get<IServerAddressesFeature>();

Assert.Collection(addresses.Addresses, (address) => Assert.Equal("http://*:8085", address));
}
finally
{
Environment.SetEnvironmentVariable("ASPNETCORE_URLS", string.Empty);
}
}

[Fact]
public void UseCloudHosting_MultipleIps_With_Same_Port()
{
try
{
Environment.SetEnvironmentVariable("ASPNETCORE_URLS", "http://192.168.1.2:8085;http://192.168.1.3:8085");

var hostBuilder = new WebHostBuilder().UseStartup<TestServerStartup>().UseKestrel();

hostBuilder.UseCloudHosting();
var server = hostBuilder.Build();

var addresses = server.ServerFeatures.Get<IServerAddressesFeature>();

Assert.Collection(
addresses.Addresses,
address => Assert.Equal("http://192.168.1.2:8085", address),
address => Assert.Equal("http://192.168.1.3:8085", address));
}
finally
{
Environment.SetEnvironmentVariable("ASPNETCORE_URLS", string.Empty);
}
}

[Fact]
Expand Down

0 comments on commit ea9c195

Please sign in to comment.