Browse code

Merge pull request #6101 from LK4D4/ip_range_#4986

Implement allocating IPs from CIDR within bridge network

unclejack authored on 2014/09/22 23:09:16
Showing 8 changed files
... ...
@@ -30,6 +30,7 @@ type Config struct {
30 30
 	DefaultIp                   net.IP
31 31
 	BridgeIface                 string
32 32
 	BridgeIP                    string
33
+	FixedCIDR                   string
33 34
 	InterContainerCommunication bool
34 35
 	GraphDriver                 string
35 36
 	GraphOptions                []string
... ...
@@ -53,6 +54,7 @@ func (config *Config) InstallFlags() {
53 53
 	flag.BoolVar(&config.EnableIpMasq, []string{"-ip-masq"}, true, "Enable IP masquerading for bridge's IP range")
54 54
 	flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b")
55 55
 	flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
56
+	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)")
56 57
 	flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication")
57 58
 	flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver")
58 59
 	flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver")
... ...
@@ -821,6 +821,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
821 821
 		job.SetenvBool("EnableIpMasq", config.EnableIpMasq)
822 822
 		job.Setenv("BridgeIface", config.BridgeIface)
823 823
 		job.Setenv("BridgeIP", config.BridgeIP)
824
+		job.Setenv("FixedCIDR", config.FixedCIDR)
824 825
 		job.Setenv("DefaultBindingIP", config.DefaultIp.String())
825 826
 
826 827
 		if err := job.Run(); err != nil {
... ...
@@ -84,6 +84,7 @@ func InitDriver(job *engine.Job) engine.Status {
84 84
 		ipMasq         = job.GetenvBool("EnableIpMasq")
85 85
 		ipForward      = job.GetenvBool("EnableIpForward")
86 86
 		bridgeIP       = job.Getenv("BridgeIP")
87
+		fixedCIDR      = job.Getenv("FixedCIDR")
87 88
 	)
88 89
 
89 90
 	if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" {
... ...
@@ -155,6 +156,16 @@ func InitDriver(job *engine.Job) engine.Status {
155 155
 	}
156 156
 
157 157
 	bridgeNetwork = network
158
+	if fixedCIDR != "" {
159
+		_, subnet, err := net.ParseCIDR(fixedCIDR)
160
+		if err != nil {
161
+			return job.Error(err)
162
+		}
163
+		log.Debugf("Subnet: %v", subnet)
164
+		if err := ipallocator.RegisterSubnet(bridgeNetwork, subnet); err != nil {
165
+			return job.Error(err)
166
+		}
167
+	}
158 168
 
159 169
 	// https://github.com/docker/docker/issues/2768
160 170
 	job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", bridgeNetwork.IP)
... ...
@@ -318,14 +329,14 @@ func createBridgeIface(name string) error {
318 318
 // Allocate a network interface
319 319
 func Allocate(job *engine.Job) engine.Status {
320 320
 	var (
321
-		ip          *net.IP
321
+		ip          net.IP
322 322
 		err         error
323 323
 		id          = job.Args[0]
324 324
 		requestedIP = net.ParseIP(job.Getenv("RequestedIP"))
325 325
 	)
326 326
 
327 327
 	if requestedIP != nil {
328
-		ip, err = ipallocator.RequestIP(bridgeNetwork, &requestedIP)
328
+		ip, err = ipallocator.RequestIP(bridgeNetwork, requestedIP)
329 329
 	} else {
330 330
 		ip, err = ipallocator.RequestIP(bridgeNetwork, nil)
331 331
 	}
... ...
@@ -343,7 +354,7 @@ func Allocate(job *engine.Job) engine.Status {
343 343
 	out.SetInt("IPPrefixLen", size)
344 344
 
345 345
 	currentInterfaces.Set(id, &networkInterface{
346
-		IP: *ip,
346
+		IP: ip,
347 347
 	})
348 348
 
349 349
 	out.WriteTo(job.Stdout)
... ...
@@ -368,7 +379,7 @@ func Release(job *engine.Job) engine.Status {
368 368
 		}
369 369
 	}
370 370
 
371
-	if err := ipallocator.ReleaseIP(bridgeNetwork, &containerInterface.IP); err != nil {
371
+	if err := ipallocator.ReleaseIP(bridgeNetwork, containerInterface.IP); err != nil {
372 372
 		log.Infof("Unable to release ip %s", err)
373 373
 	}
374 374
 	return engine.StatusOK
... ...
@@ -3,26 +3,39 @@ package ipallocator
3 3
 import (
4 4
 	"encoding/binary"
5 5
 	"errors"
6
-	"github.com/docker/docker/daemon/networkdriver"
7 6
 	"net"
8 7
 	"sync"
8
+
9
+	"github.com/docker/docker/daemon/networkdriver"
9 10
 )
10 11
 
11 12
 // allocatedMap is thread-unsafe set of allocated IP
12 13
 type allocatedMap struct {
13
-	p    map[int32]struct{}
14
-	last int32
14
+	p     map[uint32]struct{}
15
+	last  uint32
16
+	begin uint32
17
+	end   uint32
15 18
 }
16 19
 
17
-func newAllocatedMap() *allocatedMap {
18
-	return &allocatedMap{p: make(map[int32]struct{})}
20
+func newAllocatedMap(network *net.IPNet) *allocatedMap {
21
+	firstIP, lastIP := networkdriver.NetworkRange(network)
22
+	begin := ipToInt(firstIP) + 2
23
+	end := ipToInt(lastIP) - 1
24
+	return &allocatedMap{
25
+		p:     make(map[uint32]struct{}),
26
+		begin: begin,
27
+		end:   end,
28
+		last:  begin - 1, // so first allocated will be begin
29
+	}
19 30
 }
20 31
 
21 32
 type networkSet map[string]*allocatedMap
22 33
 
23 34
 var (
24
-	ErrNoAvailableIPs     = errors.New("no available ip addresses on network")
25
-	ErrIPAlreadyAllocated = errors.New("ip already allocated")
35
+	ErrNoAvailableIPs           = errors.New("no available ip addresses on network")
36
+	ErrIPAlreadyAllocated       = errors.New("ip already allocated")
37
+	ErrNetworkAlreadyRegistered = errors.New("network already registered")
38
+	ErrBadSubnet                = errors.New("network not contains specified subnet")
26 39
 )
27 40
 
28 41
 var (
... ...
@@ -30,47 +43,63 @@ var (
30 30
 	allocatedIPs = networkSet{}
31 31
 )
32 32
 
33
+// RegisterSubnet registers network in global allocator with bounds
34
+// defined by subnet. If you want to use network range you must call
35
+// this method before first RequestIP, otherwise full network range will be used
36
+func RegisterSubnet(network *net.IPNet, subnet *net.IPNet) error {
37
+	lock.Lock()
38
+	defer lock.Unlock()
39
+	key := network.String()
40
+	if _, ok := allocatedIPs[key]; ok {
41
+		return ErrNetworkAlreadyRegistered
42
+	}
43
+	n := newAllocatedMap(network)
44
+	beginIP, endIP := networkdriver.NetworkRange(subnet)
45
+	begin, end := ipToInt(beginIP)+1, ipToInt(endIP)-1
46
+	if !(begin >= n.begin && end <= n.end && begin < end) {
47
+		return ErrBadSubnet
48
+	}
49
+	n.begin = begin
50
+	n.end = end
51
+	n.last = begin - 1
52
+	allocatedIPs[key] = n
53
+	return nil
54
+}
55
+
33 56
 // RequestIP requests an available ip from the given network.  It
34 57
 // will return the next available ip if the ip provided is nil.  If the
35 58
 // ip provided is not nil it will validate that the provided ip is available
36 59
 // for use or return an error
37
-func RequestIP(network *net.IPNet, ip *net.IP) (*net.IP, error) {
60
+func RequestIP(network *net.IPNet, ip net.IP) (net.IP, error) {
38 61
 	lock.Lock()
39 62
 	defer lock.Unlock()
40 63
 	key := network.String()
41 64
 	allocated, ok := allocatedIPs[key]
42 65
 	if !ok {
43
-		allocated = newAllocatedMap()
66
+		allocated = newAllocatedMap(network)
44 67
 		allocatedIPs[key] = allocated
45 68
 	}
46 69
 
47 70
 	if ip == nil {
48
-		return allocated.getNextIP(network)
71
+		return allocated.getNextIP()
49 72
 	}
50
-	return allocated.checkIP(network, ip)
73
+	return allocated.checkIP(ip)
51 74
 }
52 75
 
53 76
 // ReleaseIP adds the provided ip back into the pool of
54 77
 // available ips to be returned for use.
55
-func ReleaseIP(network *net.IPNet, ip *net.IP) error {
78
+func ReleaseIP(network *net.IPNet, ip net.IP) error {
56 79
 	lock.Lock()
57 80
 	defer lock.Unlock()
58 81
 	if allocated, exists := allocatedIPs[network.String()]; exists {
59
-		pos := getPosition(network, ip)
82
+		pos := ipToInt(ip)
60 83
 		delete(allocated.p, pos)
61 84
 	}
62 85
 	return nil
63 86
 }
64 87
 
65
-// convert the ip into the position in the subnet.  Only
66
-// position are saved in the set
67
-func getPosition(network *net.IPNet, ip *net.IP) int32 {
68
-	first, _ := networkdriver.NetworkRange(network)
69
-	return ipToInt(ip) - ipToInt(&first)
70
-}
71
-
72
-func (allocated *allocatedMap) checkIP(network *net.IPNet, ip *net.IP) (*net.IP, error) {
73
-	pos := getPosition(network, ip)
88
+func (allocated *allocatedMap) checkIP(ip net.IP) (net.IP, error) {
89
+	pos := ipToInt(ip)
74 90
 	if _, ok := allocated.p[pos]; ok {
75 91
 		return nil, ErrIPAlreadyAllocated
76 92
 	}
... ...
@@ -81,47 +110,30 @@ func (allocated *allocatedMap) checkIP(network *net.IPNet, ip *net.IP) (*net.IP,
81 81
 
82 82
 // return an available ip if one is currently available.  If not,
83 83
 // return the next available ip for the nextwork
84
-func (allocated *allocatedMap) getNextIP(network *net.IPNet) (*net.IP, error) {
85
-	var (
86
-		ownIP    = ipToInt(&network.IP)
87
-		first, _ = networkdriver.NetworkRange(network)
88
-		base     = ipToInt(&first)
89
-		size     = int(networkdriver.NetworkSize(network.Mask))
90
-		max      = int32(size - 2) // size -1 for the broadcast network, -1 for the gateway network
91
-		pos      = allocated.last
92
-	)
93
-
94
-	var (
95
-		firstNetIP = network.IP.To4().Mask(network.Mask)
96
-		firstAsInt = ipToInt(&firstNetIP) + 1
97
-	)
98
-
99
-	for i := int32(0); i < max; i++ {
100
-		pos = pos%max + 1
101
-		next := int32(base + pos)
102
-
103
-		if next == ownIP || next == firstAsInt {
104
-			continue
84
+func (allocated *allocatedMap) getNextIP() (net.IP, error) {
85
+	for pos := allocated.last + 1; pos != allocated.last; pos++ {
86
+		if pos > allocated.end {
87
+			pos = allocated.begin
105 88
 		}
106 89
 		if _, ok := allocated.p[pos]; ok {
107 90
 			continue
108 91
 		}
109 92
 		allocated.p[pos] = struct{}{}
110 93
 		allocated.last = pos
111
-		return intToIP(next), nil
94
+		return intToIP(pos), nil
112 95
 	}
113 96
 	return nil, ErrNoAvailableIPs
114 97
 }
115 98
 
116 99
 // Converts a 4 bytes IP into a 32 bit integer
117
-func ipToInt(ip *net.IP) int32 {
118
-	return int32(binary.BigEndian.Uint32(ip.To4()))
100
+func ipToInt(ip net.IP) uint32 {
101
+	return binary.BigEndian.Uint32(ip.To4())
119 102
 }
120 103
 
121 104
 // Converts 32 bit integer into a 4 bytes IP address
122
-func intToIP(n int32) *net.IP {
105
+func intToIP(n uint32) net.IP {
123 106
 	b := make([]byte, 4)
124
-	binary.BigEndian.PutUint32(b, uint32(n))
107
+	binary.BigEndian.PutUint32(b, n)
125 108
 	ip := net.IP(b)
126
-	return &ip
109
+	return ip
127 110
 }
... ...
@@ -17,7 +17,7 @@ func TestRequestNewIps(t *testing.T) {
17 17
 		Mask: []byte{255, 255, 255, 0},
18 18
 	}
19 19
 
20
-	var ip *net.IP
20
+	var ip net.IP
21 21
 	var err error
22 22
 	for i := 2; i < 10; i++ {
23 23
 		ip, err = RequestIP(network, nil)
... ...
@@ -106,19 +106,19 @@ func TestRequesetSpecificIp(t *testing.T) {
106 106
 
107 107
 	ip := net.ParseIP("192.168.1.5")
108 108
 
109
-	if _, err := RequestIP(network, &ip); err != nil {
109
+	if _, err := RequestIP(network, ip); err != nil {
110 110
 		t.Fatal(err)
111 111
 	}
112 112
 }
113 113
 
114 114
 func TestConversion(t *testing.T) {
115 115
 	ip := net.ParseIP("127.0.0.1")
116
-	i := ipToInt(&ip)
116
+	i := ipToInt(ip)
117 117
 	if i == 0 {
118 118
 		t.Fatal("converted to zero")
119 119
 	}
120 120
 	conv := intToIP(i)
121
-	if !ip.Equal(*conv) {
121
+	if !ip.Equal(conv) {
122 122
 		t.Error(conv.String())
123 123
 	}
124 124
 }
... ...
@@ -146,7 +146,7 @@ func TestIPAllocator(t *testing.T) {
146 146
 			t.Fatal(err)
147 147
 		}
148 148
 
149
-		assertIPEquals(t, &expectedIPs[i], ip)
149
+		assertIPEquals(t, expectedIPs[i], ip)
150 150
 	}
151 151
 	// Before loop begin
152 152
 	// 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
... ...
@@ -179,19 +179,19 @@ func TestIPAllocator(t *testing.T) {
179 179
 	}
180 180
 
181 181
 	// Release some IPs in non-sequential order
182
-	if err := ReleaseIP(network, &expectedIPs[3]); err != nil {
182
+	if err := ReleaseIP(network, expectedIPs[3]); err != nil {
183 183
 		t.Fatal(err)
184 184
 	}
185 185
 	// 2(u) - 3(u) - 4(u) - 5(f) - 6(u)
186 186
 	//                       ↑
187 187
 
188
-	if err := ReleaseIP(network, &expectedIPs[2]); err != nil {
188
+	if err := ReleaseIP(network, expectedIPs[2]); err != nil {
189 189
 		t.Fatal(err)
190 190
 	}
191 191
 	// 2(u) - 3(u) - 4(f) - 5(f) - 6(u)
192 192
 	//                       ↑
193 193
 
194
-	if err := ReleaseIP(network, &expectedIPs[4]); err != nil {
194
+	if err := ReleaseIP(network, expectedIPs[4]); err != nil {
195 195
 		t.Fatal(err)
196 196
 	}
197 197
 	// 2(u) - 3(u) - 4(f) - 5(f) - 6(f)
... ...
@@ -199,7 +199,7 @@ func TestIPAllocator(t *testing.T) {
199 199
 
200 200
 	// Make sure that IPs are reused in sequential order, starting
201 201
 	// with the first released IP
202
-	newIPs := make([]*net.IP, 3)
202
+	newIPs := make([]net.IP, 3)
203 203
 	for i := 0; i < 3; i++ {
204 204
 		ip, err := RequestIP(network, nil)
205 205
 		if err != nil {
... ...
@@ -208,9 +208,9 @@ func TestIPAllocator(t *testing.T) {
208 208
 
209 209
 		newIPs[i] = ip
210 210
 	}
211
-	assertIPEquals(t, &expectedIPs[2], newIPs[0])
212
-	assertIPEquals(t, &expectedIPs[3], newIPs[1])
213
-	assertIPEquals(t, &expectedIPs[4], newIPs[2])
211
+	assertIPEquals(t, expectedIPs[2], newIPs[0])
212
+	assertIPEquals(t, expectedIPs[3], newIPs[1])
213
+	assertIPEquals(t, expectedIPs[4], newIPs[2])
214 214
 
215 215
 	_, err = RequestIP(network, nil)
216 216
 	if err == nil {
... ...
@@ -226,7 +226,7 @@ func TestAllocateFirstIP(t *testing.T) {
226 226
 	}
227 227
 
228 228
 	firstIP := network.IP.To4().Mask(network.Mask)
229
-	first := ipToInt(&firstIP) + 1
229
+	first := ipToInt(firstIP) + 1
230 230
 
231 231
 	ip, err := RequestIP(network, nil)
232 232
 	if err != nil {
... ...
@@ -247,7 +247,7 @@ func TestAllocateAllIps(t *testing.T) {
247 247
 	}
248 248
 
249 249
 	var (
250
-		current, first *net.IP
250
+		current, first net.IP
251 251
 		err            error
252 252
 		isFirst        = true
253 253
 	)
... ...
@@ -313,14 +313,94 @@ func TestAllocateDifferentSubnets(t *testing.T) {
313 313
 	if err != nil {
314 314
 		t.Fatal(err)
315 315
 	}
316
-	assertIPEquals(t, &expectedIPs[0], ip11)
317
-	assertIPEquals(t, &expectedIPs[1], ip12)
318
-	assertIPEquals(t, &expectedIPs[2], ip21)
319
-	assertIPEquals(t, &expectedIPs[3], ip22)
316
+	assertIPEquals(t, expectedIPs[0], ip11)
317
+	assertIPEquals(t, expectedIPs[1], ip12)
318
+	assertIPEquals(t, expectedIPs[2], ip21)
319
+	assertIPEquals(t, expectedIPs[3], ip22)
320
+}
321
+func TestRegisterBadTwice(t *testing.T) {
322
+	defer reset()
323
+	network := &net.IPNet{
324
+		IP:   []byte{192, 168, 1, 1},
325
+		Mask: []byte{255, 255, 255, 0},
326
+	}
327
+	subnet := &net.IPNet{
328
+		IP:   []byte{192, 168, 1, 8},
329
+		Mask: []byte{255, 255, 255, 248},
330
+	}
331
+
332
+	if err := RegisterSubnet(network, subnet); err != nil {
333
+		t.Fatal(err)
334
+	}
335
+	subnet = &net.IPNet{
336
+		IP:   []byte{192, 168, 1, 16},
337
+		Mask: []byte{255, 255, 255, 248},
338
+	}
339
+	if err := RegisterSubnet(network, subnet); err != ErrNetworkAlreadyRegistered {
340
+		t.Fatalf("Expecteded ErrNetworkAlreadyRegistered error, got %v", err)
341
+	}
342
+}
343
+
344
+func TestRegisterBadRange(t *testing.T) {
345
+	defer reset()
346
+	network := &net.IPNet{
347
+		IP:   []byte{192, 168, 1, 1},
348
+		Mask: []byte{255, 255, 255, 0},
349
+	}
350
+	subnet := &net.IPNet{
351
+		IP:   []byte{192, 168, 1, 1},
352
+		Mask: []byte{255, 255, 0, 0},
353
+	}
354
+	if err := RegisterSubnet(network, subnet); err != ErrBadSubnet {
355
+		t.Fatalf("Expected ErrBadSubnet error, got %v", err)
356
+	}
357
+}
358
+
359
+func TestAllocateFromRange(t *testing.T) {
360
+	defer reset()
361
+	network := &net.IPNet{
362
+		IP:   []byte{192, 168, 0, 1},
363
+		Mask: []byte{255, 255, 255, 0},
364
+	}
365
+	// 192.168.1.9 - 192.168.1.14
366
+	subnet := &net.IPNet{
367
+		IP:   []byte{192, 168, 0, 8},
368
+		Mask: []byte{255, 255, 255, 248},
369
+	}
370
+	if err := RegisterSubnet(network, subnet); err != nil {
371
+		t.Fatal(err)
372
+	}
373
+	expectedIPs := []net.IP{
374
+		0: net.IPv4(192, 168, 0, 9),
375
+		1: net.IPv4(192, 168, 0, 10),
376
+		2: net.IPv4(192, 168, 0, 11),
377
+		3: net.IPv4(192, 168, 0, 12),
378
+		4: net.IPv4(192, 168, 0, 13),
379
+		5: net.IPv4(192, 168, 0, 14),
380
+	}
381
+	for _, ip := range expectedIPs {
382
+		rip, err := RequestIP(network, nil)
383
+		if err != nil {
384
+			t.Fatal(err)
385
+		}
386
+		assertIPEquals(t, ip, rip)
387
+	}
388
+
389
+	if _, err := RequestIP(network, nil); err != ErrNoAvailableIPs {
390
+		t.Fatalf("Expected ErrNoAvailableIPs error, got %v", err)
391
+	}
392
+	for _, ip := range expectedIPs {
393
+		ReleaseIP(network, ip)
394
+		rip, err := RequestIP(network, nil)
395
+		if err != nil {
396
+			t.Fatal(err)
397
+		}
398
+		assertIPEquals(t, ip, rip)
399
+	}
320 400
 }
321 401
 
322
-func assertIPEquals(t *testing.T, ip1, ip2 *net.IP) {
323
-	if !ip1.Equal(*ip2) {
402
+func assertIPEquals(t *testing.T, ip1, ip2 net.IP) {
403
+	if !ip1.Equal(ip2) {
324 404
 		t.Fatalf("Expected IP %s, got %s", ip1, ip2)
325 405
 	}
326 406
 }
... ...
@@ -49,6 +49,10 @@ unix://[/path/to/socket] to use.
49 49
 **-g**=""
50 50
   Path to use as the root of the Docker runtime. Default is `/var/lib/docker`.
51 51
 
52
+
53
+**--fixed-cidr**=""
54
+  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)
55
+
52 56
 **--icc**=*true*|*false*
53 57
   Enable inter\-container communication. Default is true.
54 58
 
... ...
@@ -54,6 +54,9 @@ server when it starts up, and cannot be changed once it is running:
54 54
  *  `--bip=CIDR` — see
55 55
     [Customizing docker0](#docker0)
56 56
 
57
+ *  `--fixed-cidr` — see
58
+    [Customizing docker0](#docker0)
59
+
57 60
  *  `-H SOCKET...` or `--host=SOCKET...` —
58 61
     This might sound like it would affect container networking,
59 62
     but it actually faces in the other direction:
... ...
@@ -365,17 +368,25 @@ By default, the Docker server creates and configures the host system's
365 365
 can pass packets back and forth between other physical or virtual
366 366
 network interfaces so that they behave as a single Ethernet network.
367 367
 
368
-Docker configures `docker0` with an IP address and netmask so the host
369
-machine can both receive and send packets to containers connected to the
370
-bridge, and gives it an MTU — the *maximum transmission unit* or largest
371
-packet length that the interface will allow — of either 1,500 bytes or
372
-else a more specific value copied from the Docker host's interface that
373
-supports its default route.  Both are configurable at server startup:
368
+Docker configures `docker0` with an IP address, netmask and IP
369
+allocation range. The host machine can both receive and send packets to
370
+containers connected to the bridge, and gives it an MTU — the *maximum
371
+transmission unit* or largest packet length that the interface will
372
+allow — of either 1,500 bytes or else a more specific value copied from
373
+the Docker host's interface that supports its default route.  These
374
+options are configurable at server startup:
374 375
 
375 376
  *  `--bip=CIDR` — supply a specific IP address and netmask for the
376 377
     `docker0` bridge, using standard CIDR notation like
377 378
     `192.168.1.5/24`.
378 379
 
380
+ *  `--fixed-cidr=CIDR` — restrict the IP range from the `docker0` subnet,
381
+    using the standard CIDR notation like `172.167.1.0/28`. This range must
382
+    be and IPv4 range for fixed IPs (ex: 10.20.0.0/16) and must be a subset
383
+    of the bridge IP range (`docker0` or set using `--bridge`). For example
384
+    with `--fixed-cidr=192.168.1.0/25`, IPs for your containers will be chosen
385
+    from the first half of `192.168.1.0/24` subnet.
386
+
379 387
  *  `--mtu=BYTES` — override the maximum packet length on `docker0`.
380 388
 
381 389
 On Ubuntu you would add these to the `DOCKER_OPTS` setting in
... ...
@@ -54,6 +54,8 @@ expect an integer, and they can only be specified once.
54 54
       -b, --bridge=""                            Attach containers to a pre-existing network bridge
55 55
                                                    use 'none' to disable container networking
56 56
       --bip=""                                   Use this CIDR notation address for the network bridge's IP, not compatible with -b
57
+      --fixed-cidr=""                            IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)
58
+                                                   this subnet must be nested in the bridge subnet (which is defined by -b or --bip)
57 59
       -D, --debug=false                          Enable debug mode
58 60
       -d, --daemon=false                         Enable daemon mode
59 61
       --dns=[]                                   Force Docker to use specific DNS servers