portallocator.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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 = 49153
  13. DefaultPortRangeEnd = 65535
  14. )
  15. type ipMapping map[string]protoMap
  16. var (
  17. ErrAllPortsAllocated = errors.New("all ports are allocated")
  18. ErrUnknownProtocol = errors.New("unknown protocol")
  19. defaultIP = net.ParseIP("0.0.0.0")
  20. )
  21. type ErrPortAlreadyAllocated struct {
  22. ip string
  23. port int
  24. }
  25. func NewErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated {
  26. return ErrPortAlreadyAllocated{
  27. ip: ip,
  28. port: port,
  29. }
  30. }
  31. func (e ErrPortAlreadyAllocated) IP() string {
  32. return e.ip
  33. }
  34. func (e ErrPortAlreadyAllocated) Port() int {
  35. return e.port
  36. }
  37. func (e ErrPortAlreadyAllocated) IPPort() string {
  38. return fmt.Sprintf("%s:%d", e.ip, e.port)
  39. }
  40. func (e ErrPortAlreadyAllocated) Error() string {
  41. return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port)
  42. }
  43. type (
  44. PortAllocator struct {
  45. mutex sync.Mutex
  46. ipMap ipMapping
  47. Begin int
  48. End int
  49. }
  50. portMap struct {
  51. p map[int]struct{}
  52. begin, end int
  53. last int
  54. }
  55. protoMap map[string]*portMap
  56. )
  57. func New() *PortAllocator {
  58. start, end, err := getDynamicPortRange()
  59. if err != nil {
  60. logrus.Warn(err)
  61. start, end = DefaultPortRangeStart, DefaultPortRangeEnd
  62. }
  63. return &PortAllocator{
  64. ipMap: ipMapping{},
  65. Begin: start,
  66. End: end,
  67. }
  68. }
  69. func getDynamicPortRange() (start int, end int, err error) {
  70. const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range"
  71. portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd)
  72. file, err := os.Open(portRangeKernelParam)
  73. if err != nil {
  74. return 0, 0, fmt.Errorf("port allocator - %s due to error: %v", portRangeFallback, err)
  75. }
  76. n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end)
  77. if n != 2 || err != nil {
  78. if err == nil {
  79. err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
  80. }
  81. return 0, 0, fmt.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err)
  82. }
  83. return start, end, nil
  84. }
  85. // RequestPort requests new port from global ports pool for specified ip and proto.
  86. // If port is 0 it returns first free port. Otherwise it cheks port availability
  87. // in pool and return that port or error if port is already busy.
  88. func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) {
  89. p.mutex.Lock()
  90. defer p.mutex.Unlock()
  91. if proto != "tcp" && proto != "udp" {
  92. return 0, ErrUnknownProtocol
  93. }
  94. if ip == nil {
  95. ip = defaultIP
  96. }
  97. ipstr := ip.String()
  98. protomap, ok := p.ipMap[ipstr]
  99. if !ok {
  100. protomap = protoMap{
  101. "tcp": p.newPortMap(),
  102. "udp": p.newPortMap(),
  103. }
  104. p.ipMap[ipstr] = protomap
  105. }
  106. mapping := protomap[proto]
  107. if port > 0 {
  108. if _, ok := mapping.p[port]; !ok {
  109. mapping.p[port] = struct{}{}
  110. return port, nil
  111. }
  112. return 0, NewErrPortAlreadyAllocated(ipstr, port)
  113. }
  114. port, err := mapping.findPort()
  115. if err != nil {
  116. return 0, err
  117. }
  118. return port, nil
  119. }
  120. // ReleasePort releases port from global ports pool for specified ip and proto.
  121. func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error {
  122. p.mutex.Lock()
  123. defer p.mutex.Unlock()
  124. if ip == nil {
  125. ip = defaultIP
  126. }
  127. protomap, ok := p.ipMap[ip.String()]
  128. if !ok {
  129. return nil
  130. }
  131. delete(protomap[proto].p, port)
  132. return nil
  133. }
  134. func (p *PortAllocator) newPortMap() *portMap {
  135. return &portMap{
  136. p: map[int]struct{}{},
  137. begin: p.Begin,
  138. end: p.End,
  139. last: p.End,
  140. }
  141. }
  142. // ReleaseAll releases all ports for all ips.
  143. func (p *PortAllocator) ReleaseAll() error {
  144. p.mutex.Lock()
  145. p.ipMap = ipMapping{}
  146. p.mutex.Unlock()
  147. return nil
  148. }
  149. func (pm *portMap) findPort() (int, error) {
  150. port := pm.last
  151. for i := 0; i <= pm.end-pm.begin; i++ {
  152. port++
  153. if port > pm.end {
  154. port = pm.begin
  155. }
  156. if _, ok := pm.p[port]; !ok {
  157. pm.p[port] = struct{}{}
  158. pm.last = port
  159. return port, nil
  160. }
  161. }
  162. return 0, ErrAllPortsAllocated
  163. }