Browse Source

Merge pull request #10238 from miminar/ephemeral-port-range-allocation

Use system's ephemeral port range for port allocation
Alexander Morozov 10 years ago
parent
commit
02c1dd899a

+ 43 - 9
daemon/networkdriver/portallocator/portallocator.go

@@ -1,10 +1,24 @@
 package portallocator
 package portallocator
 
 
 import (
 import (
+	"bufio"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"net"
 	"net"
+	"os"
 	"sync"
 	"sync"
+
+	log "github.com/Sirupsen/logrus"
+)
+
+const (
+	DefaultPortRangeStart = 49153
+	DefaultPortRangeEnd   = 65535
+)
+
+var (
+	beginPortRange = DefaultPortRangeStart
+	endPortRange   = DefaultPortRangeEnd
 )
 )
 
 
 type portMap struct {
 type portMap struct {
@@ -15,7 +29,7 @@ type portMap struct {
 func newPortMap() *portMap {
 func newPortMap() *portMap {
 	return &portMap{
 	return &portMap{
 		p:    map[int]struct{}{},
 		p:    map[int]struct{}{},
-		last: EndPortRange,
+		last: endPortRange,
 	}
 	}
 }
 }
 
 
@@ -30,11 +44,6 @@ func newProtoMap() protoMap {
 
 
 type ipMapping map[string]protoMap
 type ipMapping map[string]protoMap
 
 
-const (
-	BeginPortRange = 49153
-	EndPortRange   = 65535
-)
-
 var (
 var (
 	ErrAllPortsAllocated = errors.New("all ports are allocated")
 	ErrAllPortsAllocated = errors.New("all ports are allocated")
 	ErrUnknownProtocol   = errors.New("unknown protocol")
 	ErrUnknownProtocol   = errors.New("unknown protocol")
@@ -59,6 +68,31 @@ func NewErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated {
 	}
 	}
 }
 }
 
 
+func init() {
+	const param = "/proc/sys/net/ipv4/ip_local_port_range"
+
+	file, err := os.Open(param)
+	if err != nil {
+		log.Errorf("Failed to read %s kernel parameter: %s", param, err.Error())
+		return
+	}
+	var start, end int
+	n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end)
+	if n != 2 || err != nil {
+		if err == nil {
+			err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
+		}
+		log.Errorf("Failed to parse port range from %s: %v", param, err)
+		return
+	}
+	beginPortRange = start
+	endPortRange = end
+}
+
+func PortRange() (int, int) {
+	return beginPortRange, endPortRange
+}
+
 func (e ErrPortAlreadyAllocated) IP() string {
 func (e ErrPortAlreadyAllocated) IP() string {
 	return e.ip
 	return e.ip
 }
 }
@@ -137,10 +171,10 @@ func ReleaseAll() error {
 
 
 func (pm *portMap) findPort() (int, error) {
 func (pm *portMap) findPort() (int, error) {
 	port := pm.last
 	port := pm.last
-	for i := 0; i <= EndPortRange-BeginPortRange; i++ {
+	for i := 0; i <= endPortRange-beginPortRange; i++ {
 		port++
 		port++
-		if port > EndPortRange {
-			port = BeginPortRange
+		if port > endPortRange {
+			port = beginPortRange
 		}
 		}
 
 
 		if _, ok := pm.p[port]; !ok {
 		if _, ok := pm.p[port]; !ok {

+ 15 - 10
daemon/networkdriver/portallocator/portallocator_test.go

@@ -5,6 +5,11 @@ import (
 	"testing"
 	"testing"
 )
 )
 
 
+func init() {
+	beginPortRange = DefaultPortRangeStart
+	endPortRange = DefaultPortRangeEnd
+}
+
 func reset() {
 func reset() {
 	ReleaseAll()
 	ReleaseAll()
 }
 }
@@ -17,7 +22,7 @@ func TestRequestNewPort(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	if expected := BeginPortRange; port != expected {
+	if expected := beginPortRange; port != expected {
 		t.Fatalf("Expected port %d got %d", expected, port)
 		t.Fatalf("Expected port %d got %d", expected, port)
 	}
 	}
 }
 }
@@ -102,13 +107,13 @@ func TestUnknowProtocol(t *testing.T) {
 func TestAllocateAllPorts(t *testing.T) {
 func TestAllocateAllPorts(t *testing.T) {
 	defer reset()
 	defer reset()
 
 
-	for i := 0; i <= EndPortRange-BeginPortRange; i++ {
+	for i := 0; i <= endPortRange-beginPortRange; i++ {
 		port, err := RequestPort(defaultIP, "tcp", 0)
 		port, err := RequestPort(defaultIP, "tcp", 0)
 		if err != nil {
 		if err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
 
 
-		if expected := BeginPortRange + i; port != expected {
+		if expected := beginPortRange + i; port != expected {
 			t.Fatalf("Expected port %d got %d", expected, port)
 			t.Fatalf("Expected port %d got %d", expected, port)
 		}
 		}
 	}
 	}
@@ -123,7 +128,7 @@ func TestAllocateAllPorts(t *testing.T) {
 	}
 	}
 
 
 	// release a port in the middle and ensure we get another tcp port
 	// release a port in the middle and ensure we get another tcp port
-	port := BeginPortRange + 5
+	port := beginPortRange + 5
 	if err := ReleasePort(defaultIP, "tcp", port); err != nil {
 	if err := ReleasePort(defaultIP, "tcp", port); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -153,13 +158,13 @@ func BenchmarkAllocatePorts(b *testing.B) {
 	defer reset()
 	defer reset()
 
 
 	for i := 0; i < b.N; i++ {
 	for i := 0; i < b.N; i++ {
-		for i := 0; i <= EndPortRange-BeginPortRange; i++ {
+		for i := 0; i <= endPortRange-beginPortRange; i++ {
 			port, err := RequestPort(defaultIP, "tcp", 0)
 			port, err := RequestPort(defaultIP, "tcp", 0)
 			if err != nil {
 			if err != nil {
 				b.Fatal(err)
 				b.Fatal(err)
 			}
 			}
 
 
-			if expected := BeginPortRange + i; port != expected {
+			if expected := beginPortRange + i; port != expected {
 				b.Fatalf("Expected port %d got %d", expected, port)
 				b.Fatalf("Expected port %d got %d", expected, port)
 			}
 			}
 		}
 		}
@@ -231,15 +236,15 @@ func TestPortAllocation(t *testing.T) {
 func TestNoDuplicateBPR(t *testing.T) {
 func TestNoDuplicateBPR(t *testing.T) {
 	defer reset()
 	defer reset()
 
 
-	if port, err := RequestPort(defaultIP, "tcp", BeginPortRange); err != nil {
+	if port, err := RequestPort(defaultIP, "tcp", beginPortRange); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
-	} else if port != BeginPortRange {
-		t.Fatalf("Expected port %d got %d", BeginPortRange, port)
+	} else if port != beginPortRange {
+		t.Fatalf("Expected port %d got %d", beginPortRange, port)
 	}
 	}
 
 
 	if port, err := RequestPort(defaultIP, "tcp", 0); err != nil {
 	if port, err := RequestPort(defaultIP, "tcp", 0); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
-	} else if port == BeginPortRange {
+	} else if port == beginPortRange {
 		t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
 		t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
 	}
 	}
 }
 }

+ 4 - 3
daemon/networkdriver/portmapper/mapper_test.go

@@ -129,7 +129,8 @@ func TestMapAllPortsSingleInterface(t *testing.T) {
 	}()
 	}()
 
 
 	for i := 0; i < 10; i++ {
 	for i := 0; i < 10; i++ {
-		for i := portallocator.BeginPortRange; i < portallocator.EndPortRange; i++ {
+		start, end := portallocator.PortRange()
+		for i := start; i < end; i++ {
 			if host, err = Map(srcAddr1, dstIp1, 0); err != nil {
 			if host, err = Map(srcAddr1, dstIp1, 0); err != nil {
 				t.Fatal(err)
 				t.Fatal(err)
 			}
 			}
@@ -137,8 +138,8 @@ func TestMapAllPortsSingleInterface(t *testing.T) {
 			hosts = append(hosts, host)
 			hosts = append(hosts, host)
 		}
 		}
 
 
-		if _, err := Map(srcAddr1, dstIp1, portallocator.BeginPortRange); err == nil {
-			t.Fatalf("Port %d should be bound but is not", portallocator.BeginPortRange)
+		if _, err := Map(srcAddr1, dstIp1, start); err == nil {
+			t.Fatalf("Port %d should be bound but is not", start)
 		}
 		}
 
 
 		for _, val := range hosts {
 		for _, val := range hosts {

+ 4 - 3
docs/man/docker-run.1.md

@@ -258,9 +258,10 @@ and foreground Docker containers.
    When set to true publish all exposed ports to the host interfaces. The
    When set to true publish all exposed ports to the host interfaces. The
 default is false. If the operator uses -P (or -p) then Docker will make the
 default is false. If the operator uses -P (or -p) then Docker will make the
 exposed port accessible on the host and the ports will be available to any
 exposed port accessible on the host and the ports will be available to any
-client that can reach the host. When using -P, Docker will bind the exposed
-ports to a random port on the host between 49153 and 65535. To find the
-mapping between the host ports and the exposed ports, use **docker port**.
+client that can reach the host. When using -P, Docker will bind any exposed
+port to a random port on the host within an *ephemeral port range* defined by
+`/proc/sys/net/ipv4/ip_local_port_range`. To find the mapping between the host
+ports and the exposed ports, use `docker port`.
 
 
 **-p**, **--publish**=[]
 **-p**, **--publish**=[]
    Publish a container's port, or range of ports, to the host.
    Publish a container's port, or range of ports, to the host.

+ 12 - 11
docs/sources/articles/networking.md

@@ -385,17 +385,18 @@ to provide special options when invoking `docker run`.  These options
 are covered in more detail in the [Docker User Guide](/userguide/dockerlinks)
 are covered in more detail in the [Docker User Guide](/userguide/dockerlinks)
 page.  There are two approaches.
 page.  There are two approaches.
 
 
-First, you can supply `-P` or `--publish-all=true|false` to `docker run`
-which is a blanket operation that identifies every port with an `EXPOSE`
-line in the image's `Dockerfile` and maps it to a host port somewhere in
-the range 49153–65535.  This tends to be a bit inconvenient, since you
-then have to run other `docker` sub-commands to learn which external
-port a given service was mapped to.
-
-More convenient is the `-p SPEC` or `--publish=SPEC` option which lets
-you be explicit about exactly which external port on the Docker server —
-which can be any port at all, not just those in the 49153-65535 block —
-you want mapped to which port in the container.
+First, you can supply `-P` or `--publish-all=true|false` to `docker run` which
+is a blanket operation that identifies every port with an `EXPOSE` line in the
+image's `Dockerfile` or `--expose <port>` commandline flag and maps it to a
+host port somewhere within an *ephemeral port range*. The `docker port` command
+then needs to be used to inspect created mapping. The *ephemeral port range* is
+configured by `/proc/sys/net/ipv4/ip_local_port_range` kernel parameter,
+typically ranging from 32768 to 61000.
+
+Mapping can be specified explicitly using `-p SPEC` or `--publish=SPEC` option.
+It allows you to particularize which port on docker server - which can be any
+port at all, not just one within the *ephemeral port range* — you want mapped
+to which port in the container.
 
 
 Either way, you should be able to peek at what Docker has accomplished
 Either way, you should be able to peek at what Docker has accomplished
 in your network stack by examining your NAT tables.
 in your network stack by examining your NAT tables.

+ 5 - 4
docs/sources/reference/run.md

@@ -651,10 +651,11 @@ developer, the operator has three choices: start the server container
 with `-P` or `-p,` or start the client container with `--link`.
 with `-P` or `-p,` or start the client container with `--link`.
 
 
 If the operator uses `-P` or `-p` then Docker will make the exposed port
 If the operator uses `-P` or `-p` then Docker will make the exposed port
-accessible on the host and the ports will be available to any client
-that can reach the host. When using `-P`, Docker will bind the exposed 
-ports to a random port on the host between 49153 and 65535. To find the
-mapping between the host ports and the exposed ports, use `docker port`.
+accessible on the host and the ports will be available to any client that can
+reach the host. When using `-P`, Docker will bind the exposed port to a random
+port on the host within an *ephemeral port range* defined by
+`/proc/sys/net/ipv4/ip_local_port_range`. To find the mapping between the host
+ports and the exposed ports, use `docker port`.
 
 
 If the operator uses `--link` when starting the new client container,
 If the operator uses `--link` when starting the new client container,
 then the client container can access the exposed port via a private
 then the client container can access the exposed port via a private

+ 4 - 4
docs/sources/userguide/dockerlinks.md

@@ -25,10 +25,10 @@ container that ran a Python Flask application:
 > Docker can have a variety of network configurations. You can see more
 > Docker can have a variety of network configurations. You can see more
 > information on Docker networking [here](/articles/networking/).
 > information on Docker networking [here](/articles/networking/).
 
 
-When that container was created, the `-P` flag was used to automatically map any
-network ports inside it to a random high port from the range 49153
-to 65535 on our Docker host.  Next, when `docker ps` was run, you saw that
-port 5000 in the container was bound to port 49155 on the host.
+When that container was created, the `-P` flag was used to automatically map
+any network port inside it to a random high port within an *ephemeral port
+range* on your Docker host. Next, when `docker ps` was run, you saw that port
+5000 in the container was bound to port 49155 on the host.
 
 
     $ sudo docker ps nostalgic_morse
     $ sudo docker ps nostalgic_morse
     CONTAINER ID  IMAGE                   COMMAND       CREATED        STATUS        PORTS                    NAMES
     CONTAINER ID  IMAGE                   COMMAND       CREATED        STATUS        PORTS                    NAMES

+ 5 - 5
docs/sources/userguide/usingdocker.md

@@ -154,11 +154,11 @@ ports exposed in our image to our host.
 In this case Docker has exposed port 5000 (the default Python Flask
 In this case Docker has exposed port 5000 (the default Python Flask
 port) on port 49155.
 port) on port 49155.
 
 
-Network port bindings are very configurable in Docker. In our last
-example the `-P` flag is a shortcut for `-p 5000` that maps port 5000
-inside the container to a high port (from the range 49153 to 65535) on
-the local Docker host. We can also bind Docker containers to specific
-ports using the `-p` flag, for example:
+Network port bindings are very configurable in Docker. In our last example the
+`-P` flag is a shortcut for `-p 5000` that maps port 5000 inside the container
+to a high port (from *ephemeral port range* which typically ranges from 32768
+to 61000) on the local Docker host. We can also bind Docker containers to
+specific ports using the `-p` flag, for example:
 
 
     $ sudo docker run -d -p 5000:5000 training/webapp python app.py
     $ sudo docker run -d -p 5000:5000 training/webapp python app.py