getport.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package getport
  15. import (
  16. "fmt"
  17. "math/rand"
  18. "net"
  19. "strconv"
  20. "strings"
  21. "yunion.io/x/pkg/errors"
  22. "yunion.io/x/pkg/util/sets"
  23. )
  24. // REF: https://github.com/jsumners/go-getport/blob/master/getport.go
  25. // Protocol indicates the communication protocol (tcp or udp) and network
  26. // stack (IPv4, IPv6, or OS choice) to target when finding an available port.
  27. type Protocol int
  28. const (
  29. // TCP indicates to let the OS decide between IPv4 and IPv6 when finding
  30. // an open TCP based port.
  31. TCP Protocol = iota
  32. // TCP4 indicates to find an open IPv4 port.
  33. TCP4
  34. // TCP6 indicates to find an open IPv6 port.
  35. TCP6
  36. // UDP indicates to let the OS decide between IPv4 and IPv6 when finding
  37. // an open UDP based port.
  38. UDP
  39. // UDP4 indicates to find an open IPv4 port.
  40. UDP4
  41. // UDP6 indicates to find an open IPv6 port.
  42. UDP6
  43. )
  44. // PortResult represents the result of [GetPort]. It indicates the IP address
  45. // and port number combination that resulted in finding an open port.
  46. type PortResult struct {
  47. // IP is either an IPv4 or IPv6 string as returned by [net.SplitHostPort].
  48. IP string
  49. // Port is the determined available port number.
  50. Port int
  51. }
  52. // GetPort finds an open port for a given [Protocol] and address and returns
  53. // that port number. If the [Protocol] is not recognized, or some problem is
  54. // encountered while verifying the port, then the returned [PortResult.Port]
  55. // number will be `-1` along with an error. The address parameter should be a
  56. // simple IP address string, e.g. `127.0.0.1` or `::1`. The [PortResult.IP] will
  57. // be set to the IP address that was actually used to find the open port. If
  58. // address is the empty string (`""`), then the returned IP address will be the
  59. // one determined by the OS when finding the port.
  60. //
  61. // Note: it is not guaranteed the port will remain open long enough to actually
  62. // be used. Errors should still be checked when attempting to use the found
  63. // port.
  64. func GetPort(protocol Protocol, address string) (PortResult, error) {
  65. return getPort(protocol, address, 0)
  66. }
  67. func getPort(protocol Protocol, address string, port int) (PortResult, error) {
  68. stack := resolveProtocol(protocol)
  69. result := PortResult{
  70. IP: "",
  71. Port: -1,
  72. }
  73. resolvedAddress, listenError := listen(
  74. stack,
  75. net.JoinHostPort(address, fmt.Sprintf("%d", port)),
  76. )
  77. if listenError != nil {
  78. return result, listenError
  79. }
  80. // I do not see how it's possible to get an error from [net.SplitHostPort]
  81. // here given how we have already validated the stack and successfully
  82. // issued a [net.Listen].
  83. addr, portStr, _ := net.SplitHostPort(resolvedAddress.String())
  84. hPort, _ := strconv.Atoi(portStr)
  85. result.IP = addr
  86. result.Port = hPort
  87. return result, nil
  88. }
  89. func IsPortUsed(protocol Protocol, address string, port int) bool {
  90. _, err := getPort(protocol, address, port)
  91. if err != nil {
  92. return true
  93. }
  94. return false
  95. }
  96. func GetPortByRange(proto Protocol, start int, end int) (PortResult, error) {
  97. return GetPortByRangeBySets(proto, start, end, sets.NewInt())
  98. }
  99. func GetPortByRangeBySets(proto Protocol, start int, end int, usedPorts sets.Int) (PortResult, error) {
  100. errs := []error{}
  101. for i := start; i <= end; i++ {
  102. rPort := rand.Intn(end-start) + start
  103. if usedPorts.Has(rPort) {
  104. continue
  105. }
  106. result, err := getPort(proto, "", rPort)
  107. if err != nil {
  108. usedPorts.Insert(rPort)
  109. errs = append(errs, errors.Wrapf(err, "check random port: %d", rPort))
  110. } else {
  111. return result, nil
  112. }
  113. }
  114. return PortResult{
  115. IP: "",
  116. Port: -1,
  117. }, errors.Wrapf(errors.NewAggregate(errs), "can't get free port in [%d, %d]", start, end)
  118. }
  119. // GetTcpPort gets a port for some random available address using either
  120. // TCP4 or TCP6. See [GetPort] for more detail
  121. func GetTcpPort() (PortResult, error) {
  122. return GetPort(TCP, "")
  123. }
  124. // GetTcp4Port gets a port for some random available address using TCP4.
  125. // See [GetPort] for more detail
  126. func GetTcp4Port() (PortResult, error) {
  127. return GetPort(TCP4, "")
  128. }
  129. // GetTcp6Port gets a port for some random available address using TCP6.
  130. // See [GetPort] for more detail
  131. func GetTcp6Port() (PortResult, error) {
  132. return GetPort(TCP6, "")
  133. }
  134. // GetUdpPort gets a port for some random available address using either
  135. // UDP4 or UDP6. See [GetPort] for more detail
  136. func GetUdpPort() (PortResult, error) {
  137. return GetPort(UDP, "")
  138. }
  139. // GetUdp4Port gets a port for some random available address using UDP4.
  140. // See [GetPort] for more detail
  141. func GetUdp4Port() (PortResult, error) {
  142. return GetPort(UDP4, "")
  143. }
  144. // GetUdp6Port gets a port for some random available address using UDP6.
  145. // See [GetPort] for more detail
  146. func GetUdp6Port() (PortResult, error) {
  147. return GetPort(UDP6, "")
  148. }
  149. // GetTcpPortForAddress gets either a TCP4 or TCP6 port for the given address.
  150. // See [GetPort] for more detail.
  151. func GetTcpPortForAddress(address string) (PortResult, error) {
  152. return GetPort(TCP, address)
  153. }
  154. // GetTcp4PortForAddress gets a TCP4 port for the given address.
  155. // See [GetPort] for more detail.
  156. func GetTcp4PortForAddress(address string) (PortResult, error) {
  157. return GetPort(TCP4, address)
  158. }
  159. // GetTcp6PortForAddress gets a TCP6 port for the given address.
  160. // See [GetPort] for more detail.
  161. func GetTcp6PortForAddress(address string) (PortResult, error) {
  162. return GetPort(TCP6, address)
  163. }
  164. // GetUdpPortForAddress gets either a UDP4 or UDP6 port for the given address.
  165. // See [GetPort] for more detail.
  166. func GetUdpPortForAddress(address string) (PortResult, error) {
  167. return GetPort(UDP, address)
  168. }
  169. // GetUdp4PortForAddress gets a UDP4 port for the given address.
  170. // See [GetPort] for more detail.
  171. func GetUdp4PortForAddress(address string) (PortResult, error) {
  172. return GetPort(UDP4, address)
  173. }
  174. // GetUdp6PortForAddress gets a UDP6 port for the given address.
  175. // See [GetPort] for more detail.
  176. func GetUdp6PortForAddress(address string) (PortResult, error) {
  177. return GetPort(UDP6, address)
  178. }
  179. // PortResultToAddress converts a [PortResult] into a traditional host:port
  180. // string usable by [net.Listen] or [net.ListenPacket].
  181. func PortResultToAddress(portResult PortResult) string {
  182. return net.JoinHostPort(portResult.IP, fmt.Sprintf("%d", portResult.Port))
  183. }
  184. // listen is an internal wrapper for [net.Listen] and [net.ListenPacket].
  185. func listen(stack string, addrWithPort string) (net.Addr, error) {
  186. if strings.HasPrefix(stack, "tcp") {
  187. l, err := net.Listen(stack, addrWithPort)
  188. if err != nil {
  189. return nil, err
  190. }
  191. defer l.Close()
  192. return l.Addr(), nil
  193. }
  194. if strings.HasPrefix(stack, "udp") {
  195. l, err := net.ListenPacket(stack, addrWithPort)
  196. if err != nil {
  197. return nil, err
  198. }
  199. defer l.Close()
  200. return l.LocalAddr(), nil
  201. }
  202. return nil, errors.Errorf("stack not recognized: %s", stack)
  203. }
  204. // resolveProtocol maps the [Protocol] value to a network stack string
  205. // that is supported by [net.Listen] and [net.ListenPacket].
  206. func resolveProtocol(protocol Protocol) string {
  207. var stack string
  208. switch protocol {
  209. case TCP:
  210. stack = "tcp"
  211. case TCP4:
  212. stack = "tcp4"
  213. case TCP6:
  214. stack = "tcp6"
  215. case UDP:
  216. stack = "udp"
  217. case UDP4:
  218. stack = "udp4"
  219. case UDP6:
  220. stack = "udp6"
  221. }
  222. return stack
  223. }