diff --git a/src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs b/src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs
index aa06fec6..d070e9eb 100644
--- a/src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs
+++ b/src/Microsoft.Tye.Core/ConfigModel/ConfigApplication.cs
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
+using System.Runtime.InteropServices;
using Tye;
using Tye.Serialization;
using YamlDotNet.Serialization;
@@ -106,7 +107,7 @@ namespace Microsoft.Tye.ConfigModel
throw new TyeYamlException(CoreStrings.FormatMultipleBindingWithSameName("service"));
}
- if (service.Bindings.Count(o => o.Port != null && o.Port == binding.Port) > 1)
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && service.Bindings.Count(o => o.Port != null && o.Port == binding.Port) > 1)
{
throw new TyeYamlException(CoreStrings.FormatMultipleBindingWithSamePort("service"));
}
@@ -166,7 +167,8 @@ namespace Microsoft.Tye.ConfigModel
{
throw new TyeYamlException(CoreStrings.IngressBindingMustBeHttpOrHttps);
}
- if (ingress.Bindings.Count(o => o.Port == binding.Port) > 1)
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && ingress.Bindings.Count(o => o.Port == binding.Port) > 1)
{
throw new TyeYamlException(CoreStrings.FormatMultipleBindingWithSamePort("ingress"));
}
diff --git a/src/Microsoft.Tye.Hosting/PortAssigner.cs b/src/Microsoft.Tye.Hosting/AddressAssigner.cs
similarity index 60%
rename from src/Microsoft.Tye.Hosting/PortAssigner.cs
rename to src/Microsoft.Tye.Hosting/AddressAssigner.cs
index d5a1c670..767cc0b7 100644
--- a/src/Microsoft.Tye.Hosting/PortAssigner.cs
+++ b/src/Microsoft.Tye.Hosting/AddressAssigner.cs
@@ -9,20 +9,23 @@ using System.Net.Sockets;
using System.Threading.Tasks;
using Microsoft.Tye.Hosting.Model;
using Microsoft.Extensions.Logging;
+using System.Runtime.InteropServices;
namespace Microsoft.Tye.Hosting
{
- public class PortAssigner : IApplicationProcessor
+ public class AddressAssigner : IApplicationProcessor
{
private readonly ILogger _logger;
- public PortAssigner(ILogger logger)
+ public AddressAssigner(ILogger logger)
{
_logger = logger;
}
public Task StartAsync(Application application)
{
+ // Nobody's going to have > 255 services right?
+ var octect = 0;
foreach (var service in application.Services.Values)
{
if (service.Description.RunInfo == null)
@@ -30,7 +33,9 @@ namespace Microsoft.Tye.Hosting
continue;
}
- static int GetNextPort()
+ octect++;
+
+ static int GetNextPort(IPAddress address)
{
// Let the OS assign the next available port. Unless we cycle through all ports
// on a test run, the OS will always increment the port number when making these calls.
@@ -38,16 +43,53 @@ namespace Microsoft.Tye.Hosting
// a given port, and a new test is able to bind to the same port due to port
// reuse being enabled by default by the OS.
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+ socket.Bind(new IPEndPoint(address, 0));
return ((IPEndPoint)socket.LocalEndPoint).Port;
}
+ static bool IsPortAlreadyInUse(IPAddress address, int port)
+ {
+ var endpoint = new IPEndPoint(address, port);
+ try
+ {
+ using var socket = new Socket(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+ socket.Bind(endpoint);
+ return false;
+ }
+ catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse)
+ {
+ return true;
+ }
+ }
+
+ var httpUsed = false;
+ var httpsUsed = false;
+
+ // We need to bind to all interfaces on linux since the container -> host communication won't work
+ // if we use the IP address to reach out of the host. This works fine on osx and windows
+ // but doesn't work on linux.
+ var address = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? IPAddress.Any : IPAddress.Parse($"127.0.0.{octect}");
+ service.Address = address;
+
foreach (var binding in service.Description.Bindings)
{
// Auto assign a port
if (binding.Port == null)
{
- binding.Port = GetNextPort();
+ if (!httpUsed && binding.Protocol == "http" && !IsPortAlreadyInUse(address, 80))
+ {
+ binding.Port = 80;
+ httpUsed = true;
+ }
+ else if (!httpsUsed && binding.Protocol == "https" && !IsPortAlreadyInUse(address, 443))
+ {
+ binding.Port = 443;
+ httpsUsed = true;
+ }
+ else
+ {
+ binding.Port = GetNextPort(address);
+ }
}
if (service.Description.Replicas == 1)
@@ -60,7 +102,7 @@ namespace Microsoft.Tye.Hosting
for (var i = 0; i < service.Description.Replicas; i++)
{
// Reserve a port for each replica
- var port = GetNextPort();
+ var port = GetNextPort(address);
binding.ReplicaPorts.Add(port);
}
diff --git a/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor b/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor
index 635c0ed1..faa97a14 100644
--- a/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor
+++ b/src/Microsoft.Tye.Hosting/Dashboard/Pages/Index.razor
@@ -44,12 +44,12 @@
{
if (b.Protocol == "http" || b.Protocol == "https")
{
- var url = GetUrl(b);
+ var url = GetUrl(service, b);
@url
}
else
{
- @GetUrl(b)
+ @GetUrl(service, b)
}
}
else
@@ -77,9 +77,14 @@
private List _subscriptions = new List();
- string GetUrl(ServiceBinding b)
+ string GetUrl(Service service, ServiceBinding b)
{
- return $"{(b.Protocol ?? "tcp")}://{b.Host ?? "localhost"}:{b.Port}";
+ var url = $"{(b.Protocol ?? "tcp")}://{service.Address}";
+ if (b.Port != 80 && b.Port != 443)
+ {
+ url += $":{b.Port}";
+ }
+ return url;
}
protected override void OnInitialized()
diff --git a/src/Microsoft.Tye.Hosting/DockerRunner.cs b/src/Microsoft.Tye.Hosting/DockerRunner.cs
index 5c56c38c..25f8e6db 100644
--- a/src/Microsoft.Tye.Hosting/DockerRunner.cs
+++ b/src/Microsoft.Tye.Hosting/DockerRunner.cs
@@ -244,6 +244,7 @@ namespace Microsoft.Tye.Hosting
status.Ports = ports.Select(p => p.Port);
// These are the ports that the application should use for binding
+ var host = service.Address!.ToString();
// 1. Tell the docker container what port to bind to
portString = docker.Private ? "" : string.Join(" ", ports.Select(p => $"-p {p.Port}:{p.ContainerPort ?? p.Port}"));
@@ -262,7 +263,7 @@ namespace Microsoft.Tye.Hosting
}
// 3. For non-ASP.NET Core apps, pass the same information in the PORT env variable as a semicolon separated list.
- environment["PORT"] = string.Join(";", ports.Select(p => $"{p.ContainerPort ?? p.Port}"));
+ environment["PORT"] = string.Join(";", ports.Select(p => $"{host}:{p.ContainerPort ?? p.Port}"));
// This the port for the container proxy (containerport:externalport)
environment["PROXY_PORT"] = string.Join(";", ports.Select(p => $"{p.ContainerPort ?? p.Port}:{p.ExternalPort}"));
diff --git a/src/Microsoft.Tye.Hosting/Model/Service.cs b/src/Microsoft.Tye.Hosting/Model/Service.cs
index 03862258..94e68003 100644
--- a/src/Microsoft.Tye.Hosting/Model/Service.cs
+++ b/src/Microsoft.Tye.Hosting/Model/Service.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Net;
using System.Reactive.Subjects;
namespace Microsoft.Tye.Hosting.Model
@@ -69,5 +70,6 @@ namespace Microsoft.Tye.Hosting.Model
public Subject Logs { get; } = new Subject();
public Subject ReplicaEvents { get; } = new Subject();
+ public IPAddress? Address { get; set; }
}
}
diff --git a/src/Microsoft.Tye.Hosting/ProcessRunner.cs b/src/Microsoft.Tye.Hosting/ProcessRunner.cs
index 776f51da..1563cc3f 100644
--- a/src/Microsoft.Tye.Hosting/ProcessRunner.cs
+++ b/src/Microsoft.Tye.Hosting/ProcessRunner.cs
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -168,10 +169,7 @@ namespace Microsoft.Tye.Hosting
if (hasPorts)
{
- // We need to bind to all interfaces on linux since the container -> host communication won't work
- // if we use the IP address to reach out of the host. This works fine on osx and windows
- // but doesn't work on linux.
- var host = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "*" : "localhost";
+ var host = service.Address == IPAddress.Any ? "*" : service.Address!.ToString();
// These are the ports that the application should use for binding
@@ -235,7 +233,7 @@ namespace Microsoft.Tye.Hosting
{
if (hasPorts)
{
- _logger.LogInformation("{ServiceName} running on process id {PID} bound to {Address}", replica, pid, string.Join(", ", ports.Select(p => $"{p.Protocol ?? "http"}://localhost:{p.Port}")));
+ _logger.LogInformation("{ServiceName} running on process id {PID} bound to {Address}", replica, pid, string.Join(", ", ports.Select(p => $"{p.Protocol ?? "http"}://{service.Address}:{p.Port}")));
}
else
{
diff --git a/src/Microsoft.Tye.Hosting/ProxyService.cs b/src/Microsoft.Tye.Hosting/ProxyService.cs
index 2723c1b2..5b606f88 100644
--- a/src/Microsoft.Tye.Hosting/ProxyService.cs
+++ b/src/Microsoft.Tye.Hosting/ProxyService.cs
@@ -63,7 +63,7 @@ namespace Microsoft.Tye.Hosting
// We need to bind to all interfaces on linux since the container -> host communication won't work
// if we use the IP address to reach out of the host. This works fine on osx and windows
// but doesn't work on linux.
- var host = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? IPAddress.Any : IPAddress.Loopback;
+ var host = service.Address;
sockets.Listen(host, binding.Port.Value, o =>
{
diff --git a/src/Microsoft.Tye.Hosting/TyeHost.cs b/src/Microsoft.Tye.Hosting/TyeHost.cs
index b40d95d3..221ab3c1 100644
--- a/src/Microsoft.Tye.Hosting/TyeHost.cs
+++ b/src/Microsoft.Tye.Hosting/TyeHost.cs
@@ -263,7 +263,7 @@ namespace Microsoft.Tye.Hosting
var processors = new List
{
new EventPipeDiagnosticsRunner(logger, diagnosticsCollector),
- new PortAssigner(logger),
+ new AddressAssigner(logger),
new ProxyService(logger),
new HttpProxyService(logger),
new DockerImagePuller(logger),