github.com/sirupsen/docker@v0.10.1-0.20150325003727-22dba32b4dab/daemon/networkdriver/portallocator/portallocator.go (about) 1 package portallocator 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "net" 8 "os" 9 "sync" 10 11 log "github.com/Sirupsen/logrus" 12 ) 13 14 const ( 15 DefaultPortRangeStart = 49153 16 DefaultPortRangeEnd = 65535 17 ) 18 19 var ( 20 beginPortRange = DefaultPortRangeStart 21 endPortRange = DefaultPortRangeEnd 22 ) 23 24 type portMap struct { 25 p map[int]struct{} 26 last int 27 } 28 29 func newPortMap() *portMap { 30 return &portMap{ 31 p: map[int]struct{}{}, 32 last: endPortRange, 33 } 34 } 35 36 type protoMap map[string]*portMap 37 38 func newProtoMap() protoMap { 39 return protoMap{ 40 "tcp": newPortMap(), 41 "udp": newPortMap(), 42 } 43 } 44 45 type ipMapping map[string]protoMap 46 47 var ( 48 ErrAllPortsAllocated = errors.New("all ports are allocated") 49 ErrUnknownProtocol = errors.New("unknown protocol") 50 ) 51 52 var ( 53 defaultIP = net.ParseIP("0.0.0.0") 54 55 DefaultPortAllocator = New() 56 RequestPort = DefaultPortAllocator.RequestPort 57 ReleasePort = DefaultPortAllocator.ReleasePort 58 ReleaseAll = DefaultPortAllocator.ReleaseAll 59 ) 60 61 type PortAllocator struct { 62 mutex sync.Mutex 63 ipMap ipMapping 64 } 65 66 func New() *PortAllocator { 67 return &PortAllocator{ 68 ipMap: ipMapping{}, 69 } 70 } 71 72 type ErrPortAlreadyAllocated struct { 73 ip string 74 port int 75 } 76 77 func NewErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated { 78 return ErrPortAlreadyAllocated{ 79 ip: ip, 80 port: port, 81 } 82 } 83 84 func init() { 85 const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range" 86 portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", beginPortRange, endPortRange) 87 88 file, err := os.Open(portRangeKernelParam) 89 if err != nil { 90 log.Warnf("port allocator - %s due to error: %v", portRangeFallback, err) 91 return 92 } 93 var start, end int 94 n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end) 95 if n != 2 || err != nil { 96 if err == nil { 97 err = fmt.Errorf("unexpected count of parsed numbers (%d)", n) 98 } 99 log.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err) 100 return 101 } 102 beginPortRange = start 103 endPortRange = end 104 } 105 106 func PortRange() (int, int) { 107 return beginPortRange, endPortRange 108 } 109 110 func (e ErrPortAlreadyAllocated) IP() string { 111 return e.ip 112 } 113 114 func (e ErrPortAlreadyAllocated) Port() int { 115 return e.port 116 } 117 118 func (e ErrPortAlreadyAllocated) IPPort() string { 119 return fmt.Sprintf("%s:%d", e.ip, e.port) 120 } 121 122 func (e ErrPortAlreadyAllocated) Error() string { 123 return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port) 124 } 125 126 // RequestPort requests new port from global ports pool for specified ip and proto. 127 // If port is 0 it returns first free port. Otherwise it cheks port availability 128 // in pool and return that port or error if port is already busy. 129 func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) { 130 p.mutex.Lock() 131 defer p.mutex.Unlock() 132 133 if proto != "tcp" && proto != "udp" { 134 return 0, ErrUnknownProtocol 135 } 136 137 if ip == nil { 138 ip = defaultIP 139 } 140 ipstr := ip.String() 141 protomap, ok := p.ipMap[ipstr] 142 if !ok { 143 protomap = newProtoMap() 144 p.ipMap[ipstr] = protomap 145 } 146 mapping := protomap[proto] 147 if port > 0 { 148 if _, ok := mapping.p[port]; !ok { 149 mapping.p[port] = struct{}{} 150 return port, nil 151 } 152 return 0, NewErrPortAlreadyAllocated(ipstr, port) 153 } 154 155 port, err := mapping.findPort() 156 if err != nil { 157 return 0, err 158 } 159 return port, nil 160 } 161 162 // ReleasePort releases port from global ports pool for specified ip and proto. 163 func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error { 164 p.mutex.Lock() 165 defer p.mutex.Unlock() 166 167 if ip == nil { 168 ip = defaultIP 169 } 170 protomap, ok := p.ipMap[ip.String()] 171 if !ok { 172 return nil 173 } 174 delete(protomap[proto].p, port) 175 return nil 176 } 177 178 // ReleaseAll releases all ports for all ips. 179 func (p *PortAllocator) ReleaseAll() error { 180 p.mutex.Lock() 181 p.ipMap = ipMapping{} 182 p.mutex.Unlock() 183 return nil 184 } 185 186 func (pm *portMap) findPort() (int, error) { 187 port := pm.last 188 for i := 0; i <= endPortRange-beginPortRange; i++ { 189 port++ 190 if port > endPortRange { 191 port = beginPortRange 192 } 193 194 if _, ok := pm.p[port]; !ok { 195 pm.p[port] = struct{}{} 196 pm.last = port 197 return port, nil 198 } 199 } 200 return 0, ErrAllPortsAllocated 201 }