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