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>
| ... | ... |
@@ -3,8 +3,22 @@ package portallocator |
| 3 | 3 |
import ( |
| 4 | 4 |
"errors" |
| 5 | 5 |
"fmt" |
| 6 |
+ "io/ioutil" |
|
| 6 | 7 |
"net" |
| 8 |
+ "strings" |
|
| 7 | 9 |
"sync" |
| 10 |
+ |
|
| 11 |
+ log "github.com/Sirupsen/logrus" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+const ( |
|
| 15 |
+ DefaultPortRangeStart = 49153 |
|
| 16 |
+ DefaultPortRangeEnd = 65535 |
|
| 17 |
+) |
|
| 18 |
+ |
|
| 19 |
+var ( |
|
| 20 |
+ beginPortRange = DefaultPortRangeStart |
|
| 21 |
+ endPortRange = DefaultPortRangeEnd |
|
| 8 | 22 |
) |
| 9 | 23 |
|
| 10 | 24 |
type portMap struct {
|
| ... | ... |
@@ -15,7 +29,7 @@ type portMap struct {
|
| 15 | 15 |
func newPortMap() *portMap {
|
| 16 | 16 |
return &portMap{
|
| 17 | 17 |
p: map[int]struct{}{},
|
| 18 |
- last: EndPortRange, |
|
| 18 |
+ last: endPortRange, |
|
| 19 | 19 |
} |
| 20 | 20 |
} |
| 21 | 21 |
|
| ... | ... |
@@ -30,11 +44,6 @@ func newProtoMap() protoMap {
|
| 30 | 30 |
|
| 31 | 31 |
type ipMapping map[string]protoMap |
| 32 | 32 |
|
| 33 |
-const ( |
|
| 34 |
- BeginPortRange = 49153 |
|
| 35 |
- EndPortRange = 65535 |
|
| 36 |
-) |
|
| 37 |
- |
|
| 38 | 33 |
var ( |
| 39 | 34 |
ErrAllPortsAllocated = errors.New("all ports are allocated")
|
| 40 | 35 |
ErrUnknownProtocol = errors.New("unknown protocol")
|
| ... | ... |
@@ -59,6 +68,29 @@ func NewErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated {
|
| 59 | 59 |
} |
| 60 | 60 |
} |
| 61 | 61 |
|
| 62 |
+func init() {
|
|
| 63 |
+ const param = "/proc/sys/net/ipv4/ip_local_port_range" |
|
| 64 |
+ |
|
| 65 |
+ if line, err := ioutil.ReadFile(param); err != nil {
|
|
| 66 |
+ log.Errorf("Failed to read %s kernel parameter: %s", param, err.Error())
|
|
| 67 |
+ } else {
|
|
| 68 |
+ var start, end int |
|
| 69 |
+ if n, err := fmt.Fscanf(strings.NewReader(string(line)), "%d\t%d", &start, &end); n != 2 || err != nil {
|
|
| 70 |
+ if err == nil {
|
|
| 71 |
+ err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
|
|
| 72 |
+ } |
|
| 73 |
+ log.Errorf("Failed to parse port range from %s: %v", param, err)
|
|
| 74 |
+ } else {
|
|
| 75 |
+ beginPortRange = start |
|
| 76 |
+ endPortRange = end |
|
| 77 |
+ } |
|
| 78 |
+ } |
|
| 79 |
+} |
|
| 80 |
+ |
|
| 81 |
+func GetPortRange() (int, int) {
|
|
| 82 |
+ return beginPortRange, endPortRange |
|
| 83 |
+} |
|
| 84 |
+ |
|
| 62 | 85 |
func (e ErrPortAlreadyAllocated) IP() string {
|
| 63 | 86 |
return e.ip |
| 64 | 87 |
} |
| ... | ... |
@@ -137,10 +169,11 @@ func ReleaseAll() error {
|
| 137 | 137 |
|
| 138 | 138 |
func (pm *portMap) findPort() (int, error) {
|
| 139 | 139 |
port := pm.last |
| 140 |
- for i := 0; i <= EndPortRange-BeginPortRange; i++ {
|
|
| 140 |
+ start, end := GetPortRange() |
|
| 141 |
+ for i := 0; i <= end-start; i++ {
|
|
| 141 | 142 |
port++ |
| 142 |
- if port > EndPortRange {
|
|
| 143 |
- port = BeginPortRange |
|
| 143 |
+ if port > end {
|
|
| 144 |
+ port = start |
|
| 144 | 145 |
} |
| 145 | 146 |
|
| 146 | 147 |
if _, ok := pm.p[port]; !ok {
|
| ... | ... |
@@ -5,6 +5,11 @@ import ( |
| 5 | 5 |
"testing" |
| 6 | 6 |
) |
| 7 | 7 |
|
| 8 |
+func init() {
|
|
| 9 |
+ beginPortRange = DefaultPortRangeStart |
|
| 10 |
+ endPortRange = DefaultPortRangeStart + 500 |
|
| 11 |
+} |
|
| 12 |
+ |
|
| 8 | 13 |
func reset() {
|
| 9 | 14 |
ReleaseAll() |
| 10 | 15 |
} |
| ... | ... |
@@ -17,7 +22,7 @@ func TestRequestNewPort(t *testing.T) {
|
| 17 | 17 |
t.Fatal(err) |
| 18 | 18 |
} |
| 19 | 19 |
|
| 20 |
- if expected := BeginPortRange; port != expected {
|
|
| 20 |
+ if expected := beginPortRange; port != expected {
|
|
| 21 | 21 |
t.Fatalf("Expected port %d got %d", expected, port)
|
| 22 | 22 |
} |
| 23 | 23 |
} |
| ... | ... |
@@ -102,13 +107,13 @@ func TestUnknowProtocol(t *testing.T) {
|
| 102 | 102 |
func TestAllocateAllPorts(t *testing.T) {
|
| 103 | 103 |
defer reset() |
| 104 | 104 |
|
| 105 |
- for i := 0; i <= EndPortRange-BeginPortRange; i++ {
|
|
| 105 |
+ for i := 0; i <= endPortRange-beginPortRange; i++ {
|
|
| 106 | 106 |
port, err := RequestPort(defaultIP, "tcp", 0) |
| 107 | 107 |
if err != nil {
|
| 108 | 108 |
t.Fatal(err) |
| 109 | 109 |
} |
| 110 | 110 |
|
| 111 |
- if expected := BeginPortRange + i; port != expected {
|
|
| 111 |
+ if expected := beginPortRange + i; port != expected {
|
|
| 112 | 112 |
t.Fatalf("Expected port %d got %d", expected, port)
|
| 113 | 113 |
} |
| 114 | 114 |
} |
| ... | ... |
@@ -123,7 +128,7 @@ func TestAllocateAllPorts(t *testing.T) {
|
| 123 | 123 |
} |
| 124 | 124 |
|
| 125 | 125 |
// release a port in the middle and ensure we get another tcp port |
| 126 |
- port := BeginPortRange + 5 |
|
| 126 |
+ port := beginPortRange + 5 |
|
| 127 | 127 |
if err := ReleasePort(defaultIP, "tcp", port); err != nil {
|
| 128 | 128 |
t.Fatal(err) |
| 129 | 129 |
} |
| ... | ... |
@@ -153,13 +158,13 @@ func BenchmarkAllocatePorts(b *testing.B) {
|
| 153 | 153 |
defer reset() |
| 154 | 154 |
|
| 155 | 155 |
for i := 0; i < b.N; i++ {
|
| 156 |
- for i := 0; i <= EndPortRange-BeginPortRange; i++ {
|
|
| 156 |
+ for i := 0; i <= endPortRange-beginPortRange; i++ {
|
|
| 157 | 157 |
port, err := RequestPort(defaultIP, "tcp", 0) |
| 158 | 158 |
if err != nil {
|
| 159 | 159 |
b.Fatal(err) |
| 160 | 160 |
} |
| 161 | 161 |
|
| 162 |
- if expected := BeginPortRange + i; port != expected {
|
|
| 162 |
+ if expected := beginPortRange + i; port != expected {
|
|
| 163 | 163 |
b.Fatalf("Expected port %d got %d", expected, port)
|
| 164 | 164 |
} |
| 165 | 165 |
} |
| ... | ... |
@@ -231,15 +236,15 @@ func TestPortAllocation(t *testing.T) {
|
| 231 | 231 |
func TestNoDuplicateBPR(t *testing.T) {
|
| 232 | 232 |
defer reset() |
| 233 | 233 |
|
| 234 |
- if port, err := RequestPort(defaultIP, "tcp", BeginPortRange); err != nil {
|
|
| 234 |
+ if port, err := RequestPort(defaultIP, "tcp", beginPortRange); err != nil {
|
|
| 235 | 235 |
t.Fatal(err) |
| 236 |
- } else if port != BeginPortRange {
|
|
| 237 |
- t.Fatalf("Expected port %d got %d", BeginPortRange, port)
|
|
| 236 |
+ } else if port != beginPortRange {
|
|
| 237 |
+ t.Fatalf("Expected port %d got %d", beginPortRange, port)
|
|
| 238 | 238 |
} |
| 239 | 239 |
|
| 240 | 240 |
if port, err := RequestPort(defaultIP, "tcp", 0); err != nil {
|
| 241 | 241 |
t.Fatal(err) |
| 242 |
- } else if port == BeginPortRange {
|
|
| 242 |
+ } else if port == beginPortRange {
|
|
| 243 | 243 |
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
|
| 244 | 244 |
} |
| 245 | 245 |
} |
| ... | ... |
@@ -129,7 +129,8 @@ func TestMapAllPortsSingleInterface(t *testing.T) {
|
| 129 | 129 |
}() |
| 130 | 130 |
|
| 131 | 131 |
for i := 0; i < 10; i++ {
|
| 132 |
- for i := portallocator.BeginPortRange; i < portallocator.EndPortRange; i++ {
|
|
| 132 |
+ start, end := portallocator.GetPortRange() |
|
| 133 |
+ for i := start; i < end; i++ {
|
|
| 133 | 134 |
if host, err = Map(srcAddr1, dstIp1, 0); err != nil {
|
| 134 | 135 |
t.Fatal(err) |
| 135 | 136 |
} |
| ... | ... |
@@ -137,8 +138,8 @@ func TestMapAllPortsSingleInterface(t *testing.T) {
|
| 137 | 137 |
hosts = append(hosts, host) |
| 138 | 138 |
} |
| 139 | 139 |
|
| 140 |
- if _, err := Map(srcAddr1, dstIp1, portallocator.BeginPortRange); err == nil {
|
|
| 141 |
- t.Fatalf("Port %d should be bound but is not", portallocator.BeginPortRange)
|
|
| 140 |
+ if _, err := Map(srcAddr1, dstIp1, start); err == nil {
|
|
| 141 |
+ t.Fatalf("Port %d should be bound but is not", start)
|
|
| 142 | 142 |
} |
| 143 | 143 |
|
| 144 | 144 |
for _, val := range hosts {
|
| ... | ... |
@@ -244,9 +244,10 @@ and foreground Docker containers. |
| 244 | 244 |
When set to true publish all exposed ports to the host interfaces. The |
| 245 | 245 |
default is false. If the operator uses -P (or -p) then Docker will make the |
| 246 | 246 |
exposed port accessible on the host and the ports will be available to any |
| 247 |
-client that can reach the host. When using -P, Docker will bind the exposed |
|
| 248 |
-ports to a random port on the host between 49153 and 65535. To find the |
|
| 249 |
-mapping between the host ports and the exposed ports, use **docker port**. |
|
| 247 |
+client that can reach the host. When using -P, Docker will bind any exposed |
|
| 248 |
+port to a random port on the host within an *ephemeral port range* defined by |
|
| 249 |
+`/proc/sys/net/ipv4/ip_local_port_range`. To find the mapping between the host |
|
| 250 |
+ports and the exposed ports, use `docker port`. |
|
| 250 | 251 |
|
| 251 | 252 |
**-p**, **--publish**=[] |
| 252 | 253 |
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 |
| 370 | 370 |
are covered in more detail in the [Docker User Guide](/userguide/dockerlinks) |
| 371 | 371 |
page. There are two approaches. |
| 372 | 372 |
|
| 373 |
-First, you can supply `-P` or `--publish-all=true|false` to `docker run` |
|
| 374 |
-which is a blanket operation that identifies every port with an `EXPOSE` |
|
| 375 |
-line in the image's `Dockerfile` and maps it to a host port somewhere in |
|
| 376 |
-the range 49153–65535. This tends to be a bit inconvenient, since you |
|
| 377 |
-then have to run other `docker` sub-commands to learn which external |
|
| 378 |
-port a given service was mapped to. |
|
| 379 |
- |
|
| 380 |
-More convenient is the `-p SPEC` or `--publish=SPEC` option which lets |
|
| 381 |
-you be explicit about exactly which external port on the Docker server — |
|
| 382 |
-which can be any port at all, not just those in the 49153-65535 block — |
|
| 383 |
-you want mapped to which port in the container. |
|
| 373 |
+First, you can supply `-P` or `--publish-all=true|false` to `docker run` which |
|
| 374 |
+is a blanket operation that identifies every port with an `EXPOSE` line in the |
|
| 375 |
+image's `Dockerfile` or `--expose <port>` commandline flag and maps it to a |
|
| 376 |
+host port somewhere within an *ephemeral port range*. The `docker port` command |
|
| 377 |
+then needs to be used to inspect created mapping. The *ephemeral port range* is |
|
| 378 |
+configured by `/proc/sys/net/ipv4/ip_local_port_range` kernel parameter, |
|
| 379 |
+typically ranging from 32768 to 61000. |
|
| 380 |
+ |
|
| 381 |
+Mapping can be specified explicitly using `-p SPEC` or `--publish=SPEC` option. |
|
| 382 |
+It allows you to particularize which port on docker server - which can be any |
|
| 383 |
+port at all, not just one within the *ephemeral port range* — you want mapped |
|
| 384 |
+to which port in the container. |
|
| 384 | 385 |
|
| 385 | 386 |
Either way, you should be able to peek at what Docker has accomplished |
| 386 | 387 |
in your network stack by examining your NAT tables. |
| ... | ... |
@@ -635,10 +635,11 @@ developer, the operator has three choices: start the server container |
| 635 | 635 |
with `-P` or `-p,` or start the client container with `--link`. |
| 636 | 636 |
|
| 637 | 637 |
If the operator uses `-P` or `-p` then Docker will make the exposed port |
| 638 |
-accessible on the host and the ports will be available to any client |
|
| 639 |
-that can reach the host. When using `-P`, Docker will bind the exposed |
|
| 640 |
-ports to a random port on the host between 49153 and 65535. To find the |
|
| 641 |
-mapping between the host ports and the exposed ports, use `docker port`. |
|
| 638 |
+accessible on the host and the ports will be available to any client that can |
|
| 639 |
+reach the host. When using `-P`, Docker will bind the exposed port to a random |
|
| 640 |
+port on the host within an *ephemeral port range* defined by |
|
| 641 |
+`/proc/sys/net/ipv4/ip_local_port_range`. To find the mapping between the host |
|
| 642 |
+ports and the exposed ports, use `docker port`. |
|
| 642 | 643 |
|
| 643 | 644 |
If the operator uses `--link` when starting the new client container, |
| 644 | 645 |
then the client container can access the exposed port via a private |
| ... | ... |
@@ -25,10 +25,10 @@ container that ran a Python Flask application: |
| 25 | 25 |
> Docker can have a variety of network configurations. You can see more |
| 26 | 26 |
> information on Docker networking [here](/articles/networking/). |
| 27 | 27 |
|
| 28 |
-When that container was created, the `-P` flag was used to automatically map any |
|
| 29 |
-network ports inside it to a random high port from the range 49153 |
|
| 30 |
-to 65535 on our Docker host. Next, when `docker ps` was run, you saw that |
|
| 31 |
-port 5000 in the container was bound to port 49155 on the host. |
|
| 28 |
+When that container was created, the `-P` flag was used to automatically map |
|
| 29 |
+any network port inside it to a random high port within an *ephemeral port |
|
| 30 |
+range* on your Docker host. Next, when `docker ps` was run, you saw that port |
|
| 31 |
+5000 in the container was bound to port 49155 on the host. |
|
| 32 | 32 |
|
| 33 | 33 |
$ sudo docker ps nostalgic_morse |
| 34 | 34 |
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
| ... | ... |
@@ -154,11 +154,11 @@ ports exposed in our image to our host. |
| 154 | 154 |
In this case Docker has exposed port 5000 (the default Python Flask |
| 155 | 155 |
port) on port 49155. |
| 156 | 156 |
|
| 157 |
-Network port bindings are very configurable in Docker. In our last |
|
| 158 |
-example the `-P` flag is a shortcut for `-p 5000` that maps port 5000 |
|
| 159 |
-inside the container to a high port (from the range 49153 to 65535) on |
|
| 160 |
-the local Docker host. We can also bind Docker containers to specific |
|
| 161 |
-ports using the `-p` flag, for example: |
|
| 157 |
+Network port bindings are very configurable in Docker. In our last example the |
|
| 158 |
+`-P` flag is a shortcut for `-p 5000` that maps port 5000 inside the container |
|
| 159 |
+to a high port (from *ephemeral port range* which typically ranges from 32768 |
|
| 160 |
+to 61000) on the local Docker host. We can also bind Docker containers to |
|
| 161 |
+specific ports using the `-p` flag, for example: |
|
| 162 | 162 |
|
| 163 | 163 |
$ sudo docker run -d -p 5000:5000 training/webapp python app.py |
| 164 | 164 |
|