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