Skip to content

Reject port spec when host range length differs from container port#4988

Open
s3onghyun wants to merge 1 commit into
containerd:mainfrom
s3onghyun:fix-port-range-mismatch
Open

Reject port spec when host range length differs from container port#4988
s3onghyun wants to merge 1 commit into
containerd:mainfrom
s3onghyun:fix-port-range-mismatch

Conversation

@s3onghyun

Copy link
Copy Markdown

What

ParseFlagP only rejected a host/container range-length mismatch when the container side was a range. A spec with a host-port range but a single container port slips through:

nerdctl run -p 127.0.0.1:3000-3001:8080/tcp ...

Here endPort == startPort (8080), so the inner if endPort != startPort guard is skipped, no error is raised, and the emit loop runs endPort-startPort+1 = 1 time — producing a single mapping 3000 -> 8080 and silently dropping host port 3001. The user gets neither the range nor an error.

Fix

Drop the inner guard so any host/container range-length mismatch is rejected:

if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
    return nil, fmt.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
}

nerdctl doesn't implement Docker's dynamic single-port allocation from within an explicit host range, so erroring is the minimal, deterministic, OS-independent correct behavior — far better than silently losing a port. (If dynamic allocation is desired, that's a larger follow-up; this PR stops the silent data loss.)

Test

Added a TestParseFlagP case for 127.0.0.1:3000-3001:8080/tcp expecting the range-mismatch error. Fails before (nil error + bogus single mapping), passes after. Existing range cases unchanged. go test ./pkg/portutil/ green.

@haytok haytok left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for creating this PR!

When the container port is a single port and the host port is specified as a range, Moby uses that range as a pool and dynamically allocates a free host port from it.

Details of Docker's behavior:

Details

> docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

> docker run -d -p 127.0.0.1:3000-3001:8080/tcp nginx
c1b08c490d3c80f9614c4dce6259f2699020c0c83c75eb7964913cbace9328eb

> docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS                              NAMES
c1b08c490d3c   nginx     "/docker-entrypoint.…"   4 seconds ago   Up 4 seconds   80/tcp, 127.0.0.1:3000->8080/tcp   nervous_feynman

> docker run -d -p 127.0.0.1:3000-3001:8080/tcp nginx
f927b67be40779c03ee7c4dd4fb15ba74de2aece81683ec7cd5c91311097423d

> docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                              NAMES
f927b67be407   nginx     "/docker-entrypoint.…"   1 second ago     Up 1 second     80/tcp, 127.0.0.1:3001->8080/tcp   thirsty_yalow
c1b08c490d3c   nginx     "/docker-entrypoint.…"   10 seconds ago   Up 10 seconds   80/tcp, 127.0.0.1:3000->8080/tcp   nervous_feynman

nerdctl does not implement dynamic allocation within an explicit host range

As you know, nerdctl currently does not allocate a free host port from the range in this case, so implementing that could be one option.

Therefore, IMO there might be a better approach than this PR.

WDYT?

Docker treats `-p 3000-3001:8080` as a host-port pool: it binds the container
port to a free host port from the range, rather than dropping the rest. nerdctl
previously collapsed this to the first host port, silently discarding the rest of
the range.

Implement the Docker-compatible behavior: when the container side is a single port
and the host side is a range, pick the first free port in that range (via the
existing getUsedPorts) and map the container port to it. A genuine mismatch (both
sides are ranges of unequal length) is still rejected.

Signed-off-by: Seonghyun Hong <s3onghyun.hong@gmail.com>
@s3onghyun s3onghyun force-pushed the fix-port-range-mismatch branch from ec1ab4c to 40d493f Compare June 20, 2026 12:41
@s3onghyun

Copy link
Copy Markdown
Author

Great point, @haytok — thank you for the references and the clear Docker walkthrough. You're right that the better fix is to implement the pool behavior rather than reject the spec.

I've reworked the PR to do exactly that. When the container side is a single port and the host side is a range (e.g. -p 3000-3001:8080), nerdctl now treats the range as a pool and binds the container port to the first free host port in it, using the existing getUsedPorts to skip ports already in use — matching Docker's behavior. The previous silent collapse (dropping the rest of the range) is gone.

A genuine mismatch (both sides are ranges of unequal length, e.g. 3000-3001:8080-8082) is still rejected, since that has no valid mapping.

go test ./pkg/portutil/ passes, including a new case for the single-container-port pool allocation. Let me know if you'd prefer the allocation to scan from a different end of the range, or any other tweak.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants