Skip to content

Commit

Permalink
🐛 Ignore UdpSocket's WSAECONNRESET error on Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
mokeyish committed Sep 26, 2024
1 parent 669e379 commit e2ff79a
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 103 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ self_update = { version = "0.40.0", default-features = false, features=["archive
windows = { version = "0.57", features = [
"Win32_System_Console",
"Win32_Foundation",
"Win32_Networking", "Win32_Networking_WinSock", "Win32_System_IO", # set UDP_CONNRESET off
] }
windows-service = "0.7.0"
self_update = { version = "0.40.0", default-features = false, features=["archive-zip", "compression-zip-deflate", "rustls"], optional = true }
Expand Down
135 changes: 32 additions & 103 deletions src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
mod https;
#[cfg(feature = "legacy_dns_server")]
mod legacy;
mod net;
#[cfg(not(feature = "legacy_dns_server"))]
mod protocol;
#[cfg(feature = "dns-over-quic")]
Expand Down Expand Up @@ -49,18 +50,28 @@ pub async fn serve(
certificate_key_file: Option<&Path>,
) -> Result<ServerHandle, crate::Error> {
use crate::rustls::load_certificate_and_key;
use net::{bind_to, tcp, udp};
use net::{bind_to, setup_tcp_socket, setup_udp_socket};
use std::time::Duration;

let dns_handle = handle.with_new_opt(listener_config.server_opts().clone());

let token = match listener_config {
ListenerConfig::Udp(listener) => {
let udp_socket = bind_to(udp, listener.sock_addr(), listener.device(), "UDP");
let udp_socket = bind_to(
setup_udp_socket,
listener.sock_addr(),
listener.device(),
"UDP",
);
udp::serve(udp_socket, dns_handle)
}
ListenerConfig::Tcp(listener) => {
let tcp_listener = bind_to(tcp, listener.sock_addr(), listener.device(), "TCP");
let tcp_listener = bind_to(
setup_tcp_socket,
listener.sock_addr(),
listener.device(),
"TCP",
);
tcp::serve(tcp_listener, dns_handle, Duration::from_secs(idle_time))
}
#[cfg(feature = "dns-over-tls")]
Expand All @@ -75,7 +86,12 @@ pub async fn serve(
LISTENER_TYPE,
)?;

let tls_listener = bind_to(tcp, listener.sock_addr(), listener.device(), LISTENER_TYPE);
let tls_listener = bind_to(
setup_tcp_socket,
listener.sock_addr(),
listener.device(),
LISTENER_TYPE,
);

tls::serve(
tls_listener,
Expand All @@ -96,8 +112,12 @@ pub async fn serve(
LISTENER_TYPE,
)?;

let https_listener =
bind_to(tcp, listener.sock_addr(), listener.device(), LISTENER_TYPE);
let https_listener = bind_to(
setup_tcp_socket,
listener.sock_addr(),
listener.device(),
LISTENER_TYPE,
);

let app = app.clone();
https::serve(
Expand All @@ -121,7 +141,12 @@ pub async fn serve(
LISTENER_TYPE,
)?;

let quic_listener = bind_to(udp, listener.sock_addr(), listener.device(), "QUIC");
let quic_listener = bind_to(
setup_udp_socket,
listener.sock_addr(),
listener.device(),
"QUIC",
);

quic::serve(
quic_listener,
Expand Down Expand Up @@ -270,99 +295,3 @@ fn sanitize_src_address(src: SocketAddr) -> Result<(), String> {
IpAddr::V6(v6) => verify_v6(v6),
}
}

mod net {
use crate::log;
use std::{io, net::SocketAddr};
use tokio::net::{TcpListener, UdpSocket};

pub fn bind_to<T>(
func: impl Fn(SocketAddr, Option<&str>, &str) -> io::Result<T>,
sock_addr: SocketAddr,
bind_device: Option<&str>,
bind_type: &str,
) -> T {
func(sock_addr, bind_device, bind_type).unwrap_or_else(|err| {
panic!("cound not bind to {bind_type}: {sock_addr}, {err}");
})
}

pub fn tcp(
sock_addr: SocketAddr,
bind_device: Option<&str>,
bind_type: &str,
) -> io::Result<TcpListener> {
let device_note = bind_device
.map(|device| format!("@{device}"))
.unwrap_or_default();

log::debug!("binding {} to {:?}{}", bind_type, sock_addr, device_note);
let tcp_listener = std::net::TcpListener::bind(sock_addr)?;

{
let sock_ref = socket2::SockRef::from(&tcp_listener);
sock_ref.set_nonblocking(true)?;
sock_ref.set_reuse_address(true)?;

#[cfg(target_os = "macos")]
sock_ref.set_reuse_port(true)?;

#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
if let Some(device) = bind_device {
sock_ref.bind_device(Some(device.as_bytes()))?;
}
}

let tcp_listener = TcpListener::from_std(tcp_listener)?;

log::info!(
"listening for {} on {:?}{}",
bind_type,
tcp_listener
.local_addr()
.expect("could not lookup local address"),
device_note
);

Ok(tcp_listener)
}

pub fn udp(
sock_addr: SocketAddr,
bind_device: Option<&str>,
bind_type: &str,
) -> io::Result<UdpSocket> {
let device_note = bind_device
.map(|device| format!("@{device}"))
.unwrap_or_default();

log::debug!("binding {} to {:?}{}", bind_type, sock_addr, device_note);
let udp_socket = std::net::UdpSocket::bind(sock_addr)?;

{
let sock_ref = socket2::SockRef::from(&udp_socket);
sock_ref.set_nonblocking(true)?;
sock_ref.set_reuse_address(true)?;

#[cfg(target_os = "macos")]
sock_ref.set_reuse_port(true)?;

#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
if let Some(device) = bind_device {
sock_ref.bind_device(Some(device.as_bytes()))?;
}
}

let udp_socket = UdpSocket::from_std(udp_socket)?;

log::info!(
"listening for {} on {:?}{}",
bind_type,
udp_socket
.local_addr()
.expect("could not lookup local address"),
device_note
);
Ok(udp_socket)
}
}
130 changes: 130 additions & 0 deletions src/server/net.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use crate::log;
use std::{io, net::SocketAddr};
use tokio::net::{TcpListener, UdpSocket};

pub fn bind_to<T>(
func: impl Fn(SocketAddr, Option<&str>, &str) -> io::Result<T>,
sock_addr: SocketAddr,
bind_device: Option<&str>,
bind_type: &str,
) -> T {
func(sock_addr, bind_device, bind_type).unwrap_or_else(|err| {
panic!("cound not bind to {bind_type}: {sock_addr}, {err}");
})
}

pub fn setup_tcp_socket(
sock_addr: SocketAddr,
bind_device: Option<&str>,
bind_type: &str,
) -> io::Result<TcpListener> {
let device_note = bind_device
.map(|device| format!("@{device}"))
.unwrap_or_default();

log::debug!("binding {} to {:?}{}", bind_type, sock_addr, device_note);
let tcp_listener = std::net::TcpListener::bind(sock_addr)?;

{
let sock_ref = socket2::SockRef::from(&tcp_listener);
sock_ref.set_nonblocking(true)?;
sock_ref.set_reuse_address(true)?;

#[cfg(target_os = "macos")]
sock_ref.set_reuse_port(true)?;

#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
if let Some(device) = bind_device {
sock_ref.bind_device(Some(device.as_bytes()))?;
}
}

let tcp_listener = TcpListener::from_std(tcp_listener)?;

log::info!(
"listening for {} on {:?}{}",
bind_type,
tcp_listener
.local_addr()
.expect("could not lookup local address"),
device_note
);

Ok(tcp_listener)
}

pub fn setup_udp_socket(
sock_addr: SocketAddr,
bind_device: Option<&str>,
bind_type: &str,
) -> io::Result<UdpSocket> {
let device_note = bind_device
.map(|device| format!("@{device}"))
.unwrap_or_default();

log::debug!("binding {} to {:?}{}", bind_type, sock_addr, device_note);
let udp_socket = std::net::UdpSocket::bind(sock_addr)?;

{
let sock_ref = socket2::SockRef::from(&udp_socket);
sock_ref.set_nonblocking(true)?;
sock_ref.set_reuse_address(true)?;

#[cfg(target_os = "macos")]
sock_ref.set_reuse_port(true)?;

#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
if let Some(device) = bind_device {
sock_ref.bind_device(Some(device.as_bytes()))?;
}
}

// set UDP_CONNRESET off to ignore UdpSocket's WSAECONNRESET error
#[cfg(target_os = "windows")]
{
// https://github.com/mokeyish/smartdns-rs/issues/391
// https://github.com/shadowsocks/shadowsocks-rust/blob/3b47fa67fac6c2bded73616a284f26c6159cbe9a/src/relay/sys/windows/mod.rs#L17
use std::ffi::c_void;
use std::{mem, os::windows::io::AsRawSocket, ptr};
use windows::Win32::Foundation::FALSE;
use windows::Win32::Networking::WinSock::{
WSAGetLastError, WSAIoctl, SIO_UDP_CONNRESET, SOCKET, SOCKET_ERROR,
};

let handle = SOCKET(udp_socket.as_raw_socket() as usize);
let mut bytes_returned: u32 = 0;
let enable = FALSE;
unsafe {
let ret = WSAIoctl(
handle,
SIO_UDP_CONNRESET,
Some(&enable as *const _ as *const c_void),
mem::size_of_val(&enable) as u32,
Some(ptr::null_mut()),
0,
&mut bytes_returned,
Some(ptr::null_mut()),
None,
);

if ret == SOCKET_ERROR {
// ignore the error here, just warn and continue
let err_code = WSAGetLastError();
log::warn!("WSAIoctl failed with error code {:?}", err_code);
// return Err(td::io::Error::from_raw_os_error(err_code.0));
}
};
}

let udp_socket = UdpSocket::from_std(udp_socket)?;

log::info!(
"listening for {} on {:?}{}",
bind_type,
udp_socket
.local_addr()
.expect("could not lookup local address"),
device_note
);
Ok(udp_socket)
}

0 comments on commit e2ff79a

Please sign in to comment.