Keep server alive on transient UDP send errors

This commit is contained in:
DuProcess
2026-07-04 22:29:52 -04:00
parent 5c54601cdf
commit 3b4931aa8f
+91 -16
View File
@@ -980,7 +980,7 @@ async fn handle_native_client_hello(
}; };
let body = protocol::to_body(&NativeServerHelloBody { hello })?; let body = protocol::to_body(&NativeServerHelloBody { hello })?;
let out = protocol::encode_plain(PacketKind::NativeServerHello, pending_id, 1, 0, &body)?; 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(()) Ok(())
} }
@@ -1058,7 +1058,7 @@ async fn handle_native_user_auth(
SERVER_TO_CLIENT, SERVER_TO_CLIENT,
&body, &body,
)?; )?;
socket.send_to(&out, peer).await?; let _ = send_udp(socket, &out, peer).await?;
return Ok(()); return Ok(());
} }
@@ -1251,7 +1251,7 @@ async fn handle_native_user_auth(
SERVER_TO_CLIENT, SERVER_TO_CLIENT,
&body, &body,
)?; )?;
socket.send_to(&out, peer).await?; let _ = send_udp(socket, &out, peer).await?;
Ok(()) Ok(())
} }
@@ -1359,7 +1359,7 @@ async fn handle_bootstrap_attach(
SERVER_TO_CLIENT, SERVER_TO_CLIENT,
&body, &body,
)?; )?;
socket.send_to(&out, peer).await?; let _ = send_udp(socket, &out, peer).await?;
Ok(()) Ok(())
} }
@@ -1494,7 +1494,7 @@ async fn handle_ticket_attach(
}; };
let body = protocol::to_body(&envelope)?; let body = protocol::to_body(&envelope)?;
let out = protocol::encode_plain(PacketKind::AttachOk, client_id, 1, 0, &body)?; 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(()) Ok(())
} }
@@ -1512,7 +1512,7 @@ async fn send_reject_to_client(
reason: reason.to_string(), reason: reason.to_string(),
})?; })?;
let out = protocol::encode_plain(PacketKind::AttachReject, client_id, 0, 0, &body)?; 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(()) Ok(())
} }
@@ -1578,7 +1578,7 @@ async fn handle_resume(
SERVER_TO_CLIENT, SERVER_TO_CLIENT,
&body, &body,
)?; )?;
socket.send_to(&out, peer).await?; let _ = send_udp(socket, &out, peer).await?;
Ok(()) Ok(())
} }
@@ -1690,7 +1690,7 @@ async fn handle_ping(
SERVER_TO_CLIENT, SERVER_TO_CLIENT,
b"", b"",
)?; )?;
socket.send_to(&out, peer).await?; let _ = send_udp(socket, &out, peer).await?;
Ok(()) Ok(())
} }
@@ -1705,6 +1705,60 @@ fn remove_client(state: &Arc<Mutex<ServerState>>, client_id: [u8; 16]) {
locked.remove_client_everywhere(&client_id); locked.remove_client_everywhere(&client_id);
} }
async fn send_udp(socket: &UdpSocket, packet: &[u8], peer: SocketAddr) -> Result<bool> {
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( async fn handle_ack(
state: &Arc<Mutex<ServerState>>, state: &Arc<Mutex<ServerState>>,
peer: SocketAddr, peer: SocketAddr,
@@ -3020,7 +3074,7 @@ async fn send_stream_open_to_client(
found found
}; };
if let Some((endpoint, packet)) = send { if let Some((endpoint, packet)) = send {
socket.send_to(&packet, endpoint).await?; let _ = send_udp(socket, &packet, endpoint).await?;
} }
Ok(()) Ok(())
} }
@@ -3114,7 +3168,7 @@ async fn queue_or_send_stream_data_to_client(
send send
}; };
if let Some((endpoint, packet)) = send { if let Some((endpoint, packet)) = send {
socket.send_to(&packet, endpoint).await?; let _ = send_udp(socket, &packet, endpoint).await?;
} }
Ok(()) Ok(())
} }
@@ -3189,7 +3243,7 @@ async fn flush_stream_pending_data_to_client(
let Some((endpoint, packet)) = send else { let Some((endpoint, packet)) = send else {
return Ok(()); 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 found
}; };
if let Some((endpoint, packet)) = send { if let Some((endpoint, packet)) = send {
socket.send_to(&packet, endpoint).await?; let _ = send_udp(socket, &packet, endpoint).await?;
} }
Ok(()) Ok(())
} }
@@ -3435,7 +3489,7 @@ async fn broadcast_output(
} }
} }
for (endpoint, packet) in sends { for (endpoint, packet) in sends {
socket.send_to(&packet, endpoint).await?; let _ = send_udp(socket, &packet, endpoint).await?;
} }
Ok(()) Ok(())
} }
@@ -3488,7 +3542,7 @@ async fn broadcast_session_exit(
persist::remove_runtime_dir(&sessions_dir, &session_name); persist::remove_runtime_dir(&sessions_dir, &session_name);
} }
for (endpoint, packet) in sends { for (endpoint, packet) in sends {
socket.send_to(&packet, endpoint).await?; let _ = send_udp(socket, &packet, endpoint).await?;
} }
Ok(()) Ok(())
} }
@@ -3595,7 +3649,7 @@ async fn retransmit_pending(
sends sends
}; };
for (endpoint, packet) in sends { for (endpoint, packet) in sends {
socket.send_to(&packet, endpoint).await?; let _ = send_udp(socket, &packet, endpoint).await?;
} }
Ok(()) Ok(())
} }
@@ -3691,7 +3745,7 @@ async fn maybe_rekey_clients(
sends sends
}; };
for (endpoint, packet) in sends { for (endpoint, packet) in sends {
socket.send_to(&packet, endpoint).await?; let _ = send_udp(socket, &packet, endpoint).await?;
} }
Ok(()) Ok(())
} }
@@ -4536,4 +4590,25 @@ mod tests {
}; };
remote_bind_allowed(&config, "0.0.0.0").unwrap(); 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"
);
}
}
} }