github.com/adityamillind98/moby@v23.0.0-rc.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  func newInstance() *PortAllocator {
    92  	start, end, err := getDynamicPortRange()
    93  	if err != nil {
    94  		logrus.WithError(err).Infof("falling back to default port range %d-%d", defaultPortRangeStart, defaultPortRangeEnd)
    95  		start, end = defaultPortRangeStart, defaultPortRangeEnd
    96  	}
    97  	return &PortAllocator{
    98  		ipMap: ipMapping{},
    99  		Begin: start,
   100  		End:   end,
   101  	}
   102  }
   103  
   104  // RequestPort requests new port from global ports pool for specified ip and proto.
   105  // If port is 0 it returns first free port. Otherwise it checks port availability
   106  // in proto's pool and returns that port or error if port is already busy.
   107  func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) {
   108  	return p.RequestPortInRange(ip, proto, port, port)
   109  }
   110  
   111  // RequestPortInRange requests new port from global ports pool for specified ip and proto.
   112  // If portStart and portEnd are 0 it returns the first free port in the default ephemeral range.
   113  // If portStart != portEnd it returns the first free port in the requested range.
   114  // Otherwise (portStart == portEnd) it checks port availability in the requested proto's port-pool
   115  // and returns that port or error if port is already busy.
   116  func (p *PortAllocator) RequestPortInRange(ip net.IP, proto string, portStart, portEnd int) (int, error) {
   117  	p.mutex.Lock()
   118  	defer p.mutex.Unlock()
   119  
   120  	if proto != "tcp" && proto != "udp" && proto != "sctp" {
   121  		return 0, ErrUnknownProtocol
   122  	}
   123  
   124  	if ip == nil {
   125  		ip = defaultIP
   126  	}
   127  	ipstr := ip.String()
   128  	protomap, ok := p.ipMap[ipstr]
   129  	if !ok {
   130  		protomap = protoMap{
   131  			"tcp":  p.newPortMap(),
   132  			"udp":  p.newPortMap(),
   133  			"sctp": p.newPortMap(),
   134  		}
   135  
   136  		p.ipMap[ipstr] = protomap
   137  	}
   138  	mapping := protomap[proto]
   139  	if portStart > 0 && portStart == portEnd {
   140  		if _, ok := mapping.p[portStart]; !ok {
   141  			mapping.p[portStart] = struct{}{}
   142  			return portStart, nil
   143  		}
   144  		return 0, newErrPortAlreadyAllocated(ipstr, portStart)
   145  	}
   146  
   147  	port, err := mapping.findPort(portStart, portEnd)
   148  	if err != nil {
   149  		return 0, err
   150  	}
   151  	return port, nil
   152  }
   153  
   154  // ReleasePort releases port from global ports pool for specified ip and proto.
   155  func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error {
   156  	p.mutex.Lock()
   157  	defer p.mutex.Unlock()
   158  
   159  	if ip == nil {
   160  		ip = defaultIP
   161  	}
   162  	protomap, ok := p.ipMap[ip.String()]
   163  	if !ok {
   164  		return nil
   165  	}
   166  	delete(protomap[proto].p, port)
   167  	return nil
   168  }
   169  
   170  func (p *PortAllocator) newPortMap() *portMap {
   171  	defaultKey := getRangeKey(p.Begin, p.End)
   172  	pm := &portMap{
   173  		p:            map[int]struct{}{},
   174  		defaultRange: defaultKey,
   175  		portRanges: map[string]*portRange{
   176  			defaultKey: newPortRange(p.Begin, p.End),
   177  		},
   178  	}
   179  	return pm
   180  }
   181  
   182  // ReleaseAll releases all ports for all ips.
   183  func (p *PortAllocator) ReleaseAll() error {
   184  	p.mutex.Lock()
   185  	p.ipMap = ipMapping{}
   186  	p.mutex.Unlock()
   187  	return nil
   188  }
   189  
   190  func getRangeKey(portStart, portEnd int) string {
   191  	return fmt.Sprintf("%d-%d", portStart, portEnd)
   192  }
   193  
   194  func newPortRange(portStart, portEnd int) *portRange {
   195  	return &portRange{
   196  		begin: portStart,
   197  		end:   portEnd,
   198  		last:  portEnd,
   199  	}
   200  }
   201  
   202  func (pm *portMap) getPortRange(portStart, portEnd int) (*portRange, error) {
   203  	var key string
   204  	if portStart == 0 && portEnd == 0 {
   205  		key = pm.defaultRange
   206  	} else {
   207  		key = getRangeKey(portStart, portEnd)
   208  		if portStart == portEnd ||
   209  			portStart == 0 || portEnd == 0 ||
   210  			portEnd < portStart {
   211  			return nil, fmt.Errorf("invalid port range: %s", key)
   212  		}
   213  	}
   214  
   215  	// Return existing port range, if already known.
   216  	if pr, exists := pm.portRanges[key]; exists {
   217  		return pr, nil
   218  	}
   219  
   220  	// Otherwise create a new port range.
   221  	pr := newPortRange(portStart, portEnd)
   222  	pm.portRanges[key] = pr
   223  	return pr, nil
   224  }
   225  
   226  func (pm *portMap) findPort(portStart, portEnd int) (int, error) {
   227  	pr, err := pm.getPortRange(portStart, portEnd)
   228  	if err != nil {
   229  		return 0, err
   230  	}
   231  	port := pr.last
   232  
   233  	for i := 0; i <= pr.end-pr.begin; i++ {
   234  		port++
   235  		if port > pr.end {
   236  			port = pr.begin
   237  		}
   238  
   239  		if _, ok := pm.p[port]; !ok {
   240  			pm.p[port] = struct{}{}
   241  			pr.last = port
   242  			return port, nil
   243  		}
   244  	}
   245  	return 0, ErrAllPortsAllocated
   246  }