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 }