github.com/grange74/docker@v1.6.0-rc3/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 }