portallocator.go 6.9 KB

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