go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/system/port/port.go (about) 1 // Copyright 2021 The LUCI Authors. 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 15 package port 16 17 import ( 18 "fmt" 19 "math/rand" 20 "sync" 21 "syscall" 22 "time" 23 ) 24 25 const ( 26 // Range of port numbers to choose. 27 minPort = 32768 28 maxPort = 60000 29 30 // Maximum number of retries to find a free port. 31 maxRetries = 256 32 ) 33 34 // A private random number generator to avoid messing with any program determinism. 35 var rng = struct { 36 sync.Mutex 37 *rand.Rand 38 }{Rand: rand.New(rand.NewSource(time.Now().UnixNano() + int64(syscall.Getpid())))} 39 40 // PickUnusedPort returns a port number that is not currently bound. 41 // There is an inherent race condition between this function being called and 42 // any other process on the same computer, so the caller should bind to the 43 // port as soon as possible. 44 func PickUnusedPort() (port int, err error) { 45 // Start with random port in range [32768, 60000] 46 rng.Lock() 47 port = minPort + rng.Intn(maxPort-minPort+1) 48 rng.Unlock() 49 50 // Check if the port is free, if not look for another one. 51 tries := 0 52 for tries < maxRetries { 53 if isPortFree(port) { 54 return port, nil 55 } 56 57 port += rng.Intn(100) 58 if port > maxPort { 59 port = minPort + port%maxPort 60 } 61 tries++ 62 } 63 64 return 0, fmt.Errorf("no unused port") 65 } 66 67 func isPortFree(port int) bool { 68 return isPortTypeFree(port, syscall.SOCK_STREAM) && 69 isPortTypeFree(port, syscall.SOCK_DGRAM) 70 } 71 72 func isPortTypeFree(port, typ int) bool { 73 // For the port to be considered available, the kernel must support at 74 // least one of (IPv6, IPv4), and the port must be available on each 75 // supported family. 76 var probes = []struct { 77 family int 78 addr syscall.Sockaddr 79 }{ 80 {syscall.AF_INET6, &syscall.SockaddrInet6{Port: port}}, 81 {syscall.AF_INET, &syscall.SockaddrInet4{Port: port}}, 82 } 83 gotSocket := false 84 for _, probe := range probes { 85 // We assume that Socket will succeed iff the kernel supports this 86 // address family. 87 fd, err := syscall.Socket(probe.family, typ, 0) 88 if err != nil { 89 continue 90 } 91 // Now that we have a socket, any subsequent error means the port is unavailable. 92 gotSocket = true 93 err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) 94 if err != nil { 95 syscall.Close(fd) 96 return false 97 } 98 err = syscall.Bind(fd, probe.addr) 99 syscall.Close(fd) 100 if err != nil { 101 return false 102 } 103 } 104 return gotSocket 105 }