testcontainers / testcontainers-dotnet

A library to support tests with throwaway instances of Docker containers for all compatible .NET Standard versions.
https://dotnet.testcontainers.org
MIT License
3.73k stars 266 forks source link

[Enhancement]: Resolve port bindings according to IPv4 and IPv6 #825

Open HofmeisterAn opened 1 year ago

HofmeisterAn commented 1 year ago

Problem

In some cases, Docker does not assign the same port to the IPv4 and IPv6 socket. Additionally, the operating system can resolve localhost to either an IPv4 or IPv6 address. To access the application or service running inside the container, it is essential to use the appropriate port binding according to the resolved IP. Otherwise, Testcontainers will be unable to connect to the app or service.

To avoid running into the issue mentioned above, Testcontainers for .NET only binds IPv4 bindings at present. However, this approach has a disadvantage, as it does not work in IPv6-only environments.

Solution

Something like:

// Contains two items:
// IsIPv6 ::1
// IsIPv4 127.0.0.1
var ips = Dns.GetHostAddresses("localhost");

var httpPort = 80;
var httpPortKey = httpPort + "/tcp";
var ipv4Binding = new PortBinding { HostIP = "0.0.0.0", HostPort = string.Format(CultureInfo.CurrentCulture, "{0}", ++httpPort) };
var ipv6Binding = new PortBinding { HostIP = "::", HostPort = string.Format(CultureInfo.CurrentCulture, "{0}", ++httpPort) };

IDictionary<string, IList<PortBinding>> mappedPortBindings = new Dictionary<string, IList<PortBinding>>();
mappedPortBindings.Add(new KeyValuePair<string, IList<PortBinding>>(httpPortKey, new List<PortBinding> { ipv4Binding, ipv6Binding }));
mappedPortBindings.TryGetValue(httpPortKey, out var portBindings);

// Lets imagine Dns.GetHostAddresses(string) returns the preferred order.
var addressFamilies = ips
  .Select(ip => ip.AddressFamily)
  .ToList();

// We can order the port bindings due to their address family, and use the first item.
var portBinding = portBindings
  .Select(portBinding => new IPEndPoint(IPAddress.Parse(portBinding.HostIP), ushort.Parse(portBinding.HostPort, NumberStyles.None, CultureInfo.InvariantCulture)))
  .OrderBy(portBinding => addressFamilies.IndexOf(portBinding.AddressFamily))
  .First();

Benefit

Support either IPv4, IPv6 or both environments.

Alternatives

Would you like to help contributing this enhancement?

Yes

HofmeisterAn commented 1 month ago

Relates: https://github.com/moby/moby/pull/47871.