Port forwarding of exposed ports behaves unexpectedly

Expected behavior

Previously (e.g. with docker machine and earlier betas) it was not possible to establish a TCP connection to a container port from the Mac host unless the both (a) the port was exposed with a port binding and (b) the container was actually listening on its internal port.

Actual behavior

Now, TCP connections are accepted for any exposed port regardless of whether the container process is actually listening.

I imagine that there’s a component in Docker for Mac that is proxying TCP connections to containers, but it seems there’s no check that the container is listening when a connection is established.

This seems to make it hard to do a simple TCP-based check for whether a container has finished starting up and is ready to accept connections - e.g. this issue which breaks an app-agnostic container startup check.

The difference in behaviour compared to docker-machine, earlier betas and presumably docker on linux may make this uncomfortable to work around without a fair amount of platform-specific code.

Information

N.B. I think that beta 14 was the first version this started to occur in, but I could be wrong.

Docker for Mac: version: mac-v1.12.0-beta16.2
OS X: version 10.11.6 (build: 15G12a)
logs: /tmp/20160618-221300.tar.gz
failure: No error was detected
[OK]     docker-cli
[OK]     app
[OK]     menubar
[OK]     virtualization
[OK]     system
[OK]     osxfs
[OK]     db
[OK]     slirp
[OK]     moby-console
[OK]     logs
[OK]     vmnetd
[OK]     env
[OK]     moby
[OK]     driver.amd64-linux

Steps to reproduce the behavior

This all manifests for me personally in Java code, but I’ve attempted to show the problem in general terms via example shell commands below:

Demonstration in Docker for Mac

# Make the container have an exposed forwarded port but don't have anything actually listen
$ docker run -p 8080 -d alpine:latest sh -c "yes > /dev/null"

# docker ps shows that 0.0.0.0:32818->8080/tcp for this container 

# Try connecting to the exposed but not listening port   (1)
$ nc localhost 32818 ; echo $?
0

# Try connecting to a port that is not exposed at all    (2)
$ bash-3.2$ nc localhost 32819 ; echo $?
1

# Now expose and actually listen on the port
$ docker run -p 8080 -d alpine:latest sh -c "true | nc -l -p 8080"

# docker ps shows that 0.0.0.0:32822->8080/tcp for this container 

# Try connecting to the exposed, listening port              (3)
$ nc localhost 32822 ; echo $?
0

The unexpected behaviour is that (1) and (3) are both successful, even though (1) was an attempted connection to what should be a ‘closed’ port. My expectation would be that (1) and (2) should fail and only (3) should succeed.

docker-machine alternative

$ docker run -p 8080 -d alpine:latest sh -c "yes > /dev/null"

# Equivalent of (1) - exposed but not listening
$ nc 192.168.99.100 32768; echo $?
1

# Equivalent of (2) - not exposed, not listening
$ nc 192.168.99.100 32769; echo $?
1

$ docker run -p 8080 -d alpine:latest sh -c "true | nc -l -p 8080"

# Equivalent of (3) - exposed and listening
$ nc 192.168.99.100 32772; echo $?
0

Note that with docker-machine (1) and (2) fail, and (3) succeeds, which matches my intuitive expectation of how this should work.

1 Like

@richnorth Thanks for the details and great steps to reproduce.
I was able to replicate the issue on my end and have created ticket #4015 within our internal issue tracker.

Keep an eye out for a fix within the changelogs.

3 Likes

Thanks @frenchben that’s great to hear.

Thank you very much, and I’ll look out for the fix :slight_smile: