Use system's ephemeral port range for port allocation

Read `/proc/sys/net/ipv4/ip_local_port_range` kernel parameter to obtain
ephemeral port range that now sets the boundaries of port allocator
which finds free host ports for those exported by containers.

Signed-off-by: Michal Minar <miminar@redhat.com>
This commit is contained in:
Michal Minar 2015-01-21 13:40:59 +01:00
parent 1ff904e045
commit 0eb3544c43
8 changed files with 90 additions and 48 deletions

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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.

View File

@ -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 4915365535. 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 <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.
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.

View File

@ -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

View File

@ -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

View File

@ -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