9

I'm running some integration tests with REST API service.
The problem is that sometimes hardcoded port isn't free at the moment the test starts for the next time. Because it was opened by previous test and isn't closed by the system yet.

I use OWIN, the application is shut down at the moment the next test starts.

Could you please suggest me a good way to determine a free port on the system without opening it in advance and then closing it? Or say that it's not possible.

Because it could be not freed by the system yet, just as it happens already.

cassandrad
  • 3,412
  • 26
  • 50

4 Answers4

11

As an alternative to TempoClick's answer, we can use the IPGlobalProperties.GetActiveTcpListeners() method to test if a port is available - without trying to open it in advance. GetActiveTcpListeners() returns all active TCP listeners on the system, and so we can use it to determine if a port is free or not.

public bool IsFree(int port)
{
    IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties();
    IPEndPoint[] listeners = properties.GetActiveTcpListeners();
    int[] openPorts = listeners.Select(item => item.Port).ToArray<int>();
    return openPorts.All(openPort => openPort != port);
}

Note that GetActiveTcpListeners() doesn't return listening UDP endpoints, but we can get them with GetActiveUdpListeners().

So, you can start with the default port (or select a random value) and keep incrementing until you find a free port with the IsFree method.

int NextFreePort(int port = 0) 
{
    port = (port > 0) ? port : new Random().Next(1, 65535);
    while (!IsFree(port)) 
    {
        port += 1;
    }
    return port;
}

A simple test:

using System;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.Linq;

class Test
{
    static void Main(string[] args)
    {
        int port = 1000;
        Console.WriteLine(IsFree(port));
        TcpListener server = new TcpListener(IPAddress.Parse("127.0.0.1"), port);
        server.Start();   
        Console.WriteLine(IsFree(port));
        Console.WriteLine(NextFreePort(port));
    }

    static bool IsFree(int port)
    {
        IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties();
        IPEndPoint[] listeners = properties.GetActiveTcpListeners();
        int[] openPorts = listeners.Select(item => item.Port).ToArray<int>();
        return openPorts.All(openPort => openPort != port);
    }

    static int NextFreePort(int port = 0) {
        port = (port > 0) ? port : new Random().Next(1, 65535);
        while (!IsFree(port)) {
            port += 1;
        }
        return port;
    }
}

A different approach is to use port zero. In this case, the system will select a random free port from the dynamic port range. We can get the number ot this port from the LocalEndpoint property.

TcpListener server = new TcpListener(IPAddress.Loopback, 0);
server.Start();
int port = ((IPEndPoint)server.LocalEndpoint).Port;
Console.WriteLine(port);
t.m.adam
  • 15,106
  • 3
  • 32
  • 52
4

To get a free port

static int FreePort()
{
  TcpListener l = new TcpListener(IPAddress.Loopback, 0);
  l.Start();
  int port = ((IPEndPoint)l.LocalEndpoint).Port;
  l.Stop();
  return port;
}
TempoClick
  • 169
  • 12
  • 2
    Isn't this approach will acquire port and won't release it immediately after `Stop` is called? I mean, there is a chance that port will still be used at the time I would like to use it with API service? – cassandrad Dec 17 '18 at 12:59
  • You can only have one TCP application listener on the same port at one time. – TempoClick Dec 17 '18 at 13:10
  • Yeah, I meant that. So while my TcpListener hasn't closed the port, I can't use it. And listener won't release it immediately. So there could be a problem I'm trying to solve with randomizing ports. – cassandrad Dec 17 '18 at 13:12
  • You can check the documentation here [TcpListener.Stop](https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.tcplistener.stop?view=netframework-4.7.2). **Stop** closes the listener. Any unaccepted connection requests in the queue will be lost. Remote hosts waiting for a connection to be accepted will throw a SocketException. The Stop() method also closes the underlying Socket, and creates a new Socket for the TcpListener. If you set any properties on the underlying Socket prior to calling the Stop() method, those properties will not carry over to the new Socket. – TempoClick Dec 17 '18 at 13:26
  • I'm talking about TIME_WAIT, when socket is closed by out app, but isn't closed by Windows. Like in https://superuser.com/a/173542 answer. – cassandrad Dec 17 '18 at 13:47
  • 3
    Are you performing `SocketShutdown` before closing it? Always call the Shutdown method before closing the Socket. This ensures that all data is sent and received on the connected socket before it is closed. – TempoClick Dec 17 '18 at 14:30
  • 3
    This answer is a copy of 10-years old answer and with same kind of comments: https://stackoverflow.com/questions/138043/find-the-next-tcp-port-in-net/150974#150974 – Simon Mourier Dec 24 '18 at 23:23
0

When using t.m.adam's NextFreePort(...) method, the port may be free, but not permitted. In my case, this happened under Linux. The following method tests, whether the free port can actually be used:

static bool CanBindPort(int port)
{
    try
    {
        var localEndPoint = new IPEndPoint(IPAddress.Any, port);
        using var listener = new Socket(IPAddress.Any.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        listener.Bind(localEndPoint);
    }
    catch
    {
        // e.g. because of "Permission denied" or other reason
        return false;
    }

    return true;
}

This is, however, not the requested solution for "without opening the port in advance".

axuno
  • 581
  • 4
  • 15
-1

The following one-liner (taken from this SO post), uses Python to quickly open and close a socket on port 0. When you do this in Python, it automatically selects an open port, which gets printed out to the screen:

python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'
speedplane
  • 15,673
  • 16
  • 86
  • 138