portallocator.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. package portallocator
  2. import (
  3. "errors"
  4. "fmt"
  5. "net"
  6. "sync"
  7. "github.com/sirupsen/logrus"
  8. )
  9. func sanitizePortRange(start int, end int) (newStart, newEnd int, err error) {
  10. if start > defaultPortRangeEnd || end < defaultPortRangeStart || start > end {
  11. return 0, 0, fmt.Errorf("Request out allowed range [%v, %v]",
  12. defaultPortRangeStart, defaultPortRangeEnd)
  13. }
  14. err = nil
  15. newStart, newEnd = start, end
  16. if start < defaultPortRangeStart {
  17. newStart = defaultPortRangeStart
  18. }
  19. if end > defaultPortRangeEnd {
  20. newEnd = defaultPortRangeEnd
  21. }
  22. return
  23. }
  24. type ipMapping map[string]protoMap
  25. var (
  26. // ErrAllPortsAllocated is returned when no more ports are available
  27. ErrAllPortsAllocated = errors.New("all ports are allocated")
  28. // ErrUnknownProtocol is returned when an unknown protocol was specified
  29. ErrUnknownProtocol = errors.New("unknown protocol")
  30. defaultIP = net.ParseIP("0.0.0.0")
  31. once sync.Once
  32. instance *PortAllocator
  33. )
  34. // ErrPortAlreadyAllocated is the returned error information when a requested port is already being used
  35. type ErrPortAlreadyAllocated struct {
  36. ip string
  37. port int
  38. }
  39. func newErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated {
  40. return ErrPortAlreadyAllocated{
  41. ip: ip,
  42. port: port,
  43. }
  44. }
  45. // IP returns the address to which the used port is associated
  46. func (e ErrPortAlreadyAllocated) IP() string {
  47. return e.ip
  48. }
  49. // Port returns the value of the already used port
  50. func (e ErrPortAlreadyAllocated) Port() int {
  51. return e.port
  52. }
  53. // IPPort returns the address and the port in the form ip:port
  54. func (e ErrPortAlreadyAllocated) IPPort() string {
  55. return fmt.Sprintf("%s:%d", e.ip, e.port)
  56. }
  57. // Error is the implementation of error.Error interface
  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. type (
  62. // PortAllocator manages the transport ports database
  63. PortAllocator struct {
  64. mutex sync.Mutex
  65. ipMap ipMapping
  66. Begin int
  67. End int
  68. }
  69. portRange struct {
  70. begin int
  71. end int
  72. last int
  73. }
  74. portMap struct {
  75. p map[int]struct{}
  76. defaultRange string
  77. portRanges map[string]*portRange
  78. }
  79. protoMap map[string]*portMap
  80. )
  81. // Get returns the default instance of PortAllocator
  82. func Get() *PortAllocator {
  83. // Port Allocator is a singleton
  84. // Note: Long term solution will be each PortAllocator will have access to
  85. // the OS so that it can have up to date view of the OS port allocation.
  86. // When this happens singleton behavior will be removed. Clients do not
  87. // need to worry about this, they will not see a change in behavior.
  88. once.Do(func() {
  89. instance = newInstance()
  90. })
  91. return instance
  92. }
  93. func getDefaultPortRange() (int, int) {
  94. start, end, err := getDynamicPortRange()
  95. if err == nil {
  96. start, end, err = sanitizePortRange(start, end)
  97. }
  98. if err != nil {
  99. logrus.WithError(err).Infof("falling back to default port range %d-%d", defaultPortRangeStart, defaultPortRangeEnd)
  100. start, end = defaultPortRangeStart, defaultPortRangeEnd
  101. }
  102. return start, end
  103. }
  104. func newInstance() *PortAllocator {
  105. start, end := getDefaultPortRange()
  106. return &PortAllocator{
  107. ipMap: ipMapping{},
  108. Begin: start,
  109. End: end,
  110. }
  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" && proto != "sctp" {
  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. "sctp": p.newPortMap(),
  139. }
  140. p.ipMap[ipstr] = protomap
  141. }
  142. mapping := protomap[proto]
  143. if portStart > 0 && portStart == portEnd {
  144. if _, ok := mapping.p[portStart]; !ok {
  145. mapping.p[portStart] = struct{}{}
  146. return portStart, nil
  147. }
  148. return 0, newErrPortAlreadyAllocated(ipstr, portStart)
  149. }
  150. port, err := mapping.findPort(portStart, portEnd)
  151. if err != nil {
  152. return 0, err
  153. }
  154. return port, nil
  155. }
  156. // ReleasePort releases port from global ports pool for specified ip and proto.
  157. func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error {
  158. p.mutex.Lock()
  159. defer p.mutex.Unlock()
  160. if ip == nil {
  161. ip = defaultIP
  162. }
  163. protomap, ok := p.ipMap[ip.String()]
  164. if !ok {
  165. return nil
  166. }
  167. delete(protomap[proto].p, port)
  168. return nil
  169. }
  170. // SetPortRange sets dynamic port allocation range.
  171. // if both portBegin and portEnd are 0, the port range reverts to default
  172. // value. Otherwise they are sanitized against the default values to
  173. // ensure their validity.
  174. func (p *PortAllocator) SetPortRange(portBegin, portEnd int) error {
  175. // if begin and end is zero, revert to default values
  176. var begin, end int
  177. var err error
  178. if portBegin == 0 && portEnd == 0 {
  179. begin, end = getDefaultPortRange()
  180. } else if begin, end, err = sanitizePortRange(portBegin, portEnd); err != nil {
  181. return err
  182. }
  183. logrus.Debugf("Setting up port allocator to range %v-%v, current %v-%v", begin, end, p.Begin, p.End)
  184. p.mutex.Lock()
  185. defer p.mutex.Unlock()
  186. if p.Begin == begin && p.End == end {
  187. return nil
  188. }
  189. p.ipMap = ipMapping{}
  190. p.Begin, p.End = begin, end
  191. return nil
  192. }
  193. func (p *PortAllocator) newPortMap() *portMap {
  194. defaultKey := getRangeKey(p.Begin, p.End)
  195. pm := &portMap{
  196. p: map[int]struct{}{},
  197. defaultRange: defaultKey,
  198. portRanges: map[string]*portRange{
  199. defaultKey: newPortRange(p.Begin, p.End),
  200. },
  201. }
  202. return pm
  203. }
  204. // ReleaseAll releases all ports for all ips.
  205. func (p *PortAllocator) ReleaseAll() error {
  206. p.mutex.Lock()
  207. p.ipMap = ipMapping{}
  208. p.mutex.Unlock()
  209. return nil
  210. }
  211. func getRangeKey(portStart, portEnd int) string {
  212. return fmt.Sprintf("%d-%d", portStart, portEnd)
  213. }
  214. func newPortRange(portStart, portEnd int) *portRange {
  215. return &portRange{
  216. begin: portStart,
  217. end: portEnd,
  218. last: portEnd,
  219. }
  220. }
  221. func (pm *portMap) getPortRange(portStart, portEnd int) (*portRange, error) {
  222. var key string
  223. if portStart == 0 && portEnd == 0 {
  224. key = pm.defaultRange
  225. } else {
  226. key = getRangeKey(portStart, portEnd)
  227. if portStart == portEnd ||
  228. portStart == 0 || portEnd == 0 ||
  229. portEnd < portStart {
  230. return nil, fmt.Errorf("invalid port range: %s", key)
  231. }
  232. }
  233. // Return existing port range, if already known.
  234. if pr, exists := pm.portRanges[key]; exists {
  235. return pr, nil
  236. }
  237. // Otherwise create a new port range.
  238. pr := newPortRange(portStart, portEnd)
  239. pm.portRanges[key] = pr
  240. return pr, nil
  241. }
  242. func (pm *portMap) findPort(portStart, portEnd int) (int, error) {
  243. pr, err := pm.getPortRange(portStart, portEnd)
  244. if err != nil {
  245. return 0, err
  246. }
  247. port := pr.last
  248. for i := 0; i <= pr.end-pr.begin; i++ {
  249. port++
  250. if port > pr.end {
  251. port = pr.begin
  252. }
  253. if _, ok := pm.p[port]; !ok {
  254. pm.p[port] = struct{}{}
  255. pr.last = port
  256. return port, nil
  257. }
  258. }
  259. return 0, ErrAllPortsAllocated
  260. }