From 7b9c2905883df5171fda10a364a81b8c6176c8e2 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Mon, 26 Apr 2021 15:28:40 +0900 Subject: [PATCH] fix port forwarding with ipv6.disable=1 Make `docker run -p 80:80` functional again on environments with kernel boot parameter `ipv6.disable=1`. Fix moby/moby issue 42288 Signed-off-by: Akihiro Suda --- drivers/bridge/port_mapping.go | 31 +++++++++++++++++++++++++++++++ libnetwork_test.go | 7 ++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/drivers/bridge/port_mapping.go b/drivers/bridge/port_mapping.go index 946130ecdd..17bf36f9dd 100644 --- a/drivers/bridge/port_mapping.go +++ b/drivers/bridge/port_mapping.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net" + "sync" "github.com/docker/libnetwork/types" "github.com/ishidawataru/sctp" @@ -50,6 +51,13 @@ func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, cont bs = append(bs, bIPv4) } + // skip adding implicit v6 addr, when the kernel was booted with `ipv6.disable=1` + // https://github.com/moby/moby/issues/42288 + isV6Binding := c.HostIP != nil && c.HostIP.To4() == nil + if !isV6Binding && !IsV6Listenable() { + continue + } + // Allocate IPv6 Port mappings // If the container has no IPv6 address, allow proxying host IPv6 traffic to it // by setting up the binding with the IPv4 interface if the userland proxy is enabled @@ -211,3 +219,26 @@ func (n *bridgeNetwork) releasePort(bnd types.PortBinding) error { return portmapper.Unmap(host) } + +var ( + v6ListenableCached bool + v6ListenableOnce sync.Once +) + +// IsV6Listenable returns true when `[::1]:0` is listenable. +// IsV6Listenable returns false mostly when the kernel was booted with `ipv6.disable=1` option. +func IsV6Listenable() bool { + v6ListenableOnce.Do(func() { + ln, err := net.Listen("tcp6", "[::1]:0") + if err != nil { + // When the kernel was booted with `ipv6.disable=1`, + // we get err "listen tcp6 [::1]:0: socket: address family not supported by protocol" + // https://github.com/moby/moby/issues/42288 + logrus.Debugf("port_mapping: v6Listenable=false (%v)", err) + } else { + v6ListenableCached = true + ln.Close() + } + }) + return v6ListenableCached +} diff --git a/libnetwork_test.go b/libnetwork_test.go index d8659ddf2b..39c96d6f5e 100644 --- a/libnetwork_test.go +++ b/libnetwork_test.go @@ -16,6 +16,7 @@ import ( "github.com/docker/libnetwork/config" "github.com/docker/libnetwork/datastore" "github.com/docker/libnetwork/driverapi" + "github.com/docker/libnetwork/drivers/bridge" "github.com/docker/libnetwork/ipamapi" "github.com/docker/libnetwork/netlabel" "github.com/docker/libnetwork/options" @@ -199,7 +200,11 @@ func TestBridge(t *testing.T) { if !ok { t.Fatalf("Unexpected format for port mapping in endpoint operational data") } - if len(pm) != 10 { + expectedLen := 10 + if !bridge.IsV6Listenable() { + expectedLen = 5 + } + if len(pm) != expectedLen { t.Fatalf("Incomplete data for port mapping in endpoint operational data: %d", len(pm)) } }