Переглянути джерело

Implement allocating IPs from CIDR within bridge network

Fixes #4986

Signed-off-by: Alexandr Morozov <lk4d4math@gmail.com>
Alexandr Morozov 11 роки тому
батько
коміт
b101022dbe

+ 2 - 0
daemon/config.go

@@ -28,6 +28,7 @@ type Config struct {
 	DefaultIp                   net.IP
 	BridgeIface                 string
 	BridgeIP                    string
+	FixedCIDR                   string
 	InterContainerCommunication bool
 	GraphDriver                 string
 	GraphOptions                []string
@@ -50,6 +51,7 @@ func (config *Config) InstallFlags() {
 	flag.BoolVar(&config.EnableIpForward, []string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward")
 	flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b")
 	flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
+	flag.StringVar(&config.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)\nthis subnet must be nested in bridge subnet (which is defined by -b or --bip)")
 	flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication")
 	flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver")
 	flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver")

+ 1 - 0
daemon/daemon.go

@@ -803,6 +803,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
 		job.SetenvBool("EnableIpForward", config.EnableIpForward)
 		job.Setenv("BridgeIface", config.BridgeIface)
 		job.Setenv("BridgeIP", config.BridgeIP)
+		job.Setenv("FixedCIDR", config.FixedCIDR)
 		job.Setenv("DefaultBindingIP", config.DefaultIp.String())
 
 		if err := job.Run(); err != nil {

+ 11 - 0
daemon/networkdriver/bridge/driver.go

@@ -83,6 +83,7 @@ func InitDriver(job *engine.Job) engine.Status {
 		icc            = job.GetenvBool("InterContainerCommunication")
 		ipForward      = job.GetenvBool("EnableIpForward")
 		bridgeIP       = job.Getenv("BridgeIP")
+		fixedCIDR      = job.Getenv("FixedCIDR")
 	)
 
 	if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" {
@@ -157,6 +158,16 @@ func InitDriver(job *engine.Job) engine.Status {
 	}
 
 	bridgeNetwork = network
+	if fixedCIDR != "" {
+		_, subnet, err := net.ParseCIDR(fixedCIDR)
+		if err != nil {
+			return job.Error(err)
+		}
+		log.Debugf("Subnet: %v", subnet)
+		if err := ipallocator.RegisterSubnet(bridgeNetwork, subnet); err != nil {
+			return job.Error(err)
+		}
+	}
 
 	// https://github.com/docker/docker/issues/2768
 	job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", bridgeNetwork.IP)

+ 30 - 5
daemon/networkdriver/ipallocator/allocator.go

@@ -6,7 +6,7 @@ import (
 	"net"
 	"sync"
 
-	"github.com/dotcloud/docker/daemon/networkdriver"
+	"github.com/docker/docker/daemon/networkdriver"
 )
 
 // allocatedMap is thread-unsafe set of allocated IP
@@ -23,8 +23,8 @@ func newAllocatedMap(network *net.IPNet) *allocatedMap {
 	end := ipToInt(lastIP) - 1
 	return &allocatedMap{
 		p:     make(map[uint32]struct{}),
-		begin: begin,     // - network
-		end:   end,       // - broadcast
+		begin: begin,
+		end:   end,
 		last:  begin - 1, // so first allocated will be begin
 	}
 }
@@ -32,8 +32,10 @@ func newAllocatedMap(network *net.IPNet) *allocatedMap {
 type networkSet map[string]*allocatedMap
 
 var (
-	ErrNoAvailableIPs     = errors.New("no available ip addresses on network")
-	ErrIPAlreadyAllocated = errors.New("ip already allocated")
+	ErrNoAvailableIPs           = errors.New("no available ip addresses on network")
+	ErrIPAlreadyAllocated       = errors.New("ip already allocated")
+	ErrNetworkAlreadyRegistered = errors.New("network already registered")
+	ErrBadSubnet                = errors.New("network not contains specified subnet")
 )
 
 var (
@@ -41,6 +43,29 @@ var (
 	allocatedIPs = networkSet{}
 )
 
+// RegisterSubnet registers network in global allocator with bounds
+// defined by subnet. If you want to use network range you must call
+// this method before first RequestIP, otherwise full network range will be used
+func RegisterSubnet(network *net.IPNet, subnet *net.IPNet) error {
+	lock.Lock()
+	defer lock.Unlock()
+	key := network.String()
+	if _, ok := allocatedIPs[key]; ok {
+		return ErrNetworkAlreadyRegistered
+	}
+	n := newAllocatedMap(network)
+	beginIP, endIP := networkdriver.NetworkRange(subnet)
+	begin, end := ipToInt(beginIP)+1, ipToInt(endIP)-1
+	if !(begin >= n.begin && end <= n.end && begin < end) {
+		return ErrBadSubnet
+	}
+	n.begin = begin
+	n.end = end
+	n.last = begin - 1
+	allocatedIPs[key] = n
+	return nil
+}
+
 // RequestIP requests an available ip from the given network.  It
 // will return the next available ip if the ip provided is nil.  If the
 // ip provided is not nil it will validate that the provided ip is available

+ 80 - 0
daemon/networkdriver/ipallocator/allocator_test.go

@@ -318,6 +318,86 @@ func TestAllocateDifferentSubnets(t *testing.T) {
 	assertIPEquals(t, expectedIPs[2], ip21)
 	assertIPEquals(t, expectedIPs[3], ip22)
 }
+func TestRegisterBadTwice(t *testing.T) {
+	defer reset()
+	network := &net.IPNet{
+		IP:   []byte{192, 168, 1, 1},
+		Mask: []byte{255, 255, 255, 0},
+	}
+	subnet := &net.IPNet{
+		IP:   []byte{192, 168, 1, 8},
+		Mask: []byte{255, 255, 255, 248},
+	}
+
+	if err := RegisterSubnet(network, subnet); err != nil {
+		t.Fatal(err)
+	}
+	subnet = &net.IPNet{
+		IP:   []byte{192, 168, 1, 16},
+		Mask: []byte{255, 255, 255, 248},
+	}
+	if err := RegisterSubnet(network, subnet); err != ErrNetworkAlreadyRegistered {
+		t.Fatalf("Expecteded ErrNetworkAlreadyRegistered error, got %v", err)
+	}
+}
+
+func TestRegisterBadRange(t *testing.T) {
+	defer reset()
+	network := &net.IPNet{
+		IP:   []byte{192, 168, 1, 1},
+		Mask: []byte{255, 255, 255, 0},
+	}
+	subnet := &net.IPNet{
+		IP:   []byte{192, 168, 1, 1},
+		Mask: []byte{255, 255, 0, 0},
+	}
+	if err := RegisterSubnet(network, subnet); err != ErrBadSubnet {
+		t.Fatalf("Expected ErrBadSubnet error, got %v", err)
+	}
+}
+
+func TestAllocateFromRange(t *testing.T) {
+	defer reset()
+	network := &net.IPNet{
+		IP:   []byte{192, 168, 0, 1},
+		Mask: []byte{255, 255, 255, 0},
+	}
+	// 192.168.1.9 - 192.168.1.14
+	subnet := &net.IPNet{
+		IP:   []byte{192, 168, 0, 8},
+		Mask: []byte{255, 255, 255, 248},
+	}
+	if err := RegisterSubnet(network, subnet); err != nil {
+		t.Fatal(err)
+	}
+	expectedIPs := []net.IP{
+		0: net.IPv4(192, 168, 0, 9),
+		1: net.IPv4(192, 168, 0, 10),
+		2: net.IPv4(192, 168, 0, 11),
+		3: net.IPv4(192, 168, 0, 12),
+		4: net.IPv4(192, 168, 0, 13),
+		5: net.IPv4(192, 168, 0, 14),
+	}
+	for _, ip := range expectedIPs {
+		rip, err := RequestIP(network, nil)
+		if err != nil {
+			t.Fatal(err)
+		}
+		assertIPEquals(t, ip, rip)
+	}
+
+	if _, err := RequestIP(network, nil); err != ErrNoAvailableIPs {
+		t.Fatalf("Expected ErrNoAvailableIPs error, got %v", err)
+	}
+	for _, ip := range expectedIPs {
+		ReleaseIP(network, ip)
+		rip, err := RequestIP(network, nil)
+		if err != nil {
+			t.Fatal(err)
+		}
+		assertIPEquals(t, ip, rip)
+	}
+}
 
 func assertIPEquals(t *testing.T, ip1, ip2 net.IP) {
 	if !ip1.Equal(ip2) {

+ 1 - 0
docs/sources/reference/commandline/cli.md

@@ -54,6 +54,7 @@ expect an integer, and they can only be specified once.
       -b, --bridge=""                            Attach containers to a pre-existing network bridge
                                                    use 'none' to disable container networking
       --bip=""                                   Use this CIDR notation address for the network bridge's IP, not compatible with -b
+      --fixed-cidr=""                            IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)
       -D, --debug=false                          Enable debug mode
       -d, --daemon=false                         Enable daemon mode
       --dns=[]                                   Force Docker to use specific DNS servers