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