github.com/docker/engine@v22.0.0-20211208180946-d456264580cf+incompatible/libnetwork/portallocator/portallocator.go (about)

     1  package portallocator
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  	"sync"
     8  
     9  	"github.com/sirupsen/logrus"
    10  )
    11  
    12  func sanitizePortRange(start int, end int) (newStart, newEnd int, err error) {
    13  	if start > defaultPortRangeEnd || end < defaultPortRangeStart || start > end {
    14  		return 0, 0, fmt.Errorf("Request out allowed range [%v, %v]",
    15  			defaultPortRangeStart, defaultPortRangeEnd)
    16  	}
    17  	err = nil
    18  	newStart, newEnd = start, end
    19  	if start < defaultPortRangeStart {
    20  		newStart = defaultPortRangeStart
    21  	}
    22  	if end > defaultPortRangeEnd {
    23  		newEnd = defaultPortRangeEnd
    24  	}
    25  	return
    26  }
    27  
    28  type ipMapping map[string]protoMap
    29  
    30  var (
    31  	// ErrAllPortsAllocated is returned when no more ports are available
    32  	ErrAllPortsAllocated = errors.New("all ports are allocated")
    33  	// ErrUnknownProtocol is returned when an unknown protocol was specified
    34  	ErrUnknownProtocol = errors.New("unknown protocol")
    35  	defaultIP          = net.ParseIP("0.0.0.0")
    36  	once               sync.Once
    37  	instance           *PortAllocator
    38  )
    39  
    40  // ErrPortAlreadyAllocated is the returned error information when a requested port is already being used
    41  type ErrPortAlreadyAllocated struct {
    42  	ip   string
    43  	port int
    44  }
    45  
    46  func newErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated {
    47  	return ErrPortAlreadyAllocated{
    48  		ip:   ip,
    49  		port: port,
    50  	}
    51  }
    52  
    53  // IP returns the address to which the used port is associated
    54  func (e ErrPortAlreadyAllocated) IP() string {
    55  	return e.ip
    56  }
    57  
    58  // Port returns the value of the already used port
    59  func (e ErrPortAlreadyAllocated) Port() int {
    60  	return e.port
    61  }
    62  
    63  // IPPort returns the address and the port in the form ip:port
    64  func (e ErrPortAlreadyAllocated) IPPort() string {
    65  	return fmt.Sprintf("%s:%d", e.ip, e.port)
    66  }
    67  
    68  // Error is the implementation of error.Error interface
    69  func (e ErrPortAlreadyAllocated) Error() string {
    70  	return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port)
    71  }
    72  
    73  type (
    74  	// PortAllocator manages the transport ports database
    75  	PortAllocator struct {
    76  		mutex sync.Mutex
    77  		ipMap ipMapping
    78  		Begin int
    79  		End   int
    80  	}
    81  	portRange struct {
    82  		begin int
    83  		end   int
    84  		last  int
    85  	}
    86  	portMap struct {
    87  		p            map[int]struct{}
    88  		defaultRange string
    89  		portRanges   map[string]*portRange
    90  	}
    91  	protoMap map[string]*portMap
    92  )
    93  
    94  // Get returns the default instance of PortAllocator
    95  func Get() *PortAllocator {
    96  	// Port Allocator is a singleton
    97  	// Note: Long term solution will be each PortAllocator will have access to
    98  	// the OS so that it can have up to date view of the OS port allocation.
    99  	// When this happens singleton behavior will be removed. Clients do not
   100  	// need to worry about this, they will not see a change in behavior.
   101  	once.Do(func() {
   102  		instance = newInstance()
   103  	})
   104  	return instance
   105  }
   106  
   107  func getDefaultPortRange() (int, int) {
   108  	start, end, err := getDynamicPortRange()
   109  	if err == nil {
   110  		start, end, err = sanitizePortRange(start, end)
   111  	}
   112  	if err != nil {
   113  		logrus.WithError(err).Infof("falling back to default port range %d-%d", defaultPortRangeStart, defaultPortRangeEnd)
   114  		start, end = defaultPortRangeStart, defaultPortRangeEnd
   115  	}
   116  	return start, end
   117  }
   118  
   119  func newInstance() *PortAllocator {
   120  	start, end := getDefaultPortRange()
   121  	return &PortAllocator{
   122  		ipMap: ipMapping{},
   123  		Begin: start,
   124  		End:   end,
   125  	}
   126  }
   127  
   128  // RequestPort requests new port from global ports pool for specified ip and proto.
   129  // If port is 0 it returns first free port. Otherwise it checks port availability
   130  // in proto's pool and returns that port or error if port is already busy.
   131  func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) {
   132  	return p.RequestPortInRange(ip, proto, port, port)
   133  }
   134  
   135  // RequestPortInRange requests new port from global ports pool for specified ip and proto.
   136  // If portStart and portEnd are 0 it returns the first free port in the default ephemeral range.
   137  // If portStart != portEnd it returns the first free port in the requested range.
   138  // Otherwise (portStart == portEnd) it checks port availability in the requested proto's port-pool
   139  // and returns that port or error if port is already busy.
   140  func (p *PortAllocator) RequestPortInRange(ip net.IP, proto string, portStart, portEnd int) (int, error) {
   141  	p.mutex.Lock()
   142  	defer p.mutex.Unlock()
   143  
   144  	if proto != "tcp" && proto != "udp" && proto != "sctp" {
   145  		return 0, ErrUnknownProtocol
   146  	}
   147  
   148  	if ip == nil {
   149  		ip = defaultIP
   150  	}
   151  	ipstr := ip.String()
   152  	protomap, ok := p.ipMap[ipstr]
   153  	if !ok {
   154  		protomap = protoMap{
   155  			"tcp":  p.newPortMap(),
   156  			"udp":  p.newPortMap(),
   157  			"sctp": p.newPortMap(),
   158  		}
   159  
   160  		p.ipMap[ipstr] = protomap
   161  	}
   162  	mapping := protomap[proto]
   163  	if portStart > 0 && portStart == portEnd {
   164  		if _, ok := mapping.p[portStart]; !ok {
   165  			mapping.p[portStart] = struct{}{}
   166  			return portStart, nil
   167  		}
   168  		return 0, newErrPortAlreadyAllocated(ipstr, portStart)
   169  	}
   170  
   171  	port, err := mapping.findPort(portStart, portEnd)
   172  	if err != nil {
   173  		return 0, err
   174  	}
   175  	return port, nil
   176  }
   177  
   178  // ReleasePort releases port from global ports pool for specified ip and proto.
   179  func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error {
   180  	p.mutex.Lock()
   181  	defer p.mutex.Unlock()
   182  
   183  	if ip == nil {
   184  		ip = defaultIP
   185  	}
   186  	protomap, ok := p.ipMap[ip.String()]
   187  	if !ok {
   188  		return nil
   189  	}
   190  	delete(protomap[proto].p, port)
   191  	return nil
   192  }
   193  
   194  // SetPortRange sets dynamic port allocation range.
   195  // if both portBegin and portEnd are 0, the port range reverts to default
   196  // value. Otherwise they are sanitized against the default values to
   197  // ensure their validity.
   198  func (p *PortAllocator) SetPortRange(portBegin, portEnd int) error {
   199  	// if begin and end is zero, revert to default values
   200  	var begin, end int
   201  	var err error
   202  	if portBegin == 0 && portEnd == 0 {
   203  		begin, end = getDefaultPortRange()
   204  	} else if begin, end, err = sanitizePortRange(portBegin, portEnd); err != nil {
   205  		return err
   206  	}
   207  	logrus.Debugf("Setting up port allocator to range %v-%v, current %v-%v", begin, end, p.Begin, p.End)
   208  	p.mutex.Lock()
   209  	defer p.mutex.Unlock()
   210  	if p.Begin == begin && p.End == end {
   211  		return nil
   212  	}
   213  	p.ipMap = ipMapping{}
   214  	p.Begin, p.End = begin, end
   215  	return nil
   216  }
   217  
   218  func (p *PortAllocator) newPortMap() *portMap {
   219  	defaultKey := getRangeKey(p.Begin, p.End)
   220  	pm := &portMap{
   221  		p:            map[int]struct{}{},
   222  		defaultRange: defaultKey,
   223  		portRanges: map[string]*portRange{
   224  			defaultKey: newPortRange(p.Begin, p.End),
   225  		},
   226  	}
   227  	return pm
   228  }
   229  
   230  // ReleaseAll releases all ports for all ips.
   231  func (p *PortAllocator) ReleaseAll() error {
   232  	p.mutex.Lock()
   233  	p.ipMap = ipMapping{}
   234  	p.mutex.Unlock()
   235  	return nil
   236  }
   237  
   238  func getRangeKey(portStart, portEnd int) string {
   239  	return fmt.Sprintf("%d-%d", portStart, portEnd)
   240  }
   241  
   242  func newPortRange(portStart, portEnd int) *portRange {
   243  	return &portRange{
   244  		begin: portStart,
   245  		end:   portEnd,
   246  		last:  portEnd,
   247  	}
   248  }
   249  
   250  func (pm *portMap) getPortRange(portStart, portEnd int) (*portRange, error) {
   251  	var key string
   252  	if portStart == 0 && portEnd == 0 {
   253  		key = pm.defaultRange
   254  	} else {
   255  		key = getRangeKey(portStart, portEnd)
   256  		if portStart == portEnd ||
   257  			portStart == 0 || portEnd == 0 ||
   258  			portEnd < portStart {
   259  			return nil, fmt.Errorf("invalid port range: %s", key)
   260  		}
   261  	}
   262  
   263  	// Return existing port range, if already known.
   264  	if pr, exists := pm.portRanges[key]; exists {
   265  		return pr, nil
   266  	}
   267  
   268  	// Otherwise create a new port range.
   269  	pr := newPortRange(portStart, portEnd)
   270  	pm.portRanges[key] = pr
   271  	return pr, nil
   272  }
   273  
   274  func (pm *portMap) findPort(portStart, portEnd int) (int, error) {
   275  	pr, err := pm.getPortRange(portStart, portEnd)
   276  	if err != nil {
   277  		return 0, err
   278  	}
   279  	port := pr.last
   280  
   281  	for i := 0; i <= pr.end-pr.begin; i++ {
   282  		port++
   283  		if port > pr.end {
   284  			port = pr.begin
   285  		}
   286  
   287  		if _, ok := pm.p[port]; !ok {
   288  			pm.p[port] = struct{}{}
   289  			pr.last = port
   290  			return port, nil
   291  		}
   292  	}
   293  	return 0, ErrAllPortsAllocated
   294  }