Преглед изворни кода

Merge pull request from GHSA-x84c-p2g9-rqv9

Disable IPv6 for endpoints in '--ipv6=false' networks.
Sebastiaan van Stijn пре 1 година
родитељ
комит
82d8f8d6e6

+ 25 - 1
integration/network/ipvlan/ipvlan_test.go

@@ -94,6 +94,9 @@ func TestDockerNetworkIpvlan(t *testing.T) {
 		}, {
 		}, {
 			name: "L3Addressing",
 			name: "L3Addressing",
 			test: testIpvlanL3Addressing,
 			test: testIpvlanL3Addressing,
+		}, {
+			name: "NoIPv6",
+			test: testIpvlanNoIPv6,
 		},
 		},
 	} {
 	} {
 
 
@@ -441,6 +444,28 @@ func testIpvlanL3Addressing(t *testing.T, ctx context.Context, client dclient.AP
 	assert.Check(t, is.Contains(result.Combined(), "default dev eth0"))
 	assert.Check(t, is.Contains(result.Combined(), "default dev eth0"))
 }
 }
 
 
+// Check that an ipvlan interface with '--ipv6=false' doesn't get kernel-assigned
+// IPv6 addresses, but the loopback interface does still have an IPv6 address ('::1').
+func testIpvlanNoIPv6(t *testing.T, ctx context.Context, client dclient.APIClient) {
+	const netName = "ipvlannet"
+	net.CreateNoError(ctx, t, client, netName, net.WithIPvlan("", "l3"))
+	assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
+
+	id := container.Run(ctx, t, client, container.WithNetworkMode(netName))
+
+	loRes := container.ExecT(ctx, t, client, id, []string{"ip", "a", "show", "dev", "lo"})
+	assert.Check(t, is.Contains(loRes.Combined(), " inet "))
+	assert.Check(t, is.Contains(loRes.Combined(), " inet6 "))
+
+	eth0Res := container.ExecT(ctx, t, client, id, []string{"ip", "a", "show", "dev", "eth0"})
+	assert.Check(t, is.Contains(eth0Res.Combined(), " inet "))
+	assert.Check(t, !strings.Contains(eth0Res.Combined(), " inet6 "),
+		"result.Combined(): %s", eth0Res.Combined())
+
+	sysctlRes := container.ExecT(ctx, t, client, id, []string{"sysctl", "-n", "net.ipv6.conf.eth0.disable_ipv6"})
+	assert.Check(t, is.Equal(strings.TrimSpace(sysctlRes.Combined()), "1"))
+}
+
 // TestIPVlanDNS checks whether DNS is forwarded, for combinations of l2/l3 mode,
 // TestIPVlanDNS checks whether DNS is forwarded, for combinations of l2/l3 mode,
 // with/without a parent interface, and with '--internal'. Note that, there's no
 // with/without a parent interface, and with '--internal'. Note that, there's no
 // attempt here to give the ipvlan network external connectivity - when this test
 // attempt here to give the ipvlan network external connectivity - when this test
@@ -452,7 +477,6 @@ func testIpvlanL3Addressing(t *testing.T, ctx context.Context, client dclient.AP
 // https://github.com/moby/moby/issues/47662
 // https://github.com/moby/moby/issues/47662
 func TestIPVlanDNS(t *testing.T) {
 func TestIPVlanDNS(t *testing.T) {
 	skip.If(t, testEnv.IsRootless, "rootless mode has different view of network")
 	skip.If(t, testEnv.IsRootless, "rootless mode has different view of network")
-
 	ctx := testutil.StartSpan(baseContext, t)
 	ctx := testutil.StartSpan(baseContext, t)
 
 
 	net.StartDaftDNS(t, "127.0.0.1")
 	net.StartDaftDNS(t, "127.0.0.1")

+ 29 - 0
integration/network/macvlan/macvlan_test.go

@@ -77,6 +77,9 @@ func TestDockerNetworkMacvlan(t *testing.T) {
 		}, {
 		}, {
 			name: "Addressing",
 			name: "Addressing",
 			test: testMacvlanAddressing,
 			test: testMacvlanAddressing,
+		}, {
+			name: "NoIPv6",
+			test: testMacvlanNoIPv6,
 		},
 		},
 	} {
 	} {
 		tc := tc
 		tc := tc
@@ -298,6 +301,32 @@ func testMacvlanAddressing(t *testing.T, ctx context.Context, client client.APIC
 	assert.Check(t, strings.Contains(result.Combined(), "default via 2001:db8:abca::254 dev eth0"))
 	assert.Check(t, strings.Contains(result.Combined(), "default via 2001:db8:abca::254 dev eth0"))
 }
 }
 
 
+// Check that a macvlan interface with '--ipv6=false' doesn't get kernel-assigned
+// IPv6 addresses, but the loopback interface does still have an IPv6 address ('::1').
+func testMacvlanNoIPv6(t *testing.T, ctx context.Context, client client.APIClient) {
+	const netName = "macvlannet"
+
+	net.CreateNoError(ctx, t, client, netName,
+		net.WithMacvlan(""),
+		net.WithOption("macvlan_mode", "bridge"),
+	)
+	assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
+
+	id := container.Run(ctx, t, client, container.WithNetworkMode(netName))
+
+	loRes := container.ExecT(ctx, t, client, id, []string{"ip", "a", "show", "dev", "lo"})
+	assert.Check(t, is.Contains(loRes.Combined(), " inet "))
+	assert.Check(t, is.Contains(loRes.Combined(), " inet6 "))
+
+	eth0Res := container.ExecT(ctx, t, client, id, []string{"ip", "a", "show", "dev", "eth0"})
+	assert.Check(t, is.Contains(eth0Res.Combined(), " inet "))
+	assert.Check(t, !strings.Contains(eth0Res.Combined(), " inet6 "),
+		"result.Combined(): %s", eth0Res.Combined())
+
+	sysctlRes := container.ExecT(ctx, t, client, id, []string{"sysctl", "-n", "net.ipv6.conf.eth0.disable_ipv6"})
+	assert.Check(t, is.Equal(strings.TrimSpace(sysctlRes.Combined()), "1"))
+}
+
 // TestMACVlanDNS checks whether DNS is forwarded, with/without a parent
 // TestMACVlanDNS checks whether DNS is forwarded, with/without a parent
 // interface, and with '--internal'. Note that there's no attempt here to give
 // interface, and with '--internal'. Note that there's no attempt here to give
 // the macvlan network external connectivity - when this test supplies a parent
 // the macvlan network external connectivity - when this test supplies a parent

+ 37 - 2
integration/networking/bridge_test.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
 	"regexp"
 	"regexp"
+	"strings"
 	"testing"
 	"testing"
 	"time"
 	"time"
 
 
@@ -611,8 +612,8 @@ func TestInternalNwConnectivity(t *testing.T) {
 	assert.Check(t, is.Contains(res.Stderr(), "Network is unreachable"))
 	assert.Check(t, is.Contains(res.Stderr(), "Network is unreachable"))
 }
 }
 
 
-// Check that the container's interface has no IPv6 address when IPv6 is
-// disabled in a container via sysctl.
+// Check that the container's interfaces have no IPv6 address when IPv6 is
+// disabled in a container via sysctl (including 'lo').
 func TestDisableIPv6Addrs(t *testing.T) {
 func TestDisableIPv6Addrs(t *testing.T) {
 	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
 	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
 
 
@@ -676,6 +677,40 @@ func TestDisableIPv6Addrs(t *testing.T) {
 	}
 	}
 }
 }
 
 
+// Check that an interface to an '--ipv6=false' network has no IPv6
+// address - either IPAM assigned, or kernel-assigned LL, but the loopback
+// interface does still have an IPv6 address ('::1').
+func TestNonIPv6Network(t *testing.T) {
+	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
+
+	ctx := setupTest(t)
+	d := daemon.New(t)
+	d.StartWithBusybox(ctx, t)
+	defer d.Stop(t)
+
+	c := d.NewClientT(t)
+	defer c.Close()
+
+	const netName = "testnet"
+	network.CreateNoError(ctx, t, c, netName)
+	defer network.RemoveNoError(ctx, t, c, netName)
+
+	id := container.Run(ctx, t, c, container.WithNetworkMode(netName))
+	defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true})
+
+	loRes := container.ExecT(ctx, t, c, id, []string{"ip", "a", "show", "dev", "lo"})
+	assert.Check(t, is.Contains(loRes.Combined(), " inet "))
+	assert.Check(t, is.Contains(loRes.Combined(), " inet6 "))
+
+	eth0Res := container.ExecT(ctx, t, c, id, []string{"ip", "a", "show", "dev", "eth0"})
+	assert.Check(t, is.Contains(eth0Res.Combined(), " inet "))
+	assert.Check(t, !strings.Contains(eth0Res.Combined(), " inet6 "),
+		"result.Combined(): %s", eth0Res.Combined())
+
+	sysctlRes := container.ExecT(ctx, t, c, id, []string{"sysctl", "-n", "net.ipv6.conf.eth0.disable_ipv6"})
+	assert.Check(t, is.Equal(strings.TrimSpace(sysctlRes.Combined()), "1"))
+}
+
 // Test that it's possible to set a sysctl on an interface in the container.
 // Test that it's possible to set a sysctl on an interface in the container.
 // Regression test for https://github.com/moby/moby/issues/47619
 // Regression test for https://github.com/moby/moby/issues/47619
 func TestSetInterfaceSysctl(t *testing.T) {
 func TestSetInterfaceSysctl(t *testing.T) {

+ 14 - 7
libnetwork/osl/interface_linux.go

@@ -363,17 +363,24 @@ func setInterfaceIP(nlh *netlink.Handle, iface netlink.Link, i *Interface) error
 }
 }
 
 
 func setInterfaceIPv6(nlh *netlink.Handle, iface netlink.Link, i *Interface) error {
 func setInterfaceIPv6(nlh *netlink.Handle, iface netlink.Link, i *Interface) error {
-	if i.AddressIPv6() == nil {
+	addr := i.AddressIPv6()
+	// IPv6 must be enabled on the interface if and only if the network is
+	// IPv6-enabled. For an interface on an IPv4-only network, if IPv6 isn't
+	// disabled, the interface will be put into IPv6 multicast groups making
+	// it unexpectedly susceptible to NDP cache poisoning, route injection, etc.
+	// (At present, there will always be a pre-configured IPv6 address if the
+	// network is IPv6-enabled.)
+	if err := setIPv6(i.ns.path, i.DstName(), addr != nil); err != nil {
+		return fmt.Errorf("failed to configure ipv6: %v", err)
+	}
+	if addr == nil {
 		return nil
 		return nil
 	}
 	}
-	if err := checkRouteConflict(nlh, i.AddressIPv6(), netlink.FAMILY_V6); err != nil {
+	if err := checkRouteConflict(nlh, addr, netlink.FAMILY_V6); err != nil {
 		return err
 		return err
 	}
 	}
-	if err := setIPv6(i.ns.path, i.DstName(), true); err != nil {
-		return fmt.Errorf("failed to enable ipv6: %v", err)
-	}
-	ipAddr := &netlink.Addr{IPNet: i.AddressIPv6(), Label: "", Flags: syscall.IFA_F_NODAD}
-	return nlh.AddrAdd(iface, ipAddr)
+	nlAddr := &netlink.Addr{IPNet: addr, Label: "", Flags: syscall.IFA_F_NODAD}
+	return nlh.AddrAdd(iface, nlAddr)
 }
 }
 
 
 func setInterfaceLinkLocalIPs(nlh *netlink.Handle, iface netlink.Link, i *Interface) error {
 func setInterfaceLinkLocalIPs(nlh *netlink.Handle, iface netlink.Link, i *Interface) error {