github.com/jjyr/docker@v1.5.0-rc2/daemon/networkdriver/portallocator/portallocator.go (about) 1 package portallocator 2 3 import ( 4 "errors" 5 "fmt" 6 "net" 7 "sync" 8 ) 9 10 type portMap struct { 11 p map[int]struct{} 12 last int 13 } 14 15 func newPortMap() *portMap { 16 return &portMap{ 17 p: map[int]struct{}{}, 18 last: EndPortRange, 19 } 20 } 21 22 type protoMap map[string]*portMap 23 24 func newProtoMap() protoMap { 25 return protoMap{ 26 "tcp": newPortMap(), 27 "udp": newPortMap(), 28 } 29 } 30 31 type ipMapping map[string]protoMap 32 33 const ( 34 BeginPortRange = 49153 35 EndPortRange = 65535 36 ) 37 38 var ( 39 ErrAllPortsAllocated = errors.New("all ports are allocated") 40 ErrUnknownProtocol = errors.New("unknown protocol") 41 ) 42 43 var ( 44 mutex sync.Mutex 45 46 defaultIP = net.ParseIP("0.0.0.0") 47 globalMap = ipMapping{} 48 ) 49 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 func (e ErrPortAlreadyAllocated) IP() string { 63 return e.ip 64 } 65 66 func (e ErrPortAlreadyAllocated) Port() int { 67 return e.port 68 } 69 70 func (e ErrPortAlreadyAllocated) IPPort() string { 71 return fmt.Sprintf("%s:%d", e.ip, e.port) 72 } 73 74 func (e ErrPortAlreadyAllocated) Error() string { 75 return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port) 76 } 77 78 // RequestPort requests new port from global ports pool for specified ip and proto. 79 // If port is 0 it returns first free port. Otherwise it cheks port availability 80 // in pool and return that port or error if port is already busy. 81 func RequestPort(ip net.IP, proto string, port int) (int, error) { 82 mutex.Lock() 83 defer mutex.Unlock() 84 85 if proto != "tcp" && proto != "udp" { 86 return 0, ErrUnknownProtocol 87 } 88 89 if ip == nil { 90 ip = defaultIP 91 } 92 ipstr := ip.String() 93 protomap, ok := globalMap[ipstr] 94 if !ok { 95 protomap = newProtoMap() 96 globalMap[ipstr] = protomap 97 } 98 mapping := protomap[proto] 99 if port > 0 { 100 if _, ok := mapping.p[port]; !ok { 101 mapping.p[port] = struct{}{} 102 return port, nil 103 } 104 return 0, NewErrPortAlreadyAllocated(ipstr, port) 105 } 106 107 port, err := mapping.findPort() 108 if err != nil { 109 return 0, err 110 } 111 return port, nil 112 } 113 114 // ReleasePort releases port from global ports pool for specified ip and proto. 115 func ReleasePort(ip net.IP, proto string, port int) error { 116 mutex.Lock() 117 defer mutex.Unlock() 118 119 if ip == nil { 120 ip = defaultIP 121 } 122 protomap, ok := globalMap[ip.String()] 123 if !ok { 124 return nil 125 } 126 delete(protomap[proto].p, port) 127 return nil 128 } 129 130 // ReleaseAll releases all ports for all ips. 131 func ReleaseAll() error { 132 mutex.Lock() 133 globalMap = ipMapping{} 134 mutex.Unlock() 135 return nil 136 } 137 138 func (pm *portMap) findPort() (int, error) { 139 port := pm.last 140 for i := 0; i <= EndPortRange-BeginPortRange; i++ { 141 port++ 142 if port > EndPortRange { 143 port = BeginPortRange 144 } 145 146 if _, ok := pm.p[port]; !ok { 147 pm.p[port] = struct{}{} 148 pm.last = port 149 return port, nil 150 } 151 } 152 return 0, ErrAllPortsAllocated 153 }