From 7ca9e9b4969b7b3d669bcccacb5cb828d8866af7 Mon Sep 17 00:00:00 2001 From: Albin Kerouanton Date: Tue, 24 Sep 2024 22:55:41 +0200 Subject: [PATCH] libnet/d/bridge: port mapping: proxy LL connections Link-local connections were DNATed like other non-loopback connections, but the kernel would drop them even before their reach the container. This commit changes the DNAT rule inserted in ip6tables to exclude link-local addresses. Instead, these connections will be proxied by docker-proxy, at least if --userland-proxy=true. If dockerd is started with the userland-proxy disabled, link-local port-bindings won't be supported (ie. silently discarded). Signed-off-by: Albin Kerouanton --- integration/networking/port_mapping_linux_test.go | 15 +++++++++++---- libnetwork/drivers/bridge/port_mapping_linux.go | 3 +++ .../drivers/bridge/port_mapping_linux_test.go | 5 ++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/integration/networking/port_mapping_linux_test.go b/integration/networking/port_mapping_linux_test.go index cb0955497a..ede1920531 100644 --- a/integration/networking/port_mapping_linux_test.go +++ b/integration/networking/port_mapping_linux_test.go @@ -323,14 +323,21 @@ func TestAccessPublishedPortFromHost(t *testing.T) { // loopback address. continue } + + addr := hostAddr.String() if hostAddr.IsLinkLocalUnicast() { - // Mapping ports on link-local addresses is currently - // unsupported. - continue + if !tc.ulpEnabled { + // iptables can DNAT packets addressed to link-local + // addresses, but they won't be SNATed, so the + // target server won't know where to reply. Thus, + // the userland-proxy is required for these addresses. + continue + } + addr += "%25" + iface } httpClient := &http.Client{Timeout: 3 * time.Second} - resp, err := httpClient.Get("http://" + net.JoinHostPort(hostAddr.String(), hostPort)) + resp, err := httpClient.Get("http://" + net.JoinHostPort(addr, hostPort)) assert.NilError(t, err) assert.Check(t, is.Equal(resp.StatusCode, 404)) } diff --git a/libnetwork/drivers/bridge/port_mapping_linux.go b/libnetwork/drivers/bridge/port_mapping_linux.go index 95b3db45da..14d3753afe 100644 --- a/libnetwork/drivers/bridge/port_mapping_linux.go +++ b/libnetwork/drivers/bridge/port_mapping_linux.go @@ -772,6 +772,9 @@ func setPerPortNAT(b portBinding, ipv iptables.IPVersion, proxyPath string, brid if !hairpinMode { args = append(args, "!", "-i", bridgeName) } + if ipv == iptables.IPv6 { + args = append(args, "!", "-s", "fe80::/10") + } rule := iptRule{ipv: ipv, table: iptables.Nat, chain: DockerChain, args: args} if err := appendOrDelChainRule(rule, "DNAT", enable); err != nil { return err diff --git a/libnetwork/drivers/bridge/port_mapping_linux_test.go b/libnetwork/drivers/bridge/port_mapping_linux_test.go index 82e1fc8a13..80b39b36dd 100644 --- a/libnetwork/drivers/bridge/port_mapping_linux_test.go +++ b/libnetwork/drivers/bridge/port_mapping_linux_test.go @@ -921,9 +921,12 @@ func TestAddPortMappings(t *testing.T) { // Check the DNAT rule. dnatRule := "" + if ipv == iptables.IPv6 && !tc.gwMode6.natDisabled() { + dnatRule += "! -s fe80::/10 " + } if tc.proxyPath != "" { // No docker-proxy, so expect "hairpinMode". - dnatRule = "! -i dummybridge " + dnatRule += "! -i dummybridge " } dnatRule += fmt.Sprintf("-d %s -p %s -m %s --dport %d -j DNAT --to-destination %s:%d", addrH, expPB.Proto, expPB.Proto, expPB.HostPort, addrD, expPB.Port)