Host Networking feature does not properly identity all ports to bind to Mac host
Description
Host Information:
System Version: macOS 12.7.5(21H1222)
Machine Identifier: MacBookPro13,2
I have a go binary that runs inside an Alpine container. When developing locally, this container must be run with host networking. On my primary Ubuntu development machine, I am able to build the listener using 0.0.0.0:$PORT or 127.0.0.1:$PORT. On this Mac that I was provided to test updates I make for Mac user compatibility, the only way I am able to get Docker Desktop to bind the host's loopback interface with the expected port(s) is to specifically bind them to 127.0.0.1:$PORT. Using 0.0.0.0:$PORT does not work.
Attached is code I used to reproduce this issue in isolation: mac_dd_nethost_bind_test.zip
The original source code demonstrates what happens with 0.0.0.0:9002 as the listener bind address. This does not propagate to the host loopback interface on macos. To see a working example, you will need to edit the provided main.go file line 28 from 0.0.0.0:9002 to 127.0.0.1:9002, rebuild and re-run.
Reproduce
- Download & unzip attached code
- cd into expanded archive dir
To see outcome of 0.0.0.0 bind:
docker build -t mac_dd_nethost_test --load .docker run --rm --net=host mac_dd_nethost_test- Attempt to open browser to
http://127.0.0.1:9002
To see outcome of 127.0.0.1 bind:
- Edit line 28 of attached
main.go, replacing0.0.0.0with127.0.0.1 - Repeat steps 1-3 above.
Expected behavior
Creating a listener that binds 0.0.0.0 should result in the in-container binds being propagated to the macos host loopback interface.
docker version
Client:
Version: 26.1.4
API version: 1.45
Go version: go1.21.11
Git commit: 5650f9b
Built: Wed Jun 5 11:26:02 2024
OS/Arch: darwin/amd64
Context: desktop-linux
Server: Docker Desktop 4.31.0 (153195)
Engine:
Version: 26.1.4
API version: 1.45 (minimum version 1.24)
Go version: go1.21.11
Git commit: de5c9cf
Built: Wed Jun 5 11:29:22 2024
OS/Arch: linux/amd64
Experimental: true
containerd:
Version: 1.6.33
GitCommit: d2d58213f83a351ca8f528a95fbd145f5654e957
runc:
Version: 1.1.12
GitCommit: v1.1.12-0-g51d5e94
docker-init:
Version: 0.19.0
GitCommit: de40ad0
docker info
Client:
Version: 26.1.4
Context: desktop-linux
Debug Mode: false
Plugins:
buildx: Docker Buildx (Docker Inc.)
Version: v0.14.1-desktop.1
Path: $HOME/.docker/cli-plugins/docker-buildx
compose: Docker Compose (Docker Inc.)
Version: v2.27.1-desktop.1
Path: $HOME/.docker/cli-plugins/docker-compose
debug: Get a shell into any image or container (Docker Inc.)
Version: 0.0.32
Path: $HOME/.docker/cli-plugins/docker-debug
dev: Docker Dev Environments (Docker Inc.)
Version: v0.1.2
Path: $HOME/.docker/cli-plugins/docker-dev
extension: Manages Docker extensions (Docker Inc.)
Version: v0.2.24
Path: $HOME/.docker/cli-plugins/docker-extension
feedback: Provide feedback, right in your terminal! (Docker Inc.)
Version: v1.0.5
Path: $HOME/.docker/cli-plugins/docker-feedback
init: Creates Docker-related starter files for your project (Docker Inc.)
Version: v1.2.0
Path: $HOME/.docker/cli-plugins/docker-init
sbom: View the packaged-based Software Bill Of Materials (SBOM) for an image (Anchore Inc.)
Version: 0.6.0
Path: $HOME/.docker/cli-plugins/docker-sbom
scout: Docker Scout (Docker Inc.)
Version: v1.9.3
Path: $HOME/.docker/cli-plugins/docker-scout
Server:
Containers: 3
Running: 1
Paused: 0
Stopped: 2
Images: 18
Server Version: 26.1.4
Storage Driver: overlay2
Backing Filesystem: extfs
Supports d_type: true
Using metacopy: false
Native Overlay Diff: true
userxattr: false
Logging Driver: json-file
Cgroup Driver: cgroupfs
Cgroup Version: 2
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
Swarm: inactive
Runtimes: io.containerd.runc.v2 runc
Default Runtime: runc
Init Binary: docker-init
containerd version: d2d58213f83a351ca8f528a95fbd145f5654e957
runc version: v1.1.12-0-g51d5e94
init version: de40ad0
Security Options:
seccomp
Profile: unconfined
cgroupns
Kernel Version: 6.6.31-linuxkit
Operating System: Docker Desktop
OSType: linux
Architecture: x86_64
CPUs: 4
Total Memory: 3.823GiB
Name: docker-desktop
ID: a85fcfab-bc95-4b91-8463-9118c98f17d4
Docker Root Dir: /var/lib/docker
Debug Mode: false
HTTP Proxy: http.docker.internal:3128
HTTPS Proxy: http.docker.internal:3128
No Proxy: hubproxy.docker.internal
Labels:
com.docker.desktop.address=unix://$HOME/Library/Containers/com.docker.docker/Data/docker-cli.sock
Experimental: true
Insecure Registries:
hubproxy.docker.internal:5555
127.0.0.0/8
Live Restore Enabled: false
WARNING: daemon is not using the default seccomp profile
Diagnostics ID
3935486C-8631-4461-B3FB-19E3AE42907D/20240708172250
Additional Info
No response
lsof output with sample code:
When binding 0.0.0.0:
# sudo lsof -nP -i4TCP | grep LISTEN | grep 9002
#
(results in empty output, exit code 1)
When binding 127.0.0.1:
# sudo lsof -nP -i4TCP | grep LISTEN | grep 9002
com.docke 5874 dcarbone 177u IPv4 0x7f1382407af2058d 0t0 TCP 127.0.0.1:9002 (LISTEN)
#
(results in 1 row returned, exit code 0)
I can repro this issue on the latest nightly build of Docker Desktop. I also tested it with my wip branch where I add support for v6-only and dual-stack, and I can confirm it doesn't suffer from this bug.
I'm pretty sure this bug has already been reported but I can't find the original ticket. I'll close this one as duplicate if I can get hold of it.
For DD versions released up until now, the workaround is to make sure the socket is bound to the IPv4 ANY address.
diff --git a/main.go b/main.go
index f00ac31..a21f667 100644
--- a/main.go
+++ b/main.go
@@ -25,7 +25,7 @@ func main() {
_, _ = w.Write([]byte("hellord"))
})
- if l, err = lcfg.Listen(ctx, "tcp", "0.0.0.0:9002"); err != nil {
+ if l, err = lcfg.Listen(ctx, "tcp4", "0.0.0.0:9002"); err != nil {
panic(err.Error())
}
The provided repro makes a call to ListenConfig.Listen(), which get passed tcp as its 2nd argument. net.Dial() defines the semantic of that argument:
Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only)
That means tcp is dual-stack. This can be observed with strace:
# With tcp
$ strace -f --trace=bind ./testapp
...
[pid 1615] bind(3, {sa_family=AF_INET6, sin6_port=htons(9002), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::", &sin6_addr), sin6_scope_id=0}, 28) = 0
# With tcp4
$ strace -f --trace=bind ./testapp
[pid 1392] bind(3, {sa_family=AF_INET, sin_port=htons(9002), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
@dcarbone IPv6 / dual-stack support was added to the host networkwing feature in the latest Docker Desktop release (4.33.0). The workaround above should not be needed anymore.
I'll close this issue as completed, but feel free to continue the discussion and ping me if you still face some issues.