I have created a method:
public bool CheckPortAvailability(int port)
{
if (IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners().Select(p => p.Port).Contains(port))
{
return false;
}
if (IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections().Select(p => p.LocalEndPoint.Port).Contains(port))
{
return false;
}
// Linux - it should always be true because we have already returned false for ports thats are in use
// Windows - some ports are reserved by Windows, you can see them using "netsh interface ipv4 show excludedportrange protocol=tcp"
// also there are some other ports on Windows on which we can not start TCP server for some reason
// using only TryBindTCPServer method is not enough on Windows, because in some cases it allows to start TCP server on already taken ip and port
if (TryBindTCPServer(IPAddress.Any, port) && TryBindTCPServer(IPAddress.IPv6Any, port))
{
return true;
}
return false;
}
private bool TryBindTCPServer(IPAddress localAddr, int port)
{
var tcpServer = new TcpListener(localAddr, port);
try
{
tcpServer.Start();
}
catch (SocketException)
{
return false;
}
tcpServer.Stop();
return true;
}
On my test environment on Linux, ports numbers I got using
IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()
and IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections()
methods was exactly the same as ports for which TryBindTCPServer(IPAddress.Any, port) && TryBindTCPServer(IPAddress.IPv6Any, port)
is false, that is expected behavior, and means that we don't have to mix both solutions.
On Windows...
We can start TcpListener on a few already taken port(on the same IP address)
We cannot start TcpListener on some free ports. Most of them are reserved by windows, see https://superuser.com/questions/1486417/unable-to-start-kestrel-getting-an-attempt-was-made-to-access-a-socket-in-a-way or https://ardalis.com/attempt-made-to-access-socket/. 
But there are some that are not in use, not reserved and we still cannot start TcpListener on them (If somebody knows why please let me know), in my case: 
So I think that one of the best solution on Windows is to mix both, trying to start TcpListener and checking already taken ports.
The code I was using for testing:
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
static bool CanStartServer(int port)
{
var ip = IPAddress.Any;
var tcpServer = new TcpListener(ip, port);
try
{
tcpServer.Start();
}
catch (SocketException e)
{
return false;
}
tcpServer.Stop();
ip = IPAddress.IPv6Any;
tcpServer = new TcpListener(ip, port);
try
{
tcpServer.Start();
}
catch (SocketException e)
{
return false;
}
tcpServer.Stop();
return true;
}
var connections = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections().Select(x => x.LocalEndPoint.Port).Distinct().OrderBy(x => x).ToList();
var listeners = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners().Select(x => x.Port).Distinct().OrderBy(x => x).ToList();
Console.WriteLine("Listeners:");
Console.WriteLine(string.Join(", ", listeners));
foreach (var port in listeners)
{
if (CanStartServer(port))
{
Console.WriteLine("TCP Listener starts correctly on port: " + port);
}
}
Console.WriteLine("Connections:");
Console.WriteLine(string.Join(", ", connections));
foreach (var port in connections)
{
if (CanStartServer(port))
{
Console.WriteLine("TCP Listener starts correctly on port: " + port);
}
}
connections.AddRange(listeners);
for (var i = connections.Max(); i >= 1024; i--)
{
if (i >= 50000 && i <= 50059) continue;
if (i >= 80 && i <= 80) continue;
if (i >= 5357 && i <= 5357) continue;
if (i >= 8884 && i <= 8884) continue;
if (i >= 50866 && i <= 50965) continue;
if (i >= 50966 && i <= 51065) continue;
if (i >= 52658 && i <= 52757) continue;
if (i >= 52858 && i <= 52957) continue;
if (i >= 53058 && i <= 53157) continue;
if (i >= 53158 && i <= 53257) continue;
if (i >= 53258 && i <= 53357) continue;
if (i >= 53358 && i <= 53457) continue;
if (i >= 53629 && i <= 53728) continue;
if (i >= 54830 && i <= 54929) continue;
if (i >= 55030 && i <= 55129) continue;
if (i >= 55130 && i <= 55229) continue;
if (i >= 55230 && i <= 55329) continue;
if (i >= 55330 && i <= 55429) continue;
if (i >= 55430 && i <= 55529) continue;
if (i >= 55530 && i <= 55629) continue;
if (i >= 64120 && i <= 64120) continue;
if (i >= 64121 && i <= 64121) continue;
if (i >= 64122 && i <= 64122) continue;
if (connections.Contains(i))
{
continue;
}
if (!CanStartServer(i))
{
Console.WriteLine("TcpListener cannot start on port: " + i);
}
}