mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	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:
		
							parent
							
								
									1ff904e045
								
							
						
					
					
						commit
						0eb3544c43
					
				
					 8 changed files with 90 additions and 48 deletions
				
			
		| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 <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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue