github.com/erriapo/docker@v1.6.0-rc2/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 mutex sync.Mutex 54 55 defaultIP = net.ParseIP("0.0.0.0") 56 globalMap = ipMapping{} 57 ) 58 59 type ErrPortAlreadyAllocated struct { 60 ip string 61 port int 62 } 63 64 func NewErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated { 65 return ErrPortAlreadyAllocated{ 66 ip: ip, 67 port: port, 68 } 69 } 70 71 func init() { 72 const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range" 73 portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", beginPortRange, endPortRange) 74 75 file, err := os.Open(portRangeKernelParam) 76 if err != nil { 77 log.Warnf("port allocator - %s due to error: %v", portRangeFallback, err) 78 return 79 } 80 var start, end int 81 n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end) 82 if n != 2 || err != nil { 83 if err == nil { 84 err = fmt.Errorf("unexpected count of parsed numbers (%d)", n) 85 } 86 log.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err) 87 return 88 } 89 beginPortRange = start 90 endPortRange = end 91 } 92 93 func PortRange() (int, int) { 94 return beginPortRange, endPortRange 95 } 96 97 func (e ErrPortAlreadyAllocated) IP() string { 98 return e.ip 99 } 100 101 func (e ErrPortAlreadyAllocated) Port() int { 102 return e.port 103 } 104 105 func (e ErrPortAlreadyAllocated) IPPort() string { 106 return fmt.Sprintf("%s:%d", e.ip, e.port) 107 } 108 109 func (e ErrPortAlreadyAllocated) Error() string { 110 return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port) 111 } 112 113 // RequestPort requests new port from global ports pool for specified ip and proto. 114 // If port is 0 it returns first free port. Otherwise it cheks port availability 115 // in pool and return that port or error if port is already busy. 116 func RequestPort(ip net.IP, proto string, port int) (int, error) { 117 mutex.Lock() 118 defer mutex.Unlock() 119 120 if proto != "tcp" && proto != "udp" { 121 return 0, ErrUnknownProtocol 122 } 123 124 if ip == nil { 125 ip = defaultIP 126 } 127 ipstr := ip.String() 128 protomap, ok := globalMap[ipstr] 129 if !ok { 130 protomap = newProtoMap() 131 globalMap[ipstr] = protomap 132 } 133 mapping := protomap[proto] 134 if port > 0 { 135 if _, ok := mapping.p[port]; !ok { 136 mapping.p[port] = struct{}{} 137 return port, nil 138 } 139 return 0, NewErrPortAlreadyAllocated(ipstr, port) 140 } 141 142 port, err := mapping.findPort() 143 if err != nil { 144 return 0, err 145 } 146 return port, nil 147 } 148 149 // ReleasePort releases port from global ports pool for specified ip and proto. 150 func ReleasePort(ip net.IP, proto string, port int) error { 151 mutex.Lock() 152 defer mutex.Unlock() 153 154 if ip == nil { 155 ip = defaultIP 156 } 157 protomap, ok := globalMap[ip.String()] 158 if !ok { 159 return nil 160 } 161 delete(protomap[proto].p, port) 162 return nil 163 } 164 165 // ReleaseAll releases all ports for all ips. 166 func ReleaseAll() error { 167 mutex.Lock() 168 globalMap = ipMapping{} 169 mutex.Unlock() 170 return nil 171 } 172 173 func (pm *portMap) findPort() (int, error) { 174 port := pm.last 175 for i := 0; i <= endPortRange-beginPortRange; i++ { 176 port++ 177 if port > endPortRange { 178 port = beginPortRange 179 } 180 181 if _, ok := pm.p[port]; !ok { 182 pm.p[port] = struct{}{} 183 pm.last = port 184 return port, nil 185 } 186 } 187 return 0, ErrAllPortsAllocated 188 }