Skip to content

Use next available port when the default port is already used #2682

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public override ICommandLineParserResult ParseArgs(string[] args)
Parser
.Setup<int>('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
Expand Down
41 changes: 40 additions & 1 deletion src/Azure.Functions.Cli/Helpers/NetworkHelpers.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<int>();
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;
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if all ports are exhausted, then should we log the errors?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After checking the ports after the start port, the GetAvailablePort method will try to get any available port at random.

// If no port is available after the specified start port, return the first available port
return GetAvailablePort();
}
}
}
}
56 changes: 56 additions & 0 deletions test/Azure.Functions.Cli.Tests/NetworkHelpersTests.cs
Original file line number Diff line number Diff line change
@@ -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<ArgumentOutOfRangeException>(() => NetworkHelpers.GetNextAvailablePort(1023));
Assert.Throws<ArgumentOutOfRangeException>(() => NetworkHelpers.GetNextAvailablePort(65536));
}
}
}