github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/libnetwork/portallocator/portallocator.go (about)

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