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