diff --git a/libnetwork/config/config.go b/libnetwork/config/config.go index b7c66e9884..6af5a01953 100644 --- a/libnetwork/config/config.go +++ b/libnetwork/config/config.go @@ -1,6 +1,7 @@ package config import ( + "fmt" "strings" "github.com/BurntSushi/toml" @@ -13,6 +14,7 @@ import ( "github.com/docker/libnetwork/ipamutils" "github.com/docker/libnetwork/netlabel" "github.com/docker/libnetwork/osl" + "github.com/docker/libnetwork/portallocator" "github.com/sirupsen/logrus" ) @@ -238,6 +240,23 @@ func OptionExperimental(exp bool) Option { } } +// OptionDynamicPortRange function returns an option setter for service port allocation range +func OptionDynamicPortRange(in string) Option { + return func(c *Config) { + start, end := 0, 0 + if len(in) > 0 { + n, err := fmt.Sscanf(in, "%d-%d", &start, &end) + if n != 2 || err != nil { + logrus.Errorf("Failed to parse range string with err %v", err) + return + } + } + if err := portallocator.Get().SetPortRange(start, end); err != nil { + logrus.Errorf("Failed to set port range with err %v", err) + } + } +} + // OptionNetworkControlPlaneMTU function returns an option setter for control plane MTU func OptionNetworkControlPlaneMTU(exp int) Option { return func(c *Config) { diff --git a/libnetwork/portallocator/portallocator.go b/libnetwork/portallocator/portallocator.go index 9798d23eb1..c87478c0cd 100644 --- a/libnetwork/portallocator/portallocator.go +++ b/libnetwork/portallocator/portallocator.go @@ -3,17 +3,36 @@ package portallocator import ( "errors" "fmt" + "github.com/sirupsen/logrus" "net" "sync" ) -const ( - // DefaultPortRangeStart indicates the first port in port range - DefaultPortRangeStart = 49153 - // DefaultPortRangeEnd indicates the last port in port range - DefaultPortRangeEnd = 65535 +var ( + // defaultPortRangeStart indicates the first port in port range + defaultPortRangeStart = 49153 + // defaultPortRangeEnd indicates the last port in port range + // consistent with default /proc/sys/net/ipv4/ip_local_port_range + // upper bound on linux + defaultPortRangeEnd = 60999 ) +func sanitizePortRange(start int, end int) (newStart, newEnd int, err error) { + if start > defaultPortRangeEnd || end < defaultPortRangeStart || start > end { + return 0, 0, fmt.Errorf("Request out allowed range [%v, %v]", + defaultPortRangeStart, defaultPortRangeEnd) + } + err = nil + newStart, newEnd = start, end + if start < defaultPortRangeStart { + newStart = defaultPortRangeStart + } + if end > defaultPortRangeEnd { + newEnd = defaultPortRangeEnd + } + return +} + type ipMapping map[string]protoMap var ( @@ -92,11 +111,19 @@ func Get() *PortAllocator { return instance } -func newInstance() *PortAllocator { +func getDefaultPortRange() (int, int) { start, end, err := getDynamicPortRange() - if err != nil { - start, end = DefaultPortRangeStart, DefaultPortRangeEnd + if err == nil { + start, end, err = sanitizePortRange(start, end) } + if err != nil { + start, end = defaultPortRangeStart, defaultPortRangeEnd + } + return start, end +} + +func newInstance() *PortAllocator { + start, end := getDefaultPortRange() return &PortAllocator{ ipMap: ipMapping{}, Begin: start, @@ -170,6 +197,35 @@ func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error { return nil } +// SetPortRange sets dynamic port allocation range. +// if both portBegin and portEnd are 0, the port range reverts to default +// value. Otherwise they are sanitized against the default values to +// ensure their validity. +func (p *PortAllocator) SetPortRange(portBegin, portEnd int) error { + // if begin and end is zero, revert to default values + var begin, end int + var err error + if portBegin == 0 && portEnd == 0 { + begin, end = getDefaultPortRange() + + } else { + begin, end, err = sanitizePortRange(portBegin, portEnd) + if err != nil { + return err + } + } + logrus.Debugf("Setting up port allocator to range %v-%v, current %v-%v", + begin, end, p.Begin, p.End) + p.mutex.Lock() + defer p.mutex.Unlock() + if p.Begin == begin && p.End == end { + return nil + } + p.ipMap = ipMapping{} + p.Begin, p.End = begin, end + return nil +} + func (p *PortAllocator) newPortMap() *portMap { defaultKey := getRangeKey(p.Begin, p.End) pm := &portMap{ diff --git a/libnetwork/portallocator/portallocator_freebsd.go b/libnetwork/portallocator/portallocator_freebsd.go index 97d7fbb49d..d71038ed54 100644 --- a/libnetwork/portallocator/portallocator_freebsd.go +++ b/libnetwork/portallocator/portallocator_freebsd.go @@ -8,7 +8,7 @@ import ( func getDynamicPortRange() (start int, end int, err error) { portRangeKernelSysctl := []string{"net.inet.ip.portrange.hifirst", "net.ip.portrange.hilast"} - portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd) + portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", defaultPortRangeStart, defaultPortRangeEnd) portRangeLowCmd := exec.Command("/sbin/sysctl", portRangeKernelSysctl[0]) var portRangeLowOut bytes.Buffer portRangeLowCmd.Stdout = &portRangeLowOut diff --git a/libnetwork/portallocator/portallocator_linux.go b/libnetwork/portallocator/portallocator_linux.go index 687f6dabb7..8ce696273e 100644 --- a/libnetwork/portallocator/portallocator_linux.go +++ b/libnetwork/portallocator/portallocator_linux.go @@ -8,7 +8,7 @@ import ( func getDynamicPortRange() (start int, end int, err error) { const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range" - portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd) + portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", defaultPortRangeStart, defaultPortRangeEnd) file, err := os.Open(portRangeKernelParam) if err != nil { return 0, 0, fmt.Errorf("port allocator - %s due to error: %v", portRangeFallback, err) diff --git a/libnetwork/portallocator/portallocator_test.go b/libnetwork/portallocator/portallocator_test.go index 42d779f417..0fe0b44534 100644 --- a/libnetwork/portallocator/portallocator_test.go +++ b/libnetwork/portallocator/portallocator_test.go @@ -1,6 +1,7 @@ package portallocator import ( + "fmt" "net" "testing" @@ -321,3 +322,47 @@ func TestNoDuplicateBPR(t *testing.T) { t.Fatalf("Acquire(0) allocated the same port twice: %d", port) } } + +func TestChangePortRange(t *testing.T) { + var tests = []struct { + begin int + end int + setErr error + reqRlt int + }{ + {defaultPortRangeEnd + 1, defaultPortRangeEnd + 10, fmt.Errorf("begin out of range"), 0}, + {defaultPortRangeStart - 10, defaultPortRangeStart - 1, fmt.Errorf("end out of range"), 0}, + {defaultPortRangeEnd, defaultPortRangeStart, fmt.Errorf("out of order"), 0}, + {defaultPortRangeStart + 100, defaultPortRangeEnd + 10, nil, defaultPortRangeStart + 100}, + {0, 0, nil, defaultPortRangeStart}, // revert to default if no value given + {defaultPortRangeStart - 100, defaultPortRangeEnd, nil, defaultPortRangeStart + 1}, + } + p := Get() + port := 0 + for _, c := range tests { + t.Logf("test: port allocate range %v-%v, setErr=%v, reqPort=%v", + c.begin, c.end, c.setErr, c.reqRlt) + err := p.SetPortRange(c.begin, c.end) + if (c.setErr == nil && c.setErr != err) || + (c.setErr != nil && err == nil) { + t.Fatalf("Unexpected set range result, expected=%v, actual=%v", c.setErr, err) + } + if err != nil { + continue + } + if port > 0 { + err := p.ReleasePort(defaultIP, "tcp", port) + if err != nil { + t.Fatalf("Releasing port %v failed, err=%v", port, err) + } + } + + port, err = p.RequestPort(defaultIP, "tcp", 0) + if err != nil { + t.Fatalf("Request failed, err %v", err) + } + if port != c.reqRlt { + t.Fatalf("Incorrect port returned, expected=%v, actual=%v", c.reqRlt, port) + } + } +} diff --git a/libnetwork/portallocator/portallocator_windows.go b/libnetwork/portallocator/portallocator_windows.go index 98cae14f68..7d0d5c8037 100644 --- a/libnetwork/portallocator/portallocator_windows.go +++ b/libnetwork/portallocator/portallocator_windows.go @@ -1,10 +1,10 @@ package portallocator -const ( - StartPortRange = 60000 - EndPortRange = 65000 -) +func init() { + defaultPortRangeStart = 60000 + defaultPortRangeEnd = 65000 +} func getDynamicPortRange() (start int, end int, err error) { - return StartPortRange, EndPortRange, nil + return defaultPortRangeStart, defaultPortRangeEnd, nil }