github.com/leoh0/docker@v1.6.0-rc4/daemon/networkdriver/portallocator/portallocator.go (about)

     1  package portallocator
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  	"os"
     9  	"sync"
    10  
    11  	log "github.com/Sirupsen/logrus"
    12  )
    13  
    14  const (
    15  	DefaultPortRangeStart = 49153
    16  	DefaultPortRangeEnd   = 65535
    17  )
    18  
    19  type ipMapping map[string]protoMap
    20  
    21  var (
    22  	ErrAllPortsAllocated = errors.New("all ports are allocated")
    23  	ErrUnknownProtocol   = errors.New("unknown protocol")
    24  	defaultIP            = net.ParseIP("0.0.0.0")
    25  )
    26  
    27  type ErrPortAlreadyAllocated struct {
    28  	ip   string
    29  	port int
    30  }
    31  
    32  func NewErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated {
    33  	return ErrPortAlreadyAllocated{
    34  		ip:   ip,
    35  		port: port,
    36  	}
    37  }
    38  
    39  func (e ErrPortAlreadyAllocated) IP() string {
    40  	return e.ip
    41  }
    42  
    43  func (e ErrPortAlreadyAllocated) Port() int {
    44  	return e.port
    45  }
    46  
    47  func (e ErrPortAlreadyAllocated) IPPort() string {
    48  	return fmt.Sprintf("%s:%d", e.ip, e.port)
    49  }
    50  
    51  func (e ErrPortAlreadyAllocated) Error() string {
    52  	return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port)
    53  }
    54  
    55  type (
    56  	PortAllocator struct {
    57  		mutex sync.Mutex
    58  		ipMap ipMapping
    59  		Begin int
    60  		End   int
    61  	}
    62  	portMap struct {
    63  		p          map[int]struct{}
    64  		begin, end int
    65  		last       int
    66  	}
    67  	protoMap map[string]*portMap
    68  )
    69  
    70  func New() *PortAllocator {
    71  	start, end, err := getDynamicPortRange()
    72  	if err != nil {
    73  		log.Warn(err)
    74  		start, end = DefaultPortRangeStart, DefaultPortRangeEnd
    75  	}
    76  	return &PortAllocator{
    77  		ipMap: ipMapping{},
    78  		Begin: start,
    79  		End:   end,
    80  	}
    81  }
    82  
    83  func getDynamicPortRange() (start int, end int, err error) {
    84  	const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range"
    85  	portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd)
    86  	file, err := os.Open(portRangeKernelParam)
    87  	if err != nil {
    88  		return 0, 0, fmt.Errorf("port allocator - %s due to error: %v", portRangeFallback, err)
    89  	}
    90  	n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end)
    91  	if n != 2 || err != nil {
    92  		if err == nil {
    93  			err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
    94  		}
    95  		return 0, 0, fmt.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err)
    96  	}
    97  	return start, end, nil
    98  }
    99  
   100  // RequestPort requests new port from global ports pool for specified ip and proto.
   101  // If port is 0 it returns first free port. Otherwise it cheks port availability
   102  // in pool and return that port or error if port is already busy.
   103  func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) {
   104  	p.mutex.Lock()
   105  	defer p.mutex.Unlock()
   106  
   107  	if proto != "tcp" && proto != "udp" {
   108  		return 0, ErrUnknownProtocol
   109  	}
   110  
   111  	if ip == nil {
   112  		ip = defaultIP
   113  	}
   114  	ipstr := ip.String()
   115  	protomap, ok := p.ipMap[ipstr]
   116  	if !ok {
   117  		protomap = protoMap{
   118  			"tcp": p.newPortMap(),
   119  			"udp": p.newPortMap(),
   120  		}
   121  
   122  		p.ipMap[ipstr] = protomap
   123  	}
   124  	mapping := protomap[proto]
   125  	if port > 0 {
   126  		if _, ok := mapping.p[port]; !ok {
   127  			mapping.p[port] = struct{}{}
   128  			return port, nil
   129  		}
   130  		return 0, NewErrPortAlreadyAllocated(ipstr, port)
   131  	}
   132  
   133  	port, err := mapping.findPort()
   134  	if err != nil {
   135  		return 0, err
   136  	}
   137  	return port, nil
   138  }
   139  
   140  // ReleasePort releases port from global ports pool for specified ip and proto.
   141  func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error {
   142  	p.mutex.Lock()
   143  	defer p.mutex.Unlock()
   144  
   145  	if ip == nil {
   146  		ip = defaultIP
   147  	}
   148  	protomap, ok := p.ipMap[ip.String()]
   149  	if !ok {
   150  		return nil
   151  	}
   152  	delete(protomap[proto].p, port)
   153  	return nil
   154  }
   155  
   156  func (p *PortAllocator) newPortMap() *portMap {
   157  	return &portMap{
   158  		p:     map[int]struct{}{},
   159  		begin: p.Begin,
   160  		end:   p.End,
   161  		last:  p.End,
   162  	}
   163  }
   164  
   165  // ReleaseAll releases all ports for all ips.
   166  func (p *PortAllocator) ReleaseAll() error {
   167  	p.mutex.Lock()
   168  	p.ipMap = ipMapping{}
   169  	p.mutex.Unlock()
   170  	return nil
   171  }
   172  
   173  func (pm *portMap) findPort() (int, error) {
   174  	port := pm.last
   175  	for i := 0; i <= pm.end-pm.begin; i++ {
   176  		port++
   177  		if port > pm.end {
   178  			port = pm.begin
   179  		}
   180  
   181  		if _, ok := pm.p[port]; !ok {
   182  			pm.p[port] = struct{}{}
   183  			pm.last = port
   184  			return port, nil
   185  		}
   186  	}
   187  	return 0, ErrAllPortsAllocated
   188  }