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