浏览代码

Merge pull request #45246 from akerouanton/cherrypick-44827

[23.0 backport] daemon: let libnetwork assign default bridge IPAM
Sebastiaan van Stijn 2 年之前
父节点
当前提交
15d6037c1e
共有 3 个文件被更改,包括 133 次插入18 次删除
  1. 41 0
      daemon/daemon_linux.go
  2. 68 0
      daemon/daemon_linux_test.go
  3. 24 18
      daemon/daemon_unix.go

+ 41 - 0
daemon/daemon_linux.go

@@ -4,16 +4,19 @@ import (
 	"bufio"
 	"fmt"
 	"io"
+	"net"
 	"os"
 	"regexp"
 	"strings"
 
 	"github.com/docker/docker/daemon/config"
+	"github.com/docker/docker/libnetwork/ns"
 	"github.com/docker/docker/libnetwork/resolvconf"
 	"github.com/moby/sys/mount"
 	"github.com/moby/sys/mountinfo"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
+	"github.com/vishvananda/netlink"
 )
 
 // On Linux, plugins use a static path for storing execution state,
@@ -141,3 +144,41 @@ func setupResolvConf(config *config.Config) {
 	}
 	config.ResolvConf = resolvconf.Path()
 }
+
+// ifaceAddrs returns the IPv4 and IPv6 addresses assigned to the network
+// interface with name linkName.
+//
+// No error is returned if the named interface does not exist.
+func ifaceAddrs(linkName string) (v4, v6 []*net.IPNet, err error) {
+	nl := ns.NlHandle()
+	link, err := nl.LinkByName(linkName)
+	if err != nil {
+		if !errors.As(err, new(netlink.LinkNotFoundError)) {
+			return nil, nil, err
+		}
+		return nil, nil, nil
+	}
+
+	get := func(family int) ([]*net.IPNet, error) {
+		addrs, err := nl.AddrList(link, family)
+		if err != nil {
+			return nil, err
+		}
+
+		ipnets := make([]*net.IPNet, len(addrs))
+		for i := range addrs {
+			ipnets[i] = addrs[i].IPNet
+		}
+		return ipnets, nil
+	}
+
+	v4, err = get(netlink.FAMILY_V4)
+	if err != nil {
+		return nil, nil, err
+	}
+	v6, err = get(netlink.FAMILY_V6)
+	if err != nil {
+		return nil, nil, err
+	}
+	return v4, v6, nil
+}

+ 68 - 0
daemon/daemon_linux_test.go

@@ -4,6 +4,7 @@
 package daemon // import "github.com/docker/docker/daemon"
 
 import (
+	"net"
 	"os"
 	"path/filepath"
 	"strings"
@@ -11,8 +12,12 @@ import (
 
 	containertypes "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/daemon/config"
+	"github.com/docker/docker/libnetwork/testutils"
+	"github.com/docker/docker/libnetwork/types"
+	"github.com/google/go-cmp/cmp/cmpopts"
 	"github.com/moby/sys/mount"
 	"github.com/moby/sys/mountinfo"
+	"github.com/vishvananda/netlink"
 	"gotest.tools/v3/assert"
 	is "gotest.tools/v3/assert/cmp"
 )
@@ -343,3 +348,66 @@ func TestRootMountCleanup(t *testing.T) {
 		assert.Assert(t, d.cleanupMounts())
 	})
 }
+
+func TestIfaceAddrs(t *testing.T) {
+	CIDR := func(cidr string) *net.IPNet {
+		t.Helper()
+		nw, err := types.ParseCIDR(cidr)
+		assert.NilError(t, err)
+		return nw
+	}
+
+	for _, tt := range []struct {
+		name string
+		nws  []*net.IPNet
+	}{
+		{
+			name: "Single",
+			nws:  []*net.IPNet{CIDR("172.101.202.254/16")},
+		},
+		{
+			name: "Multiple",
+			nws: []*net.IPNet{
+				CIDR("172.101.202.254/16"),
+				CIDR("172.102.202.254/16"),
+			},
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			defer testutils.SetupTestOSContext(t)()
+
+			createBridge(t, "test", tt.nws...)
+
+			ipv4Nw, ipv6Nw, err := ifaceAddrs("test")
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			assert.Check(t, is.DeepEqual(tt.nws, ipv4Nw,
+				cmpopts.SortSlices(func(a, b *net.IPNet) bool { return a.String() < b.String() })))
+			// IPv6 link-local address
+			assert.Check(t, is.Len(ipv6Nw, 1))
+		})
+	}
+}
+
+func createBridge(t *testing.T, name string, bips ...*net.IPNet) {
+	t.Helper()
+
+	link := &netlink.Bridge{
+		LinkAttrs: netlink.LinkAttrs{
+			Name: name,
+		},
+	}
+	if err := netlink.LinkAdd(link); err != nil {
+		t.Fatalf("Failed to create interface via netlink: %v", err)
+	}
+	for _, bip := range bips {
+		if err := netlink.AddrAdd(link, &netlink.Addr{IPNet: bip}); err != nil {
+			t.Fatal(err)
+		}
+	}
+	if err := netlink.LinkSetUp(link); err != nil {
+		t.Fatal(err)
+	}
+}

+ 24 - 18
daemon/daemon_unix.go

@@ -34,7 +34,6 @@ import (
 	nwconfig "github.com/docker/docker/libnetwork/config"
 	"github.com/docker/docker/libnetwork/drivers/bridge"
 	"github.com/docker/docker/libnetwork/netlabel"
-	"github.com/docker/docker/libnetwork/netutils"
 	"github.com/docker/docker/libnetwork/options"
 	lntypes "github.com/docker/docker/libnetwork/types"
 	"github.com/docker/docker/opts"
@@ -950,30 +949,37 @@ func initBridgeDriver(controller libnetwork.NetworkController, config *config.Co
 
 	ipamV4Conf := &libnetwork.IpamConf{AuxAddresses: make(map[string]string)}
 
-	nwList, nw6List, err := netutils.ElectInterfaceAddresses(bridgeName)
+	// By default, libnetwork will request an arbitrary available address
+	// pool for the network from the configured IPAM allocator.
+	// Configure it to use the IPv4 network ranges of the existing bridge
+	// interface if one exists with IPv4 addresses assigned to it.
+
+	nwList, nw6List, err := ifaceAddrs(bridgeName)
 	if err != nil {
 		return errors.Wrap(err, "list bridge addresses failed")
 	}
 
-	nw := nwList[0]
-	if len(nwList) > 1 && config.BridgeConfig.FixedCIDR != "" {
-		_, fCIDR, err := net.ParseCIDR(config.BridgeConfig.FixedCIDR)
-		if err != nil {
-			return errors.Wrap(err, "parse CIDR failed")
-		}
-		// Iterate through in case there are multiple addresses for the bridge
-		for _, entry := range nwList {
-			if fCIDR.Contains(entry.IP) {
-				nw = entry
-				break
+	if len(nwList) > 0 {
+		nw := nwList[0]
+		if len(nwList) > 1 && config.BridgeConfig.FixedCIDR != "" {
+			_, fCIDR, err := net.ParseCIDR(config.BridgeConfig.FixedCIDR)
+			if err != nil {
+				return errors.Wrap(err, "parse CIDR failed")
+			}
+			// Iterate through in case there are multiple addresses for the bridge
+			for _, entry := range nwList {
+				if fCIDR.Contains(entry.IP) {
+					nw = entry
+					break
+				}
 			}
 		}
-	}
 
-	ipamV4Conf.PreferredPool = lntypes.GetIPNetCanonical(nw).String()
-	hip, _ := lntypes.GetHostPartIP(nw.IP, nw.Mask)
-	if hip.IsGlobalUnicast() {
-		ipamV4Conf.Gateway = nw.IP.String()
+		ipamV4Conf.PreferredPool = lntypes.GetIPNetCanonical(nw).String()
+		hip, _ := lntypes.GetHostPartIP(nw.IP, nw.Mask)
+		if hip.IsGlobalUnicast() {
+			ipamV4Conf.Gateway = nw.IP.String()
+		}
 	}
 
 	if config.BridgeConfig.IP != "" {