From b101022dbe6daa36ebf11df53ad01a399a655963 Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Thu, 29 May 2014 00:06:23 +0400 Subject: [PATCH] Implement allocating IPs from CIDR within bridge network Fixes #4986 Signed-off-by: Alexandr Morozov --- daemon/config.go | 2 + daemon/daemon.go | 1 + daemon/networkdriver/bridge/driver.go | 11 +++ daemon/networkdriver/ipallocator/allocator.go | 35 ++++++-- .../ipallocator/allocator_test.go | 80 +++++++++++++++++++ docs/sources/reference/commandline/cli.md | 1 + 6 files changed, 125 insertions(+), 5 deletions(-) diff --git a/daemon/config.go b/daemon/config.go index 1ca8086933..20d839efa4 100644 --- a/daemon/config.go +++ b/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") diff --git a/daemon/daemon.go b/daemon/daemon.go index 0a4d6e0bc5..8bf92be15b 100644 --- a/daemon/daemon.go +++ b/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 { diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index c4b10fe07f..0ffad75fc1 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/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) diff --git a/daemon/networkdriver/ipallocator/allocator.go b/daemon/networkdriver/ipallocator/allocator.go index 7abb3ffdf4..ad7547a4a0 100644 --- a/daemon/networkdriver/ipallocator/allocator.go +++ b/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 diff --git a/daemon/networkdriver/ipallocator/allocator_test.go b/daemon/networkdriver/ipallocator/allocator_test.go index 57575bc7bd..0b5d97e605 100644 --- a/daemon/networkdriver/ipallocator/allocator_test.go +++ b/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) { diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index ef30113882..3d117fe3c9 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/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