File: Helpers\Network.cs
Web Access
Project: src\tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj (Aspire.Hosting.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Net;
using System.Net.Sockets;
using Polly;
using Polly.Retry;
using Polly.Timeout;
 
namespace Aspire.Hosting.Tests.Helpers;
 
internal static class Network
{
    /// <summary>
    /// Finds an available (unoccupied) TCP port on the specified network address by attempting
    /// to bind a socket to randomly chosen ports within the given range.
    /// </summary>
    /// <param name="minPort">The inclusive lower bound of the random port range.</param>
    /// <param name="maxPort">The inclusive upper bound of the random port range.</param>
    /// <param name="address">The network address to bind to (e.g. "127.0.0.1").</param>
    /// <returns>An available port number.</returns>
    /// <exception cref="InvalidOperationException">Thrown when no available port is found within the timeout period.</exception>
    public static async Task<int> GetAvailablePortAsync(
        int minPort = 10000,
        int maxPort = 20000,
        string address = "127.0.0.1")
    {
        var triedPorts = new List<int>();
        var random = new Random();
        var ipAddress = IPAddress.Parse(address);
 
        var pipeline = new ResiliencePipelineBuilder<int>()
            .AddTimeout(new TimeoutStrategyOptions { Timeout = TimeSpan.FromSeconds(10) })
            .AddRetry(new RetryStrategyOptions<int>
            {
                ShouldHandle = new PredicateBuilder<int>().Handle<SocketException>(),
                BackoffType = DelayBackoffType.Exponential,
                Delay = TimeSpan.FromMilliseconds(50),
                MaxDelay = TimeSpan.FromSeconds(2),
                MaxRetryAttempts = int.MaxValue,
            })
            .Build();
 
        try
        {
            return await pipeline.ExecuteAsync(async (ct) =>
            {
                var port = random.Next(minPort, maxPort + 1);
                triedPorts.Add(port);
 
                using var socket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                socket.Bind(new IPEndPoint(ipAddress, port));
                socket.Close();
 
                return port;
            });
        }
        catch (TimeoutRejectedException)
        {
            throw new InvalidOperationException(
                $"Could not find an available port on address '{address}' after 10 seconds. " +
                $"Tried ports: {string.Join(", ", triedPorts)}");
        }
    }
}