diff --git a/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs b/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs index 95d60cb1e..4343e50df 100644 --- a/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs +++ b/src/Azure.Functions.Cli/Actions/HostActions/StartHostAction.cs @@ -91,7 +91,7 @@ public override ICommandLineParserResult ParseArgs(string[] args) Parser .Setup('p', "port") .WithDescription($"Local port to listen on. Default: {DefaultPort}") - .SetDefault(hostSettings.LocalHttpPort == default(int) ? DefaultPort : hostSettings.LocalHttpPort) + .SetDefault(hostSettings.LocalHttpPort == default(int) ? NetworkHelpers.GetNextAvailablePort(DefaultPort) : hostSettings.LocalHttpPort) .Callback(p => Port = p); Parser diff --git a/src/Azure.Functions.Cli/Helpers/NetworkHelpers.cs b/src/Azure.Functions.Cli/Helpers/NetworkHelpers.cs index af0aec8c3..16370bef5 100644 --- a/src/Azure.Functions.Cli/Helpers/NetworkHelpers.cs +++ b/src/Azure.Functions.Cli/Helpers/NetworkHelpers.cs @@ -1,4 +1,8 @@ +using System; +using System.Collections.Generic; +using System.Linq; using System.Net; +using System.Net.NetworkInformation; using System.Net.Sockets; namespace Azure.Functions.Cli.Helpers @@ -33,5 +37,40 @@ public static int GetAvailablePort() listener.Stop(); } } + + public static int GetNextAvailablePort(int startPort) + { + // Check if the port is in the valid range + // Port numbers are in the range of 0 to 65535, but ports below 1024 are reserved for system use + if (startPort < 1024 || startPort > 65535) + { + throw new ArgumentOutOfRangeException(nameof(startPort), "Port number must be between 1024 and 65535."); + } + + var usedPorts = new HashSet(); + var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); + + usedPorts.UnionWith(ipGlobalProperties + .GetActiveTcpConnections() + .Where(c => c.LocalEndPoint.Port >= startPort) + .Select(c => c.LocalEndPoint.Port)); + + usedPorts.UnionWith(ipGlobalProperties + .GetActiveTcpListeners() + .Where(l => l.Port >= startPort) + .Select(l => l.Port)); + + // Find the next available port starting from the specified port + for (int port = startPort; port <= 65535; port++) + { + if (!usedPorts.Contains(port)) + { + return port; + } + } + + // If no port is available after the specified start port, return the first available port + return GetAvailablePort(); + } } -} \ No newline at end of file +} diff --git a/test/Azure.Functions.Cli.Tests/NetworkHelpersTests.cs b/test/Azure.Functions.Cli.Tests/NetworkHelpersTests.cs new file mode 100644 index 000000000..51e8017b3 --- /dev/null +++ b/test/Azure.Functions.Cli.Tests/NetworkHelpersTests.cs @@ -0,0 +1,56 @@ +using System; +using System.Net.Sockets; +using Xunit; +using Azure.Functions.Cli.Helpers; +using Xunit.Abstractions; + +namespace Azure.Functions.Cli.Tests.E2E +{ + public class NetworkHelpersTests : BaseE2ETest + { + public NetworkHelpersTests(ITestOutputHelper output) : base(output) { } + + [Fact] + public void IsPortAvailable_PortIsAvailable_ReturnsTrue() + { + int port = NetworkHelpers.GetAvailablePort(); + bool result = NetworkHelpers.IsPortAvailable(port); + Assert.True(result); + } + + [Fact] + public void IsPortAvailable_PortIsNotAvailable_ReturnsFalse() + { + int port = NetworkHelpers.GetAvailablePort(); + var listener = new TcpListener(System.Net.IPAddress.Any, port); + listener.Start(); + + bool result = NetworkHelpers.IsPortAvailable(port); + listener.Stop(); + + Assert.False(result); + } + + [Fact] + public void GetAvailablePort_ReturnsValidPort() + { + int port = NetworkHelpers.GetAvailablePort(); + Assert.InRange(port, 1024, 65535); + } + + [Fact] + public void GetNextAvailablePort_ValidStartPort_ReturnsNextAvailablePort() + { + int startPort = 5000; + int port = NetworkHelpers.GetNextAvailablePort(startPort); + Assert.InRange(port, startPort, 65535); + } + + [Fact] + public void GetNextAvailablePort_InvalidStartPort_ThrowsArgumentOutOfRangeException() + { + Assert.Throws(() => NetworkHelpers.GetNextAvailablePort(1023)); + Assert.Throws(() => NetworkHelpers.GetNextAvailablePort(65536)); + } + } +}