portallocator.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. package portallocator
  2. import (
  3. "errors"
  4. "fmt"
  5. "net"
  6. "sync"
  7. )
  8. type portMap struct {
  9. p map[int]struct{}
  10. last int
  11. }
  12. func newPortMap() *portMap {
  13. return &portMap{
  14. p: map[int]struct{}{},
  15. last: EndPortRange,
  16. }
  17. }
  18. type protoMap map[string]*portMap
  19. func newProtoMap() protoMap {
  20. return protoMap{
  21. "tcp": newPortMap(),
  22. "udp": newPortMap(),
  23. }
  24. }
  25. type ipMapping map[string]protoMap
  26. const (
  27. BeginPortRange = 49153
  28. EndPortRange = 65535
  29. )
  30. var (
  31. ErrAllPortsAllocated = errors.New("all ports are allocated")
  32. ErrUnknownProtocol = errors.New("unknown protocol")
  33. )
  34. var (
  35. mutex sync.Mutex
  36. defaultIP = net.ParseIP("0.0.0.0")
  37. globalMap = ipMapping{}
  38. )
  39. type ErrPortAlreadyAllocated struct {
  40. ip string
  41. port int
  42. }
  43. func NewErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated {
  44. return ErrPortAlreadyAllocated{
  45. ip: ip,
  46. port: port,
  47. }
  48. }
  49. func (e ErrPortAlreadyAllocated) IP() string {
  50. return e.ip
  51. }
  52. func (e ErrPortAlreadyAllocated) Port() int {
  53. return e.port
  54. }
  55. func (e ErrPortAlreadyAllocated) IPPort() string {
  56. return fmt.Sprintf("%s:%d", e.ip, e.port)
  57. }
  58. func (e ErrPortAlreadyAllocated) Error() string {
  59. return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port)
  60. }
  61. // RequestPort requests new port from global ports pool for specified ip and proto.
  62. // If port is 0 it returns first free port. Otherwise it cheks port availability
  63. // in pool and return that port or error if port is already busy.
  64. func RequestPort(ip net.IP, proto string, port int) (int, error) {
  65. mutex.Lock()
  66. defer mutex.Unlock()
  67. if proto != "tcp" && proto != "udp" {
  68. return 0, ErrUnknownProtocol
  69. }
  70. if ip == nil {
  71. ip = defaultIP
  72. }
  73. ipstr := ip.String()
  74. protomap, ok := globalMap[ipstr]
  75. if !ok {
  76. protomap = newProtoMap()
  77. globalMap[ipstr] = protomap
  78. }
  79. mapping := protomap[proto]
  80. if port > 0 {
  81. if _, ok := mapping.p[port]; !ok {
  82. mapping.p[port] = struct{}{}
  83. return port, nil
  84. }
  85. return 0, NewErrPortAlreadyAllocated(ipstr, port)
  86. }
  87. port, err := mapping.findPort()
  88. if err != nil {
  89. return 0, err
  90. }
  91. return port, nil
  92. }
  93. // ReleasePort releases port from global ports pool for specified ip and proto.
  94. func ReleasePort(ip net.IP, proto string, port int) error {
  95. mutex.Lock()
  96. defer mutex.Unlock()
  97. if ip == nil {
  98. ip = defaultIP
  99. }
  100. protomap, ok := globalMap[ip.String()]
  101. if !ok {
  102. return nil
  103. }
  104. delete(protomap[proto].p, port)
  105. return nil
  106. }
  107. // ReleaseAll releases all ports for all ips.
  108. func ReleaseAll() error {
  109. mutex.Lock()
  110. globalMap = ipMapping{}
  111. mutex.Unlock()
  112. return nil
  113. }
  114. func (pm *portMap) findPort() (int, error) {
  115. for port := pm.last + 1; port != pm.last; port++ {
  116. if port > EndPortRange {
  117. port = BeginPortRange
  118. }
  119. if _, ok := pm.p[port]; !ok {
  120. pm.p[port] = struct{}{}
  121. pm.last = port
  122. return port, nil
  123. }
  124. }
  125. return 0, ErrAllPortsAllocated
  126. }