github.com/bazelbuild/remote-apis-sdks@v0.0.0-20240425170053-8a36686a6350/go/pkg/portpicker/portpicker.go (about)

     1  // Package portpicker allows Go programs and tests to receive the best guess of
     2  // an unused port that may be used for ad hoc purposes.
     3  package portpicker
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"math/rand"
    11  	"net"
    12  	"os"
    13  	"strconv"
    14  	"sync"
    15  	"syscall"
    16  	"time"
    17  )
    18  
    19  var (
    20  	// A private random number generator to avoid messing with any program determinism.
    21  	rng = struct {
    22  		sync.Mutex
    23  		*rand.Rand
    24  	}{Rand: rand.New(rand.NewSource(time.Now().UnixNano() + int64(syscall.Getpid())))}
    25  
    26  	// errNoUnusedPort is the error returned when an unused port is not found.
    27  	errNoUnusedPort = errors.New("portpicker: no unused port")
    28  
    29  	// Ports that have previously been assigned by portserver to this process.
    30  	served struct {
    31  		sync.Mutex
    32  		m map[int]bool // port => {true if in use, false if recycled}
    33  	}
    34  )
    35  
    36  const (
    37  	// Range of port numbers allocated by portpicker
    38  	minPort = 32768
    39  	maxPort = 60000
    40  )
    41  
    42  func isPortFree(port int) bool {
    43  	return isPortTypeFree(port, syscall.SOCK_STREAM) &&
    44  		isPortTypeFree(port, syscall.SOCK_DGRAM)
    45  }
    46  
    47  func isPortTypeFree(port, typ int) bool {
    48  	// For the port to be considered available, the kernel must support at
    49  	// least one of (IPv6, IPv4), and the port must be available on each
    50  	// supported family.
    51  	var probes = []struct {
    52  		family int
    53  		addr   syscall.Sockaddr
    54  	}{
    55  		{syscall.AF_INET6, &syscall.SockaddrInet6{Port: port}},
    56  		{syscall.AF_INET, &syscall.SockaddrInet4{Port: port}},
    57  	}
    58  	gotSocket := false
    59  	var socketErrs []error
    60  	for _, probe := range probes {
    61  		// We assume that Socket will succeed iff the kernel supports this
    62  		// address family.
    63  		fd, err := syscall.Socket(probe.family, typ, 0)
    64  		if err != nil {
    65  			socketErrs = append(socketErrs, err)
    66  			continue
    67  		}
    68  		// Now that we have a socket, any subsequent error means the port is unavailable.
    69  		gotSocket = true
    70  		err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
    71  		if err != nil {
    72  			log.Printf("portpicker: failed to set SO_REUSEADDR: %v", err)
    73  			syscall.Close(fd)
    74  			return false
    75  		}
    76  		err = syscall.Bind(fd, probe.addr)
    77  		syscall.Close(fd)
    78  		if err != nil {
    79  			return false
    80  		}
    81  	}
    82  	// At this point, all supported families have bound successfully,
    83  	// but we still need to check that at least one family was supported.
    84  	if !gotSocket {
    85  		log.Printf("portpicker: failed to create sockets: %q", socketErrs)
    86  	}
    87  	return gotSocket
    88  }
    89  
    90  func queryPortServer(addr string) int {
    91  	if addr == "" {
    92  		addr = "@google-unittest-portserver"
    93  	}
    94  	// TODO(dsymonds): NUL-byte shenanigans?
    95  	c, err := net.Dial("unix", addr)
    96  	if err != nil {
    97  		// failed connection to portserver; this is normal in many circumstances.
    98  		return -1
    99  	}
   100  	defer c.Close()
   101  	if _, err := fmt.Fprintf(c, "%d\n", os.Getpid()); err != nil {
   102  		log.Printf("portpicker: failed writing request to portserver: %v", err)
   103  		return -1
   104  	}
   105  	buf, err := io.ReadAll(c)
   106  	if err != nil || len(buf) == 0 {
   107  		log.Printf("portpicker: failed reading response from portserver: %v", err)
   108  		return -1
   109  	}
   110  	buf = buf[:len(buf)-1] // remove newline char
   111  	port, err := strconv.Atoi(string(buf))
   112  	if err != nil {
   113  		log.Printf("portpicker: bad response from portserver: %v", err)
   114  		return -1
   115  	}
   116  
   117  	served.Lock()
   118  	if served.m == nil {
   119  		served.m = make(map[int]bool)
   120  	}
   121  	served.m[port] = true
   122  	served.Unlock()
   123  
   124  	return port
   125  }
   126  
   127  // RecycleUnusedPort recycles a port obtained by PickUnusedPort after it is no
   128  // longer needed.
   129  func RecycleUnusedPort(port int) error {
   130  	served.Lock()
   131  	defer served.Unlock()
   132  
   133  	// We only recycle ports from the port server since we expect to own those
   134  	// ports until the current process dies.
   135  	if _, ok := served.m[port]; !ok {
   136  		return nil
   137  	}
   138  	if !isPortFree(port) {
   139  		return fmt.Errorf("portpicker: recycling port still in use: %d", port)
   140  	}
   141  	if !served.m[port] {
   142  		return fmt.Errorf("portpicker: double recycle for port: %d", port)
   143  	}
   144  	served.m[port] = false
   145  	return nil
   146  }
   147  
   148  func recycledPort() int {
   149  	served.Lock()
   150  	defer served.Unlock()
   151  
   152  	for port, inuse := range served.m {
   153  		if !inuse {
   154  			served.m[port] = true
   155  			return port
   156  		}
   157  	}
   158  	return 0
   159  }
   160  
   161  // PickUnusedPort returns a port number that is not currently bound.
   162  // There is an inherent race condition between this function being called and
   163  // any other process on the same computer, so the caller should bind to the port as soon as possible.
   164  func PickUnusedPort() (port int, err error) {
   165  	// Try PortServer first.
   166  	portserver := os.Getenv("PORTSERVER_ADDRESS")
   167  	if portserver != "" {
   168  		if port = recycledPort(); port > 0 {
   169  			return port, nil
   170  		}
   171  		if port = queryPortServer(portserver); port > 0 {
   172  			return port, nil
   173  		}
   174  		log.Printf("portpicker: portserver configured, but couldn't get a port from it. Already got %d ports. Perhaps the pool is exhausted.",
   175  			len(served.m))
   176  		return 0, errNoUnusedPort
   177  	}
   178  
   179  	// Start with random port in range [32768, 60000]
   180  	rng.Lock()
   181  	port = minPort + rng.Intn(maxPort-minPort+1)
   182  	rng.Unlock()
   183  
   184  	// Test entire range of ports
   185  	stop := port
   186  	for {
   187  		if isPortFree(port) {
   188  			return port, nil
   189  		}
   190  		port++
   191  		if port > maxPort {
   192  			port = minPort
   193  		}
   194  		if port == stop {
   195  			break
   196  		}
   197  	}
   198  
   199  	return 0, errNoUnusedPort
   200  }
   201  
   202  // TB is the minimal viable interface that captures testing.TB.  It accepts
   203  // implementations of
   204  //
   205  //	*testing.B
   206  //	*testing.T
   207  //
   208  // This interface is introduced for the sole reason that portpicker is not a
   209  // testonly library, and we want to avoid having non-testing code suddenly
   210  // depend on package testing.  In short, per https://go-proverbs.github.io
   211  // "A little bit of copying is better than a little dependency."
   212  type TB interface {
   213  	// Helper corresponds to (testing.TB).Helper.
   214  	Helper()
   215  	// Helper corresponds to (testing.TB).Cleanup.
   216  	Cleanup(func())
   217  	// Helper corresponds to (testing.TB).Fatalf.
   218  	Fatalf(string, ...interface{})
   219  	// Helper corresponds to (testing.TB).Logf.
   220  	Logf(string, ...interface{})
   221  }
   222  
   223  // PickUnusedPortTB is a package testing-aware variant of PickUnusedPort that
   224  // treats provisioning errors as fatal and handles port releasing for the user.
   225  //
   226  //	func TestSmoke(t *testing.T) {
   227  //	  backend := startTestBackendOnPort(portpicker.PickUnusedPortTB(t))
   228  //
   229  //	  // test backend
   230  //	}
   231  //
   232  // The returned port should NOT be manually returned with RecycleUnusedPort.
   233  //
   234  // # Caution
   235  //
   236  // Port release failures do not result in test failures but leave error message
   237  // in test logs. Investigate logs to ensure that your servers under test (SUT)
   238  // terminate when the test is done.
   239  func PickUnusedPortTB(tb TB) int {
   240  	tb.Helper()
   241  	port, err := PickUnusedPort()
   242  	if err != nil {
   243  		tb.Fatalf("could not pick a port: %v", err)
   244  	}
   245  	tb.Cleanup(func() {
   246  		if err := RecycleUnusedPort(port); err != nil {
   247  			tb.Logf("could not return port %v: %v", port, err)
   248  		}
   249  	})
   250  	return port
   251  }