ConnectionReset on UdpSocket in Windows

Hi, It's been a while since I've been here :slight_smile:

I have a toy dns server which opens a udp socket. When testing it using Resolve-DnsName powershell command repeatedly I get ConnectionReset error after a few requests. My code is async using tokio, but the same happens on UdpSocket from standard library. I found few references to the fact that it happens on windows, but no real solution. The only way I found to override it is to wrap the code and ignore all errors:

pub async fn dns_server(host: Ipv4Addr, port: u16) -> Result<()> {
    let addr = SocketAddr::from((host, port));
    let socket = UdpSocket::bind(addr).await?;
    println!("Listening on: {}", addr);
    loop {
        match handle_query(&socket).await {
            Ok(_) => (),
            Err(e) => {
                dbg!(e);
            }
        }
    }
}

async fn handle_query(socket: &UdpSocket) -> Result<()> {
    let mut req_buffer = BytePacketBuffer::new();
    let (len, peer) = socket.recv_from(&mut req_buffer.buf).await?;
    let request = DnsPacket::from_buffer(&mut req_buffer).await?;
    let mut response = lookup(&request)?;
    let mut res_buffer = BytePacketBuffer::new();
    response.write(&mut res_buffer)?;
    let pos = res_buffer.pos();
    let data = res_buffer.get_range(0, pos)?;
    let _ = socket.send_to(data, peer).await?;
    Ok(())
}

The above code prints every few requests the following output:

[src\dns\mod.rs:18:17] e = Os {
    code: 10054,
    kind: ConnectionReset,
    message: "An existing connection was forcibly closed by the remote host.",
}


This is of course not a real solution :frowning:

Does anyone have an idea how to solve this issue?

Thanks

Haim

This seems to be ICMP errors messages being bubbled as read errors.

This SO answer suggests that it can be turned off via an ioctl

1 Like

Thanks, I'll try to figure out the rust version.

So, with the help of the internet and ChapGPT (which I hardly ever use) I reached this code:

#[cfg(target_os = "windows")]
async fn mk_udp_socket(port: u16) -> std::io::Result<UdpSocket> {
    use socket2::{Domain, Protocol, Socket, Type, SockAddr};
    use std::io::Error;
    use std::os::windows::io::AsRawSocket;
    use windows_sys::Win32::Networking::WinSock::{setsockopt, SOL_SOCKET};

    let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
    const SIO_UDP_CONNRESET: i32 = -1744830452;
    let in_value: [u8; 1] = [0];
    let result = unsafe {
        setsockopt(
            socket.as_raw_socket() as _,
            SOL_SOCKET,
            SIO_UDP_CONNRESET,
            in_value.as_ptr() as *const _,
            in_value.len() as i32,
        )
    };
    if result != 0 {
        return Err(Error::last_os_error());
    }
    let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, port));
    socket.bind(&addr.into())?;
    let std_udp_socket = std::net::UdpSocket::from(socket);
    tokio::net::UdpSocket::from_std(std_udp_socket)
}

To my surprise the code compiled, but when trying to run it I got the following error:

[src\main.rs:11:13] result = Err(
    Os {
        code: 10022,
        kind: InvalidInput,
        message: "An invalid argument was supplied.",
    },

I'm not a low-level developer and this kind of code is well above my head. The setsockopt code is causing the problem but I have no idea why.

Can anyone help?

Thanks

the linked SO thread uses ioctl (DeviceIoControl in Win32), not setsockopt. this is also suggested by this page, which lists ioctl codes, not socket option codes:

According to the internet-at-large the parameter (in_value) is four bytes instead of one.

Thanks, I also found WSAIoctl which might be relevant. I'll try to figure out something that works :crossed_fingers:.

Ok, I think I solved it. I used this GH issue as a guide (although it uses an older winapi crate). If someone encounter this in the future, here's a working construction of the socket:

#[cfg(target_os = "windows")]
async fn mk_udp_socket(addr: &SocketAddr) -> std::io::Result<UdpSocket> {
    use std::io::Error;
    use std::ptr::null_mut;
    use std::os::windows::io::AsRawSocket;
    use windows_sys::Win32::Networking::WinSock::{WSAIoctl, SIO_UDP_CONNRESET, SOCKET};


    let socket = std::net::UdpSocket::bind(addr)?;
    let handle = socket.as_raw_socket() as SOCKET;
    let mut enable: BOOL = FALSE;
    let mut bytes_returned: u32 = 0;
    let result = unsafe {
        WSAIoctl(
            handle,
            SIO_UDP_CONNRESET,
            &mut enable as *mut _ as _,
            size_of_val(&enable) as _,
            null_mut(),
            0,
            &mut bytes_returned,
            null_mut(),
            None,
         )
    };
    if result != 0 {
        return Err(Error::last_os_error());
    }
    UdpSocket::from_std(socket)
}

I tested it with many requests and it seems to work correctly. The one thing I'm not sure of is that I saw somewhere that's it's not desirable to convert from UdpSocket in the standard library to tokio, but I'll leave with that (especially because this is a toy project).

Thanks for all the help.

Edit: It seems that tokio's UdpSocket can be used as a SOCKET pointer so no conversion :muscle:.

2 Likes

Thanks, this was the wrong function anyway. I posted the correct answer below.

the rust type wraps some low level native socket, and the proper way to obtain such native socket is AsRawSocket on Windows, and AsRawFd on Linux, both of which are implemented by tokio.

1 Like