dotnet / dotnet/Docker.DotNet

`ListContainersAsync` response contains inconsistent public host ports

Open
#565 1 comment 2 reactions 0 assignees View on GitHub
Dominant language
C#
Stars
2.4k
Forks
416
PR merge metrics
No merged PRs in 30d

Description

**Output of `dotnet --info`:**

```
.NET SDK (reflecting any global.json):
Version: 6.0.300
Commit: 8473146e7d

Runtime Environment:
OS Name: Windows
OS Version: 10.0.19044
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\6.0.300\

Host (useful for support):
Version: 6.0.5
Commit: 70ae3df4a6

.NET SDKs installed:
6.0.300 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET runtimes or SDKs:
https://aka.ms/dotnet-download
```

**What version of Docker.DotNet?:**

```
3.125.5
```

**Steps to reproduce the issue:**

```cs
public sealed class ExampleContainer
{
private static readonly IDockerClient DockerClient = new DockerClientConfiguration(new Uri("npipe://./pipe/docker_engine")).CreateClient();

private static readonly CreateContainerParameters CreateContainerParameters = new CreateContainerParameters();

private readonly ISet hostPorts = new HashSet();

private int tryCount;

private string ID;

static ExampleContainer()
{
var hostConfig = new HostConfig();
hostConfig.PortBindings = new Dictionary> { { "80/tcp", GetFailureConfig() } };
CreateContainerParameters.Image = "nginx";
CreateContainerParameters.HostConfig = hostConfig;
}

[Fact]
public Task ShouldReturnOnePublicHostPort()
{
return Task.WhenAll(Enumerable.Range(0, 25)
.Select(_ => new ExampleContainer())
.Select(example => Task.Run(async () =>
{
try
{
await example.InitializeAsync()
.ConfigureAwait(false);
await example.GetPublicHostPort()
.ConfigureAwait(false);
}
finally
{
await example.DisposeAsync()
.ConfigureAwait(false);
}
})));
}

private static IList GetSuccessConfig()
{
return new[] { new PortBinding { HostPort = "0" } };
}

private static IList GetFailureConfig()
{
return new[] { new PortBinding { HostPort = null } };
}

private async Task InitializeAsync()
{
var createContainerResponse = await DockerClient.Containers.CreateContainerAsync(CreateContainerParameters)
.ConfigureAwait(false);
this.ID = createContainerResponse.ID;

_ = await DockerClient.Containers.StartContainerAsync(this.ID, new ContainerStartParameters())
.ConfigureAwait(false);
}

private Task DisposeAsync()
{
var containerRemoveParameters = new ContainerRemoveParameters();
containerRemoveParameters.Force = true;
return DockerClient.Containers.RemoveContainerAsync(this.ID, containerRemoveParameters);
}

private async Task GetPublicHostPort()
{
do
{
var containersListParameters = new ContainersListParameters();
containersListParameters.All = true;
containersListParameters.Filters = new Dictionary>();
containersListParameters.Filters.Add("id", new Dictionary { { this.ID, true } });

var listContainersResponse = await DockerClient.Containers.ListContainersAsync(containersListParameters)
.ConfigureAwait(false);

foreach (var publicHostPort in listContainersResponse.Single().Ports.Select(portBindings => portBindings.PublicPort))
{
this.hostPorts.Add(publicHostPort.ToString(CultureInfo.InvariantCulture));
}

if (this.hostPorts.Count > 1)
{
throw new InvalidOperationException(string.Join(',', this.hostPorts));
}
}
while (++this.tryCount < 10);
}
}
```

**What actually happened?:**

The response of `ListContainersAsync` sometimes contains for a single port binding multiple public host ports. In this case, a service behind the mapped port is not available. It looks like setting the port binding host port to `null` (assign random port) breaks it. I attached a reproducible example above. E.g. `L:89` throws: `System.InvalidOperationException: 49196,52481`.

- `... new[] { new PortBinding { HostPort = null } }`: fails
- `... new[] { new PortBinding { HostPort = "0" } }`: successful

This also breaks `InspectContainerAsync`. In this case the response (list of port bindings) is empty for at least the first call.

**What did you expect to happen?:**

A consistent response.

**Additional information:**

\-

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.