diff --git a/daemon/networkdriver/portallocator/portallocator.go b/daemon/networkdriver/portallocator/portallocator.go index 3414d11e7a..58d908615b 100644 --- a/daemon/networkdriver/portallocator/portallocator.go +++ b/daemon/networkdriver/portallocator/portallocator.go @@ -3,8 +3,22 @@ package portallocator import ( "errors" "fmt" + "io/ioutil" "net" + "strings" "sync" + + log "github.com/Sirupsen/logrus" +) + +const ( + DefaultPortRangeStart = 49153 + DefaultPortRangeEnd = 65535 +) + +var ( + beginPortRange = DefaultPortRangeStart + endPortRange = DefaultPortRangeEnd ) type portMap struct { @@ -15,7 +29,7 @@ type portMap struct { func newPortMap() *portMap { return &portMap{ p: map[int]struct{}{}, - last: EndPortRange, + last: endPortRange, } } @@ -30,11 +44,6 @@ func newProtoMap() protoMap { type ipMapping map[string]protoMap -const ( - BeginPortRange = 49153 - EndPortRange = 65535 -) - var ( ErrAllPortsAllocated = errors.New("all ports are allocated") ErrUnknownProtocol = errors.New("unknown protocol") @@ -59,6 +68,29 @@ func NewErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated { } } +func init() { + const param = "/proc/sys/net/ipv4/ip_local_port_range" + + if line, err := ioutil.ReadFile(param); err != nil { + log.Errorf("Failed to read %s kernel parameter: %s", param, err.Error()) + } else { + var start, end int + if n, err := fmt.Fscanf(strings.NewReader(string(line)), "%d\t%d", &start, &end); 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) + } else { + beginPortRange = start + endPortRange = end + } + } +} + +func GetPortRange() (int, int) { + return beginPortRange, endPortRange +} + func (e ErrPortAlreadyAllocated) IP() string { return e.ip } @@ -137,10 +169,11 @@ func ReleaseAll() error { func (pm *portMap) findPort() (int, error) { port := pm.last - for i := 0; i <= EndPortRange-BeginPortRange; i++ { + start, end := GetPortRange() + for i := 0; i <= end-start; i++ { port++ - if port > EndPortRange { - port = BeginPortRange + if port > end { + port = start } if _, ok := pm.p[port]; !ok { diff --git a/daemon/networkdriver/portallocator/portallocator_test.go b/daemon/networkdriver/portallocator/portallocator_test.go index 72581f1040..58c48b2509 100644 --- a/daemon/networkdriver/portallocator/portallocator_test.go +++ b/daemon/networkdriver/portallocator/portallocator_test.go @@ -5,6 +5,11 @@ import ( "testing" ) +func init() { + beginPortRange = DefaultPortRangeStart + endPortRange = DefaultPortRangeStart + 500 +} + func reset() { ReleaseAll() } @@ -17,7 +22,7 @@ func TestRequestNewPort(t *testing.T) { t.Fatal(err) } - if expected := BeginPortRange; port != expected { + if expected := beginPortRange; port != expected { t.Fatalf("Expected port %d got %d", expected, port) } } @@ -102,13 +107,13 @@ func TestUnknowProtocol(t *testing.T) { func TestAllocateAllPorts(t *testing.T) { defer reset() - for i := 0; i <= EndPortRange-BeginPortRange; i++ { + for i := 0; i <= endPortRange-beginPortRange; i++ { port, err := RequestPort(defaultIP, "tcp", 0) if err != nil { t.Fatal(err) } - if expected := BeginPortRange + i; port != expected { + if expected := beginPortRange + i; port != expected { 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 - port := BeginPortRange + 5 + port := beginPortRange + 5 if err := ReleasePort(defaultIP, "tcp", port); err != nil { t.Fatal(err) } @@ -153,13 +158,13 @@ func BenchmarkAllocatePorts(b *testing.B) { defer reset() 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) if err != nil { b.Fatal(err) } - if expected := BeginPortRange + i; port != expected { + if expected := beginPortRange + i; port != expected { b.Fatalf("Expected port %d got %d", expected, port) } } @@ -231,15 +236,15 @@ func TestPortAllocation(t *testing.T) { func TestNoDuplicateBPR(t *testing.T) { defer reset() - if port, err := RequestPort(defaultIP, "tcp", BeginPortRange); err != nil { + if port, err := RequestPort(defaultIP, "tcp", beginPortRange); err != nil { 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 { t.Fatal(err) - } else if port == BeginPortRange { + } else if port == beginPortRange { t.Fatalf("Acquire(0) allocated the same port twice: %d", port) } } diff --git a/daemon/networkdriver/portmapper/mapper_test.go b/daemon/networkdriver/portmapper/mapper_test.go index 42e44a11df..1ace1e01bc 100644 --- a/daemon/networkdriver/portmapper/mapper_test.go +++ b/daemon/networkdriver/portmapper/mapper_test.go @@ -129,7 +129,8 @@ func TestMapAllPortsSingleInterface(t *testing.T) { }() for i := 0; i < 10; i++ { - for i := portallocator.BeginPortRange; i < portallocator.EndPortRange; i++ { + start, end := portallocator.GetPortRange() + for i := start; i < end; i++ { if host, err = Map(srcAddr1, dstIp1, 0); err != nil { t.Fatal(err) } @@ -137,8 +138,8 @@ func TestMapAllPortsSingleInterface(t *testing.T) { 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 { diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index 7e3875afcc..4db45a614e 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -244,9 +244,10 @@ and foreground Docker containers. 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 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**=[] Publish a container's port, or range of ports, to the host. diff --git a/docs/sources/articles/networking.md b/docs/sources/articles/networking.md index e1195e10d1..1c53d390d4 100644 --- a/docs/sources/articles/networking.md +++ b/docs/sources/articles/networking.md @@ -370,17 +370,18 @@ to provide special options when invoking `docker run`. These options are covered in more detail in the [Docker User Guide](/userguide/dockerlinks) 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. +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 ` 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. -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. +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 in your network stack by examining your NAT tables. diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 268ade05ee..5f6b1996a4 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -635,10 +635,11 @@ developer, the operator has three choices: start the server container 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 -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, then the client container can access the exposed port via a private diff --git a/docs/sources/userguide/dockerlinks.md b/docs/sources/userguide/dockerlinks.md index 0a88092ae0..79ba17900e 100644 --- a/docs/sources/userguide/dockerlinks.md +++ b/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 > 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 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES diff --git a/docs/sources/userguide/usingdocker.md b/docs/sources/userguide/usingdocker.md index 12a6b6fb2f..8d57def4ed 100644 --- a/docs/sources/userguide/usingdocker.md +++ b/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 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