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