Skip to content

Commit 94facac

Browse files
author
Su Wang
committed
Added API to set ephemeral port allocator range.
Also reduce the allowed port range as the total number of containers per host is typically less than 1K. This change helps in scenarios where there are other services on the same host that uses ephemeral ports in iptables manipulation. The workflow requires changes in docker engine ( moby/moby#40055) and this change. It works as follows: 1. user can now specified to docker engine an option --published-port-range="50000-60000" as cmdline argument or in daemon.json. 2. docker engine read and pass this info to libnetwork via config.go:OptionDynamicPortRange. 3. libnetwork uses this range to allocate dynamic port henceforth. 4. --published-port-range can be set either via SIGHUP or restart docker engine 5. if --published-port-range is not set by user, a OS specific default range is used for dynamic port allocation. Linux: 49153-60999, Windows: 60000-65000 6 if --published-port-range is invalid, that is, the range given is outside of allowed default range, no change takes place. libnetwork will continue to use old/existing port range for dynamic port allocation. Signed-off-by: Su Wang <[email protected]>
1 parent 3e10ae9 commit 94facac

File tree

6 files changed

+134
-14
lines changed

6 files changed

+134
-14
lines changed

config/config.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"fmt"
45
"strings"
56

67
"github.com/BurntSushi/toml"
@@ -13,6 +14,7 @@ import (
1314
"github.com/docker/libnetwork/ipamutils"
1415
"github.com/docker/libnetwork/netlabel"
1516
"github.com/docker/libnetwork/osl"
17+
"github.com/docker/libnetwork/portallocator"
1618
"github.com/sirupsen/logrus"
1719
)
1820

@@ -238,6 +240,23 @@ func OptionExperimental(exp bool) Option {
238240
}
239241
}
240242

243+
// OptionDynamicPortRange function returns an option setter for service port allocation range
244+
func OptionDynamicPortRange(in string) Option {
245+
return func(c *Config) {
246+
start, end := 0, 0
247+
if len(in) > 0 {
248+
n, err := fmt.Sscanf(in, "%d-%d", &start, &end)
249+
if n != 2 || err != nil {
250+
logrus.Errorf("Failed to parse range string with err %v", err)
251+
return
252+
}
253+
}
254+
if err := portallocator.Get().SetPortRange(start, end); err != nil {
255+
logrus.Errorf("Failed to set port range with err %v", err)
256+
}
257+
}
258+
}
259+
241260
// OptionNetworkControlPlaneMTU function returns an option setter for control plane MTU
242261
func OptionNetworkControlPlaneMTU(exp int) Option {
243262
return func(c *Config) {

portallocator/portallocator.go

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,36 @@ package portallocator
33
import (
44
"errors"
55
"fmt"
6+
"github.com/sirupsen/logrus"
67
"net"
78
"sync"
89
)
910

10-
const (
11-
// DefaultPortRangeStart indicates the first port in port range
12-
DefaultPortRangeStart = 49153
13-
// DefaultPortRangeEnd indicates the last port in port range
14-
DefaultPortRangeEnd = 65535
11+
var (
12+
// defaultPortRangeStart indicates the first port in port range
13+
defaultPortRangeStart = 49153
14+
// defaultPortRangeEnd indicates the last port in port range
15+
// consistent with default /proc/sys/net/ipv4/ip_local_port_range
16+
// upper bound on linux
17+
defaultPortRangeEnd = 60999
1518
)
1619

20+
func sanitizePortRange(start int, end int) (newStart, newEnd int, err error) {
21+
if start > defaultPortRangeEnd || end < defaultPortRangeStart || start > end {
22+
return 0, 0, fmt.Errorf("Request out allowed range [%v, %v]",
23+
defaultPortRangeStart, defaultPortRangeEnd)
24+
}
25+
err = nil
26+
newStart, newEnd = start, end
27+
if start < defaultPortRangeStart {
28+
newStart = defaultPortRangeStart
29+
}
30+
if end > defaultPortRangeEnd {
31+
newEnd = defaultPortRangeEnd
32+
}
33+
return
34+
}
35+
1736
type ipMapping map[string]protoMap
1837

1938
var (
@@ -92,11 +111,19 @@ func Get() *PortAllocator {
92111
return instance
93112
}
94113

95-
func newInstance() *PortAllocator {
114+
func getDefaultPortRange() (int, int) {
96115
start, end, err := getDynamicPortRange()
116+
if err == nil {
117+
start, end, err = sanitizePortRange(start, end)
118+
}
97119
if err != nil {
98-
start, end = DefaultPortRangeStart, DefaultPortRangeEnd
120+
start, end = defaultPortRangeStart, defaultPortRangeEnd
99121
}
122+
return start, end
123+
}
124+
125+
func newInstance() *PortAllocator {
126+
start, end := getDefaultPortRange()
100127
return &PortAllocator{
101128
ipMap: ipMapping{},
102129
Begin: start,
@@ -170,6 +197,35 @@ func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error {
170197
return nil
171198
}
172199

200+
// SetPortRange sets dynamic port allocation range.
201+
// if both portBegin and portEnd are 0, the port range reverts to default
202+
// value. Otherwise they are sanitized against the default values to
203+
// ensure their validity.
204+
func (p *PortAllocator) SetPortRange(portBegin, portEnd int) error {
205+
// if begin and end is zero, revert to default values
206+
var begin, end int
207+
var err error
208+
if portBegin == 0 && portEnd == 0 {
209+
begin, end = getDefaultPortRange()
210+
211+
} else {
212+
begin, end, err = sanitizePortRange(portBegin, portEnd)
213+
if err != nil {
214+
return err
215+
}
216+
}
217+
logrus.Debugf("Setting up port allocator to range %v-%v, current %v-%v",
218+
begin, end, p.Begin, p.End)
219+
p.mutex.Lock()
220+
defer p.mutex.Unlock()
221+
if p.Begin == begin && p.End == end {
222+
return nil
223+
}
224+
p.ipMap = ipMapping{}
225+
p.Begin, p.End = begin, end
226+
return nil
227+
}
228+
173229
func (p *PortAllocator) newPortMap() *portMap {
174230
defaultKey := getRangeKey(p.Begin, p.End)
175231
pm := &portMap{

portallocator/portallocator_freebsd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88

99
func getDynamicPortRange() (start int, end int, err error) {
1010
portRangeKernelSysctl := []string{"net.inet.ip.portrange.hifirst", "net.ip.portrange.hilast"}
11-
portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd)
11+
portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", defaultPortRangeStart, defaultPortRangeEnd)
1212
portRangeLowCmd := exec.Command("/sbin/sysctl", portRangeKernelSysctl[0])
1313
var portRangeLowOut bytes.Buffer
1414
portRangeLowCmd.Stdout = &portRangeLowOut

portallocator/portallocator_linux.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88

99
func getDynamicPortRange() (start int, end int, err error) {
1010
const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range"
11-
portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd)
11+
portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", defaultPortRangeStart, defaultPortRangeEnd)
1212
file, err := os.Open(portRangeKernelParam)
1313
if err != nil {
1414
return 0, 0, fmt.Errorf("port allocator - %s due to error: %v", portRangeFallback, err)

portallocator/portallocator_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package portallocator
22

33
import (
4+
"fmt"
45
"net"
56
"testing"
67

@@ -321,3 +322,47 @@ func TestNoDuplicateBPR(t *testing.T) {
321322
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
322323
}
323324
}
325+
326+
func TestChangePortRange(t *testing.T) {
327+
var tests = []struct {
328+
begin int
329+
end int
330+
setErr error
331+
reqRlt int
332+
}{
333+
{defaultPortRangeEnd + 1, defaultPortRangeEnd + 10, fmt.Errorf("begin out of range"), 0},
334+
{defaultPortRangeStart - 10, defaultPortRangeStart - 1, fmt.Errorf("end out of range"), 0},
335+
{defaultPortRangeEnd, defaultPortRangeStart, fmt.Errorf("out of order"), 0},
336+
{defaultPortRangeStart + 100, defaultPortRangeEnd + 10, nil, defaultPortRangeStart + 100},
337+
{0, 0, nil, defaultPortRangeStart}, // revert to default if no value given
338+
{defaultPortRangeStart - 100, defaultPortRangeEnd, nil, defaultPortRangeStart + 1},
339+
}
340+
p := Get()
341+
port := 0
342+
for _, c := range tests {
343+
t.Logf("test: port allocate range %v-%v, setErr=%v, reqPort=%v",
344+
c.begin, c.end, c.setErr, c.reqRlt)
345+
err := p.SetPortRange(c.begin, c.end)
346+
if (c.setErr == nil && c.setErr != err) ||
347+
(c.setErr != nil && err == nil) {
348+
t.Fatalf("Unexpected set range result, expected=%v, actual=%v", c.setErr, err)
349+
}
350+
if err != nil {
351+
continue
352+
}
353+
if port > 0 {
354+
err := p.ReleasePort(defaultIP, "tcp", port)
355+
if err != nil {
356+
t.Fatalf("Releasing port %v failed, err=%v", port, err)
357+
}
358+
}
359+
360+
port, err = p.RequestPort(defaultIP, "tcp", 0)
361+
if err != nil {
362+
t.Fatalf("Request failed, err %v", err)
363+
}
364+
if port != c.reqRlt {
365+
t.Fatalf("Incorrect port returned, expected=%v, actual=%v", c.reqRlt, port)
366+
}
367+
}
368+
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package portallocator
22

3-
const (
4-
StartPortRange = 60000
5-
EndPortRange = 65000
6-
)
3+
func init() {
4+
defaultPortRangeStart = 60000
5+
defaultPortRangeEnd = 65000
6+
}
77

88
func getDynamicPortRange() (start int, end int, err error) {
9-
return StartPortRange, EndPortRange, nil
9+
return defaultPortRangeStart, defaultPortRangeEnd, nil
1010
}

0 commit comments

Comments
 (0)