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.",
}
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:
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 .
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.