rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
96.62k stars 12.48k forks source link

UdpSocket `recv()` fails after `send()` to a closed port #128072

Open errorxyz opened 1 month ago

errorxyz commented 1 month ago

Trying to recv() on a UdpSocket after a send() to a closed UDP socket fails

I tried this code:

use std::net::UdpSocket;

fn main() {
    let client = UdpSocket::bind("127.0.0.1:0").expect("failed to bind");
    let server = UdpSocket::bind("127.0.0.1:8080").expect("failed to bind");

    client.connect("127.0.0.1:1234").expect("connect function failed"); // no server at this address
    client.send(&[0; 10]).expect("couldn't send data"); // no error at this point

    let mut buf = [0; 10];
    match client.recv(&mut buf) {
        Ok(received) => println!("received {received} bytes {:?}", &buf[..received]),
        Err(e) => println!("recv function failed: {e:?}"), // we get an error here
    }
}

I expected to see this happen: We should be able to recv() without any error

Instead, this happened: recv() fails

Golang currently handles this using SIO_UDP_CONNRESET here: https://github.com/golang/go/blob/e8c5bed7ea43e1a533c322e6b928ed06327721db/src/internal/poll/fd_windows.go#L340-L351

Meta

rustc --version --verbose:

rustc 1.79.0 (129f3b996 2024-06-10)
binary: rustc
commit-hash: 129f3b9964af4d4a709d1383930ade12dfe7c081
commit-date: 2024-06-10
host: x86_64-pc-windows-msvc
release: 1.79.0
LLVM version: 18.1.7
Backtrace

``` recv function failed: Os { code: 10054, kind: ConnectionReset, message: "An existing connection was forcibly closed by the remote host." } ```

ChrisDenton commented 1 month ago

The behaviour seems consistent with Linux, no?

Playground:

recv function failed: Os { code: 111, kind: ConnectionRefused, message: "Connection refused" }
errorxyz commented 1 month ago

The behaviour seems consistent with Linux, no?

Yes, I can confirm this behaviour with linux as well. I had stumbled upon this issue while working with tokio::net::UdpSocket which does not have this issue on linux and I presumed it to be the case with std::net::UdpSocket as well. Thanks for pointing it out

the8472 commented 1 month ago

man 7 udp:

Error handling All fatal errors will be passed to the user as an error return even when the socket is not connected. This includes asynchronous errors received from the network. You may get an error for an earlier packet that was sent on the same socket. This behavior differs from many other BSD socket implementations which don't pass any errors unless the socket is connected. Linux's behavior is mandated by RFC 1122.

So this likely stems from an ICMP error reply to the send that couldn't be delivered. If you don't connect the socket and use send_to / recv_from instead then this shouldn't happen and the errors would only be visible in the auxiliary error queue.

errorxyz commented 1 month ago

If you don't connect the socket and use send_to / recv_from instead then this shouldn't happen and the errors would only be visible in the auxiliary error queue.

I still do get the error

Code ``` let client = UdpSocket::bind("127.0.0.1:0").expect("failed to bind"); let _server = UdpSocket::bind("127.0.0.1:8080").expect("failed to bind"); client.send_to(&[0; 10], "127.0.0.1:1234").expect("couldn't send data"); // no error at this point let mut buf = [0; 10]; match client.recv_from(&mut buf) { Ok(received) => println!("received {:?} bytes {:?} from {:?}", received.0, &buf[..received.0], received.1), Err(e) => println!("recv function failed: {e:?}"), // we get an error here } ``` o/p: `recv function failed: Os { code: 10054, kind: ConnectionReset, message: "An existing connection was forcibly closed by the remote host." }`