portallocator.go 6.1 KB

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