diff --git a/src/bin/dosh-server.rs b/src/bin/dosh-server.rs index aa7a057..b5bd9d7 100644 --- a/src/bin/dosh-server.rs +++ b/src/bin/dosh-server.rs @@ -980,7 +980,7 @@ async fn handle_native_client_hello( }; let body = protocol::to_body(&NativeServerHelloBody { hello })?; let out = protocol::encode_plain(PacketKind::NativeServerHello, pending_id, 1, 0, &body)?; - socket.send_to(&out, peer).await?; + let _ = send_udp(socket, &out, peer).await?; Ok(()) } @@ -1058,7 +1058,7 @@ async fn handle_native_user_auth( SERVER_TO_CLIENT, &body, )?; - socket.send_to(&out, peer).await?; + let _ = send_udp(socket, &out, peer).await?; return Ok(()); } @@ -1251,7 +1251,7 @@ async fn handle_native_user_auth( SERVER_TO_CLIENT, &body, )?; - socket.send_to(&out, peer).await?; + let _ = send_udp(socket, &out, peer).await?; Ok(()) } @@ -1359,7 +1359,7 @@ async fn handle_bootstrap_attach( SERVER_TO_CLIENT, &body, )?; - socket.send_to(&out, peer).await?; + let _ = send_udp(socket, &out, peer).await?; Ok(()) } @@ -1494,7 +1494,7 @@ async fn handle_ticket_attach( }; let body = protocol::to_body(&envelope)?; let out = protocol::encode_plain(PacketKind::AttachOk, client_id, 1, 0, &body)?; - socket.send_to(&out, peer).await?; + let _ = send_udp(socket, &out, peer).await?; Ok(()) } @@ -1512,7 +1512,7 @@ async fn send_reject_to_client( reason: reason.to_string(), })?; let out = protocol::encode_plain(PacketKind::AttachReject, client_id, 0, 0, &body)?; - socket.send_to(&out, peer).await?; + let _ = send_udp(socket, &out, peer).await?; Ok(()) } @@ -1578,7 +1578,7 @@ async fn handle_resume( SERVER_TO_CLIENT, &body, )?; - socket.send_to(&out, peer).await?; + let _ = send_udp(socket, &out, peer).await?; Ok(()) } @@ -1690,7 +1690,7 @@ async fn handle_ping( SERVER_TO_CLIENT, b"", )?; - socket.send_to(&out, peer).await?; + let _ = send_udp(socket, &out, peer).await?; Ok(()) } @@ -1705,6 +1705,60 @@ fn remove_client(state: &Arc>, client_id: [u8; 16]) { locked.remove_client_everywhere(&client_id); } +async fn send_udp(socket: &UdpSocket, packet: &[u8], peer: SocketAddr) -> Result { + match socket.send_to(packet, peer).await { + Ok(_) => Ok(true), + Err(err) if is_transient_udp_send_error(&err) => Ok(false), + Err(err) => Err(err).with_context(|| format!("send UDP packet to {peer}")), + } +} + +fn is_transient_udp_send_error(err: &std::io::Error) -> bool { + matches!( + err.kind(), + std::io::ErrorKind::Interrupted + | std::io::ErrorKind::TimedOut + | std::io::ErrorKind::WouldBlock + ) || err.raw_os_error().is_some_and(is_transient_udp_os_error) +} + +#[cfg(unix)] +fn is_transient_udp_os_error(code: i32) -> bool { + matches!( + code, + libc::EADDRNOTAVAIL + | libc::ECONNREFUSED + | libc::ECONNRESET + | libc::EHOSTDOWN + | libc::EHOSTUNREACH + | libc::ENETDOWN + | libc::ENETRESET + | libc::ENETUNREACH + | libc::ETIMEDOUT + ) +} + +#[cfg(windows)] +fn is_transient_udp_os_error(code: i32) -> bool { + matches!( + code, + 10049 // WSAEADDRNOTAVAIL + | 10050 // WSAENETDOWN + | 10051 // WSAENETUNREACH + | 10052 // WSAENETRESET + | 10054 // WSAECONNRESET + | 10060 // WSAETIMEDOUT + | 10061 // WSAECONNREFUSED + | 10064 // WSAEHOSTDOWN + | 10065 // WSAEHOSTUNREACH + ) +} + +#[cfg(not(any(unix, windows)))] +fn is_transient_udp_os_error(_code: i32) -> bool { + false +} + async fn handle_ack( state: &Arc>, peer: SocketAddr, @@ -3020,7 +3074,7 @@ async fn send_stream_open_to_client( found }; if let Some((endpoint, packet)) = send { - socket.send_to(&packet, endpoint).await?; + let _ = send_udp(socket, &packet, endpoint).await?; } Ok(()) } @@ -3114,7 +3168,7 @@ async fn queue_or_send_stream_data_to_client( send }; if let Some((endpoint, packet)) = send { - socket.send_to(&packet, endpoint).await?; + let _ = send_udp(socket, &packet, endpoint).await?; } Ok(()) } @@ -3189,7 +3243,7 @@ async fn flush_stream_pending_data_to_client( let Some((endpoint, packet)) = send else { return Ok(()); }; - socket.send_to(&packet, endpoint).await?; + let _ = send_udp(socket, &packet, endpoint).await?; } } @@ -3333,7 +3387,7 @@ async fn send_stream_packet_to_client( found }; if let Some((endpoint, packet)) = send { - socket.send_to(&packet, endpoint).await?; + let _ = send_udp(socket, &packet, endpoint).await?; } Ok(()) } @@ -3435,7 +3489,7 @@ async fn broadcast_output( } } for (endpoint, packet) in sends { - socket.send_to(&packet, endpoint).await?; + let _ = send_udp(socket, &packet, endpoint).await?; } Ok(()) } @@ -3488,7 +3542,7 @@ async fn broadcast_session_exit( persist::remove_runtime_dir(&sessions_dir, &session_name); } for (endpoint, packet) in sends { - socket.send_to(&packet, endpoint).await?; + let _ = send_udp(socket, &packet, endpoint).await?; } Ok(()) } @@ -3595,7 +3649,7 @@ async fn retransmit_pending( sends }; for (endpoint, packet) in sends { - socket.send_to(&packet, endpoint).await?; + let _ = send_udp(socket, &packet, endpoint).await?; } Ok(()) } @@ -3691,7 +3745,7 @@ async fn maybe_rekey_clients( sends }; for (endpoint, packet) in sends { - socket.send_to(&packet, endpoint).await?; + let _ = send_udp(socket, &packet, endpoint).await?; } Ok(()) } @@ -4536,4 +4590,25 @@ mod tests { }; remote_bind_allowed(&config, "0.0.0.0").unwrap(); } + + #[cfg(unix)] + #[test] + fn server_udp_send_classifier_treats_network_roaming_errors_as_transient() { + for code in [ + libc::EADDRNOTAVAIL, + libc::ECONNREFUSED, + libc::ECONNRESET, + libc::EHOSTDOWN, + libc::EHOSTUNREACH, + libc::ENETDOWN, + libc::ENETRESET, + libc::ENETUNREACH, + libc::ETIMEDOUT, + ] { + assert!( + is_transient_udp_send_error(&std::io::Error::from_raw_os_error(code)), + "expected errno {code} to be transient" + ); + } + } }