浏览代码

Merge pull request #6101 from LK4D4/ip_range_#4986

Implement allocating IPs from CIDR within bridge network
unclejack 10 年之前
父节点
当前提交
9fb34ae571

+ 2 - 0
daemon/config.go

@@ -30,6 +30,7 @@ type Config struct {
 	DefaultIp                   net.IP
 	DefaultIp                   net.IP
 	BridgeIface                 string
 	BridgeIface                 string
 	BridgeIP                    string
 	BridgeIP                    string
+	FixedCIDR                   string
 	InterContainerCommunication bool
 	InterContainerCommunication bool
 	GraphDriver                 string
 	GraphDriver                 string
 	GraphOptions                []string
 	GraphOptions                []string
@@ -53,6 +54,7 @@ func (config *Config) InstallFlags() {
 	flag.BoolVar(&config.EnableIpMasq, []string{"-ip-masq"}, true, "Enable IP masquerading for bridge's IP range")
 	flag.BoolVar(&config.EnableIpMasq, []string{"-ip-masq"}, true, "Enable IP masquerading for bridge's IP range")
 	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.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.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 the bridge subnet (which is defined by -b or --bip)")
 	flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication")
 	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.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")
 	flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver")

+ 1 - 0
daemon/daemon.go

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

+ 15 - 4
daemon/networkdriver/bridge/driver.go

@@ -84,6 +84,7 @@ func InitDriver(job *engine.Job) engine.Status {
 		ipMasq         = job.GetenvBool("EnableIpMasq")
 		ipMasq         = job.GetenvBool("EnableIpMasq")
 		ipForward      = job.GetenvBool("EnableIpForward")
 		ipForward      = job.GetenvBool("EnableIpForward")
 		bridgeIP       = job.Getenv("BridgeIP")
 		bridgeIP       = job.Getenv("BridgeIP")
+		fixedCIDR      = job.Getenv("FixedCIDR")
 	)
 	)
 
 
 	if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" {
 	if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" {
@@ -155,6 +156,16 @@ func InitDriver(job *engine.Job) engine.Status {
 	}
 	}
 
 
 	bridgeNetwork = network
 	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
 	// https://github.com/docker/docker/issues/2768
 	job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", bridgeNetwork.IP)
 	job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", bridgeNetwork.IP)
@@ -318,14 +329,14 @@ func createBridgeIface(name string) error {
 // Allocate a network interface
 // Allocate a network interface
 func Allocate(job *engine.Job) engine.Status {
 func Allocate(job *engine.Job) engine.Status {
 	var (
 	var (
-		ip          *net.IP
+		ip          net.IP
 		err         error
 		err         error
 		id          = job.Args[0]
 		id          = job.Args[0]
 		requestedIP = net.ParseIP(job.Getenv("RequestedIP"))
 		requestedIP = net.ParseIP(job.Getenv("RequestedIP"))
 	)
 	)
 
 
 	if requestedIP != nil {
 	if requestedIP != nil {
-		ip, err = ipallocator.RequestIP(bridgeNetwork, &requestedIP)
+		ip, err = ipallocator.RequestIP(bridgeNetwork, requestedIP)
 	} else {
 	} else {
 		ip, err = ipallocator.RequestIP(bridgeNetwork, nil)
 		ip, err = ipallocator.RequestIP(bridgeNetwork, nil)
 	}
 	}
@@ -343,7 +354,7 @@ func Allocate(job *engine.Job) engine.Status {
 	out.SetInt("IPPrefixLen", size)
 	out.SetInt("IPPrefixLen", size)
 
 
 	currentInterfaces.Set(id, &networkInterface{
 	currentInterfaces.Set(id, &networkInterface{
-		IP: *ip,
+		IP: ip,
 	})
 	})
 
 
 	out.WriteTo(job.Stdout)
 	out.WriteTo(job.Stdout)
@@ -368,7 +379,7 @@ func Release(job *engine.Job) engine.Status {
 		}
 		}
 	}
 	}
 
 
-	if err := ipallocator.ReleaseIP(bridgeNetwork, &containerInterface.IP); err != nil {
+	if err := ipallocator.ReleaseIP(bridgeNetwork, containerInterface.IP); err != nil {
 		log.Infof("Unable to release ip %s", err)
 		log.Infof("Unable to release ip %s", err)
 	}
 	}
 	return engine.StatusOK
 	return engine.StatusOK

+ 61 - 49
daemon/networkdriver/ipallocator/allocator.go

@@ -3,26 +3,39 @@ package ipallocator
 import (
 import (
 	"encoding/binary"
 	"encoding/binary"
 	"errors"
 	"errors"
-	"github.com/docker/docker/daemon/networkdriver"
 	"net"
 	"net"
 	"sync"
 	"sync"
+
+	"github.com/docker/docker/daemon/networkdriver"
 )
 )
 
 
 // allocatedMap is thread-unsafe set of allocated IP
 // allocatedMap is thread-unsafe set of allocated IP
 type allocatedMap struct {
 type allocatedMap struct {
-	p    map[int32]struct{}
-	last int32
+	p     map[uint32]struct{}
+	last  uint32
+	begin uint32
+	end   uint32
 }
 }
 
 
-func newAllocatedMap() *allocatedMap {
-	return &allocatedMap{p: make(map[int32]struct{})}
+func newAllocatedMap(network *net.IPNet) *allocatedMap {
+	firstIP, lastIP := networkdriver.NetworkRange(network)
+	begin := ipToInt(firstIP) + 2
+	end := ipToInt(lastIP) - 1
+	return &allocatedMap{
+		p:     make(map[uint32]struct{}),
+		begin: begin,
+		end:   end,
+		last:  begin - 1, // so first allocated will be begin
+	}
 }
 }
 
 
 type networkSet map[string]*allocatedMap
 type networkSet map[string]*allocatedMap
 
 
 var (
 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 (
 var (
@@ -30,47 +43,63 @@ var (
 	allocatedIPs = networkSet{}
 	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
 // RequestIP requests an available ip from the given network.  It
 // will return the next available ip if the ip provided is nil.  If the
 // 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
 // ip provided is not nil it will validate that the provided ip is available
 // for use or return an error
 // for use or return an error
-func RequestIP(network *net.IPNet, ip *net.IP) (*net.IP, error) {
+func RequestIP(network *net.IPNet, ip net.IP) (net.IP, error) {
 	lock.Lock()
 	lock.Lock()
 	defer lock.Unlock()
 	defer lock.Unlock()
 	key := network.String()
 	key := network.String()
 	allocated, ok := allocatedIPs[key]
 	allocated, ok := allocatedIPs[key]
 	if !ok {
 	if !ok {
-		allocated = newAllocatedMap()
+		allocated = newAllocatedMap(network)
 		allocatedIPs[key] = allocated
 		allocatedIPs[key] = allocated
 	}
 	}
 
 
 	if ip == nil {
 	if ip == nil {
-		return allocated.getNextIP(network)
+		return allocated.getNextIP()
 	}
 	}
-	return allocated.checkIP(network, ip)
+	return allocated.checkIP(ip)
 }
 }
 
 
 // ReleaseIP adds the provided ip back into the pool of
 // ReleaseIP adds the provided ip back into the pool of
 // available ips to be returned for use.
 // available ips to be returned for use.
-func ReleaseIP(network *net.IPNet, ip *net.IP) error {
+func ReleaseIP(network *net.IPNet, ip net.IP) error {
 	lock.Lock()
 	lock.Lock()
 	defer lock.Unlock()
 	defer lock.Unlock()
 	if allocated, exists := allocatedIPs[network.String()]; exists {
 	if allocated, exists := allocatedIPs[network.String()]; exists {
-		pos := getPosition(network, ip)
+		pos := ipToInt(ip)
 		delete(allocated.p, pos)
 		delete(allocated.p, pos)
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-// convert the ip into the position in the subnet.  Only
-// position are saved in the set
-func getPosition(network *net.IPNet, ip *net.IP) int32 {
-	first, _ := networkdriver.NetworkRange(network)
-	return ipToInt(ip) - ipToInt(&first)
-}
-
-func (allocated *allocatedMap) checkIP(network *net.IPNet, ip *net.IP) (*net.IP, error) {
-	pos := getPosition(network, ip)
+func (allocated *allocatedMap) checkIP(ip net.IP) (net.IP, error) {
+	pos := ipToInt(ip)
 	if _, ok := allocated.p[pos]; ok {
 	if _, ok := allocated.p[pos]; ok {
 		return nil, ErrIPAlreadyAllocated
 		return nil, ErrIPAlreadyAllocated
 	}
 	}
@@ -81,47 +110,30 @@ func (allocated *allocatedMap) checkIP(network *net.IPNet, ip *net.IP) (*net.IP,
 
 
 // return an available ip if one is currently available.  If not,
 // return an available ip if one is currently available.  If not,
 // return the next available ip for the nextwork
 // return the next available ip for the nextwork
-func (allocated *allocatedMap) getNextIP(network *net.IPNet) (*net.IP, error) {
-	var (
-		ownIP    = ipToInt(&network.IP)
-		first, _ = networkdriver.NetworkRange(network)
-		base     = ipToInt(&first)
-		size     = int(networkdriver.NetworkSize(network.Mask))
-		max      = int32(size - 2) // size -1 for the broadcast network, -1 for the gateway network
-		pos      = allocated.last
-	)
-
-	var (
-		firstNetIP = network.IP.To4().Mask(network.Mask)
-		firstAsInt = ipToInt(&firstNetIP) + 1
-	)
-
-	for i := int32(0); i < max; i++ {
-		pos = pos%max + 1
-		next := int32(base + pos)
-
-		if next == ownIP || next == firstAsInt {
-			continue
+func (allocated *allocatedMap) getNextIP() (net.IP, error) {
+	for pos := allocated.last + 1; pos != allocated.last; pos++ {
+		if pos > allocated.end {
+			pos = allocated.begin
 		}
 		}
 		if _, ok := allocated.p[pos]; ok {
 		if _, ok := allocated.p[pos]; ok {
 			continue
 			continue
 		}
 		}
 		allocated.p[pos] = struct{}{}
 		allocated.p[pos] = struct{}{}
 		allocated.last = pos
 		allocated.last = pos
-		return intToIP(next), nil
+		return intToIP(pos), nil
 	}
 	}
 	return nil, ErrNoAvailableIPs
 	return nil, ErrNoAvailableIPs
 }
 }
 
 
 // Converts a 4 bytes IP into a 32 bit integer
 // Converts a 4 bytes IP into a 32 bit integer
-func ipToInt(ip *net.IP) int32 {
-	return int32(binary.BigEndian.Uint32(ip.To4()))
+func ipToInt(ip net.IP) uint32 {
+	return binary.BigEndian.Uint32(ip.To4())
 }
 }
 
 
 // Converts 32 bit integer into a 4 bytes IP address
 // Converts 32 bit integer into a 4 bytes IP address
-func intToIP(n int32) *net.IP {
+func intToIP(n uint32) net.IP {
 	b := make([]byte, 4)
 	b := make([]byte, 4)
-	binary.BigEndian.PutUint32(b, uint32(n))
+	binary.BigEndian.PutUint32(b, n)
 	ip := net.IP(b)
 	ip := net.IP(b)
-	return &ip
+	return ip
 }
 }

+ 100 - 20
daemon/networkdriver/ipallocator/allocator_test.go

@@ -17,7 +17,7 @@ func TestRequestNewIps(t *testing.T) {
 		Mask: []byte{255, 255, 255, 0},
 		Mask: []byte{255, 255, 255, 0},
 	}
 	}
 
 
-	var ip *net.IP
+	var ip net.IP
 	var err error
 	var err error
 	for i := 2; i < 10; i++ {
 	for i := 2; i < 10; i++ {
 		ip, err = RequestIP(network, nil)
 		ip, err = RequestIP(network, nil)
@@ -106,19 +106,19 @@ func TestRequesetSpecificIp(t *testing.T) {
 
 
 	ip := net.ParseIP("192.168.1.5")
 	ip := net.ParseIP("192.168.1.5")
 
 
-	if _, err := RequestIP(network, &ip); err != nil {
+	if _, err := RequestIP(network, ip); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 }
 }
 
 
 func TestConversion(t *testing.T) {
 func TestConversion(t *testing.T) {
 	ip := net.ParseIP("127.0.0.1")
 	ip := net.ParseIP("127.0.0.1")
-	i := ipToInt(&ip)
+	i := ipToInt(ip)
 	if i == 0 {
 	if i == 0 {
 		t.Fatal("converted to zero")
 		t.Fatal("converted to zero")
 	}
 	}
 	conv := intToIP(i)
 	conv := intToIP(i)
-	if !ip.Equal(*conv) {
+	if !ip.Equal(conv) {
 		t.Error(conv.String())
 		t.Error(conv.String())
 	}
 	}
 }
 }
@@ -146,7 +146,7 @@ func TestIPAllocator(t *testing.T) {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
 
 
-		assertIPEquals(t, &expectedIPs[i], ip)
+		assertIPEquals(t, expectedIPs[i], ip)
 	}
 	}
 	// Before loop begin
 	// Before loop begin
 	// 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
 	// 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
@@ -179,19 +179,19 @@ func TestIPAllocator(t *testing.T) {
 	}
 	}
 
 
 	// Release some IPs in non-sequential order
 	// Release some IPs in non-sequential order
-	if err := ReleaseIP(network, &expectedIPs[3]); err != nil {
+	if err := ReleaseIP(network, expectedIPs[3]); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	// 2(u) - 3(u) - 4(u) - 5(f) - 6(u)
 	// 2(u) - 3(u) - 4(u) - 5(f) - 6(u)
 	//                       ↑
 	//                       ↑
 
 
-	if err := ReleaseIP(network, &expectedIPs[2]); err != nil {
+	if err := ReleaseIP(network, expectedIPs[2]); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	// 2(u) - 3(u) - 4(f) - 5(f) - 6(u)
 	// 2(u) - 3(u) - 4(f) - 5(f) - 6(u)
 	//                       ↑
 	//                       ↑
 
 
-	if err := ReleaseIP(network, &expectedIPs[4]); err != nil {
+	if err := ReleaseIP(network, expectedIPs[4]); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	// 2(u) - 3(u) - 4(f) - 5(f) - 6(f)
 	// 2(u) - 3(u) - 4(f) - 5(f) - 6(f)
@@ -199,7 +199,7 @@ func TestIPAllocator(t *testing.T) {
 
 
 	// Make sure that IPs are reused in sequential order, starting
 	// Make sure that IPs are reused in sequential order, starting
 	// with the first released IP
 	// with the first released IP
-	newIPs := make([]*net.IP, 3)
+	newIPs := make([]net.IP, 3)
 	for i := 0; i < 3; i++ {
 	for i := 0; i < 3; i++ {
 		ip, err := RequestIP(network, nil)
 		ip, err := RequestIP(network, nil)
 		if err != nil {
 		if err != nil {
@@ -208,9 +208,9 @@ func TestIPAllocator(t *testing.T) {
 
 
 		newIPs[i] = ip
 		newIPs[i] = ip
 	}
 	}
-	assertIPEquals(t, &expectedIPs[2], newIPs[0])
-	assertIPEquals(t, &expectedIPs[3], newIPs[1])
-	assertIPEquals(t, &expectedIPs[4], newIPs[2])
+	assertIPEquals(t, expectedIPs[2], newIPs[0])
+	assertIPEquals(t, expectedIPs[3], newIPs[1])
+	assertIPEquals(t, expectedIPs[4], newIPs[2])
 
 
 	_, err = RequestIP(network, nil)
 	_, err = RequestIP(network, nil)
 	if err == nil {
 	if err == nil {
@@ -226,7 +226,7 @@ func TestAllocateFirstIP(t *testing.T) {
 	}
 	}
 
 
 	firstIP := network.IP.To4().Mask(network.Mask)
 	firstIP := network.IP.To4().Mask(network.Mask)
-	first := ipToInt(&firstIP) + 1
+	first := ipToInt(firstIP) + 1
 
 
 	ip, err := RequestIP(network, nil)
 	ip, err := RequestIP(network, nil)
 	if err != nil {
 	if err != nil {
@@ -247,7 +247,7 @@ func TestAllocateAllIps(t *testing.T) {
 	}
 	}
 
 
 	var (
 	var (
-		current, first *net.IP
+		current, first net.IP
 		err            error
 		err            error
 		isFirst        = true
 		isFirst        = true
 	)
 	)
@@ -313,14 +313,94 @@ func TestAllocateDifferentSubnets(t *testing.T) {
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	assertIPEquals(t, &expectedIPs[0], ip11)
-	assertIPEquals(t, &expectedIPs[1], ip12)
-	assertIPEquals(t, &expectedIPs[2], ip21)
-	assertIPEquals(t, &expectedIPs[3], ip22)
+	assertIPEquals(t, expectedIPs[0], ip11)
+	assertIPEquals(t, expectedIPs[1], ip12)
+	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) {
+func assertIPEquals(t *testing.T, ip1, ip2 net.IP) {
+	if !ip1.Equal(ip2) {
 		t.Fatalf("Expected IP %s, got %s", ip1, ip2)
 		t.Fatalf("Expected IP %s, got %s", ip1, ip2)
 	}
 	}
 }
 }

+ 4 - 0
docs/man/docker.1.md

@@ -49,6 +49,10 @@ unix://[/path/to/socket] to use.
 **-g**=""
 **-g**=""
   Path to use as the root of the Docker runtime. Default is `/var/lib/docker`.
   Path to use as the root of the Docker runtime. Default is `/var/lib/docker`.
 
 
+
+**--fixed-cidr**=""
+  IPv4 subnet for fixed IPs (ex: 10.20.0.0/16); this subnet must be nested in the bridge subnet (which is defined by \-b or \-\-bip)
+
 **--icc**=*true*|*false*
 **--icc**=*true*|*false*
   Enable inter\-container communication. Default is true.
   Enable inter\-container communication. Default is true.
 
 

+ 17 - 6
docs/sources/articles/networking.md

@@ -54,6 +54,9 @@ server when it starts up, and cannot be changed once it is running:
  *  `--bip=CIDR` — see
  *  `--bip=CIDR` — see
     [Customizing docker0](#docker0)
     [Customizing docker0](#docker0)
 
 
+ *  `--fixed-cidr` — see
+    [Customizing docker0](#docker0)
+
  *  `-H SOCKET...` or `--host=SOCKET...` —
  *  `-H SOCKET...` or `--host=SOCKET...` —
     This might sound like it would affect container networking,
     This might sound like it would affect container networking,
     but it actually faces in the other direction:
     but it actually faces in the other direction:
@@ -365,17 +368,25 @@ By default, the Docker server creates and configures the host system's
 can pass packets back and forth between other physical or virtual
 can pass packets back and forth between other physical or virtual
 network interfaces so that they behave as a single Ethernet network.
 network interfaces so that they behave as a single Ethernet network.
 
 
-Docker configures `docker0` with an IP address and netmask so the host
-machine can both receive and send packets to containers connected to the
-bridge, and gives it an MTU — the *maximum transmission unit* or largest
-packet length that the interface will allow — of either 1,500 bytes or
-else a more specific value copied from the Docker host's interface that
-supports its default route.  Both are configurable at server startup:
+Docker configures `docker0` with an IP address, netmask and IP
+allocation range. The host machine can both receive and send packets to
+containers connected to the bridge, and gives it an MTU — the *maximum
+transmission unit* or largest packet length that the interface will
+allow — of either 1,500 bytes or else a more specific value copied from
+the Docker host's interface that supports its default route.  These
+options are configurable at server startup:
 
 
  *  `--bip=CIDR` — supply a specific IP address and netmask for the
  *  `--bip=CIDR` — supply a specific IP address and netmask for the
     `docker0` bridge, using standard CIDR notation like
     `docker0` bridge, using standard CIDR notation like
     `192.168.1.5/24`.
     `192.168.1.5/24`.
 
 
+ *  `--fixed-cidr=CIDR` — restrict the IP range from the `docker0` subnet,
+    using the standard CIDR notation like `172.167.1.0/28`. This range must
+    be and IPv4 range for fixed IPs (ex: 10.20.0.0/16) and must be a subset
+    of the bridge IP range (`docker0` or set using `--bridge`). For example
+    with `--fixed-cidr=192.168.1.0/25`, IPs for your containers will be chosen
+    from the first half of `192.168.1.0/24` subnet.
+
  *  `--mtu=BYTES` — override the maximum packet length on `docker0`.
  *  `--mtu=BYTES` — override the maximum packet length on `docker0`.
 
 
 On Ubuntu you would add these to the `DOCKER_OPTS` setting in
 On Ubuntu you would add these to the `DOCKER_OPTS` setting in

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

@@ -54,6 +54,8 @@ expect an integer, and they can only be specified once.
       -b, --bridge=""                            Attach containers to a pre-existing network bridge
       -b, --bridge=""                            Attach containers to a pre-existing network bridge
                                                    use 'none' to disable container networking
                                                    use 'none' to disable container networking
       --bip=""                                   Use this CIDR notation address for the network bridge's IP, not compatible with -b
       --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)
+                                                   this subnet must be nested in the bridge subnet (which is defined by -b or --bip)
       -D, --debug=false                          Enable debug mode
       -D, --debug=false                          Enable debug mode
       -d, --daemon=false                         Enable daemon mode
       -d, --daemon=false                         Enable daemon mode
       --dns=[]                                   Force Docker to use specific DNS servers
       --dns=[]                                   Force Docker to use specific DNS servers