TcpStream::bind only to the first address is wrong


#1

TcpStream::bind has an interface problem: it only binds to the first resolved address.

When TcpStream::bind is called with param &("localhost", 12345) it should bind to all addresses of localhost. Currently it binds only to IPv6 address on my Mac.

It is usually not what developers expect. If server has both IPv4 and IPv6 address, they want to bind on all addresses.

To address this problem TcpStream::bind should return something like io::Result<Vec<TcpListener>>.

(And probably, simpler functions

fn bind_one(addr: SocketAddr) -> io::Result<TcpListener>
fn bind_any(addr: ToSocketAddrs) -> io::Result<TcpListener>

should be introduced).

(BTW, empty string for hostname, &("", 12345).to_socket_addrs() should be allowed, and should be resolved to the list of 0.0.0.0 and ::, so developers could easily bind all protocols, this idea is from Python).

bind signature change is easy. However, it implies harder thing.

zero port

When the port is zero, operating system has an algorithm to bind to some available port. However, there is no syscall to bind several sockets to the same port at once. So binding algorithm inside TcpStream should be something like this (pseudocode):

fn bind_all(addrs: Iterator<SocketAddr>)
        -> io::Result<Vec<TcpListener>>
{
    let first_addr = addrs.next().unwrap();

    // bind first socket to find available port from the operating system
    // and hope that other protocols do not already use this address
    let first_socket = try!(bind(first_addr));

    let common_port = first_socket.get_sock_name().port;

    let mut sockets = vec![first_socket];

    for addr in addrs {
        if (first_addr.port != 0 || addr.port != 0) && first_addr.port != addr.port {
            return Err("cannot have both zero and non-zero port");
        }
        if addr.port == 0 {
            // set the port from the first socket
            // to make sure all sockets use the same port
            addr.set_port(common_port);
        }

        // fail if we could not bind to any address
        sockets.push(try!(bind(addr));
    }
    return Ok(sockets);
}

Binding to the same random port for all protocols is important, because it allows application to announce single port for hostname (e. g. server1.company.com:45654), instead of a list of (socketaddr, port) pairs for hostname.

The summary of proposed changes

  1. ToSocketAddrs for ("", port) should return ["0.0.0.0", "::"]
  2. Introduce simpler bind_one(addr: SocketAddr) and/or bind_any(addr: ToSocketAddr)
  3. TcpStream::bind should bind to all socket addresses
  4. TcpStream::bind shoud bind all sockets to the same port if port is unspecified

Alternatively

If Vec<TcpListener> is too much for the standard library, then at least bind should fail, if ToSocketAddrs resolves to more than one address. Because it is easier to crash at start than spend time discovering why clients cannot connect to the server (due to different IP versions on clients, firewall configurations, different client implementations etc).

And even if that is unacceptable, then current behavior should be at least documented.