mirror of
https://github.com/clearlinux/libnetwork.git
synced 2026-09-06 05:41:50 +00:00
Port Allocator as a libnetwork package
DESCRIPTION: As part of bringing libnetwork bridge driver features in parity with docker/daemon/network/driver/bridge features (Issue #46), this commit addresses the bridge.RequestPort() API. Currenlty docker/api/server.go needs an hold of port allocator in order to reserve a transport port which will be used by the http server on the host machine, so that portallocator does not give out that port when queried by portmapper as part of network driver operations. ISSUE: Current implementation in docker is server.go directly access portmapper and then portallocator from bridge pkg calling bridge.RequestPort(). This also forces that function to trigger portmapper initialization (in case bridge init() was not executed yet), while portmapper life cycle should only be controlled by bridge network driver. We cannot mantain this behavior with libnetwrok as this violates the modularization of networking code which libnetwork is bringing in. FIX: Make portallocator a singleton, now both docker core and portmapper code can initialize it and get the only one instance (Change in docker core code will happen when docker code will migrate to use libnetwork), given it is being used for host specific needs. NOTE: Long term fix is having multiple portallocator instances (so no more singleton) each capable to be in sync with OS regarding current port allocation. When this change comes, no change whould be required on portallocator' clients side, changes will be confined to portallocator package. Signed-off-by: Alessandro Boch <aboch@docker.com>
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
package portallocator
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultPortRangeStart indicates the first port in port range
|
||||
DefaultPortRangeStart = 49153
|
||||
// DefaultPortRangeEnd indicates the last port in port range
|
||||
DefaultPortRangeEnd = 65535
|
||||
)
|
||||
|
||||
type ipMapping map[string]protoMap
|
||||
|
||||
var (
|
||||
// ErrAllPortsAllocated is returned when no more ports are available
|
||||
ErrAllPortsAllocated = errors.New("all ports are allocated")
|
||||
// ErrUnknownProtocol is returned when an unknown protocol was specified
|
||||
ErrUnknownProtocol = errors.New("unknown protocol")
|
||||
defaultIP = net.ParseIP("0.0.0.0")
|
||||
once sync.Once
|
||||
instance *PortAllocator
|
||||
createInstance = func() { instance = newInstance() }
|
||||
)
|
||||
|
||||
// ErrPortAlreadyAllocated is the returned error information when a requested port is already being used
|
||||
type ErrPortAlreadyAllocated struct {
|
||||
ip string
|
||||
port int
|
||||
}
|
||||
|
||||
func newErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated {
|
||||
return ErrPortAlreadyAllocated{
|
||||
ip: ip,
|
||||
port: port,
|
||||
}
|
||||
}
|
||||
|
||||
// IP returns the address to which the used port is associated
|
||||
func (e ErrPortAlreadyAllocated) IP() string {
|
||||
return e.ip
|
||||
}
|
||||
|
||||
// Port returns the value of the already used port
|
||||
func (e ErrPortAlreadyAllocated) Port() int {
|
||||
return e.port
|
||||
}
|
||||
|
||||
// IPPort returns the address and the port in the form ip:port
|
||||
func (e ErrPortAlreadyAllocated) IPPort() string {
|
||||
return fmt.Sprintf("%s:%d", e.ip, e.port)
|
||||
}
|
||||
|
||||
// Error is the implementation of error.Error interface
|
||||
func (e ErrPortAlreadyAllocated) Error() string {
|
||||
return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port)
|
||||
}
|
||||
|
||||
type (
|
||||
// PortAllocator manages the transport ports database
|
||||
PortAllocator struct {
|
||||
mutex sync.Mutex
|
||||
ipMap ipMapping
|
||||
Begin int
|
||||
End int
|
||||
}
|
||||
portMap struct {
|
||||
p map[int]struct{}
|
||||
begin, end int
|
||||
last int
|
||||
}
|
||||
protoMap map[string]*portMap
|
||||
)
|
||||
|
||||
// New returns a new instance of PortAllocator
|
||||
func New() *PortAllocator {
|
||||
// Port Allocator is a singleton
|
||||
// Note: Long term solution will be each PortAllocator will have access to
|
||||
// the OS so that it can have up to date view of the OS port allocation.
|
||||
// When this happens singleton behavior will be removed. Clients do not
|
||||
// need to worry about this, they will not see a change in behavior.
|
||||
once.Do(createInstance)
|
||||
return instance
|
||||
}
|
||||
|
||||
func newInstance() *PortAllocator {
|
||||
start, end, err := getDynamicPortRange()
|
||||
if err != nil {
|
||||
logrus.Warn(err)
|
||||
start, end = DefaultPortRangeStart, DefaultPortRangeEnd
|
||||
}
|
||||
return &PortAllocator{
|
||||
ipMap: ipMapping{},
|
||||
Begin: start,
|
||||
End: end,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
file, err := os.Open(portRangeKernelParam)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("port allocator - %s due to error: %v", portRangeFallback, err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
return 0, 0, fmt.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err)
|
||||
}
|
||||
return start, end, nil
|
||||
}
|
||||
|
||||
// RequestPort requests new port from global ports pool for specified ip and proto.
|
||||
// If port is 0 it returns first free port. Otherwise it cheks port availability
|
||||
// in pool and return that port or error if port is already busy.
|
||||
func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
if proto != "tcp" && proto != "udp" {
|
||||
return 0, ErrUnknownProtocol
|
||||
}
|
||||
|
||||
if ip == nil {
|
||||
ip = defaultIP
|
||||
}
|
||||
ipstr := ip.String()
|
||||
protomap, ok := p.ipMap[ipstr]
|
||||
if !ok {
|
||||
protomap = protoMap{
|
||||
"tcp": p.newPortMap(),
|
||||
"udp": p.newPortMap(),
|
||||
}
|
||||
|
||||
p.ipMap[ipstr] = protomap
|
||||
}
|
||||
mapping := protomap[proto]
|
||||
if port > 0 {
|
||||
if _, ok := mapping.p[port]; !ok {
|
||||
mapping.p[port] = struct{}{}
|
||||
return port, nil
|
||||
}
|
||||
return 0, newErrPortAlreadyAllocated(ipstr, port)
|
||||
}
|
||||
|
||||
port, err := mapping.findPort()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return port, nil
|
||||
}
|
||||
|
||||
// ReleasePort releases port from global ports pool for specified ip and proto.
|
||||
func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
if ip == nil {
|
||||
ip = defaultIP
|
||||
}
|
||||
protomap, ok := p.ipMap[ip.String()]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
delete(protomap[proto].p, port)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PortAllocator) newPortMap() *portMap {
|
||||
return &portMap{
|
||||
p: map[int]struct{}{},
|
||||
begin: p.Begin,
|
||||
end: p.End,
|
||||
last: p.End,
|
||||
}
|
||||
}
|
||||
|
||||
// ReleaseAll releases all ports for all ips.
|
||||
func (p *PortAllocator) ReleaseAll() error {
|
||||
p.mutex.Lock()
|
||||
p.ipMap = ipMapping{}
|
||||
p.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *portMap) findPort() (int, error) {
|
||||
port := pm.last
|
||||
for i := 0; i <= pm.end-pm.begin; i++ {
|
||||
port++
|
||||
if port > pm.end {
|
||||
port = pm.begin
|
||||
}
|
||||
|
||||
if _, ok := pm.p[port]; !ok {
|
||||
pm.p[port] = struct{}{}
|
||||
pm.last = port
|
||||
return port, nil
|
||||
}
|
||||
}
|
||||
return 0, ErrAllPortsAllocated
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
package portallocator
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func resetPortAllocator() {
|
||||
instance = newInstance()
|
||||
}
|
||||
|
||||
func TestRequestNewPort(t *testing.T) {
|
||||
p := New()
|
||||
defer resetPortAllocator()
|
||||
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := p.Begin; port != expected {
|
||||
t.Fatalf("Expected port %d got %d", expected, port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestSpecificPort(t *testing.T) {
|
||||
p := New()
|
||||
defer resetPortAllocator()
|
||||
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if port != 5000 {
|
||||
t.Fatalf("Expected port 5000 got %d", port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleasePort(t *testing.T) {
|
||||
p := New()
|
||||
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port != 5000 {
|
||||
t.Fatalf("Expected port 5000 got %d", port)
|
||||
}
|
||||
|
||||
if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReuseReleasedPort(t *testing.T) {
|
||||
p := New()
|
||||
defer resetPortAllocator()
|
||||
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port != 5000 {
|
||||
t.Fatalf("Expected port 5000 got %d", port)
|
||||
}
|
||||
|
||||
if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
port, err = p.RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseUnreadledPort(t *testing.T) {
|
||||
p := New()
|
||||
defer resetPortAllocator()
|
||||
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port != 5000 {
|
||||
t.Fatalf("Expected port 5000 got %d", port)
|
||||
}
|
||||
|
||||
port, err = p.RequestPort(defaultIP, "tcp", 5000)
|
||||
|
||||
switch err.(type) {
|
||||
case ErrPortAlreadyAllocated:
|
||||
default:
|
||||
t.Fatalf("Expected port allocation error got %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnknowProtocol(t *testing.T) {
|
||||
if _, err := New().RequestPort(defaultIP, "tcpp", 0); err != ErrUnknownProtocol {
|
||||
t.Fatalf("Expected error %s got %s", ErrUnknownProtocol, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocateAllPorts(t *testing.T) {
|
||||
p := New()
|
||||
defer resetPortAllocator()
|
||||
|
||||
for i := 0; i <= p.End-p.Begin; i++ {
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := p.Begin + i; port != expected {
|
||||
t.Fatalf("Expected port %d got %d", expected, port)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := p.RequestPort(defaultIP, "tcp", 0); err != ErrAllPortsAllocated {
|
||||
t.Fatalf("Expected error %s got %s", ErrAllPortsAllocated, err)
|
||||
}
|
||||
|
||||
_, err := p.RequestPort(defaultIP, "udp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// release a port in the middle and ensure we get another tcp port
|
||||
port := p.Begin + 5
|
||||
if err := p.ReleasePort(defaultIP, "tcp", port); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
newPort, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if newPort != port {
|
||||
t.Fatalf("Expected port %d got %d", port, newPort)
|
||||
}
|
||||
|
||||
// now pm.last == newPort, release it so that it's the only free port of
|
||||
// the range, and ensure we get it back
|
||||
if err := p.ReleasePort(defaultIP, "tcp", newPort); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port, err = p.RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if newPort != port {
|
||||
t.Fatalf("Expected port %d got %d", newPort, port)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAllocatePorts(b *testing.B) {
|
||||
p := New()
|
||||
defer resetPortAllocator()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := 0; i <= p.End-p.Begin; i++ {
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := p.Begin + i; port != expected {
|
||||
b.Fatalf("Expected port %d got %d", expected, port)
|
||||
}
|
||||
}
|
||||
p.ReleaseAll()
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortAllocation(t *testing.T) {
|
||||
p := New()
|
||||
defer resetPortAllocator()
|
||||
|
||||
ip := net.ParseIP("192.168.0.1")
|
||||
ip2 := net.ParseIP("192.168.0.2")
|
||||
if port, err := p.RequestPort(ip, "tcp", 80); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if port != 80 {
|
||||
t.Fatalf("Acquire(80) should return 80, not %d", port)
|
||||
}
|
||||
port, err := p.RequestPort(ip, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port <= 0 {
|
||||
t.Fatalf("Acquire(0) should return a non-zero port")
|
||||
}
|
||||
|
||||
if _, err := p.RequestPort(ip, "tcp", port); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
|
||||
if newPort, err := p.RequestPort(ip, "tcp", 0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if newPort == port {
|
||||
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
|
||||
}
|
||||
|
||||
if _, err := p.RequestPort(ip, "tcp", 80); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
if _, err := p.RequestPort(ip2, "tcp", 80); err != nil {
|
||||
t.Fatalf("It should be possible to allocate the same port on a different interface")
|
||||
}
|
||||
if _, err := p.RequestPort(ip2, "tcp", 80); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
if err := p.ReleasePort(ip, "tcp", 80); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := p.RequestPort(ip, "tcp", 80); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
port, err = p.RequestPort(ip, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port2, err := p.RequestPort(ip, "tcp", port+1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port3, err := p.RequestPort(ip, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port3 == port2 {
|
||||
t.Fatal("Requesting a dynamic port should never allocate a used port")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoDuplicateBPR(t *testing.T) {
|
||||
p := New()
|
||||
defer resetPortAllocator()
|
||||
|
||||
if port, err := p.RequestPort(defaultIP, "tcp", p.Begin); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if port != p.Begin {
|
||||
t.Fatalf("Expected port %d got %d", p.Begin, port)
|
||||
}
|
||||
|
||||
if port, err := p.RequestPort(defaultIP, "tcp", 0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if port == p.Begin {
|
||||
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user