Merge pull request #10238 from miminar/ephemeral-port-range-allocation
Use system's ephemeral port range for port allocation
This commit is contained in:
commit
02c1dd899a
8 changed files with 91 additions and 48 deletions
|
@ -1,10 +1,24 @@
|
|||
package portallocator
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"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,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 {
|
||||
return e.ip
|
||||
}
|
||||
|
@ -137,10 +171,10 @@ func ReleaseAll() error {
|
|||
|
||||
func (pm *portMap) findPort() (int, error) {
|
||||
port := pm.last
|
||||
for i := 0; i <= EndPortRange-BeginPortRange; i++ {
|
||||
for i := 0; i <= endPortRange-beginPortRange; i++ {
|
||||
port++
|
||||
if port > EndPortRange {
|
||||
port = BeginPortRange
|
||||
if port > endPortRange {
|
||||
port = beginPortRange
|
||||
}
|
||||
|
||||
if _, ok := pm.p[port]; !ok {
|
||||
|
|
|
@ -5,6 +5,11 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
beginPortRange = DefaultPortRangeStart
|
||||
endPortRange = DefaultPortRangeEnd
|
||||
}
|
||||
|
||||
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.PortRange()
|
||||
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 {
|
||||
|
|
|
@ -258,9 +258,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.
|
||||
|
|
|
@ -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)
|
||||
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.
|
||||
|
|
|
@ -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`.
|
||||
|
||||
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…
Reference in a new issue