| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package getport
- import (
- "fmt"
- "math/rand"
- "net"
- "strconv"
- "strings"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/util/sets"
- )
- // REF: https://github.com/jsumners/go-getport/blob/master/getport.go
- // Protocol indicates the communication protocol (tcp or udp) and network
- // stack (IPv4, IPv6, or OS choice) to target when finding an available port.
- type Protocol int
- const (
- // TCP indicates to let the OS decide between IPv4 and IPv6 when finding
- // an open TCP based port.
- TCP Protocol = iota
- // TCP4 indicates to find an open IPv4 port.
- TCP4
- // TCP6 indicates to find an open IPv6 port.
- TCP6
- // UDP indicates to let the OS decide between IPv4 and IPv6 when finding
- // an open UDP based port.
- UDP
- // UDP4 indicates to find an open IPv4 port.
- UDP4
- // UDP6 indicates to find an open IPv6 port.
- UDP6
- )
- // PortResult represents the result of [GetPort]. It indicates the IP address
- // and port number combination that resulted in finding an open port.
- type PortResult struct {
- // IP is either an IPv4 or IPv6 string as returned by [net.SplitHostPort].
- IP string
- // Port is the determined available port number.
- Port int
- }
- // GetPort finds an open port for a given [Protocol] and address and returns
- // that port number. If the [Protocol] is not recognized, or some problem is
- // encountered while verifying the port, then the returned [PortResult.Port]
- // number will be `-1` along with an error. The address parameter should be a
- // simple IP address string, e.g. `127.0.0.1` or `::1`. The [PortResult.IP] will
- // be set to the IP address that was actually used to find the open port. If
- // address is the empty string (`""`), then the returned IP address will be the
- // one determined by the OS when finding the port.
- //
- // Note: it is not guaranteed the port will remain open long enough to actually
- // be used. Errors should still be checked when attempting to use the found
- // port.
- func GetPort(protocol Protocol, address string) (PortResult, error) {
- return getPort(protocol, address, 0)
- }
- func getPort(protocol Protocol, address string, port int) (PortResult, error) {
- stack := resolveProtocol(protocol)
- result := PortResult{
- IP: "",
- Port: -1,
- }
- resolvedAddress, listenError := listen(
- stack,
- net.JoinHostPort(address, fmt.Sprintf("%d", port)),
- )
- if listenError != nil {
- return result, listenError
- }
- // I do not see how it's possible to get an error from [net.SplitHostPort]
- // here given how we have already validated the stack and successfully
- // issued a [net.Listen].
- addr, portStr, _ := net.SplitHostPort(resolvedAddress.String())
- hPort, _ := strconv.Atoi(portStr)
- result.IP = addr
- result.Port = hPort
- return result, nil
- }
- func IsPortUsed(protocol Protocol, address string, port int) bool {
- _, err := getPort(protocol, address, port)
- if err != nil {
- return true
- }
- return false
- }
- func GetPortByRange(proto Protocol, start int, end int) (PortResult, error) {
- return GetPortByRangeBySets(proto, start, end, sets.NewInt())
- }
- func GetPortByRangeBySets(proto Protocol, start int, end int, usedPorts sets.Int) (PortResult, error) {
- errs := []error{}
- for i := start; i <= end; i++ {
- rPort := rand.Intn(end-start) + start
- if usedPorts.Has(rPort) {
- continue
- }
- result, err := getPort(proto, "", rPort)
- if err != nil {
- usedPorts.Insert(rPort)
- errs = append(errs, errors.Wrapf(err, "check random port: %d", rPort))
- } else {
- return result, nil
- }
- }
- return PortResult{
- IP: "",
- Port: -1,
- }, errors.Wrapf(errors.NewAggregate(errs), "can't get free port in [%d, %d]", start, end)
- }
- // GetTcpPort gets a port for some random available address using either
- // TCP4 or TCP6. See [GetPort] for more detail
- func GetTcpPort() (PortResult, error) {
- return GetPort(TCP, "")
- }
- // GetTcp4Port gets a port for some random available address using TCP4.
- // See [GetPort] for more detail
- func GetTcp4Port() (PortResult, error) {
- return GetPort(TCP4, "")
- }
- // GetTcp6Port gets a port for some random available address using TCP6.
- // See [GetPort] for more detail
- func GetTcp6Port() (PortResult, error) {
- return GetPort(TCP6, "")
- }
- // GetUdpPort gets a port for some random available address using either
- // UDP4 or UDP6. See [GetPort] for more detail
- func GetUdpPort() (PortResult, error) {
- return GetPort(UDP, "")
- }
- // GetUdp4Port gets a port for some random available address using UDP4.
- // See [GetPort] for more detail
- func GetUdp4Port() (PortResult, error) {
- return GetPort(UDP4, "")
- }
- // GetUdp6Port gets a port for some random available address using UDP6.
- // See [GetPort] for more detail
- func GetUdp6Port() (PortResult, error) {
- return GetPort(UDP6, "")
- }
- // GetTcpPortForAddress gets either a TCP4 or TCP6 port for the given address.
- // See [GetPort] for more detail.
- func GetTcpPortForAddress(address string) (PortResult, error) {
- return GetPort(TCP, address)
- }
- // GetTcp4PortForAddress gets a TCP4 port for the given address.
- // See [GetPort] for more detail.
- func GetTcp4PortForAddress(address string) (PortResult, error) {
- return GetPort(TCP4, address)
- }
- // GetTcp6PortForAddress gets a TCP6 port for the given address.
- // See [GetPort] for more detail.
- func GetTcp6PortForAddress(address string) (PortResult, error) {
- return GetPort(TCP6, address)
- }
- // GetUdpPortForAddress gets either a UDP4 or UDP6 port for the given address.
- // See [GetPort] for more detail.
- func GetUdpPortForAddress(address string) (PortResult, error) {
- return GetPort(UDP, address)
- }
- // GetUdp4PortForAddress gets a UDP4 port for the given address.
- // See [GetPort] for more detail.
- func GetUdp4PortForAddress(address string) (PortResult, error) {
- return GetPort(UDP4, address)
- }
- // GetUdp6PortForAddress gets a UDP6 port for the given address.
- // See [GetPort] for more detail.
- func GetUdp6PortForAddress(address string) (PortResult, error) {
- return GetPort(UDP6, address)
- }
- // PortResultToAddress converts a [PortResult] into a traditional host:port
- // string usable by [net.Listen] or [net.ListenPacket].
- func PortResultToAddress(portResult PortResult) string {
- return net.JoinHostPort(portResult.IP, fmt.Sprintf("%d", portResult.Port))
- }
- // listen is an internal wrapper for [net.Listen] and [net.ListenPacket].
- func listen(stack string, addrWithPort string) (net.Addr, error) {
- if strings.HasPrefix(stack, "tcp") {
- l, err := net.Listen(stack, addrWithPort)
- if err != nil {
- return nil, err
- }
- defer l.Close()
- return l.Addr(), nil
- }
- if strings.HasPrefix(stack, "udp") {
- l, err := net.ListenPacket(stack, addrWithPort)
- if err != nil {
- return nil, err
- }
- defer l.Close()
- return l.LocalAddr(), nil
- }
- return nil, errors.Errorf("stack not recognized: %s", stack)
- }
- // resolveProtocol maps the [Protocol] value to a network stack string
- // that is supported by [net.Listen] and [net.ListenPacket].
- func resolveProtocol(protocol Protocol) string {
- var stack string
- switch protocol {
- case TCP:
- stack = "tcp"
- case TCP4:
- stack = "tcp4"
- case TCP6:
- stack = "tcp6"
- case UDP:
- stack = "udp"
- case UDP4:
- stack = "udp4"
- case UDP6:
- stack = "udp6"
- }
- return stack
- }
|