rust-lang / rfcs

RFCs for changes to Rust
https://rust-lang.github.io/rfcs/
Apache License 2.0
5.93k stars 1.57k forks source link

std::net wishlist issue #957

Open stepancheg opened 9 years ago

stepancheg commented 9 years ago

std::net could be better. Here is a list of things that could be added or changed to std::net.

Some changes need to be made before 1.0, because they are not backward compatible.

The list:

TcpListener::from_socket and into_socket

Sometimes a socket is got from outside, for example, from another process, or just created with custom options. For that case TcpListener needs TcpListener::from_socket constructor.

And similarly, TcpListener needs into_socket reverse operation.

nginx http server can upgrade to new executable without losing and single connection. It clears FD_CLOEXEC flag and execs. To make such code possible in rust, from_socket and into_socket operations are necessary.

TcpListener::bind: make it cross-platform

Currently, there's a huge difference in TcpListener::bind between Windows and other OSes.

Rust on Windows creates a socket with flag IPV6_V6ONLY set to true (that's default behavior on Windows), and false on other OSes.

So on Linux developer can create one socket to serve both IPV4 and IPv6 clients, and developers on Windows need two sockets.

Latter issue could be resolved but allowing socket customization in some form, but I think it is very important for Rust to be as cross-platform as possible by default.

I have a patch set that resolves this issue.

TcpListener::bind_to_port

There's need for simple fn TcpListener::bind_to_port that creates a socket, capable of simply serving on port, hiding implementation details from end user.

Listening on port is the most common operation, so it deserves a shortcut.

Such shortcut exists in Java, it is ServerSocket(int) constructor.

Option to disable IPv6

Sometimes systems (especially on servers) have IPv6 enabled but incorrectly configured. On such systems it is useful to have a simple option to disable IPv6.

For example, a server may have both IPv4 and IPv6 addresses, and IPv6 is blocked by a firewall. It a client connects to the IPv6 address first, they may need to wait 2 minutes until the first connect times out.

In Java such a mode can be set with the -Djava.net.preferIPv4Stack=true property.

I think Rust could use an environment variable like RUST_PREFER_IPV4=true.

Turning on such an option would make these changes:

However, when IPv6 explicitly requested in code, it should work, e. g. connect("[2a03:2880:2130:cf05:face:b00c::1]:80") should work regardless of that option.

This option won't be needed in 10 years after widespread adoption of IPv6, but now it is useful for quick-and-dirty fixes.

TcpListener::bind should fail if there's more than one address.

Currently TcpListener::bind resolves parameter to a list of IPs, and binds to them until one first successful bind. This is dangerous. Consider a situation. You call bind with param like TcpListener::bind("foo:80"), and foo is resolved to both IPv4 and IPv6 addresses. Depending on your luck, you get a socket bound to IPv4 only or both IPv4/IPv6 addresses (on Linux). Fail-fast would be better here.

Better behavior is report an error host foo is resolved to more than one address.

IpAddrFamily enum

IpAddrFamily enum is trivial and useful. Rust should have it.

pub enum IpAddrFamily {
    V4,
    V6
}

lookup_host_as_family utility

fn lookup_host_as_family(host: &str, family: IpAddrFamily) ...

Sometimes it is useful to resolve host as specific address family (for example, when you know server only talks to IPv4).

This could be done by calling filter function on resulting iterator, but having this function is convenient. So the issue is minor.

Terminating UdpSocket::recv_from remotely

detailed in https://github.com/rust-lang/rust/issues/23272

Reverse DNS

see https://github.com/rust-lang/rust/pull/23419 and https://github.com/rust-lang/rust/issues/22608

More socket options

There's lots of stuff you can do with sockets before they're bound or connected, and right now the convenience methods we provide package a lot of these steps into one bundle when they should be separable. This would probably manifest itself as some form of Socket API which just deals with generic sockets and such.

alexcrichton commented 9 years ago

Thanks for writing this up @stepancheg!

kwantam commented 9 years ago

One approach here could be to make a "builder" akin to std::fs::OpenOptions or std::process::Command, but for network connections.

let server_sock = net::SocketBuilder.new()
                                    .tcp()   // or .tcp4() or .tcp6(), for example
                                    .bind("127.0.0.1:9999")
                                    .listen()
                                    .unwrap();

let client_sock = net::SocketBuilder.new()
                                    .tcp()
                                    .peer("10.0.2.1:80")
                                    .connect()
                                    .unwrap();

This could encompass some of @stepancheg's suggestions as well:

let server_sock = net::SocketBuilder.new()
                                    .use_socket(sock)
                                    .listen()
                                    .unwrap();

and presumably less common things like @tilpner's request in #25102

// connect from a specific port
let client_sock = net::SocketBuilder.new()
                                    .tcp()
                                    .peer("10.0.2.1:80")
                                    .bind("9999")
                                    .connect()
                                    .unwrap();

And of course you can still have shortcut constructors for common cases, just like File::open() acts as sugar for some longer OpenOptions call. It ought to be possible to make this backward compatible, i.e., connect() returns a TcpStream, and listen() returns a TcpListener.

A possible issue is that I don't know whether this can be done platform independently.

If this seems even remotely reasonable I could think a bit more about it and write up an RFC.

alexcrichton commented 9 years ago

@kwantam I couldn't agree more! That's precisely the idea I had in mind. I am just uncertain about the precise API, but at the high level it's what I want!

eternaleye commented 9 years ago

Some commentary on one item:

Option to disable IPv6

(Spelling/grammar corrections bolded)

Sometimes systems (especially on servers) have IPv6 enabled but incorrectly configured. On such systems it is useful to have a simple option to disable IPv6.

For example, a server may have both IPv4 and IPv6 addresses, and IPv6 is blocked by a firewall. If a client connects to the IPv6 address first, they may need to wait up to 2 minutes until the first connect has timed out.

This is exactly the issue that Happy Eyeballs, a.k.a. RFC 6555, is intended to address.

In Java such a mode can be set with the -Djava.net.preferIPv4Stack=true property.

I think Rust could use an environment variable like RUST_PREFER_IPV4=true.

Turning on such an option would make these changes:

  • bind_on_port binds only on IPv4
  • connect("host:port") will connect only to IPv4 hosts
  • lookup would return only IPv4 addresses

However, when IPv6 is explicitly requested in code, it should work, e. g. connect("[2a03:2880:2130:cf05:face:b00c::1]:80") should work regardless of that option.

This option won't be needed in 10 years after widespread adoption of IPv6, but now it is useful for quick-and-dirty fixes.

The problem with quick-and-dirty fixes is that sometimes they impede clean solutions. In particular, Happy Eyeballs (which works by trying IPv6 and IPv4 in parallel, and using whichever succeeds more quickly) gracefully migrates to IPv6 as coverage improves. End machines disabling IPv6, however, in itself impedes IPv6 coverage improving - and also introduces Yet Another Flag Day Eventually when they re-enable it, which is just asking for problems... and thus turning it back on will never actually happen.

stepancheg commented 9 years ago

@eternaleye thanks for the corrections.

The problem with quick-and-dirty fixes is that sometimes they impede clean solutions.

Sometimes they are. It doesn't mean that such such quick-and-dirty should be impossible. If you have a problem right here right now on production system, you should fix it now, and not waste precious time on clean solution. If your clients do not implement happy eyeballs, you are unlucky.

stepancheg commented 9 years ago

Any news on the issues? I've noticed today, that in Hyper documentation example, a server is created that listens only on IPv4:

use hyper::server::{Server, Request, Response};

fn hello(req: Request, res: Response) {
    // handle things here
}

Server::http("0.0.0.0:0").unwrap().handle(hello).unwrap();

They cannot (easily) do better, because Rust doesn't provide a way to just bind on port.

seanmonstar commented 9 years ago

Just as a clarification, Server::http takes a ToSocketAddrs, so you could provide an IPv6 as well.

However, it is true that I can't expose a way a to simply listen on a certain port, regardless of IP address.

stepancheg commented 9 years ago

@seanmonstar no, you can't, because on Linux by default binding to IPv6 makes a socket that listens on both IPv4 and IPv6, so you need to bind on IPv6 only on Linux, and on both IPv4 and IPv6 on Windows. See explanation of how IPV6_V6ONLY works.

Boscop commented 7 years ago

What's the status on this? (I'm also interested in Reverse DNS lookup.)

arcrose commented 7 years ago

Commenting because I'd also like to know what the status is.

sfackler commented 7 years ago

@zsck what features are you looking for in particular?

jeltz commented 7 years ago

Is there anyone working on allowing TCP client to bind to a local address, or is this still the proposal for that? Personally I suspect we need to move to some kind of builder pattern to best solve this issue since the bind need to happen before the connect (but after creating the socket).

sfackler commented 7 years ago

@jeltz The socket2 crate is what we're currently thinking - a single low-level socket wrapper that can be manually configured and then turned into a TcpStream.

tomwhoiscontrary commented 4 years ago

Would love a reverse DNS lookup!