rust-cookbook
rust-cookbook copied to clipboard
Get An Unused TCP/IP (or UDP) Port
This is a duplicate of #123 , but I was hoping for a slightly different solution than the one provided there.
I was hoping to get a free port so I can make a temporary postgres database listen on it while I am running database tests.
I found a nice answer on this blog: https://elliotekj.com/2017/07/25/find-available-tcp-port-rust/
Here is how I look for available ports:
fn port_is_available(port: u16) -> bool {
match TcpListener::bind(("127.0.0.1", port)) {
Ok(_) => true,
Err(_) => false,
}
}
fn get_available_port() -> Option<u16> {
(1025..65535).find(|port| port_is_available(*port))
}
I have made one slight change; get_available_port checks all possible user-assignable ports.
This works, but it can fail due to TOCTOU - someone else can snatch the port before you open the real socket. A solution would be to return the socket you used for probing, e.g. in Option, or to use retries.
This works only due to SO_REUSEPORT that was introduced in Linux 3.9 (search here). This flag allows you to bind to a port and then rebind to it without closing the file descriptor (from what I understand). The regular behavior after you bind to a port (either if you use it or not) is for it to become unavailable for a minute or two (until the kernel does some kind of cleanup). And yes, rust, uses SO_REUSEPORT by default when calling libc's bind - check with strace.
For example this does not work on OS X and I have no idea if this will work on Windows. A better approach that works on all OSes (solving the TOCTOU problem as well) is the following (not tested though!!):
fn listen_available_port() -> Option<TcpListener> {
for port in (1025..65535) {
match TcpListener::bind(("127.0.0.1", port)) {
Ok(l) => return Some(l),
_ => {}
}
}
None
}
Still this approach does not take into account other TcpListener errors. The loop should continue only for port unavailable error and return a Result (not an Option) indicating the exact error or all ports unavailable error.
Binding with a port number of 0 will request that the OS assigns a port to this listener. The port allocated can be queried via the TcpListener::local_addr method.
docs https://doc.rust-lang.org/std/net/struct.TcpListener.html#method.bind