portallocator.go 5.2 KB

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