github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/libnetwork/portallocator/portallocator.go (about)

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