(host string)
| 36 | } |
| 37 | |
| 38 | func getFreePort(host string) (int, error) { |
| 39 | l, err := net.Listen("tcp", host+":0") |
| 40 | if err != nil { |
| 41 | return 0, fmt.Errorf("failed to assign a free port: %v", err) |
| 42 | } |
| 43 | defer l.Close() |
| 44 | port := l.Addr().(*net.TCPAddr).Port |
| 45 | |
| 46 | // On Linux and some BSD variants, ephemeral ports are randomized, and may |
| 47 | // consequently repeat within a short time frame after the listening end |
| 48 | // has been closed. To avoid this, we make a connection to the port, then |
| 49 | // close that connection from the server's side (this is very important), |
| 50 | // which puts the connection in TIME_WAIT state for some time (by default, |
| 51 | // 60s on Linux). While it remains in that state, the OS will not reallocate |
| 52 | // that port number for bind(:0) syscalls, yet we are not prevented from |
| 53 | // explicitly binding to it (thanks to SO_REUSEADDR). |
| 54 | // |
| 55 | // On macOS and Windows, the above technique is not necessary, as the OS |
| 56 | // allocates ephemeral ports sequentially, meaning a port number will only |
| 57 | // be reused after the entire range has been exhausted. Quite the opposite, |
| 58 | // given that these OSes use a significantly smaller range for ephemeral |
| 59 | // ports, making an extra connection just to reserve a port might actually |
| 60 | // be harmful (by hastening ephemeral port exhaustion). |
| 61 | if runtime.GOOS != "darwin" && runtime.GOOS != "windows" { |
| 62 | r, err := net.DialTCP("tcp", nil, l.Addr().(*net.TCPAddr)) |
| 63 | if err != nil { |
| 64 | return 0, fmt.Errorf("failed to assign a free port: %v", err) |
| 65 | } |
| 66 | c, err := l.Accept() |
| 67 | if err != nil { |
| 68 | return 0, fmt.Errorf("failed to assign a free port: %v", err) |
| 69 | } |
| 70 | // Closing the socket from the server side |
| 71 | _ = c.Close() |
| 72 | defer r.Close() |
| 73 | } |
| 74 | |
| 75 | return port, nil |
| 76 | } |
no test coverage detected