rust-cookbook icon indicating copy to clipboard operation
rust-cookbook copied to clipboard

Get An Unused TCP/IP (or UDP) Port

Open xcthulhu opened this issue 6 years ago • 3 comments
trafficstars

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.

xcthulhu avatar Nov 25 '18 14:11 xcthulhu

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.

MightyPork avatar Oct 13 '19 19:10 MightyPork

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.

dzervas avatar Jan 25 '20 21:01 dzervas

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

foo69 avatar Sep 02 '23 17:09 foo69