github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/libnetwork/portallocator/portallocator.go (about)

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